[M3] add lockfile types + parse/write

This commit is contained in:
2026-05-08 12:25:49 +00:00
parent cafa403a58
commit 86e88f236f
6 changed files with 351 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ cargoxx_add_test(semver_satisfies)
cargoxx_add_test(manifest_parse)
cargoxx_add_test(manifest_write)
cargoxx_add_test(layout_discovery)
cargoxx_add_test(lockfile_round_trip)
cargoxx_add_test(linkdb_lookup)
cargoxx_add_test(linkdb_overlay)
cargoxx_add_test(cmd_new)

View File

@@ -0,0 +1,140 @@
#include <catch2/catch_test_macros.hpp>
import cargoxx.lockfile;
import cargoxx.util;
import std;
using cargoxx::lockfile::Lockfile;
using cargoxx::lockfile::LockfilePackage;
using cargoxx::lockfile::parse;
using cargoxx::lockfile::write;
using cargoxx::util::ErrorCode;
namespace {
auto tmp_path() -> std::filesystem::path {
auto d = std::filesystem::temp_directory_path() /
std::format("cargoxx-lockfile-test-{}", std::random_device{}());
std::filesystem::create_directories(d);
return d / "Cargoxx.lock";
}
auto root_pkg(std::string name, std::string version,
std::vector<std::string> deps = {}) -> LockfilePackage {
return LockfilePackage{
.name = std::move(name),
.version = std::move(version),
.dependencies = std::move(deps),
.nixpkgs_attr = std::nullopt,
.nixpkgs_rev = std::nullopt,
.linkdb_source = std::nullopt,
};
}
auto dep_pkg(std::string name, std::string version, std::string attr, std::string rev,
std::string source = "curated") -> LockfilePackage {
return LockfilePackage{
.name = std::move(name),
.version = std::move(version),
.dependencies = {},
.nixpkgs_attr = std::move(attr),
.nixpkgs_rev = std::move(rev),
.linkdb_source = std::move(source),
};
}
auto round_trip(const Lockfile& l) -> Lockfile {
auto path = tmp_path();
REQUIRE(write(l, path).has_value());
auto parsed = parse(path);
REQUIRE(parsed.has_value());
return *parsed;
}
} // namespace
TEST_CASE("write round-trips a minimal lockfile", "[lockfile]") {
Lockfile l{1, {root_pkg("my-project", "0.1.0")}};
REQUIRE(round_trip(l) == l);
}
TEST_CASE("write round-trips a lockfile with deps", "[lockfile]") {
Lockfile l{
1,
{
root_pkg("my-project", "0.1.0", {"fmt 10.2.1", "spdlog 1.13.0"}),
dep_pkg("fmt", "10.2.1", "fmt_10", "8a3f...c2d1"),
dep_pkg("spdlog", "1.13.0", "spdlog", "8a3f...c2d1"),
},
};
REQUIRE(round_trip(l) == l);
}
TEST_CASE("Lockfile::nixpkgs_rev returns the shared rev", "[lockfile]") {
Lockfile l{
1,
{
root_pkg("p", "0.1.0", {"fmt 10.2.1"}),
dep_pkg("fmt", "10.2.1", "fmt_10", "abc123"),
},
};
REQUIRE(l.nixpkgs_rev() == "abc123");
}
TEST_CASE("Lockfile::nixpkgs_rev is nullopt when no deps", "[lockfile]") {
Lockfile l{1, {root_pkg("p", "0.1.0")}};
REQUIRE_FALSE(l.nixpkgs_rev().has_value());
}
TEST_CASE("parse rejects a missing file", "[lockfile]") {
auto r = parse("/nonexistent/Cargoxx.lock");
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestNotFound);
}
TEST_CASE("parse rejects malformed toml", "[lockfile]") {
auto path = tmp_path();
std::ofstream{path} << "version = \nname = \"x\"\n";
auto r = parse(path);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestParseError);
}
TEST_CASE("parse rejects a package missing name", "[lockfile]") {
auto path = tmp_path();
std::ofstream{path} << R"(
version = 1
[[package]]
version = "0.1.0"
)";
auto r = parse(path);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
}
TEST_CASE("parse rejects a package missing version", "[lockfile]") {
auto path = tmp_path();
std::ofstream{path} << R"(
version = 1
[[package]]
name = "p"
)";
auto r = parse(path);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
}
TEST_CASE("parse defaults version to 1 when omitted", "[lockfile]") {
auto path = tmp_path();
std::ofstream{path} << R"(
[[package]]
name = "p"
version = "0.1.0"
)";
auto r = parse(path);
REQUIRE(r.has_value());
REQUIRE(r->version == 1);
REQUIRE(r->packages.size() == 1);
}