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.
138 lines
5.9 KiB
C++
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);
|
|
}
|