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:
203
tests/utils/dependencies/ownership.cpp
Normal file
203
tests/utils/dependencies/ownership.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "serif/utils/dependencies/dependencies.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace dependencies = serif::utils::dependencies;
|
||||
struct Source
|
||||
{
|
||||
};
|
||||
struct Result
|
||||
{
|
||||
};
|
||||
struct OtherResult
|
||||
{
|
||||
};
|
||||
struct FinalResult
|
||||
{
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Shared dependency results survive refresh rejection invalidation and cache destruction", "[unit][utils][dependencies]")
|
||||
{
|
||||
std::shared_ptr<const std::vector<int>> first;
|
||||
std::shared_ptr<const std::vector<int>> second;
|
||||
std::weak_ptr<const std::vector<int>> released;
|
||||
{
|
||||
int value = 4;
|
||||
auto context = dependencies::make_dependency_context(dependencies::borrow<Source>(value));
|
||||
auto graph = dependencies::make_computation_graph(dependencies::make_computation<Result, dependencies::InputList<Source>>(
|
||||
[](const int& input) -> std::expected<std::vector<int>, std::string>
|
||||
{
|
||||
if (input < 0)
|
||||
{
|
||||
return std::unexpected("negative trial");
|
||||
}
|
||||
return std::vector<int>{input, input + 1};
|
||||
}));
|
||||
auto cache = dependencies::make_computation_cache<std::string>(std::move(graph), context);
|
||||
STATIC_CHECK(std::same_as<decltype(cache.share<Result>()), std::shared_ptr<const std::vector<int>>>);
|
||||
CHECK_THROWS(cache.share<Result>());
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
first = cache.share<Result>();
|
||||
CHECK((*first == std::vector<int>{4, 5}));
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
CHECK(cache.share<Result>() == first);
|
||||
value = 9;
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
second = cache.share<Result>();
|
||||
CHECK(second != first);
|
||||
CHECK((*first == std::vector<int>{4, 5}));
|
||||
CHECK((*second == std::vector<int>{9, 10}));
|
||||
value = -1;
|
||||
CHECK_FALSE(cache.try_prepare(context));
|
||||
CHECK_FALSE(cache.ready());
|
||||
CHECK_THROWS(cache.share<Result>());
|
||||
CHECK((*first == std::vector<int>{4, 5}));
|
||||
CHECK((*second == std::vector<int>{9, 10}));
|
||||
value = 9;
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
CHECK(cache.share<Result>() == second);
|
||||
cache.invalidate();
|
||||
CHECK_THROWS(cache.share<Result>());
|
||||
CHECK((*second == std::vector<int>{9, 10}));
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
released = cache.share<Result>();
|
||||
CHECK_FALSE(released.expired());
|
||||
}
|
||||
CHECK(released.expired());
|
||||
CHECK((*first == std::vector<int>{4, 5}));
|
||||
CHECK((*second == std::vector<int>{9, 10}));
|
||||
released = first;
|
||||
first.reset();
|
||||
CHECK(released.expired());
|
||||
}
|
||||
|
||||
TEST_CASE("Only currently requested dependency results can be shared while retained snapshots stay valid", "[unit][utils][dependencies]")
|
||||
{
|
||||
const auto context = dependencies::make_dependency_context(dependencies::own<Source>(3));
|
||||
auto graph = dependencies::make_computation_graph(
|
||||
dependencies::make_computation<Result, dependencies::InputList<Source>>([](const int& value)
|
||||
{
|
||||
return value;
|
||||
}),
|
||||
dependencies::make_computation<OtherResult, dependencies::InputList<Source>>([](const int& value)
|
||||
{
|
||||
return 2 * value;
|
||||
}));
|
||||
auto cache = dependencies::make_computation_cache(std::move(graph), context);
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
const auto previous = cache.share<OtherResult>();
|
||||
REQUIRE(cache.try_prepare(context, dependencies::KeyList<Result>{}));
|
||||
CHECK_THROWS(cache.share<OtherResult>());
|
||||
CHECK(*previous == 6);
|
||||
CHECK(*cache.share<Result>() == 3);
|
||||
auto moved = std::move(cache);
|
||||
CHECK_THROWS(cache.share<Result>());
|
||||
CHECK(*moved.share<Result>() == 3);
|
||||
CHECK(*previous == 6);
|
||||
}
|
||||
|
||||
TEST_CASE("Owning computations retain the exact move-only prerequisite across cache generations and owner lifetimes", "[unit][utils][dependencies]")
|
||||
{
|
||||
using Snapshot = std::shared_ptr<const std::unique_ptr<int>>;
|
||||
Snapshot first;
|
||||
Snapshot second;
|
||||
std::weak_ptr<const std::unique_ptr<int>> unretained;
|
||||
{
|
||||
int value = 4;
|
||||
auto context = dependencies::make_dependency_context(dependencies::borrow<Source>(value));
|
||||
auto graph = dependencies::make_computation_graph(
|
||||
dependencies::make_owning_computation<OtherResult, dependencies::InputList<Result>>([](Snapshot prerequisite)
|
||||
{
|
||||
return prerequisite;
|
||||
}),
|
||||
dependencies::make_computation<Result, dependencies::InputList<Source>>([](const int input)
|
||||
{
|
||||
return std::make_unique<int>(input);
|
||||
}),
|
||||
dependencies::make_computation<FinalResult, dependencies::InputList<OtherResult>>([](const Snapshot& input)
|
||||
{
|
||||
return **input + 1;
|
||||
}));
|
||||
auto cache = dependencies::make_computation_cache(std::move(graph), context);
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
first = cache.get<OtherResult>();
|
||||
CHECK(first == cache.share<Result>());
|
||||
CHECK(cache.get<FinalResult>() == 5);
|
||||
|
||||
const auto reused = cache.try_prepare(context);
|
||||
REQUIRE(reused);
|
||||
CHECK(reused->reused_count() == 3);
|
||||
CHECK(cache.get<OtherResult>() == first);
|
||||
|
||||
value = 9;
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
second = cache.get<OtherResult>();
|
||||
CHECK(second == cache.share<Result>());
|
||||
CHECK(second != first);
|
||||
CHECK(**first == 4);
|
||||
CHECK(**second == 9);
|
||||
CHECK(cache.get<FinalResult>() == 10);
|
||||
|
||||
cache.invalidate();
|
||||
CHECK(**first == 4);
|
||||
CHECK(**second == 9);
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
unretained = cache.share<Result>();
|
||||
CHECK_FALSE(unretained.expired());
|
||||
}
|
||||
CHECK(unretained.expired());
|
||||
CHECK(**first == 4);
|
||||
CHECK(**second == 9);
|
||||
}
|
||||
|
||||
TEST_CASE("Rejected owning computations release candidate prerequisites and preserve the accepted generation", "[unit][utils][dependencies]")
|
||||
{
|
||||
using Snapshot = std::shared_ptr<const std::unique_ptr<int>>;
|
||||
int value = 4;
|
||||
std::weak_ptr<const std::unique_ptr<int>> rejected;
|
||||
auto context = dependencies::make_dependency_context(dependencies::borrow<Source>(value));
|
||||
auto graph = dependencies::make_computation_graph(
|
||||
dependencies::make_computation<Result, dependencies::InputList<Source>>([](const int input)
|
||||
{
|
||||
return std::make_unique<int>(input);
|
||||
}),
|
||||
dependencies::make_owning_computation<OtherResult, dependencies::InputList<Result>>([&rejected](Snapshot prerequisite) -> std::expected<Snapshot, std::string>
|
||||
{
|
||||
if (**prerequisite < 0)
|
||||
{
|
||||
rejected = prerequisite;
|
||||
return std::unexpected("negative candidate");
|
||||
}
|
||||
return prerequisite;
|
||||
}));
|
||||
auto cache = dependencies::make_computation_cache(std::move(graph), context);
|
||||
REQUIRE(cache.try_prepare(context));
|
||||
const Snapshot accepted = cache.get<OtherResult>();
|
||||
|
||||
value = -1;
|
||||
const auto failure = cache.try_prepare(context);
|
||||
REQUIRE_FALSE(failure);
|
||||
CHECK(failure.error().error == "negative candidate");
|
||||
CHECK(rejected.expired());
|
||||
CHECK_FALSE(cache.ready());
|
||||
CHECK(**accepted == 4);
|
||||
|
||||
value = 4;
|
||||
const auto recovered = cache.try_prepare(context);
|
||||
REQUIRE(recovered);
|
||||
CHECK(recovered->reused_count() == 2);
|
||||
CHECK(cache.get<OtherResult>() == accepted);
|
||||
|
||||
const auto rebuilt = cache.try_prepare(context, dependencies::KeyList<OtherResult>{}, dependencies::PreparationMode::rebuild);
|
||||
REQUIRE(rebuilt);
|
||||
CHECK(rebuilt->computed_count() == 2);
|
||||
CHECK(cache.get<OtherResult>() != accepted);
|
||||
CHECK(**accepted == 4);
|
||||
}
|
||||
Reference in New Issue
Block a user