diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index aa173aa3..3a90ade9 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -124,7 +124,7 @@ jobs: - name: Test working-directory: ${{github.workspace}}/build - run: ctest -j $(nproc) -C ${{matrix.build_type}} + run: ctest --output-on-failure -j $(nproc) -C ${{matrix.build_type}} quality_checks_pass: runs-on: ${{ github.repository_owner == 'intel' && 'intel-' || '' }}ubuntu-22.04 @@ -218,7 +218,7 @@ jobs: - name: Test working-directory: ${{github.workspace}}/build run: | - ctest -j $(nproc) -E EXPECT_FAIL -T memcheck + ctest --output-on-failure -j $(nproc) -E "EXPECT_FAIL|PYTHON" -T memcheck LOGFILE=$(ls ./Testing/Temporary/LastDynamicAnalysis_*.log) FAILSIZE=$(du -c ./Testing/Temporary/MemoryChecker.* | tail -1 | cut -f1) diff --git a/CMakeLists.txt b/CMakeLists.txt index c619bed2..077ef6d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ add_versioned_package( NAME fmt GIT_TAG - 10.2.0 + 10.2.1 GITHUB_REPOSITORY fmtlib/fmt OPTIONS diff --git a/docs/flows.adoc b/docs/flows.adoc index 3745dd80..0824580d 100644 --- a/docs/flows.adoc +++ b/docs/flows.adoc @@ -324,3 +324,59 @@ namespace example_component { SOME_ACTION && SOME_OTHER_ACTION)); } ---- + +=== Alternative flow builders + +The default flow service uses a graph builder that outputs the flow steps as an +array of function pointers. Traversing the array and calling those functions +ensures the correct relative ordering of flow steps in the graph, and this is +what happens by default when we run the flow. + +[source,cpp] +---- +// the default flow builder and service +template +using builder = flow::graph>; +template +struct service : cib::builder_meta, flow::FunctionPtr> {}; + +// declare a flow service +struct MorningRoutine : public service<"MorningRoutine"> {}; + +// add steps, etc, then at runtime, run the flow: +nexus.service<"MorningRoutine">(); +---- + +Here `graph_builder` is the type that renders the flow description into the +array of function pointers, and `flow::FunctionPtr` is the type-erased interface +(here a function taking and returning `void`) that is called to run a flow. + +But given a flow, other renderings are possible. + +[source,cpp] +---- +// a flow builder and service that produces a graphviz rendering +template +using viz_builder = flow::graph; +template +struct viz_service : cib::builder_meta, flow::VizFunctionPtr> {}; +---- + +Here, `viz_service` will produce a graphviz rendering of a flow using the +`graphviz_builder`. `flow::VizFunctionPtr` is the type-erased interface once +more, and it is defined to take `void` and return a `std::string`. When we "run" +the flow, we get the graphviz rendering. + +[source,cpp] +---- +// instead of the default flow::service, use the viz_service +struct MorningRoutine : public viz_service<"MorningRoutine"> {}; + +// add steps, etc, as before +// this time, when we "run" the flow, we get a string representing the graphviz rendering +auto graphviz_str = nexus.service<"MorningRoutine">(); +---- + +`graphviz_builder` is available as a debugging aid. But in general, having the +flow rendering separate from the flow definition enables any kind of rendering +with correponding runtime behaviour. diff --git a/include/flow/graphviz_builder.hpp b/include/flow/graphviz_builder.hpp index 358e0b97..771c3c4c 100644 --- a/include/flow/graphviz_builder.hpp +++ b/include/flow/graphviz_builder.hpp @@ -7,6 +7,8 @@ #include namespace flow { +using VizFunctionPtr = auto (*)() -> std::string; + struct graphviz_builder { template [[nodiscard]] static auto build(Graph const &input) { @@ -49,5 +51,25 @@ struct graphviz_builder { output += "}"; return output; } + + template class built_flow { + static auto built() { + auto const v = Initialized::value; + return build(v); + } + + static auto run() -> std::string { return built(); } + + public: + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator VizFunctionPtr() const { return run; } + auto operator()() const -> std::string { return run(); } + constexpr static bool active = true; + }; + + template + [[nodiscard]] constexpr static auto render() -> built_flow { + return {}; + } }; } // namespace flow diff --git a/test/flow/flow.cpp b/test/flow/flow.cpp index ea6b5370..a677f012 100644 --- a/test/flow/flow.cpp +++ b/test/flow/flow.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -147,3 +148,28 @@ TEST_CASE("named flow logs milestones", "[flow]") { nexus.service(); CHECK(log_buffer.find("flow.milestone(ms)") != std::string::npos); } + +namespace { +template +using alt_builder = flow::graph; +template +struct alt_flow_service + : cib::builder_meta, flow::VizFunctionPtr> {}; + +struct VizFlow : public alt_flow_service<"debug"> {}; +struct VizConfig { + constexpr static auto config = + cib::config(cib::exports, cib::extend(a)); +}; +} // namespace + +TEST_CASE("vizualize flow", "[flow]") { + cib::nexus nexus{}; + auto viz = nexus.service(); + auto expected = std::string{ + R"__debug__(digraph debug { +start -> a +a -> end +})__debug__"}; + CHECK(viz == expected); +} diff --git a/test/flow/graph.cpp b/test/flow/graph.cpp index f76613f9..ebcff152 100644 --- a/test/flow/graph.cpp +++ b/test/flow/graph.cpp @@ -20,32 +20,32 @@ constexpr auto b = flow::action<"b">([] {}); constexpr auto c = flow::action<"c">([] {}); } // namespace -TEST_CASE("node size (empty graph)", "[flow]") { +TEST_CASE("node size (empty graph)", "[graph]") { constexpr auto g = flow::graph<>{}; static_assert(node_size(g) == 0); } -TEST_CASE("node size (single action)", "[flow]") { +TEST_CASE("node size (single action)", "[graph]") { constexpr auto g = flow::graph<>{}.add(a >> b); static_assert(node_size(g) == 2); } -TEST_CASE("node size (overlapping actions)", "[flow]") { +TEST_CASE("node size (overlapping actions)", "[graph]") { constexpr auto g = flow::graph<>{}.add(a >> b).add(b >> c); static_assert(node_size(g) == 3); } -TEST_CASE("edge size (empty flow)", "[flow]") { +TEST_CASE("edge size (empty flow)", "[graph]") { constexpr auto g = flow::graph<>{}; static_assert(edge_size(g) == 1); } -TEST_CASE("edge size (single action)", "[flow]") { +TEST_CASE("edge size (single action)", "[graph]") { constexpr auto g = flow::graph<>{}.add(a >> b); static_assert(edge_size(g) == 1); } -TEST_CASE("edge size (overlapping actions)", "[flow]") { +TEST_CASE("edge size (overlapping actions)", "[graph]") { constexpr auto g = flow::graph<>{}.add(a >> b).add(b >> c); static_assert(edge_size(g) == 2); } diff --git a/test/flow/graph_builder.cpp b/test/flow/graph_builder.cpp index 140047e4..a3131abe 100644 --- a/test/flow/graph_builder.cpp +++ b/test/flow/graph_builder.cpp @@ -19,14 +19,14 @@ constexpr auto d = flow::action<"d">([] { actual += "d"; }); using builder = flow::graph_builder; } // namespace -TEST_CASE("build and run empty flow", "[flow]") { +TEST_CASE("build and run empty flow", "[graph_builder]") { auto g = flow::graph<>{}; auto const flow = builder::build(g); REQUIRE(flow.has_value()); flow.value()(); } -TEST_CASE("add single action", "[flow]") { +TEST_CASE("add single action", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a); auto const flow = builder::build(g); @@ -35,7 +35,7 @@ TEST_CASE("add single action", "[flow]") { CHECK(actual == "a"); } -TEST_CASE("two milestone linear before dependency", "[flow]") { +TEST_CASE("two milestone linear before dependency", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a >> milestone0); auto const flow = builder::build(g); @@ -44,7 +44,7 @@ TEST_CASE("two milestone linear before dependency", "[flow]") { CHECK(actual == "a"); } -TEST_CASE("actions get executed once", "[flow]") { +TEST_CASE("actions get executed once", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{} .add(a >> milestone0) @@ -55,7 +55,7 @@ TEST_CASE("actions get executed once", "[flow]") { CHECK(actual == "a"); } -TEST_CASE("two milestone linear after dependency", "[flow]") { +TEST_CASE("two milestone linear after dependency", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{} .add(a >> milestone0) @@ -66,7 +66,8 @@ TEST_CASE("two milestone linear after dependency", "[flow]") { CHECK(actual == "ab"); } -TEST_CASE("three milestone linear before and after dependency", "[flow]") { +TEST_CASE("three milestone linear before and after dependency", + "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a >> b >> c); auto const flow = builder::build(g); @@ -74,7 +75,7 @@ TEST_CASE("three milestone linear before and after dependency", "[flow]") { CHECK(actual == "abc"); } -TEST_CASE("just two actions in order", "[flow]") { +TEST_CASE("just two actions in order", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a >> b); auto const flow = builder::build(g); @@ -82,7 +83,7 @@ TEST_CASE("just two actions in order", "[flow]") { CHECK(actual == "ab"); } -TEST_CASE("insert action between two actions", "[flow]") { +TEST_CASE("insert action between two actions", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a >> c).add(a >> b >> c); auto const flow = builder::build(g); @@ -90,7 +91,7 @@ TEST_CASE("insert action between two actions", "[flow]") { CHECK(actual == "abc"); } -TEST_CASE("add single parallel 2", "[flow]") { +TEST_CASE("add single parallel 2", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a && b); auto const flow = builder::build(g); @@ -101,7 +102,7 @@ TEST_CASE("add single parallel 2", "[flow]") { CHECK(actual.size() == 2); } -TEST_CASE("add single parallel 3", "[flow]") { +TEST_CASE("add single parallel 3", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a && b && c); auto const flow = builder::build(g); @@ -113,7 +114,7 @@ TEST_CASE("add single parallel 3", "[flow]") { CHECK(actual.size() == 3); } -TEST_CASE("add single parallel 3 with later dependency 1", "[flow]") { +TEST_CASE("add single parallel 3 with later dependency 1", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a && b && c).add(c >> a); auto const flow = builder::build(g); @@ -126,7 +127,7 @@ TEST_CASE("add single parallel 3 with later dependency 1", "[flow]") { CHECK(actual.size() == 3); } -TEST_CASE("add single parallel 3 with later dependency 2", "[flow]") { +TEST_CASE("add single parallel 3 with later dependency 2", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a && b && c).add(a >> c); auto const flow = builder::build(g); @@ -139,7 +140,7 @@ TEST_CASE("add single parallel 3 with later dependency 2", "[flow]") { CHECK(actual.size() == 3); } -TEST_CASE("add parallel rhs", "[flow]") { +TEST_CASE("add parallel rhs", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a >> (b && c)); auto const flow = builder::build(g); @@ -153,7 +154,7 @@ TEST_CASE("add parallel rhs", "[flow]") { CHECK(actual.size() == 3); } -TEST_CASE("add parallel lhs", "[flow]") { +TEST_CASE("add parallel lhs", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add((a && b) >> c); auto const flow = builder::build(g); @@ -167,7 +168,7 @@ TEST_CASE("add parallel lhs", "[flow]") { CHECK(actual.size() == 3); } -TEST_CASE("add parallel in the middle", "[flow]") { +TEST_CASE("add parallel in the middle", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a >> (b && c) >> d); auto const flow = builder::build(g); @@ -187,7 +188,7 @@ TEST_CASE("add parallel in the middle", "[flow]") { CHECK(actual.size() == 4); } -TEST_CASE("add dependency lhs", "[flow]") { +TEST_CASE("add dependency lhs", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add((a >> b) && c); auto const flow = builder::build(g); @@ -202,7 +203,7 @@ TEST_CASE("add dependency lhs", "[flow]") { CHECK(actual.size() == 3); } -TEST_CASE("add dependency rhs", "[flow]") { +TEST_CASE("add dependency rhs", "[graph_builder]") { actual.clear(); auto g = flow::graph<>{}.add(a && (b >> c)); auto const flow = builder::build(g); @@ -217,7 +218,7 @@ TEST_CASE("add dependency rhs", "[flow]") { CHECK(actual.size() == 3); } -TEST_CASE("alternate builder", "[flow]") { +TEST_CASE("alternate builder", "[graph_builder]") { using alt_builder = flow::graphviz_builder; auto g = flow::graph<"debug">{}.add(a && (b >> c)); auto const flow = alt_builder::build(g);