[M2] add SQLite overlay + add_manual

This commit is contained in:
2026-05-08 12:14:24 +00:00
parent d5715428ea
commit cafa403a58
8 changed files with 524 additions and 49 deletions

153
tests/linkdb_overlay.cpp Normal file
View File

@@ -0,0 +1,153 @@
#include <catch2/catch_test_macros.hpp>
import cargoxx.linkdb;
import cargoxx.util;
import std;
using cargoxx::linkdb::Database;
using cargoxx::linkdb::Recipe;
using cargoxx::util::ErrorCode;
namespace {
auto fresh_overlay() -> std::filesystem::path {
auto d = std::filesystem::temp_directory_path() /
std::format("cargoxx-overlay-test-{}", std::random_device{}());
std::filesystem::create_directories(d);
return d / "overlay.sqlite";
}
} // namespace
TEST_CASE("open creates the overlay schema and file", "[linkdb][overlay]") {
auto path = fresh_overlay();
auto db = Database::open(path);
REQUIRE(db.has_value());
REQUIRE(std::filesystem::exists(path));
}
TEST_CASE("add_manual then resolve returns the manual recipe",
"[linkdb][overlay]") {
auto db = Database::open(fresh_overlay());
REQUIRE(db.has_value());
Recipe r{
.nixpkgs_attr = "obscurelib",
.find_package = "obscurelib CONFIG REQUIRED",
.targets = {"obscurelib::obscurelib"},
.source = "manual",
};
REQUIRE(db->add_manual("obscurelib", "*", r).has_value());
auto got = db->resolve("obscurelib", "1.0.0");
REQUIRE(got.has_value());
REQUIRE(got->nixpkgs_attr == "obscurelib");
REQUIRE(got->find_package == "obscurelib CONFIG REQUIRED");
REQUIRE(got->targets == std::vector<std::string>{"obscurelib::obscurelib"});
REQUIRE(got->source == "manual");
}
TEST_CASE("manual entry overrides curated for the same package",
"[linkdb][overlay]") {
auto db = Database::open(fresh_overlay());
REQUIRE(db.has_value());
Recipe override_r{
.nixpkgs_attr = "fmt_pinned",
.find_package = "fmt CONFIG REQUIRED",
.targets = {"fmt::fmt"},
.source = "manual",
};
REQUIRE(db->add_manual("fmt", ">=10.0.0", override_r).has_value());
auto got = db->resolve("fmt", "10.2.0");
REQUIRE(got.has_value());
REQUIRE(got->nixpkgs_attr == "fmt_pinned");
REQUIRE(got->source == "manual");
}
TEST_CASE("manual entry is constrained by version_range",
"[linkdb][overlay]") {
auto db = Database::open(fresh_overlay());
REQUIRE(db.has_value());
Recipe r{
.nixpkgs_attr = "fmt_v11_only",
.find_package = "fmt CONFIG REQUIRED",
.targets = {"fmt::fmt"},
.source = "manual",
};
REQUIRE(db->add_manual("fmt", ">=11.0.0", r).has_value());
// 10.x falls outside the manual range and falls through to curated
auto curated = db->resolve("fmt", "10.2.0");
REQUIRE(curated.has_value());
REQUIRE(curated->source == "curated");
REQUIRE(curated->nixpkgs_attr == "fmt_10");
// 11.x matches the manual range
auto manual = db->resolve("fmt", "11.0.0");
REQUIRE(manual.has_value());
REQUIRE(manual->source == "manual");
REQUIRE(manual->nixpkgs_attr == "fmt_v11_only");
}
TEST_CASE("manual recipes persist across reopen", "[linkdb][overlay]") {
auto path = fresh_overlay();
{
auto db = Database::open(path);
REQUIRE(db.has_value());
Recipe r{
.nixpkgs_attr = "persistlib",
.find_package = "persistlib CONFIG REQUIRED",
.targets = {"persistlib::persistlib"},
.source = "manual",
};
REQUIRE(db->add_manual("persistlib", "*", r).has_value());
}
auto db = Database::open(path);
REQUIRE(db.has_value());
auto got = db->resolve("persistlib", "0.0.1");
REQUIRE(got.has_value());
REQUIRE(got->nixpkgs_attr == "persistlib");
REQUIRE(got->source == "manual");
}
TEST_CASE("resolve with components on a manual recipe is rejected",
"[linkdb][overlay]") {
auto db = Database::open(fresh_overlay());
REQUIRE(db.has_value());
Recipe r{
.nixpkgs_attr = "weirdlib",
.find_package = "weirdlib CONFIG REQUIRED",
.targets = {"weirdlib::weirdlib"},
.source = "manual",
};
REQUIRE(db->add_manual("weirdlib", "*", r).has_value());
auto got = db->resolve("weirdlib", "1.0.0", {"some_component"});
REQUIRE_FALSE(got.has_value());
REQUIRE(got.error().code == ErrorCode::LinkdbComponentNotSupported);
}
TEST_CASE("Database is move-constructible without leaking the handle",
"[linkdb][overlay]") {
auto db = Database::open(fresh_overlay());
REQUIRE(db.has_value());
Database moved = std::move(*db);
Recipe r{
.nixpkgs_attr = "movelib",
.find_package = "movelib CONFIG REQUIRED",
.targets = {"movelib::movelib"},
.source = "manual",
};
REQUIRE(moved.add_manual("movelib", "*", r).has_value());
auto got = moved.resolve("movelib", "1.0.0");
REQUIRE(got.has_value());
REQUIRE(got->source == "manual");
}