Tests, examples and sandbox#

Organization#

tests/
  main.cc            GoogleTest entry point
  elements/          everything a program using the library would provide: socket types, validators,
                     callbacks, node types, data bank and operators. Only for the tests and examples.
  contexts/          functions building ready-to-use situations (no GoogleTest dependency)
  fundamentals/      identifiers, qualifiers, SmallMap, Registry, DataBank
  structures/        sockets (enumerations, validators), nodes, node definitions, links and callbacks
  graph/             Graph: nodes, sockets, links, interface, topology, CmpGraph
  serialization/     primitives, components, graph round trips, corrupted buffers
  graph_manager/     graphs in memory, heads, save/load, proxy nodes
  graph_runtime/     build, run, proxies at runtime
examples/            one program per .cc, built on elements and contexts
sandbox/             quick trials, one program per .cc, not versioned

All the unit tests are compiled into build/tests/tests-nodes-graph. The examples are also registered in CTest, so ctest checks that they still run without error. The commands are in the README.

Test elements (tests/elements)#

Node type

Purpose

ng_test.math.value

Constant source. Its input is not linkable.

ng_test.math.add

sum = a + b.

ng_test.math.binary_op

operation is an enumeration (add, sub, mul, div), not linkable. Fails on a division by zero.

ng_test.text.format

Text output, to test incompatible types (Text vs Number).

ng_test.misc.spy

Accepts any type and has every callback attached: they write into ng_test::callbacks::get_log().

ng_test.misc.no_operator

Registered without operator: the runtime must throw.

ng_test.misc.failing

Its operator always fails: run() must stop.

Socket types: TYPE_NUMBER, TYPE_BOOLEAN, TYPE_TEXT (validators registered; Text refuses the empty string) and TYPE_UNCHECKED (no validator). Data bank: ng_test::DataBank = ng::DataBank<Number, Boolean, Text>. Every operator appends its name to ng_test::operators::get_execution_log() to check the execution order.

These elements are compiled as a CMake OBJECT library, otherwise their static registrations would be dropped by the linker (see Registries and self-registration).

Contexts (tests/contexts)#

  • make_arithmetic_graph(x, y): product = (x + y) * y, results “sum” and “product”.

  • make_diamond_graph(): depths 0, 1, 1, 2.

  • make_chain_graph(n): n Add nodes in a row, node i at depth i.

  • make_feature_rich_graph(): every feature that must survive serialization.

  • ScopedLibrary: resets the GraphManager and gives it a head in a new temporary folder, both cleaned on destruction. Use it in every test touching the disk or proxies.

  • save_doubler_graph(gm) / save_quadrupler_graph(gm): saved graphs for the proxy tests (x * 2 and x * 4 through two nested doublers).

The context functions throw if a step fails, so a broken context is visible immediately instead of producing confusing test failures.

Writing a test#

  1. Pick the folder of the right level of abstraction and create (or extend) a test_*.cc file: it is picked up at the next build.

  2. Prefer building the situation with a context function or test elements rather than registering new things in the global registries from the test (they are shared by all the tests).

  3. Tests touching the GraphManager must use a ScopedLibrary; tests reading the callbacks or execution logs must clear them first.

  4. Tests must not depend on their order: --gtest_shuffle --gtest_repeat=20 must pass.

To check that tests really detect a bug, re-introduce the bug temporarily and check that they fail.

Sanitizers#

The deserialization deals with untrusted bytes: after changing it, run the tests with AddressSanitizer and UndefinedBehaviorSanitizer (command in the README). The CorruptedBuffers tests are designed to be run this way.