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.
This commit is contained in:
2026-11-09 13:53:17 -05:00
parent 74e55a6859
commit cb20faeb3d
202 changed files with 35505 additions and 953 deletions

View File

@@ -0,0 +1,137 @@
#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);
}