[M5+] add resolver::verify_link + provisional overlay lifecycle

This commit is contained in:
2026-05-10 10:32:58 +00:00
parent 941d5b3284
commit 816ec993cd
9 changed files with 490 additions and 2 deletions

118
tests/verify_link_unit.cpp Normal file
View File

@@ -0,0 +1,118 @@
// Unit tests for resolver::verify_link with a mocked BuildFn that never
// invokes nix or cmake. Covers the provisional-row lifecycle and the
// scratch-project scaffolding without paying the cost of a real build.
#include <catch2/catch_test_macros.hpp>
import cargoxx.resolver;
import cargoxx.linkdb;
import cargoxx.manifest;
import cargoxx.util;
import std;
using cargoxx::resolver::verify_link;
using cargoxx::resolver::VerifyLinkRequest;
using cargoxx::linkdb::Database;
using cargoxx::linkdb::Recipe;
namespace {
auto fresh_dir() -> std::filesystem::path {
auto d = std::filesystem::temp_directory_path() /
std::format("cargoxx-verify-link-test-{}", std::random_device{}());
std::filesystem::create_directories(d);
return d;
}
auto sample_recipe() -> Recipe {
return Recipe{
.nixpkgs_attr = "fmt",
.find_package = "fmt CONFIG REQUIRED",
.targets = {"fmt::fmt"},
.source = "conan",
};
}
auto make_request(const std::filesystem::path& parent) -> VerifyLinkRequest {
return VerifyLinkRequest{
.candidate = sample_recipe(),
.source = "conan",
.package_name = "fmt",
.version_spec = "*",
.components = {},
.overlay_path = parent / "overlay.sqlite",
.scratch_root = parent / "scratch",
};
}
} // namespace
TEST_CASE("verify_link confirms the provisional row when the build succeeds",
"[resolver][verify_link]") {
auto parent = fresh_dir();
auto req = make_request(parent);
bool build_called = false;
auto r = verify_link(req, [&](const std::filesystem::path& root) {
build_called = true;
// Sanity-check the scaffold: the manifest and main.cpp must exist
// in the dir handed to the build function.
REQUIRE(std::filesystem::exists(root / "Cargoxx.toml"));
REQUIRE(std::filesystem::exists(root / "src" / "main.cpp"));
auto m = cargoxx::manifest::parse(root / "Cargoxx.toml");
REQUIRE(m.has_value());
REQUIRE(m->dependencies.size() == 1);
REQUIRE(m->dependencies[0].name == "fmt");
return cargoxx::util::Result<void>{};
});
REQUIRE(build_called);
REQUIRE(r.has_value());
// Overlay row should now be confirmed (verified_at != 0). The simplest
// way to check is to resolve through the Database — confirmed rows
// satisfy `overlay_is_fresh` and surface as the matching recipe.
auto db = Database::open(req.overlay_path);
REQUIRE(db.has_value());
auto rec = db->resolve("fmt", "*", {});
REQUIRE(rec.has_value());
REQUIRE(rec->source == "conan");
REQUIRE(rec->find_package == "fmt CONFIG REQUIRED");
}
TEST_CASE("verify_link rolls the provisional row back when the build fails",
"[resolver][verify_link]") {
auto parent = fresh_dir();
auto req = make_request(parent);
auto r = verify_link(req, [&](const std::filesystem::path&) {
return cargoxx::util::Result<void>{std::unexpected(cargoxx::util::Error{
cargoxx::util::ErrorCode::BuildCmakeFailed,
"fake build failure",
"", std::nullopt, std::nullopt,
})};
});
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == cargoxx::util::ErrorCode::BuildCmakeFailed);
// The conan-source row must be gone; resolve falls through to the
// curated linkdb (which has its own fmt recipe with source = "curated").
auto db = Database::open(req.overlay_path);
REQUIRE(db.has_value());
auto rec = db->resolve("fmt", "10.2.0", {});
REQUIRE(rec.has_value());
REQUIRE(rec->source == "curated");
}
TEST_CASE("verify_link cleans up its scratch project", "[resolver][verify_link]") {
auto parent = fresh_dir();
auto req = make_request(parent);
std::filesystem::path captured;
(void)verify_link(req, [&](const std::filesystem::path& root) {
captured = root;
return cargoxx::util::Result<void>{};
});
REQUIRE_FALSE(captured.empty());
REQUIRE_FALSE(std::filesystem::exists(captured));
}