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,77 @@
#include <memory>
#include <tuple>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
#include "serif/discretization/blocks/fields.hpp"
#include "serif/equations/concepts.hpp"
#include "serif/operators/contributions.hpp"
namespace {
/// Record exactly when an equation's owned numerical coefficient is destroyed.
struct TrackedCoefficient {
double value;
std::shared_ptr<int> destructions;
/// Construct the tracked value without an intermediate temporary.
TrackedCoefficient(double coefficient, std::shared_ptr<int> count) : value(coefficient), destructions(std::move(count)) {}
/// Release the observable coefficient lifetime.
~TrackedCoefficient() { ++*destructions; }
};
/// A move-only leaf prevents traversal from concealing lifetime mistakes through equation copies.
struct TrackedEquation {
using Test = serif::discretization::blocks::density::mass;
using Density = serif::discretization::forms::Operand<Test>;
using Operands = serif::utils::types::TypeList<Density>;
static constexpr std::string_view name = "tracked equation";
std::unique_ptr<TrackedCoefficient> slope;
/// Own a numerical coefficient whose destruction remains observable outside this equation.
TrackedEquation(double coefficient, const std::shared_ptr<int>& count) : slope(std::make_unique<TrackedCoefficient>(coefficient, count)) {}
/// Evaluate a linear residual using the retained coefficient.
[[nodiscard]] serif::equations::PointLinearization<Operands> evaluate(const serif::equations::EquationPoint<Operands>& point) const {
serif::equations::PointLinearization<Operands> result;
result.residual = slope->value * point.fields.get<Density>();
result.field_derivatives.get<Density>() = slope->value;
return result;
}
};
/// Generate a nonempty move-only contribution tuple by value, as mixed gravity generates its leaves.
struct GeneratedEquation {
using Test = TrackedEquation::Test;
static constexpr std::string_view name = "generated contribution";
double slope;
std::shared_ptr<int> destructions;
/// The caller must retain this tuple if it wants to retain references to its equation.
[[nodiscard]] auto contributions() const { return std::tuple{TrackedEquation{slope, destructions}}; }
};
static_assert(serif::equations::Equation<GeneratedEquation>);
static_assert(!std::is_copy_constructible_v<TrackedEquation>);
}
TEST_CASE("Contribution bindings retain generated tuples and borrowed move-only leaves", "[equations][contributions][unit]") {
using namespace serif::equations;
const auto destructions = std::make_shared<int>(0);
auto owner = std::make_shared<const GeneratedEquation>(3.0, destructions);
{
const auto leaves = serif::operators::bind_contributions(owner);
STATIC_CHECK(std::tuple_size_v<decltype(leaves)> == 1);
owner.reset();
// The generated tuple remains alive after its parent equation is released.
REQUIRE(*destructions == 0);
CHECK(std::get<0>(leaves).coefficient == 1.0);
EquationPoint<TrackedEquation::Operands> point;
point.fields.get<TrackedEquation::Density>() = 4.0;
CHECK(std::get<0>(leaves).equation->evaluate(point).residual == 12.0);
}
CHECK(*destructions == 1);
}