// Unit + fixture-based test for resolver::nixpkgs_git_resolve. Avoids // the multi-GB nixos/nixpkgs clone by building a tiny throwaway repo // that mirrors the relevant slice of the nixpkgs layout. #include import cargoxx.resolver; import cargoxx.exec; import cargoxx.util; import std; using cargoxx::resolver::nixpkgs_git_resolve; using cargoxx::resolver::pick_youngest_commit; namespace { auto run_git(const std::filesystem::path& cwd, std::vector args) -> int { auto r = cargoxx::exec::run( "git", args, cargoxx::exec::ExecOptions{ .cwd = cwd, .env_overrides = {{"GIT_COMMITTER_NAME", "t"}, {"GIT_COMMITTER_EMAIL", "t@t"}, {"GIT_AUTHOR_NAME", "t"}, {"GIT_AUTHOR_EMAIL", "t@t"}, {"GIT_COMMITTER_DATE", "1700000000 +0000"}, {"GIT_AUTHOR_DATE", "1700000000 +0000"}}, .timeout = std::chrono::seconds{30}, .inherit_stdio = false, }); REQUIRE(r.has_value()); return r->exit_code; } auto write_text(const std::filesystem::path& p, std::string_view content) { std::filesystem::create_directories(p.parent_path()); std::ofstream{p} << content; } auto fixture_repo(std::string_view pkg, std::string_view v1, std::string_view v2) -> std::filesystem::path { auto root = std::filesystem::temp_directory_path() / std::format("cargoxx-git-fixture-{}", std::random_device{}()); std::filesystem::create_directories(root); REQUIRE(run_git(root, {"init", "-q", "-b", "main"}) == 0); auto nix_path = root / "pkgs" / "development" / "libraries" / std::string{pkg} / "default.nix"; write_text(nix_path, std::format("{{ stdenv }}: stdenv.mkDerivation {{ " "version = \"{}\"; }}\n", v1)); REQUIRE(run_git(root, {"add", "."}) == 0); REQUIRE(run_git(root, {"commit", "-q", "-m", std::format("init {}", v1)}) == 0); write_text(nix_path, std::format("{{ stdenv }}: stdenv.mkDerivation {{ " "version = \"{}\"; }}\n", v2)); REQUIRE(run_git(root, {"add", "."}) == 0); REQUIRE(run_git(root, {"commit", "-q", "-m", std::format("bump {}", v2)}) == 0); return root; } } // namespace TEST_CASE("pick_youngest_commit picks the highest-timestamp SHA", "[resolver][git]") { constexpr std::string_view input = "abc111 1700000000\n" "def222 1800000000\n" "ghi333 1750000000\n"; auto r = pick_youngest_commit(input); REQUIRE(r == std::string{"def222"}); } TEST_CASE("pick_youngest_commit returns nullopt on empty input", "[resolver][git]") { REQUIRE_FALSE(pick_youngest_commit("").has_value()); REQUIRE_FALSE(pick_youngest_commit("\n\n").has_value()); } TEST_CASE("pick_youngest_commit skips malformed lines", "[resolver][git]") { constexpr std::string_view input = "garbage\n" "abc111 1700000000\n" "no-timestamp\n"; auto r = pick_youngest_commit(input); REQUIRE(r == std::string{"abc111"}); } TEST_CASE("nixpkgs_git_resolve finds the introducing commit", "[resolver][git]") { auto repo = fixture_repo("fmt", "10.2.0", "10.3.0"); auto r = nixpkgs_git_resolve("fmt", "10.2.0", repo); REQUIRE(r.has_value()); REQUIRE(r->size() == 40); // git sha std::error_code ec; std::filesystem::remove_all(repo, ec); } TEST_CASE("nixpkgs_git_resolve returns ResolutionVersionNotFound for an absent version", "[resolver][git]") { auto repo = fixture_repo("fmt", "10.2.0", "10.3.0"); auto r = nixpkgs_git_resolve("fmt", "99.99.99", repo); REQUIRE_FALSE(r.has_value()); REQUIRE(r.error().code == cargoxx::util::ErrorCode::ResolutionVersionNotFound); std::error_code ec; std::filesystem::remove_all(repo, ec); }