diff --git a/include/flow/graph_builder.hpp b/include/flow/graph_builder.hpp index d71796be..642caf90 100644 --- a/include/flow/graph_builder.hpp +++ b/include/flow/graph_builder.hpp @@ -26,7 +26,13 @@ namespace flow { namespace detail { template using is_duplicated = std::bool_constant<(T::size() > 1)>; -} + +template +concept is_output_compatible = requires(CTNode n) { + { Output::create_node(n) } -> std::same_as; +}; +} // namespace detail + template using name_for = typename T::name_t; [[nodiscard]] constexpr auto edge_size(auto const &nodes, @@ -56,12 +62,14 @@ template typename Impl> struct graph_builder { // NOLINTBEGIN(readability-function-cognitive-complexity) - template + template [[nodiscard]] constexpr static auto make_graph(auto const &nodes, auto const &edges) { - using graph_t = stdx::cx_multimap; + using output_node_t = typename Output::node_t; + using graph_t = stdx::cx_multimap; graph_t g{}; - for_each([&](auto const &node) { g.put(node); }, nodes); + for_each([&](Node n) { g.put(Output::create_node(n)); }, + nodes); auto const named_nodes = stdx::apply_indices(nodes); for_each( @@ -95,7 +103,7 @@ struct graph_builder { }, node_ps); - g.put(lhs, rhs); + g.put(Output::create_node(lhs), Output::create_node(rhs)); }, edges); return g; @@ -212,18 +220,16 @@ struct graph_builder { constexpr auto edge_capacity = edge_size(node_set, edges); using output_t = Impl; - using rt_node_t = typename output_t::node_t; - static_assert( all_of( [](N const &) { - return std::is_convertible_v; + return detail::is_output_compatible; }, node_set), "Output node type is not compatible with given input nodes"); - auto g = make_graph(node_set, - edges); + auto g = + make_graph(node_set, edges); return topo_sort(g); } diff --git a/include/flow/impl.hpp b/include/flow/impl.hpp index 5338f2c8..36a86215 100644 --- a/include/flow/impl.hpp +++ b/include/flow/impl.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -13,6 +14,31 @@ #include namespace flow { +namespace detail { +template constexpr auto run_func() -> void { + if (CTNode::condition) { + typename CTNode::func_t{}(); + } +} + +template constexpr auto log_func() -> void { + if (CTNode::condition) { + using log_spec_t = decltype(get_log_spec()); + CIB_LOG(typename log_spec_t::flavor, log_spec_t::level, "flow.{}({})", + typename CTNode::type_t{}, typename CTNode::name_t{}); + } +} +} // namespace detail + +struct rt_node { + FunctionPtr run{}; + FunctionPtr log_name{}; + + private: + friend constexpr auto operator==(rt_node const &, + rt_node const &) -> bool = default; +}; + /** * flow::impl is a constant representation of a series of Milestones and actions * to be executed in a specific order. @@ -45,11 +71,18 @@ template class impl { public: stdx::cx_vector functionPtrs{}; - using node_t = rt_node; constexpr static bool active = capacity > 0; - constexpr static auto name = Name; + using node_t = rt_node; + + template + constexpr static auto create_node(CTNode) -> node_t { + constexpr auto rf = detail::run_func; + constexpr auto lf = detail::log_func, CTNode>; + return node_t{rf, lf}; + } + /** * Create a new flow::impl of Milestones. * @@ -60,17 +93,16 @@ template class impl { * * @see flow::builder */ - constexpr explicit(true) - impl(stdx::span newMilestones) { + constexpr explicit(true) impl(stdx::span steps) { if constexpr (loggingEnabled) { - for (auto const &milestone : newMilestones) { - functionPtrs.push_back(milestone.log_name); - functionPtrs.push_back(milestone.run); + for (auto const &step : steps) { + functionPtrs.push_back(step.log_name); + functionPtrs.push_back(step.run); } } else { - std::transform(std::cbegin(newMilestones), std::cend(newMilestones), + std::transform(std::cbegin(steps), std::cend(steps), std::back_inserter(functionPtrs), - [](auto const &milestone) { return milestone.run; }); + [](auto const &step) { return step.run; }); } } }; @@ -78,6 +110,7 @@ template class impl { namespace detail { template struct inlined_func_list { constexpr static auto active = sizeof...(FuncPtrs) > 0; + constexpr static auto ct_name = Name; __attribute__((flatten, always_inline)) auto operator()() const -> void { constexpr static bool loggingEnabled = not Name.empty(); @@ -86,13 +119,17 @@ template struct inlined_func_list { stdx::ct_string_to_type(); if constexpr (loggingEnabled) { - CIB_TRACE("flow.start({})", name); + using log_spec_t = decltype(get_log_spec()); + CIB_LOG(typename log_spec_t::flavor, log_spec_t::level, + "flow.start({})", name); } (FuncPtrs(), ...); if constexpr (loggingEnabled) { - CIB_TRACE("flow.end({})", name); + using log_spec_t = decltype(get_log_spec()); + CIB_LOG(typename log_spec_t::flavor, log_spec_t::level, + "flow.end({})", name); } } }; diff --git a/include/flow/log.hpp b/include/flow/log.hpp new file mode 100644 index 00000000..7ed5a397 --- /dev/null +++ b/include/flow/log.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +#include + +namespace flow { +struct default_log_spec { + using flavor = logging::default_flavor_t; + constexpr static auto level = logging::level::TRACE; +}; +template +constexpr auto log_spec = default_log_spec{}; + +template struct log_spec_id_t { + constexpr static auto ct_name = Name; +}; + +template , + typename... DummyArgs> + requires(sizeof...(DummyArgs) == 0) +constexpr static auto get_log_spec() { + using log_spec_t = decltype(log_spec); + if constexpr (std::is_same_v) { + if constexpr (Fallback::ct_name == stdx::ct_string{"default"}) { + return log_spec; + } else { + return get_log_spec(); + } + } else { + return log_spec_t{}; + } +} +} // namespace flow diff --git a/include/flow/step.hpp b/include/flow/step.hpp index d4e9dbf8..38db8959 100644 --- a/include/flow/step.hpp +++ b/include/flow/step.hpp @@ -3,8 +3,8 @@ #include #include #include +#include #include -#include #include #include @@ -14,45 +14,20 @@ #include namespace flow { -struct rt_node { - FunctionPtr run; - FunctionPtr log_name; - - private: - friend constexpr auto operator==(rt_node const &, - rt_node const &) -> bool = default; -}; - template -struct ct_node : rt_node { +struct ct_node { using is_subgraph = void; using type_t = decltype(stdx::ct_string_to_type()); - using name_t = decltype(stdx::ct_string_to_type()); + using func_t = F; constexpr static auto ct_name = Name; - constexpr static auto identity = Identity; - constexpr static auto condition = Cond{}; - constexpr static auto run_func = [] { - if (condition) { - F{}(); - } - }; - - constexpr static auto log_func = [] { - if (condition) { - CIB_TRACE("flow.{}({})", type_t{}, name_t{}); - } - }; - - constexpr ct_node() : rt_node{run_func, log_func} {} - constexpr auto operator*() const { if constexpr (Identity == subgraph_identity::REFERENCE) { return ct_node{}; diff --git a/include/seq/impl.hpp b/include/seq/impl.hpp index 86cd7b1a..81b9206e 100644 --- a/include/seq/impl.hpp +++ b/include/seq/impl.hpp @@ -24,6 +24,11 @@ template struct impl { using node_t = rt_step; + template + constexpr static auto create_node(CTNode n) -> node_t { + return n; + } + constexpr explicit(true) impl(stdx::span steps) { for (auto const &step : steps) { _forward_steps.push_back(step.forward_ptr); diff --git a/test/flow/CMakeLists.txt b/test/flow/CMakeLists.txt index 6f894ccc..03066925 100644 --- a/test/flow/CMakeLists.txt +++ b/test/flow/CMakeLists.txt @@ -1,13 +1,13 @@ add_unit_test( - "flow_flow_test" + "flow_separate_actions_test" CATCH2 FILES - "flow.cpp" "flow_cib_func.cpp" + "separate_actions.cpp" LIBRARIES warnings cib) -add_tests(graph graph_builder) +add_tests(flow graph graph_builder logging log_levels custom_log_levels) add_subdirectory(fail) diff --git a/test/flow/custom_log_levels.cpp b/test/flow/custom_log_levels.cpp new file mode 100644 index 00000000..35307f54 --- /dev/null +++ b/test/flow/custom_log_levels.cpp @@ -0,0 +1,96 @@ +#include +#include + +#include + +#include +#include + +namespace { +using namespace flow::literals; + +template struct wrapper { + struct inner { + constexpr static auto config = cib::config(Cs...); + }; + constexpr static auto n = cib::nexus{}; + + wrapper() { n.init(); } + + template static auto run() -> void { n.template service(); } +}; + +std::vector log_calls{}; + +template +constexpr auto run_flow = []() -> void { + log_calls.clear(); + wrapper::template run(); +}; + +struct TestFlowA : public flow::service<"A"> {}; +struct TestFlowB : public flow::service<"B"> {}; +struct TestFlowC : public flow::service<"C"> {}; + +constexpr auto msA = flow::milestone<"msA">(); +constexpr auto msB = flow::milestone<"msB">(); + +struct log_config { + struct log_handler { + template + auto log(FilenameStringType, LineNumberType, MsgType const &) -> void { + log_calls.push_back(Level); + } + }; + log_handler logger; +}; +} // namespace + +template <> inline auto logging::config<> = log_config{}; + +struct user1_log_spec : flow::default_log_spec { + constexpr static auto level = logging::level::USER1; +}; +struct user2_log_spec : flow::default_log_spec { + constexpr static auto level = logging::level::USER2; +}; +struct info_log_spec : flow::default_log_spec { + constexpr static auto level = logging::level::INFO; +}; +template <> constexpr auto flow::log_spec<"default"> = info_log_spec{}; + +TEST_CASE("override default log level", "[flow_custom_log_levels]") { + run_flow, + cib::extend(*msA)>(); + + REQUIRE(not log_calls.empty()); + std::for_each(std::begin(log_calls), std::end(log_calls), + [](auto level) { CHECK(level == logging::level::INFO); }); +} + +template <> constexpr auto flow::log_spec<"B"> = user1_log_spec{}; +template <> constexpr auto flow::log_spec<"msB"> = user2_log_spec{}; + +TEST_CASE("override log level by name", "[flow_custom_log_levels]") { + run_flow, + cib::extend(*msB)>(); + + REQUIRE(log_calls.size() == 3); + CHECK(log_calls[0] == logging::level::USER1); + CHECK(log_calls[1] == logging::level::USER2); + CHECK(log_calls[2] == logging::level::USER1); +} + +template <> constexpr auto flow::log_spec<"C"> = user1_log_spec{}; + +TEST_CASE("default log spec for step will use overridden log spec for flow", + "[flow_custom_log_levels]") { + run_flow, + cib::extend(*msA)>(); + + REQUIRE(log_calls.size() == 3); + std::for_each(std::begin(log_calls), std::end(log_calls), + [](auto level) { CHECK(level == logging::level::USER1); }); +} diff --git a/test/flow/flow.cpp b/test/flow/flow.cpp index 1edfe1d2..9c4a139d 100644 --- a/test/flow/flow.cpp +++ b/test/flow/flow.cpp @@ -6,16 +6,14 @@ #include -#include #include +#include std::string actual = {}; namespace { using namespace flow::literals; -std::string log_buffer{}; - constexpr auto a = flow::action<"a">([] { actual += "a"; }); constexpr auto b = flow::action<"b">([] { actual += "b"; }); constexpr auto c = flow::action<"c">([] { actual += "c"; }); @@ -38,7 +36,6 @@ template struct wrapper { template constexpr auto check_flow = [](std::string_view expected) -> void { actual.clear(); - log_buffer.clear(); wrapper::template run(); CHECK(actual == expected); }; @@ -65,7 +62,7 @@ struct VizConfig { }; } // namespace -TEST_CASE("vizualize flow", "[flow]") { +TEST_CASE("visualize flow", "[flow]") { cib::nexus nexus{}; auto viz = nexus.service(); auto expected = std::string{ @@ -76,9 +73,6 @@ a -> end CHECK(viz == expected); } -#if defined(__GNUC__) && __GNUC__ == 12 -#else - TEST_CASE("add single action through cib::nexus", "[flow]") { check_flow, cib::extend(*a)>("a"); @@ -198,52 +192,3 @@ TEST_CASE("add multi action through cib::nexus, run through cib::service", cib::service(); CHECK(actual == "abcd"); } - -TEST_CASE("add actions using func_decl through cib::nexus", "[flow]") { - check_flow, - cib::extend(*"e"_action >> *"f"_action)>("ef"); -} - -namespace { -struct NamedTestFlow : public flow::service<"TestFlow"> {}; -} // namespace - -template <> -inline auto logging::config<> = - logging::fmt::config{std::back_inserter(log_buffer)}; - -TEST_CASE("unnamed flow does not log start/end", "[flow]") { - check_flow>(""); - - CHECK(std::empty(log_buffer)); -} - -TEST_CASE("empty flow logs start/end", "[flow]") { - check_flow>(""); - - CHECK(log_buffer.find("flow.start(TestFlow)") != std::string::npos); - CHECK(log_buffer.find("flow.end(TestFlow)") != std::string::npos); -} - -TEST_CASE("unnamed flow does not log actions", "[flow]") { - check_flow, - cib::extend(*a)>("a"); - - CHECK(log_buffer.find("flow.action(a)") == std::string::npos); -} - -TEST_CASE("named flow logs actions", "[flow]") { - check_flow, - cib::extend(*a)>("a"); - - CHECK(log_buffer.find("flow.action(a)") != std::string::npos); -} - -TEST_CASE("named flow logs milestones", "[flow]") { - check_flow, - cib::extend(*"ms"_milestone)>(""); - - CHECK(log_buffer.find("flow.milestone(ms)") != std::string::npos); -} - -#endif diff --git a/test/flow/log_levels.cpp b/test/flow/log_levels.cpp new file mode 100644 index 00000000..b2465934 --- /dev/null +++ b/test/flow/log_levels.cpp @@ -0,0 +1,57 @@ +#include +#include + +#include + +#include +#include + +namespace { +using namespace flow::literals; + +template struct wrapper { + struct inner { + constexpr static auto config = cib::config(Cs...); + }; + constexpr static auto n = cib::nexus{}; + + wrapper() { n.init(); } + + template static auto run() -> void { n.template service(); } +}; + +std::vector log_calls{}; + +template +constexpr auto run_flow = []() -> void { + log_calls.clear(); + wrapper::template run(); +}; + +struct NamedTestFlow : public flow::service<"TestFlow"> {}; + +constexpr auto ms = flow::milestone<"ms">(); + +struct log_config { + struct log_handler { + template + auto log(FilenameStringType, LineNumberType, MsgType const &) -> void { + log_calls.push_back(Level); + } + }; + log_handler logger; +}; +} // namespace + +template <> inline auto logging::config<> = log_config{}; + +TEST_CASE("flow logs at TRACE by default", "[flow_log_levels]") { + run_flow, + cib::extend(*ms)>(); + + REQUIRE(not log_calls.empty()); + std::for_each(std::begin(log_calls), std::end(log_calls), + [](auto level) { CHECK(level == logging::level::TRACE); }); +} diff --git a/test/flow/logging.cpp b/test/flow/logging.cpp new file mode 100644 index 00000000..3ae576f3 --- /dev/null +++ b/test/flow/logging.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +#include + +#include +#include + +namespace { +using namespace flow::literals; + +std::string log_buffer{}; + +template struct wrapper { + struct inner { + constexpr static auto config = cib::config(Cs...); + }; + constexpr static auto n = cib::nexus{}; + + wrapper() { n.init(); } + + template static auto run() -> void { n.template service(); } +}; + +template +constexpr auto run_flow = [] { + log_buffer.clear(); + wrapper::template run(); +}; + +struct TestFlowAlpha : public flow::service<> {}; +struct NamedTestFlow : public flow::service<"TestFlow"> {}; + +constexpr auto a = flow::action<"a">([] {}); +} // namespace + +template <> +inline auto logging::config<> = + logging::fmt::config{std::back_inserter(log_buffer)}; + +TEST_CASE("unnamed flow does not log start/end", "[flow_logging]") { + run_flow>(); + + CHECK(std::empty(log_buffer)); +} + +TEST_CASE("empty flow logs start/end", "[flow_logging]") { + run_flow>(); + + CHECK(log_buffer.find("flow.start(TestFlow)") != std::string::npos); + CHECK(log_buffer.find("flow.end(TestFlow)") != std::string::npos); +} + +TEST_CASE("unnamed flow does not log actions", "[flow_logging]") { + run_flow, + cib::extend(*a)>(); + + CHECK(log_buffer.find("flow.action(a)") == std::string::npos); +} + +TEST_CASE("named flow logs actions", "[flow_logging]") { + run_flow, + cib::extend(*a)>(); + + CHECK(log_buffer.find("flow.action(a)") != std::string::npos); +} + +TEST_CASE("named flow logs milestones", "[flow_logging]") { + run_flow, + cib::extend(*"ms"_milestone)>(); + + CHECK(log_buffer.find("flow.milestone(ms)") != std::string::npos); +} diff --git a/test/flow/separate_actions.cpp b/test/flow/separate_actions.cpp new file mode 100644 index 00000000..e147a1ec --- /dev/null +++ b/test/flow/separate_actions.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include + +#include +#include + +std::string actual = {}; + +namespace { +using namespace flow::literals; + +struct TestFlowAlpha : public flow::service<> {}; + +template struct wrapper { + struct inner { + constexpr static auto config = cib::config(Cs...); + }; + constexpr static auto n = cib::nexus{}; + + wrapper() { n.init(); } + + template static auto run() -> void { n.template service(); } +}; + +template +constexpr auto check_flow = [](std::string_view expected) -> void { + actual.clear(); + wrapper::template run(); + CHECK(actual == expected); +}; +} // namespace + +TEST_CASE("add actions using func_decl through cib::nexus", + "[flow_separate_actions]") { + check_flow, + cib::extend(*"e"_action >> *"f"_action)>("ef"); +}