Files
MeanField_SERiF/tests/utils/dependencies/snapshots.cpp
Emily Boudreaux cb20faeb3d test(tests): added test suite
The test suite is extensive. Note that LLMs were used to aid in the development of the test suite. All LLM generated code was manually reviewed.
2026-11-09 13:53:17 -05:00

138 lines
5.9 KiB
C++

#include "serif/utils/dependencies/dependencies.hpp"
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <numeric>
#include <utility>
#include <vector>
namespace {
namespace dependencies = serif::utils::dependencies;
struct Source {};
struct Unused {};
struct Result {};
struct OtherResult {};
struct Untrackable {};
struct View {
const int& binding;
const int& value;
};
struct ViewTracking {
/// Snapshot content without retaining the temporary view.
int capture(const View& view) const { return view.value; }
/// Vector storage may move or be copied without changing its value.
bool matches(const int& captured, const View& view) const { return captured == view.value; }
/// The persistent role, rather than its temporary wrapper, defines identity.
const void* identity(const View& view) const { return &view.binding; }
};
struct RevisionedSource {
std::unique_ptr<int> value;
unsigned revision{0};
};
struct OwnedSnapshot {
int& captures;
int& comparisons;
/// Exercise an owning snapshot that cannot itself be copied.
std::unique_ptr<int> capture(const RevisionedSource& source) const {
++captures;
return std::make_unique<int>(*source.value);
}
/// Compare the frozen content, independent of source revision counters.
bool matches(const std::unique_ptr<int>& captured, const RevisionedSource& source) const {
++comparisons;
return *captured == *source.value;
}
};
}
TEST_CASE("Dependency views can preserve underlying identity across temporary wrappers", "[unit][utils][dependencies]") {
int binding = 1, other_binding = 1, value = 4, copied_value = 4;
const auto context = dependencies::make_dependency_context(dependencies::own<Source>(View{binding, value}, ViewTracking{}));
auto graph = dependencies::make_computation_graph(dependencies::make_computation<Result, dependencies::InputList<Source>>([](const View& view) { return view.value; }));
auto cache = dependencies::make_computation_cache(std::move(graph), context);
REQUIRE(cache.try_prepare(context));
const auto copied = dependencies::make_dependency_context(dependencies::own<Source>(View{binding, copied_value}, ViewTracking{}));
auto report = cache.try_prepare(copied);
REQUIRE(report);
CHECK(report->reused_count() == 1);
const auto rebound = dependencies::make_dependency_context(dependencies::own<Source>(View{other_binding, copied_value}, ViewTracking{}));
report = cache.try_prepare(rebound);
REQUIRE(report);
CHECK(report->computed_count() == 1);
copied_value = 8;
REQUIRE(cache.try_prepare(rebound));
CHECK(cache.get<Result>() == 8);
}
TEST_CASE("Dependency value snapshots detect in-place container changes", "[unit][utils][dependencies]") {
std::vector<int> values{2, 3};
int calls = 0;
auto context = dependencies::make_dependency_context(dependencies::borrow<Source>(values), dependencies::own<Unused>(Untrackable{}));
auto graph = dependencies::make_computation_graph(dependencies::make_computation<Result, dependencies::InputList<Source>>([&calls](const std::vector<int>& input) { ++calls; return std::accumulate(input.begin(), input.end(), 0); }));
auto cache = dependencies::make_computation_cache(std::move(graph), context);
REQUIRE(cache.try_prepare(context));
CHECK(cache.get<Result>() == 5);
values[1] = 7;
REQUIRE(cache.try_prepare(context));
CHECK(cache.get<Result>() == 9);
CHECK(calls == 2);
values.push_back(4);
REQUIRE(cache.try_prepare(context));
CHECK(cache.get<Result>() == 13);
CHECK(calls == 3);
REQUIRE(cache.try_prepare(context));
CHECK(calls == 3);
}
TEST_CASE("A dependency projection tracks noncopyable sources by revision", "[unit][utils][dependencies]") {
RevisionedSource source{.value = std::make_unique<int>(2)};
int calls = 0;
auto context = dependencies::make_dependency_context(dependencies::borrow<Source>(source,
dependencies::track_by([](const RevisionedSource& input) { return input.revision; })));
auto graph = dependencies::make_computation_graph(dependencies::make_computation<Result, dependencies::InputList<Source>>([&calls](const RevisionedSource& input) { ++calls; return *input.value; }));
auto cache = dependencies::make_computation_cache(std::move(graph), context);
REQUIRE(cache.try_prepare(context));
REQUIRE(cache.try_prepare(context));
CHECK(calls == 1);
*source.value = 8;
++source.revision;
REQUIRE(cache.try_prepare(context));
CHECK(calls == 2);
CHECK(cache.get<Result>() == 8);
}
TEST_CASE("Required sources are inspected once and can use move-only snapshot state", "[unit][utils][dependencies]") {
RevisionedSource source{.value = std::make_unique<int>(2)};
int captures = 0;
int comparisons = 0;
auto context = dependencies::make_dependency_context(dependencies::borrow<Source>(source,
OwnedSnapshot{.captures = captures, .comparisons = comparisons}));
auto graph = dependencies::make_computation_graph(
dependencies::make_computation<Result, dependencies::InputList<Source>>([](const RevisionedSource& input) { return *input.value; }),
dependencies::make_computation<OtherResult, dependencies::InputList<Source>>([](const RevisionedSource& input) { return 2 * *input.value; })
);
auto cache = dependencies::make_computation_cache(std::move(graph), context);
REQUIRE(cache.try_prepare(context));
CHECK(captures == 1);
CHECK(comparisons == 0);
REQUIRE(cache.try_prepare(context));
CHECK(captures == 1);
CHECK(comparisons == 1);
*source.value = 9;
REQUIRE(cache.try_prepare(context));
CHECK(captures == 2);
CHECK(comparisons == 2);
CHECK(cache.get<Result>() == 9);
CHECK(cache.get<OtherResult>() == 18);
}