From 439193777f0623f2539f1c6a7e8bd655f2269da0 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Mon, 1 Jun 2026 13:25:51 -0500 Subject: [PATCH 1/8] Introduce source base class --- phlex/app/load_module.cpp | 3 +-- phlex/core/framework_graph.hpp | 13 +++++++--- phlex/core/glue.hpp | 11 ++++++++ phlex/core/graph_proxy.hpp | 11 ++++++++ phlex/core/node_catalog.hpp | 2 ++ phlex/core/source.hpp | 47 ++++++++++++++++++++++++++++++++++ phlex/detail/plugin_macros.hpp | 46 +++++++++++++++++++++++++++++---- phlex/driver.hpp | 2 +- phlex/source.hpp | 47 +++++++++++++++++++++++++++++----- plugins/python/src/wrap.hpp | 2 +- 10 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 phlex/core/source.hpp diff --git a/phlex/app/load_module.cpp b/phlex/app/load_module.cpp index f204c7355..48d52f72c 100644 --- a/phlex/app/load_module.cpp +++ b/phlex/app/load_module.cpp @@ -121,9 +121,8 @@ namespace phlex::experimental { // internal reference counting in classification.hpp. // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks,clang-analyzer-cplusplus.NewDelete) create_driver = plugin_loader(spec, "create_driver"); - driver_proxy const proxy{}; driver_bundle result; - create_driver(proxy, config, &result); + create_driver(driver_proxy{}, config, &result); return result; } } diff --git a/phlex/core/framework_graph.hpp b/phlex/core/framework_graph.hpp index f0b8ff1bb..5a359ab16 100644 --- a/phlex/core/framework_graph.hpp +++ b/phlex/core/framework_graph.hpp @@ -3,8 +3,6 @@ #include "phlex/phlex_core_export.hpp" -#include "phlex/core/declared_fold.hpp" -#include "phlex/core/declared_unfold.hpp" #include "phlex/core/filter.hpp" #include "phlex/core/glue.hpp" #include "phlex/core/index_router.hpp" @@ -13,6 +11,7 @@ #include "phlex/driver.hpp" #include "phlex/model/data_cell_tracker.hpp" #include "phlex/model/data_layer_hierarchy.hpp" +#include "phlex/model/fixed_hierarchy.hpp" #include "phlex/model/flush_messages.hpp" #include "phlex/model/product_store.hpp" #include "phlex/module.hpp" @@ -23,8 +22,10 @@ #include "oneapi/tbb/flow_graph.h" #include "oneapi/tbb/info.h" +#include #include #include +#include #include #include #include @@ -58,7 +59,7 @@ namespace phlex::experimental { return {config, graph_, nodes_, registration_errors_}; } - source_graph_proxy source_proxy(configuration const& config) + source_bundle source_proxy(configuration const& config) { return {config, graph_, nodes_, registration_errors_}; } @@ -113,6 +114,12 @@ namespace phlex::experimental { return make_glue().provide(std::move(name), std::move(f), c); } + template Source, typename... Args> + void source(std::string name, Args&&... args) + { + return make_glue().template source(std::move(name), std::forward(args)...); + } + template glue make(Args&&... args) { diff --git a/phlex/core/glue.hpp b/phlex/core/glue.hpp index a1101be6a..1081f829b 100644 --- a/phlex/core/glue.hpp +++ b/phlex/core/glue.hpp @@ -7,6 +7,7 @@ #include "phlex/core/concepts.hpp" #include "phlex/core/registrar.hpp" #include "phlex/core/registration_api.hpp" +#include "phlex/core/source.hpp" #include "phlex/metaprogramming/delegate.hpp" #include "oneapi/tbb/flow_graph.h" @@ -150,6 +151,16 @@ namespace phlex::experimental { c}; } + template Source, typename... Args> + void source(std::string name, Args&&... args) + { + auto [_, inserted] = + nodes_.sources.try_emplace(name, std::make_unique(std::forward(args)...)); + if (not inserted) { + detail::add_to_error_messages(errors_, name); // From registrar.hpp + } + } + private: // Non-owning references to framework-owned resources; glue is a short-lived builder. tbb::flow::graph& graph_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) diff --git a/phlex/core/graph_proxy.hpp b/phlex/core/graph_proxy.hpp index 1258ea8e1..8e3821bbb 100644 --- a/phlex/core/graph_proxy.hpp +++ b/phlex/core/graph_proxy.hpp @@ -107,6 +107,17 @@ namespace phlex::experimental { std::move(name), std::move(pred), std::move(unf), c, std::move(destination_data_layer)); } + /// @brief Registers a source (used by the framework to create provider nodes) + template Source, typename... Args> + void source(std::string name, Args&&... args) + requires(not is_bound_object) + { + // The bound object is created when invoking source(...), so we explicitly indicate that + // no bound object should be used in the create_glue(...) call. + return create_glue(false).template source(std::move(name), + std::forward(args)...); + } + /// @brief Registers an output node. auto output(std::string name, is_output_like auto f, concurrency c = concurrency::serial) { diff --git a/phlex/core/node_catalog.hpp b/phlex/core/node_catalog.hpp index d8ec0d2d2..274a50893 100644 --- a/phlex/core/node_catalog.hpp +++ b/phlex/core/node_catalog.hpp @@ -13,6 +13,7 @@ #include "phlex/core/products_consumer.hpp" #include "phlex/core/provider_node.hpp" #include "phlex/core/registrar.hpp" +#include "phlex/core/source.hpp" #include "phlex/utilities/simple_ptr_map.hpp" #include "boost/pfr.hpp" @@ -39,6 +40,7 @@ namespace phlex::experimental { simple_ptr_map unfolds{}; simple_ptr_map transforms{}; simple_ptr_map providers{}; + simple_ptr_map sources{}; }; } diff --git a/phlex/core/source.hpp b/phlex/core/source.hpp new file mode 100644 index 000000000..affe37834 --- /dev/null +++ b/phlex/core/source.hpp @@ -0,0 +1,47 @@ +#ifndef PHLEX_CORE_SOURCE_HPP +#define PHLEX_CORE_SOURCE_HPP + +#include "phlex/phlex_core_export.hpp" + +#include "phlex/core/product_selector.hpp" +#include "phlex/model/index_generator.hpp" +#include "phlex/model/product_specification.hpp" +#include "phlex/model/products.hpp" + +#include +#include + +namespace phlex::experimental { + + // ============================================================================== + + // Function type for type-erased data-product types (used by implicit providers) + using provider_function_t = product_ptr(data_cell_index const&); + + class provider_bundle { + public: + provider_bundle(product_specification, provider_function_t); + + product_specification specification() const; + product_ptr get_product(data_cell_index const&) const; + + private: + product_specification spec_; + std::function provider_function_; + }; + + using provider_bundles = std::vector; + + // ============================================================================== + class source { + public: + virtual ~source() = default; + // FIXME: Should these functions be 'const'? + virtual provider_bundles create_providers(product_selector const&) = 0; + virtual index_generator indices() = 0; + }; + + using source_ptr = std::unique_ptr; +} + +#endif // PHLEX_CORE_SOURCE_HPP diff --git a/phlex/detail/plugin_macros.hpp b/phlex/detail/plugin_macros.hpp index cc08d7dcd..f4d40694f 100644 --- a/phlex/detail/plugin_macros.hpp +++ b/phlex/detail/plugin_macros.hpp @@ -7,13 +7,16 @@ // `bugprone-macro-parentheses` is appropriate for expression-like macros, but these macros expand // to C++ signatures, where parenthesizing parameters breaks parsing. We suppress the check for this // block because line continuations make per-line suppression impractical. + +// ================================================================================================ +// Algorithm registration macros #define PHLEX_DETAIL_NARGS(...) BOOST_PP_DEC(BOOST_PP_VARIADIC_SIZE(__VA_OPT__(, ) __VA_ARGS__)) #define PHLEX_DETAIL_CREATE_1ARG(token_type, func_name, m) \ - void func_name(token_type& m, phlex::configuration const&) + void func_name(token_type m, phlex::configuration const&) #define PHLEX_DETAIL_CREATE_2ARGS(token_type, func_name, m, pset) \ - void func_name(token_type& m, phlex::configuration const& config) + void func_name(token_type m, phlex::configuration const& config) #define PHLEX_DETAIL_SELECT_SIGNATURE(token_type, func_name, ...) \ BOOST_PP_IF(BOOST_PP_EQUAL(PHLEX_DETAIL_NARGS(__VA_ARGS__), 1), \ @@ -27,12 +30,45 @@ #define PHLEX_DETAIL_REGISTER_PLUGIN(token_type, func_name, dll_alias, ...) \ extern "C" PHLEX_DETAIL_SELECT_SIGNATURE(token_type, dll_alias, __VA_ARGS__) +// ================================================================================================ +// Registration macros for source plugins and explicit-provider plugins +// +// Source plugin entry-points cannot use extern "C" directly because the user-facing proxy types +// (providers_graph_proxy, source_graph_proxy) are C++ templates. Instead we: +// 1. Forward-declare the user's C++ implementation (takes the proxy by reference). +// 2. Define a thin extern "C" shim that accepts source_bundle by value (matching +// source_creator_t exactly), constructs the appropriate proxy from the bundle, +// and calls the user's implementation. +// 3. Open the user's implementation definition for the body that follows the macro. +#define PHLEX_DETAIL_CREATE_SOURCE_1ARG(token_type, func_name, m) \ + void func_name(token_type m, phlex::configuration const&) + +#define PHLEX_DETAIL_CREATE_SOURCE_2ARGS(token_type, func_name, m, pset) \ + void func_name(token_type m, phlex::configuration const& config) + +#define PHLEX_DETAIL_SELECT_SOURCE_SIGNATURE(token_type, func_name, ...) \ + BOOST_PP_IF(BOOST_PP_EQUAL(PHLEX_DETAIL_NARGS(__VA_ARGS__), 1), \ + PHLEX_DETAIL_CREATE_SOURCE_1ARG, \ + PHLEX_DETAIL_CREATE_SOURCE_2ARGS) \ + (token_type, func_name, __VA_ARGS__) + +#define PHLEX_DETAIL_REGISTER_SOURCE_PLUGIN(token_type, func_name, dll_alias, ...) \ + static PHLEX_DETAIL_SELECT_SOURCE_SIGNATURE(token_type, func_name, __VA_ARGS__); \ + extern "C" void dll_alias(phlex::experimental::source_bundle __phlex_bundle, \ + phlex::configuration const& __phlex_config) \ + { \ + func_name(token_type{__phlex_bundle}, __phlex_config); \ + } \ + PHLEX_DETAIL_SELECT_SOURCE_SIGNATURE(token_type, func_name, __VA_ARGS__) + +// ================================================================================================ +// Driver registration plugin macros #define PHLEX_DETAIL_CREATE_DRIVER_1ARG(func_name, d) \ - phlex::experimental::driver_bundle func_name(phlex::experimental::driver_proxy const& d, \ + phlex::experimental::driver_bundle func_name(phlex::experimental::driver_proxy d, \ phlex::configuration const&) #define PHLEX_DETAIL_CREATE_DRIVER_2ARGS(func_name, d, config) \ - phlex::experimental::driver_bundle func_name(phlex::experimental::driver_proxy const& d, \ + phlex::experimental::driver_bundle func_name(phlex::experimental::driver_proxy d, \ phlex::configuration const& config) #define PHLEX_DETAIL_SELECT_DRIVER_SIGNATURE(func_name, ...) \ @@ -47,7 +83,7 @@ // linkage), and then open the user's implementation definition for the body that follows. #define PHLEX_DETAIL_REGISTER_DRIVER_PLUGIN(func_name, dll_alias, ...) \ static PHLEX_DETAIL_SELECT_DRIVER_SIGNATURE(func_name, __VA_ARGS__); \ - extern "C" void dll_alias(phlex::experimental::driver_proxy const& __phlex_proxy, \ + extern "C" void dll_alias(phlex::experimental::driver_proxy __phlex_proxy, \ phlex::configuration const& __phlex_config, \ phlex::experimental::driver_bundle* __phlex_out) \ { \ diff --git a/phlex/driver.hpp b/phlex/driver.hpp index 7542f8e72..77156e6d6 100644 --- a/phlex/driver.hpp +++ b/phlex/driver.hpp @@ -21,7 +21,7 @@ namespace phlex::experimental { using next_index_t = std::function; // Shim type for the extern "C" entry-point: out-parameter avoids returning a C++ type // across a C-linkage boundary. - using driver_shim_t = void(driver_proxy const&, configuration const&, driver_bundle*); + using driver_shim_t = void(driver_proxy, configuration const&, driver_bundle*); }; /// @brief Bundles the driver function and data hierarchy for the framework. diff --git a/phlex/source.hpp b/phlex/source.hpp index 425377bfc..10ff114f4 100644 --- a/phlex/source.hpp +++ b/phlex/source.hpp @@ -9,35 +9,70 @@ #include namespace phlex::experimental { - /// @brief Proxy for registering source (provider) nodes. + + struct source_bundle { + configuration const& config; + tbb::flow::graph& graph; + node_catalog& nodes; + std::vector& registration_errors; + }; + + /// @brief Proxy for registering explicit provider nodes. /// /// Passed to @c PHLEX_REGISTER_PROVIDERS plugin entry points. Only provide /// registration is accessible. Users never construct this type directly. template - class source_graph_proxy : graph_proxy { + class providers_graph_proxy : graph_proxy { using base = graph_proxy; public: + providers_graph_proxy(source_bundle bundle) : + base{bundle.config, bundle.graph, bundle.nodes, bundle.registration_errors} + { + } + using base::graph_proxy; template - source_graph_proxy make(Args&&... args) + providers_graph_proxy make(Args&&... args) requires(not is_bound_object) { - return this->template bind_to(std::forward(args)...); + return this->template bind_to(std::forward(args)...); } // Only provide(...) should be accessible using base::provide; }; + /// @brief Proxy for registering source nodes. + /// + /// Passed to @c PHLEX_REGISTER_SOURCE plugin entry points. Only source + /// registration is accessible. Users never construct this type directly. + template + class source_graph_proxy : graph_proxy { + using base = graph_proxy; + + public: + source_graph_proxy(source_bundle bundle) : + base{bundle.config, bundle.graph, bundle.nodes, bundle.registration_errors} + { + } + + // Only source(...) should be accessible + using base::source; + }; + namespace detail { - using source_creator_t = void(source_graph_proxy, configuration const&); + using source_creator_t = void(source_bundle, configuration const&); } } #define PHLEX_REGISTER_PROVIDERS(...) \ - PHLEX_DETAIL_REGISTER_PLUGIN( \ + PHLEX_DETAIL_REGISTER_SOURCE_PLUGIN( \ + phlex::experimental::providers_graph_proxy, create, create_source, __VA_ARGS__) + +#define PHLEX_REGISTER_SOURCE(...) \ + PHLEX_DETAIL_REGISTER_SOURCE_PLUGIN( \ phlex::experimental::source_graph_proxy, create, create_source, __VA_ARGS__) #endif // PHLEX_SOURCE_HPP diff --git a/plugins/python/src/wrap.hpp b/plugins/python/src/wrap.hpp index a93f1e479..81cef492c 100644 --- a/plugins/python/src/wrap.hpp +++ b/plugins/python/src/wrap.hpp @@ -44,7 +44,7 @@ namespace phlex::experimental { struct py_phlex_module; // Phlex' source wrapper to register providers - typedef source_graph_proxy phlex_source_t; + typedef providers_graph_proxy phlex_source_t; PyObject* wrap_source(phlex_source_t& src); // returns new reference // PyType_Ready() modifies PyTypeObject in-place; the Python C API requires non-const. // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) From 8cd334b13589ad3fb18539d1f27baafaaaf638c8 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Thu, 4 Jun 2026 15:53:24 -0500 Subject: [PATCH 2/8] Demonstrate failure to retrieve product when using sources --- phlex/core/CMakeLists.txt | 2 + phlex/core/make_computational_edges.cpp | 31 +++++++++--- phlex/core/source.cpp | 29 +++++++++++ phlex/core/source.hpp | 23 +++++++-- test/provider_test.cpp | 65 ++++++++++++++++++++++--- 5 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 phlex/core/source.cpp diff --git a/phlex/core/CMakeLists.txt b/phlex/core/CMakeLists.txt index dd8043e29..75cd08177 100644 --- a/phlex/core/CMakeLists.txt +++ b/phlex/core/CMakeLists.txt @@ -28,6 +28,7 @@ cet_make_library( provider_node.cpp registrar.cpp registration_api.cpp + source.cpp store_counters.cpp LIBRARIES PUBLIC @@ -66,6 +67,7 @@ install( provider_node.hpp registrar.hpp registration_api.hpp + source.hpp store_counters.hpp upstream_predicates.hpp DESTINATION include/phlex/core diff --git a/phlex/core/make_computational_edges.cpp b/phlex/core/make_computational_edges.cpp index 8205301d0..7f8c07b13 100644 --- a/phlex/core/make_computational_edges.cpp +++ b/phlex/core/make_computational_edges.cpp @@ -1,5 +1,6 @@ #include "phlex/core/make_computational_edges.hpp" +#include "fmt/format.h" #include "oneapi/tbb/flow_graph.h" #include "spdlog/spdlog.h" @@ -7,6 +8,7 @@ #include #include #include +#include using namespace std::string_literals; @@ -26,14 +28,14 @@ namespace phlex::experimental { return nullptr; } - index_router::provider_input_ports_t edges_from_explicit_providers( - index_router::head_ports_t head_ports, provider_nodes& explicit_providers) + std::pair + edges_from_explicit_providers(index_router::head_ports_t head_ports, + provider_nodes& explicit_providers) { assert(!head_ports.empty()); - // FIXME: Should return a list of head ports that cannot be matched to an explicit provider. - index_router::provider_input_ports_t result; + index_router::head_ports_t unconsumed_head_ports; for (auto const& [node_name, ports] : head_ports) { for (auto const& [input_product, port] : ports) { // Find the provider that has the right product name (hidden in the @@ -47,12 +49,11 @@ namespace phlex::experimental { input_product.to_string()); make_edge(matched_provider->output_port(), *port); } else { - throw std::runtime_error("No provider found for product: "s + - input_product.to_string()); + unconsumed_head_ports[node_name].push_back({input_product, port}); } } } - return result; + return {std::move(result), std::move(unconsumed_head_ports)}; } index_router::head_ports_t edges_within_computational_graph( @@ -124,8 +125,22 @@ namespace phlex::experimental { edges_to_outputs(nodes.providers, producers, nodes.outputs); - auto provider_input_ports = + auto [provider_input_ports, unconsumed_head_ports] = edges_from_explicit_providers(std::move(head_ports), nodes.providers); + + // FIXME: This is where we'll loop over the sources to create implicit providers + + if (not unconsumed_head_ports.empty()) { + std::string error_msg{"No provider found for the following required products:\n"}; + for (auto const& [node_name, ports] : unconsumed_head_ports) { + for (auto const& [input_product, _] : ports) { + error_msg += fmt::format( + " - Node '{}' requires product '{}'\n", node_name, input_product.to_string()); + } + } + throw std::runtime_error(error_msg); + } + auto multilayer_join_index_ports = multilayer_ports(consumers); return std::make_tuple(std::move(provider_input_ports), std::move(multilayer_join_index_ports)); diff --git a/phlex/core/source.cpp b/phlex/core/source.cpp new file mode 100644 index 000000000..878f7b293 --- /dev/null +++ b/phlex/core/source.cpp @@ -0,0 +1,29 @@ +#include "phlex/core/source.hpp" + +#include + +namespace phlex::experimental { + provider_bundle::provider_bundle(provider_function_t f, + concurrency c, + product_specification spec, + std::string layer, + std::string stage) : + provider_function_{std::move(f)}, + concurrency_{c}, + spec_{std::move(spec)}, + layer_{std::move(layer)}, + stage_{std::move(stage)} + { + } + + product_specification const& provider_bundle::specification() const { return spec_; } + + identifier const& provider_bundle::layer() const { return layer_; } + + std::function provider_bundle::release_provider_function() + { + return std::move(provider_function_); + } + + concurrency provider_bundle::get_concurrency() const { return concurrency_; } +} diff --git a/phlex/core/source.hpp b/phlex/core/source.hpp index affe37834..2fd9b9542 100644 --- a/phlex/core/source.hpp +++ b/phlex/core/source.hpp @@ -3,13 +3,18 @@ #include "phlex/phlex_core_export.hpp" +#include "phlex/concurrency.hpp" #include "phlex/core/product_selector.hpp" +#include "phlex/model/algorithm_name.hpp" #include "phlex/model/index_generator.hpp" #include "phlex/model/product_specification.hpp" #include "phlex/model/products.hpp" +#include "phlex/model/type_id.hpp" +#include "phlex/utilities/simple_ptr_map.hpp" #include #include +#include namespace phlex::experimental { @@ -20,14 +25,23 @@ namespace phlex::experimental { class provider_bundle { public: - provider_bundle(product_specification, provider_function_t); + provider_bundle(provider_function_t f, + concurrency c, + product_specification spec, + std::string layer, + std::string stage); - product_specification specification() const; - product_ptr get_product(data_cell_index const&) const; + product_specification const& specification() const; + identifier const& layer() const; + std::function release_provider_function(); + concurrency get_concurrency() const; private: - product_specification spec_; std::function provider_function_; + concurrency concurrency_; + product_specification spec_; + identifier layer_; + identifier stage_; }; using provider_bundles = std::vector; @@ -42,6 +56,7 @@ namespace phlex::experimental { }; using source_ptr = std::unique_ptr; + using source_map = simple_ptr_map; } #endif // PHLEX_CORE_SOURCE_HPP diff --git a/test/provider_test.cpp b/test/provider_test.cpp index 7384f47fd..5f30cbef5 100644 --- a/test/provider_test.cpp +++ b/test/provider_test.cpp @@ -1,4 +1,5 @@ #include "phlex/core/framework_graph.hpp" +#include "phlex/core/source.hpp" #include "phlex/model/data_cell_index.hpp" #include "plugins/layer_generator.hpp" @@ -13,6 +14,7 @@ namespace toy { struct VertexCollection { std::size_t data; }; + auto make_collection(std::size_t i) { return VertexCollection{i}; } } namespace { @@ -20,15 +22,41 @@ namespace { toy::VertexCollection give_me_vertices(data_cell_index const& id) { spdlog::info("give_me_vertices: {}", id.number()); - return toy::VertexCollection{id.number()}; + return toy::make_collection(id.number()); } -} -namespace { + // Type-erased provider + experimental::product_ptr give_me_vertices_erased(data_cell_index const& id) + { + spdlog::info("give_me_vertices_erased: {}", id.number()); + return std::make_unique>( + toy::make_collection(id.number())); + } + + // Vertices source for implicit provider test + class vertices_source : public experimental::source { + public: + experimental::provider_bundles create_providers(product_selector const& selector) override + { + using namespace experimental; + provider_bundles bundles; + std::string const layer = "spill"; + std::string const stage = "previous_process"; + product_specification spec{"input", "happy_vertices", make_type_id()}; + + if (selector.match(spec, identifier{layer}, identifier{stage})) { + bundles.emplace_back( + give_me_vertices_erased, concurrency::unlimited, std::move(spec), layer, stage); + } + return bundles; + } + index_generator indices() override { co_return; } + }; + unsigned pass_on(toy::VertexCollection const& vertices) { return vertices.data; } } -TEST_CASE("provider_test") +TEST_CASE("Explicit providers") { constexpr auto max_events{3u}; @@ -50,17 +78,40 @@ TEST_CASE("provider_test") CHECK(g.execution_count("my_name_here") == max_events); } +TEST_CASE("Implicit providers") +{ + constexpr auto max_events{3u}; + + experimental::layer_generator gen; + gen.add_layer("spill", {"job", max_events, 1u}); + + experimental::framework_graph g{driver_for_test(gen)}; + + g.source("vertices_source"); + + g.transform("passer", pass_on, concurrency::unlimited) + .input_family( + product_selector{.creator = "input", .layer = "spill", .suffix = "happy_vertices"}); + + g.execute(); + + CHECK(g.execution_count("passer") == max_events); +} + TEST_CASE("Throw when no provider found for required product") { experimental::framework_graph g; // Register an observer that needs a product from a creator that does not exist // in the graph. Since there is no matching provider, make_computational_edges - // should throw "No provider found for product...". + // should throw listing all unmatched products. g.observe( "observer", [](unsigned int const) {}, concurrency::unlimited) .input_family(product_selector{.creator = "nonexistent_creator", .layer = "job"}); - CHECK_THROWS_WITH(g.execute(), - Catch::Matchers::ContainsSubstring("No provider found for product")); + CHECK_THROWS_WITH( + g.execute(), + Catch::Matchers::ContainsSubstring("No provider found for the following required products:") && + Catch::Matchers::ContainsSubstring("nonexistent_creator") && + Catch::Matchers::ContainsSubstring("job")); } From 4f4b2afcabcb6bbfd5d05b65563c06229dedfae3 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Fri, 5 Jun 2026 16:52:14 -0500 Subject: [PATCH 3/8] Support creation of implicit providers from sources --- phlex/core/framework_graph.cpp | 2 +- phlex/core/make_computational_edges.cpp | 71 +++++++++++++++++++++++-- phlex/core/make_computational_edges.hpp | 6 ++- phlex/core/source.cpp | 9 ++-- phlex/core/source.hpp | 7 +-- phlex/source.hpp | 3 ++ test/provider_test.cpp | 30 +++++++++++ 7 files changed, 113 insertions(+), 15 deletions(-) diff --git a/phlex/core/framework_graph.cpp b/phlex/core/framework_graph.cpp index 54838a78c..9e50fa0d7 100644 --- a/phlex/core/framework_graph.cpp +++ b/phlex/core/framework_graph.cpp @@ -176,7 +176,7 @@ namespace phlex::experimental { make_bookkeeping_edges(); auto [provider_input_ports, multilayer_join_index_ports] = - make_computational_edges(nodes_, filters_); + make_computational_edges(nodes_, filters_, graph_); if (provider_input_ports.empty()) { assert(multilayer_join_index_ports.empty()); diff --git a/phlex/core/make_computational_edges.cpp b/phlex/core/make_computational_edges.cpp index 7f8c07b13..dbd440f95 100644 --- a/phlex/core/make_computational_edges.cpp +++ b/phlex/core/make_computational_edges.cpp @@ -28,6 +28,16 @@ namespace phlex::experimental { return nullptr; } + provider_bundles find_matching_implicit_providers(source_map const& sources, + product_selector const& input_product) + { + provider_bundles result; + for (auto const& src : sources | std::views::values) { + result.append_range(src->create_providers(input_product)); + } + return result; + } + std::pair edges_from_explicit_providers(index_router::head_ports_t head_ports, provider_nodes& explicit_providers) @@ -56,6 +66,50 @@ namespace phlex::experimental { return {std::move(result), std::move(unconsumed_head_ports)}; } + std::pair + edges_from_implicit_providers(index_router::head_ports_t head_ports, + provider_nodes& providers, + source_map const& sources, + tbb::flow::graph& g) + { + index_router::provider_input_ports_t result; + index_router::head_ports_t unconsumed_head_ports; + for (auto const& [node_name, ports] : head_ports) { + for (auto const& [input_product, port] : ports) { + // If we have a source node that can produce this product, use it. + auto bundles = find_matching_implicit_providers(sources, input_product); + if (bundles.empty()) { + unconsumed_head_ports[node_name].push_back({input_product, port}); + continue; + } + + // For now we require only one implicit provider. This will change in the future. + if (bundles.size() > 1ull) { + auto error_msg = fmt::format( + "Multiple implicit providers found for product '{}', required by node '{}':\n", + input_product.to_string(), + node_name); + throw std::runtime_error(error_msg); + } + + auto& bundle = bundles[0]; + auto const& spec = bundle.specification(); + auto node = std::make_unique(spec.creator(), + bundle.get_concurrency().value, + g, + bundle.release_provider_function(), + spec, + bundle.layer(), + bundle.stage()); + auto const provider_name = node->name().to_string(); + result.try_emplace(provider_name, input_product, node->input_port()); + make_edge(node->output_port(), *port); + providers.try_emplace(provider_name, std::move(node)); + } + } + return {std::move(result), std::move(unconsumed_head_ports)}; + } + index_router::head_ports_t edges_within_computational_graph( producer_catalog const& producers, std::map& filters, @@ -112,7 +166,9 @@ namespace phlex::experimental { } std::tuple> - make_computational_edges(node_catalog& nodes, std::map& filters) + make_computational_edges(node_catalog& nodes, + std::map& filters, + tbb::flow::graph& g) { auto const producers = nodes.producers(); auto const consumers = nodes.consumers(); @@ -125,14 +181,15 @@ namespace phlex::experimental { edges_to_outputs(nodes.providers, producers, nodes.outputs); - auto [provider_input_ports, unconsumed_head_ports] = + auto [explicit_provider_input_ports, unconsumed_head_ports] = edges_from_explicit_providers(std::move(head_ports), nodes.providers); - // FIXME: This is where we'll loop over the sources to create implicit providers + auto [implicit_provider_input_ports, unmatched_head_ports] = edges_from_implicit_providers( + std::move(unconsumed_head_ports), nodes.providers, nodes.sources, g); - if (not unconsumed_head_ports.empty()) { + if (not unmatched_head_ports.empty()) { std::string error_msg{"No provider found for the following required products:\n"}; - for (auto const& [node_name, ports] : unconsumed_head_ports) { + for (auto const& [node_name, ports] : unmatched_head_ports) { for (auto const& [input_product, _] : ports) { error_msg += fmt::format( " - Node '{}' requires product '{}'\n", node_name, input_product.to_string()); @@ -141,6 +198,10 @@ namespace phlex::experimental { throw std::runtime_error(error_msg); } + // Combine implicit and explicit provider input ports. + auto provider_input_ports = std::move(explicit_provider_input_ports); + provider_input_ports.merge(std::move(implicit_provider_input_ports)); + auto multilayer_join_index_ports = multilayer_ports(consumers); return std::make_tuple(std::move(provider_input_ports), std::move(multilayer_join_index_ports)); diff --git a/phlex/core/make_computational_edges.hpp b/phlex/core/make_computational_edges.hpp index 2a5ec165e..8efdcd426 100644 --- a/phlex/core/make_computational_edges.hpp +++ b/phlex/core/make_computational_edges.hpp @@ -19,6 +19,8 @@ #include "phlex/core/index_router.hpp" #include "phlex/core/node_catalog.hpp" +#include "oneapi/tbb/flow_graph.h" + #include #include #include @@ -28,7 +30,9 @@ namespace phlex::experimental { PHLEX_CORE_EXPORT std::tuple> - make_computational_edges(node_catalog& nodes, std::map& filters); + make_computational_edges(node_catalog& nodes, + std::map& filters, + tbb::flow::graph& g); } diff --git a/phlex/core/source.cpp b/phlex/core/source.cpp index 878f7b293..84473b4d1 100644 --- a/phlex/core/source.cpp +++ b/phlex/core/source.cpp @@ -16,14 +16,13 @@ namespace phlex::experimental { { } - product_specification const& provider_bundle::specification() const { return spec_; } - - identifier const& provider_bundle::layer() const { return layer_; } - std::function provider_bundle::release_provider_function() { return std::move(provider_function_); } - concurrency provider_bundle::get_concurrency() const { return concurrency_; } + product_specification const& provider_bundle::specification() const noexcept { return spec_; } + identifier const& provider_bundle::layer() const noexcept { return layer_; } + identifier const& provider_bundle::stage() const noexcept { return stage_; } + concurrency provider_bundle::get_concurrency() const noexcept { return concurrency_; } } diff --git a/phlex/core/source.hpp b/phlex/core/source.hpp index 2fd9b9542..80a1b341b 100644 --- a/phlex/core/source.hpp +++ b/phlex/core/source.hpp @@ -31,10 +31,11 @@ namespace phlex::experimental { std::string layer, std::string stage); - product_specification const& specification() const; - identifier const& layer() const; std::function release_provider_function(); - concurrency get_concurrency() const; + concurrency get_concurrency() const noexcept; + product_specification const& specification() const noexcept; + identifier const& layer() const noexcept; + identifier const& stage() const noexcept; private: std::function provider_function_; diff --git a/phlex/source.hpp b/phlex/source.hpp index 10ff114f4..a79870a32 100644 --- a/phlex/source.hpp +++ b/phlex/source.hpp @@ -11,10 +11,13 @@ namespace phlex::experimental { struct source_bundle { + // Non-owning references to framework-owned resources; source_bundle is a short-lived struct. + // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) configuration const& config; tbb::flow::graph& graph; node_catalog& nodes; std::vector& registration_errors; + // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) }; /// @brief Proxy for registering explicit provider nodes. diff --git a/test/provider_test.cpp b/test/provider_test.cpp index 5f30cbef5..6b71427ce 100644 --- a/test/provider_test.cpp +++ b/test/provider_test.cpp @@ -98,6 +98,36 @@ TEST_CASE("Implicit providers") CHECK(g.execution_count("passer") == max_events); } +TEST_CASE("Throw when two sources with the same name are registered") +{ + experimental::framework_graph g; + g.source("vertices_source"); + g.source("vertices_source"); + + CHECK_THROWS_WITH( + g.execute(), + Catch::Matchers::ContainsSubstring("Node with name 'vertices_source' already exists")); +} + +TEST_CASE("Throw when two implicit providers are found for the same product") +{ + experimental::framework_graph g; + + // Register two sources that can provide the same product + g.source("vertices_source_1"); + g.source("vertices_source_2"); + + g.transform("passer", pass_on, concurrency::unlimited) + .input_family( + product_selector{.creator = "input", .layer = "spill", .suffix = "happy_vertices"}); + + CHECK_THROWS_WITH(g.execute(), + Catch::Matchers::ContainsSubstring( + "Multiple implicit providers found for product 'input/happy_vertices") && + Catch::Matchers::ContainsSubstring("spill") && + Catch::Matchers::ContainsSubstring("passer")); +} + TEST_CASE("Throw when no provider found for required product") { experimental::framework_graph g; From d95494865fdce1839f30d4a4895dafcb419518aa Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Wed, 10 Jun 2026 15:51:57 -0500 Subject: [PATCH 4/8] Replace max_events with num_spills --- test/provider_test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/provider_test.cpp b/test/provider_test.cpp index 6b71427ce..558a785ce 100644 --- a/test/provider_test.cpp +++ b/test/provider_test.cpp @@ -58,10 +58,10 @@ namespace { TEST_CASE("Explicit providers") { - constexpr auto max_events{3u}; + constexpr auto num_spills{3u}; experimental::layer_generator gen; - gen.add_layer("spill", {"job", max_events, 1u}); + gen.add_layer("spill", {"job", num_spills, 1u}); experimental::framework_graph g{driver_for_test(gen)}; @@ -74,16 +74,16 @@ TEST_CASE("Explicit providers") g.execute(); - CHECK(g.execution_count("passer") == max_events); - CHECK(g.execution_count("my_name_here") == max_events); + CHECK(g.execution_count("passer") == num_spills); + CHECK(g.execution_count("my_name_here") == num_spills); } TEST_CASE("Implicit providers") { - constexpr auto max_events{3u}; + constexpr auto num_spills{3u}; experimental::layer_generator gen; - gen.add_layer("spill", {"job", max_events, 1u}); + gen.add_layer("spill", {"job", num_spills, 1u}); experimental::framework_graph g{driver_for_test(gen)}; @@ -95,7 +95,7 @@ TEST_CASE("Implicit providers") g.execute(); - CHECK(g.execution_count("passer") == max_events); + CHECK(g.execution_count("passer") == num_spills); } TEST_CASE("Throw when two sources with the same name are registered") From b7a53d82cd3d20562fd5c9fe06e95dbf8ab4d848 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Wed, 10 Jun 2026 16:02:53 -0500 Subject: [PATCH 5/8] Some cleanups --- test/provider_test.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/test/provider_test.cpp b/test/provider_test.cpp index 558a785ce..ab496a51d 100644 --- a/test/provider_test.cpp +++ b/test/provider_test.cpp @@ -9,6 +9,7 @@ #include "spdlog/spdlog.h" using namespace phlex; +using Catch::Matchers::ContainsSubstring; namespace toy { struct VertexCollection { @@ -25,7 +26,7 @@ namespace { return toy::make_collection(id.number()); } - // Type-erased provider + // Type-erased provider function experimental::product_ptr give_me_vertices_erased(data_cell_index const& id) { spdlog::info("give_me_vertices_erased: {}", id.number()); @@ -104,9 +105,8 @@ TEST_CASE("Throw when two sources with the same name are registered") g.source("vertices_source"); g.source("vertices_source"); - CHECK_THROWS_WITH( - g.execute(), - Catch::Matchers::ContainsSubstring("Node with name 'vertices_source' already exists")); + CHECK_THROWS_WITH(g.execute(), + ContainsSubstring("Node with name 'vertices_source' already exists")); } TEST_CASE("Throw when two implicit providers are found for the same product") @@ -121,27 +121,24 @@ TEST_CASE("Throw when two implicit providers are found for the same product") .input_family( product_selector{.creator = "input", .layer = "spill", .suffix = "happy_vertices"}); - CHECK_THROWS_WITH(g.execute(), - Catch::Matchers::ContainsSubstring( - "Multiple implicit providers found for product 'input/happy_vertices") && - Catch::Matchers::ContainsSubstring("spill") && - Catch::Matchers::ContainsSubstring("passer")); + CHECK_THROWS_WITH( + g.execute(), + ContainsSubstring("Multiple implicit providers found for product 'input/happy_vertices") && + ContainsSubstring("spill") && ContainsSubstring("passer")); } TEST_CASE("Throw when no provider found for required product") { experimental::framework_graph g; - // Register an observer that needs a product from a creator that does not exist - // in the graph. Since there is no matching provider, make_computational_edges - // should throw listing all unmatched products. + // Register an observer that needs a product from a creator that does not exist in the graph. + // Since there is no matching provider, make_computational_edges should throw listing all + // unmatched products. g.observe( "observer", [](unsigned int const) {}, concurrency::unlimited) .input_family(product_selector{.creator = "nonexistent_creator", .layer = "job"}); - CHECK_THROWS_WITH( - g.execute(), - Catch::Matchers::ContainsSubstring("No provider found for the following required products:") && - Catch::Matchers::ContainsSubstring("nonexistent_creator") && - Catch::Matchers::ContainsSubstring("job")); + CHECK_THROWS_WITH(g.execute(), + ContainsSubstring("No provider found for the following required products:") && + ContainsSubstring("nonexistent_creator") && ContainsSubstring("job")); } From 4faca3acfcc15e7272cb904d7f1cd56edb1e9549 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Wed, 10 Jun 2026 17:15:53 -0500 Subject: [PATCH 6/8] Change provider_bundle from class to struct --- phlex/core/CMakeLists.txt | 1 - phlex/core/make_computational_edges.cpp | 10 ++++----- phlex/core/source.cpp | 28 ------------------------- phlex/core/source.hpp | 26 ++++++----------------- test/provider_test.cpp | 7 +++++-- 5 files changed, 16 insertions(+), 56 deletions(-) delete mode 100644 phlex/core/source.cpp diff --git a/phlex/core/CMakeLists.txt b/phlex/core/CMakeLists.txt index 75cd08177..1e8f0f457 100644 --- a/phlex/core/CMakeLists.txt +++ b/phlex/core/CMakeLists.txt @@ -28,7 +28,6 @@ cet_make_library( provider_node.cpp registrar.cpp registration_api.cpp - source.cpp store_counters.cpp LIBRARIES PUBLIC diff --git a/phlex/core/make_computational_edges.cpp b/phlex/core/make_computational_edges.cpp index dbd440f95..35a3fc9ee 100644 --- a/phlex/core/make_computational_edges.cpp +++ b/phlex/core/make_computational_edges.cpp @@ -93,14 +93,14 @@ namespace phlex::experimental { } auto& bundle = bundles[0]; - auto const& spec = bundle.specification(); + auto const& spec = bundle.spec; auto node = std::make_unique(spec.creator(), - bundle.get_concurrency().value, + bundle.max_concurrency.value, g, - bundle.release_provider_function(), + std::move(bundle.provider_function), spec, - bundle.layer(), - bundle.stage()); + identifier{bundle.layer}, + identifier{bundle.stage}); auto const provider_name = node->name().to_string(); result.try_emplace(provider_name, input_product, node->input_port()); make_edge(node->output_port(), *port); diff --git a/phlex/core/source.cpp b/phlex/core/source.cpp deleted file mode 100644 index 84473b4d1..000000000 --- a/phlex/core/source.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "phlex/core/source.hpp" - -#include - -namespace phlex::experimental { - provider_bundle::provider_bundle(provider_function_t f, - concurrency c, - product_specification spec, - std::string layer, - std::string stage) : - provider_function_{std::move(f)}, - concurrency_{c}, - spec_{std::move(spec)}, - layer_{std::move(layer)}, - stage_{std::move(stage)} - { - } - - std::function provider_bundle::release_provider_function() - { - return std::move(provider_function_); - } - - product_specification const& provider_bundle::specification() const noexcept { return spec_; } - identifier const& provider_bundle::layer() const noexcept { return layer_; } - identifier const& provider_bundle::stage() const noexcept { return stage_; } - concurrency provider_bundle::get_concurrency() const noexcept { return concurrency_; } -} diff --git a/phlex/core/source.hpp b/phlex/core/source.hpp index 80a1b341b..a21eab1e0 100644 --- a/phlex/core/source.hpp +++ b/phlex/core/source.hpp @@ -23,26 +23,12 @@ namespace phlex::experimental { // Function type for type-erased data-product types (used by implicit providers) using provider_function_t = product_ptr(data_cell_index const&); - class provider_bundle { - public: - provider_bundle(provider_function_t f, - concurrency c, - product_specification spec, - std::string layer, - std::string stage); - - std::function release_provider_function(); - concurrency get_concurrency() const noexcept; - product_specification const& specification() const noexcept; - identifier const& layer() const noexcept; - identifier const& stage() const noexcept; - - private: - std::function provider_function_; - concurrency concurrency_; - product_specification spec_; - identifier layer_; - identifier stage_; + struct provider_bundle { + std::function provider_function; + concurrency max_concurrency; + product_specification spec; + std::string layer; + std::string stage; }; using provider_bundles = std::vector; diff --git a/test/provider_test.cpp b/test/provider_test.cpp index ab496a51d..7c6b887b3 100644 --- a/test/provider_test.cpp +++ b/test/provider_test.cpp @@ -46,8 +46,11 @@ namespace { product_specification spec{"input", "happy_vertices", make_type_id()}; if (selector.match(spec, identifier{layer}, identifier{stage})) { - bundles.emplace_back( - give_me_vertices_erased, concurrency::unlimited, std::move(spec), layer, stage); + bundles.push_back(provider_bundle{.provider_function = give_me_vertices_erased, + .max_concurrency = concurrency::unlimited, + .spec = std::move(spec), + .layer = layer, + .stage = stage}); } return bundles; } From f892241cb2eb7ca36dcba1ff7bca7b15638a8d10 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Thu, 11 Jun 2026 09:17:10 -0500 Subject: [PATCH 7/8] Rename return variable --- phlex/core/make_computational_edges.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/phlex/core/make_computational_edges.cpp b/phlex/core/make_computational_edges.cpp index 35a3fc9ee..c73e279c8 100644 --- a/phlex/core/make_computational_edges.cpp +++ b/phlex/core/make_computational_edges.cpp @@ -44,7 +44,7 @@ namespace phlex::experimental { { assert(!head_ports.empty()); - index_router::provider_input_ports_t result; + index_router::provider_input_ports_t provider_input_ports; index_router::head_ports_t unconsumed_head_ports; for (auto const& [node_name, ports] : head_ports) { for (auto const& [input_product, port] : ports) { @@ -52,7 +52,8 @@ namespace phlex::experimental { // output port) and the right family (hidden in the input port). if (auto* matched_provider = find_matching_provider(explicit_providers, input_product)) { auto const provider_name = matched_provider->name().to_string(); - result.try_emplace(provider_name, input_product, matched_provider->input_port()); + provider_input_ports.try_emplace( + provider_name, input_product, matched_provider->input_port()); spdlog::debug("Connecting provider {} to node {} (product: {})", provider_name, node_name, @@ -63,7 +64,7 @@ namespace phlex::experimental { } } } - return {std::move(result), std::move(unconsumed_head_ports)}; + return {std::move(provider_input_ports), std::move(unconsumed_head_ports)}; } std::pair @@ -72,7 +73,7 @@ namespace phlex::experimental { source_map const& sources, tbb::flow::graph& g) { - index_router::provider_input_ports_t result; + index_router::provider_input_ports_t provider_input_ports; index_router::head_ports_t unconsumed_head_ports; for (auto const& [node_name, ports] : head_ports) { for (auto const& [input_product, port] : ports) { @@ -102,12 +103,12 @@ namespace phlex::experimental { identifier{bundle.layer}, identifier{bundle.stage}); auto const provider_name = node->name().to_string(); - result.try_emplace(provider_name, input_product, node->input_port()); + provider_input_ports.try_emplace(provider_name, input_product, node->input_port()); make_edge(node->output_port(), *port); providers.try_emplace(provider_name, std::move(node)); } } - return {std::move(result), std::move(unconsumed_head_ports)}; + return {std::move(provider_input_ports), std::move(unconsumed_head_ports)}; } index_router::head_ports_t edges_within_computational_graph( From 9f58c3835c3af1a9b64edfc15baf6102de9e8e30 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Thu, 11 Jun 2026 12:47:28 -0500 Subject: [PATCH 8/8] Change error message --- phlex/core/glue.hpp | 2 +- phlex/core/registrar.cpp | 6 ++++-- phlex/core/registrar.hpp | 3 ++- test/core_misc_test.cpp | 2 +- test/provider_test.cpp | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/phlex/core/glue.hpp b/phlex/core/glue.hpp index 1081f829b..75c54412e 100644 --- a/phlex/core/glue.hpp +++ b/phlex/core/glue.hpp @@ -157,7 +157,7 @@ namespace phlex::experimental { auto [_, inserted] = nodes_.sources.try_emplace(name, std::make_unique(std::forward(args)...)); if (not inserted) { - detail::add_to_error_messages(errors_, name); // From registrar.hpp + detail::add_to_error_messages(errors_, "Source", name); // From registrar.hpp } } diff --git a/phlex/core/registrar.cpp b/phlex/core/registrar.cpp index a9878349e..ad80c4f35 100644 --- a/phlex/core/registrar.cpp +++ b/phlex/core/registrar.cpp @@ -3,8 +3,10 @@ #include "fmt/format.h" namespace phlex::experimental::detail { - void add_to_error_messages(std::vector& errors, std::string const& name) + void add_to_error_messages(std::vector& errors, + std::string const& entity, + std::string const& name) { - errors.push_back(fmt::format("Node with name '{}' already exists", name)); + errors.push_back(fmt::format("{} with name '{}' already exists", entity, name)); } } diff --git a/phlex/core/registrar.hpp b/phlex/core/registrar.hpp index d8c0b3abd..d96c1a38d 100644 --- a/phlex/core/registrar.hpp +++ b/phlex/core/registrar.hpp @@ -61,6 +61,7 @@ namespace phlex::experimental { namespace detail { PHLEX_CORE_EXPORT void add_to_error_messages(std::vector& errors, + std::string const& entity, std::string const& name); } @@ -115,7 +116,7 @@ namespace phlex::experimental { auto name = ptr->name().to_string(); auto [_, inserted] = nodes_->try_emplace(name, std::move(ptr)); if (not inserted) { - detail::add_to_error_messages(*errors_, name); + detail::add_to_error_messages(*errors_, "Node", name); } } diff --git a/test/core_misc_test.cpp b/test/core_misc_test.cpp index 259aebe45..1a092a67f 100644 --- a/test/core_misc_test.cpp +++ b/test/core_misc_test.cpp @@ -89,7 +89,7 @@ TEST_CASE("add_to_error_messages tests", "[core]") using namespace phlex::experimental::detail; std::vector errors; - add_to_error_messages(errors, "duplicate_node"); + add_to_error_messages(errors, "Node", "duplicate_node"); REQUIRE(errors.size() == 1); CHECK(errors[0].find("duplicate_node") != std::string::npos); diff --git a/test/provider_test.cpp b/test/provider_test.cpp index 7c6b887b3..7f0917825 100644 --- a/test/provider_test.cpp +++ b/test/provider_test.cpp @@ -109,7 +109,7 @@ TEST_CASE("Throw when two sources with the same name are registered") g.source("vertices_source"); CHECK_THROWS_WITH(g.execute(), - ContainsSubstring("Node with name 'vertices_source' already exists")); + ContainsSubstring("Source with name 'vertices_source' already exists")); } TEST_CASE("Throw when two implicit providers are found for the same product")