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:
137
tests/utils/dependencies/snapshots.cpp
Normal file
137
tests/utils/dependencies/snapshots.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user