#include "serif/utils/dependencies/dependencies.hpp" #include #include #include #include namespace { namespace dependencies = serif::utils::dependencies; struct Owned {}; struct Borrowed {}; struct Result {}; } TEST_CASE("Dependency contexts distinguish ownership and borrowed roles", "[unit][utils][dependencies]") { int value = 3; auto context = dependencies::make_dependency_context(dependencies::own(value), dependencies::borrow(value)); auto selection = context.select(dependencies::KeyList{}); value = 8; CHECK(context.get() == 3); CHECK(context.get() == 8); CHECK(std::addressof(selection.get()) == std::addressof(value)); auto graph = dependencies::make_computation_graph(dependencies::make_computation>([](int input) { return 2 * input; })); auto cache = dependencies::make_computation_cache(std::move(graph), selection); REQUIRE(cache.try_prepare(selection)); CHECK(cache.get() == 16); value = 9; CHECK(cache.get() == 16); // An accepted result remains frozen until explicitly refreshed. REQUIRE(cache.try_prepare(selection)); CHECK(cache.get() == 18); } TEST_CASE("Replacing a borrowed source invalidates its identity even at equal value", "[unit][utils][dependencies]") { int original = 4; int replacement = 4; int calls = 0; auto context = dependencies::make_dependency_context(dependencies::borrow(original)); auto graph = dependencies::make_computation_graph(dependencies::make_computation>([&calls](int input) { ++calls; return input; })); auto cache = dependencies::make_computation_cache(std::move(graph), context); REQUIRE(cache.try_prepare(context)); context = dependencies::make_dependency_context(dependencies::borrow(replacement)); auto report = cache.try_prepare(context); REQUIRE(report); CHECK(report->source_changed()); CHECK(calls == 2); original = 7; REQUIRE(cache.try_prepare(context)); CHECK(calls == 2); CHECK(cache.get() == 4); } TEST_CASE("ComputationCache dependency results can move without leaving readable moved-from state", "[unit][utils][dependencies]") { const auto context = dependencies::make_dependency_context(); auto graph = dependencies::make_computation_graph(dependencies::make_computation>([coefficient = std::make_unique(7)] { return std::make_unique(*coefficient); })); auto original = dependencies::make_computation_cache(std::move(graph), context); REQUIRE(original.try_prepare(context)); auto moved = std::move(original); CHECK_FALSE(original.ready()); CHECK_FALSE(original.contains()); CHECK_THROWS_AS(original.get(), std::logic_error); REQUIRE(moved.contains()); CHECK(*moved.get() == 7); const auto report = moved.try_prepare(context); REQUIRE(report); CHECK(report->reused_count() == 1); } TEST_CASE("Combining disjoint contexts preserves owned values borrowed identities and selected tracking policies", "[unit][utils][dependencies]") { int value = 4; const auto borrowed = dependencies::make_dependency_context(dependencies::borrow(value, dependencies::track_by([](const int input) { return input / 10; }))); const auto context = dependencies::combine_dependency_contexts( dependencies::make_dependency_context(dependencies::own(3)), borrowed.select(dependencies::KeyList{})); CHECK(context.get() == 3); CHECK(&context.get() == &value); STATIC_CHECK(std::same_as>); auto cache = dependencies::make_computation_cache(context, dependencies::make_computation>([](const int owned, const int input) { return owned + input / 10; })); REQUIRE(cache.try_prepare(context)); CHECK(cache.get() == 3); value = 8; const auto unchanged = cache.try_prepare(context); REQUIRE(unchanged); CHECK(unchanged->reused_count() == 1); value = 12; const auto changed = cache.try_prepare(context); REQUIRE(changed); CHECK(changed->source_changed()); CHECK(cache.get() == 4); }