Files
cargoxx/tests/codegen_flake.cpp

245 lines
8.6 KiB
C++

#include <catch2/catch_test_macros.hpp>
import cargoxx.codegen;
import cargoxx.manifest;
import cargoxx.layout;
import cargoxx.lockfile;
import cargoxx.linkdb;
import std;
using cargoxx::codegen::flake_nix;
using cargoxx::codegen::GenerateInputs;
using cargoxx::layout::DiscoveredLayout;
using cargoxx::linkdb::Recipe;
using cargoxx::lockfile::Lockfile;
using cargoxx::lockfile::LockfilePackage;
using cargoxx::manifest::Dependency;
using cargoxx::manifest::Edition;
using cargoxx::manifest::Manifest;
using cargoxx::manifest::Package;
namespace {
auto pkg(std::string name) -> Package {
return Package{
.name = std::move(name),
.version = "0.1.0",
.edition = Edition::Cpp23,
.authors = {},
.license = std::nullopt,
};
}
auto dep(std::string name, std::string version) -> Dependency {
return Dependency{
.name = std::move(name),
.version_spec = std::move(version),
.components = {},
};
}
auto recipe(std::string attr) -> Recipe {
return Recipe{
.nixpkgs_attr = std::move(attr),
.find_package = "",
.targets = {},
.source = "curated",
};
}
auto root_pkg(std::string name, std::string version,
std::optional<std::string> rev = std::nullopt) -> LockfilePackage {
return LockfilePackage{
.name = std::move(name),
.version = std::move(version),
.dependencies = {},
.nixpkgs_attr = std::nullopt,
.nixpkgs_rev = std::move(rev),
.linkdb_source = std::nullopt,
};
}
auto dep_pkg(std::string name, std::string version,
std::optional<std::string> rev) -> LockfilePackage {
return LockfilePackage{
.name = std::move(name),
.version = std::move(version),
.dependencies = {},
.nixpkgs_attr = std::nullopt,
.nixpkgs_rev = std::move(rev),
.linkdb_source = std::nullopt,
};
}
} // namespace
TEST_CASE("flake_nix adds pkgs.pkg-config to nativeBuildInputs only when needed",
"[codegen][flake]") {
Manifest m{pkg("app"), {dep("sqlite", "*")}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {root_pkg("app", "0.1.0")}};
std::vector<Recipe> recipes = {Recipe{
.nixpkgs_attr = "sqlite",
.find_package = "PkgConfig REQUIRED",
.targets = {"PkgConfig::SQLITE3"},
.source = "pkg-config",
.pkg_config_module = "sqlite3",
}};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
auto out = flake_nix(in);
REQUIRE(out.find("pkgs.pkg-config") != std::string::npos);
REQUIRE(out.find("pkgs.sqlite") != std::string::npos);
}
TEST_CASE("flake_nix omits pkgs.pkg-config when no recipe needs it",
"[codegen][flake]") {
Manifest m{pkg("hello"), {dep("fmt", "*")}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {root_pkg("hello", "0.1.0")}};
std::vector<Recipe> recipes = {recipe("fmt_10")};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/hello"};
auto out = flake_nix(in);
REQUIRE(out.find("pkgs.pkg-config") == std::string::npos);
}
TEST_CASE("flake_nix always emits the shared nixos-unstable nixpkgs input",
"[codegen][flake]") {
Manifest m{pkg("hello"), {}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {root_pkg("hello", "0.1.0")}};
GenerateInputs in{m, layout, lock, {}, {}, "/tmp/hello"};
auto out = flake_nix(in);
REQUIRE(out.find("description = \"hello\";") != std::string::npos);
REQUIRE(out.find("nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";") !=
std::string::npos);
// No pinned deps → no extra `nixpkgs_*` inputs.
REQUIRE(out.find("nixpkgs_") == std::string::npos);
}
TEST_CASE("flake_nix emits a per-pinned-dep nixpkgs input", "[codegen][flake]") {
Manifest m{pkg("app"), {dep("fmt", "10.2.1")}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {
root_pkg("app", "0.1.0"),
dep_pkg("fmt", "10.2.1", "abc123def456"),
}};
std::vector<Recipe> recipes = {recipe("fmt_10")};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
auto out = flake_nix(in);
// Per-dep input attribute
REQUIRE(out.find("nixpkgs_fmt_10_2_1.url = \"github:NixOS/nixpkgs/abc123def456\";") !=
std::string::npos);
// Outputs lambda binds it
REQUIRE(out.find(", nixpkgs_fmt_10_2_1,") != std::string::npos);
// let-binding constructs pkgs_<sanitized>
REQUIRE(out.find("pkgs_nixpkgs_fmt_10_2_1 = import nixpkgs_fmt_10_2_1") !=
std::string::npos);
// buildInputs uses the pinned set, not the shared `pkgs`
REQUIRE(out.find("pkgs_nixpkgs_fmt_10_2_1.fmt_10") != std::string::npos);
}
TEST_CASE("flake_nix uses shared `pkgs` for unpinned deps",
"[codegen][flake]") {
Manifest m{pkg("app"), {dep("fmt", "*")}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {root_pkg("app", "0.1.0"), dep_pkg("fmt", "*", std::nullopt)}};
std::vector<Recipe> recipes = {recipe("fmt_10")};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
auto out = flake_nix(in);
REQUIRE(out.find("pkgs.fmt_10") != std::string::npos);
REQUIRE(out.find("nixpkgs_fmt_") == std::string::npos);
}
TEST_CASE("flake_nix mixes pinned and unpinned deps", "[codegen][flake]") {
Manifest m{pkg("app"), {dep("fmt", "10.2.1"), dep("zlib", "*")}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {
root_pkg("app", "0.1.0"),
dep_pkg("fmt", "10.2.1", "abc"),
dep_pkg("zlib", "*", std::nullopt),
}};
std::vector<Recipe> recipes = {recipe("fmt_10"), recipe("zlib")};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
auto out = flake_nix(in);
REQUIRE(out.find("pkgs_nixpkgs_fmt_10_2_1.fmt_10") != std::string::npos);
REQUIRE(out.find("pkgs.zlib") != std::string::npos);
}
TEST_CASE("flake_nix emits an empty buildInputs list when there are no deps",
"[codegen][flake]") {
Manifest m{pkg("hello"), {}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {root_pkg("hello", "0.1.0")}};
GenerateInputs in{m, layout, lock, {}, {}, "/tmp/hello"};
auto out = flake_nix(in);
REQUIRE(out.find("buildInputs = [\n ];") != std::string::npos);
}
TEST_CASE("flake_nix dedupes deps that share input + attr",
"[codegen][flake]") {
Manifest m{pkg("app"),
{dep("boost", "1.84.0"), dep("boost", "1.84.0")},
{}};
DiscoveredLayout layout{};
Lockfile lock{1, {
root_pkg("app", "0.1.0"),
dep_pkg("boost", "1.84.0", "rev42"),
}};
std::vector<Recipe> recipes = {recipe("boost"), recipe("boost")};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
auto out = flake_nix(in);
auto first = out.find("pkgs_nixpkgs_boost_1_84_0.boost");
REQUIRE(first != std::string::npos);
REQUIRE(out.find("pkgs_nixpkgs_boost_1_84_0.boost", first + 1) ==
std::string::npos);
}
TEST_CASE("flake_nix produces deterministic output", "[codegen][flake]") {
Manifest m{pkg("app"), {dep("fmt", "10.2.1"), dep("spdlog", "*")}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {
root_pkg("app", "0.1.0"),
dep_pkg("fmt", "10.2.1", "abc"),
dep_pkg("spdlog", "*", std::nullopt),
}};
std::vector<Recipe> recipes = {recipe("fmt_10"), recipe("spdlog")};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
REQUIRE(flake_nix(in) == flake_nix(in));
}
TEST_CASE("flake_nix output ends with a newline", "[codegen][flake]") {
Manifest m{pkg("hello"), {}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {root_pkg("hello", "0.1.0")}};
GenerateInputs in{m, layout, lock, {}, {}, "/tmp/hello"};
auto out = flake_nix(in);
REQUIRE_FALSE(out.empty());
REQUIRE(out.back() == '\n');
}
TEST_CASE("flake_nix sanitizes hyphens and dots in dep names",
"[codegen][flake]") {
Manifest m{pkg("app"), {dep("range-v3", "0.12.0")}, {}};
DiscoveredLayout layout{};
Lockfile lock{1, {
root_pkg("app", "0.1.0"),
dep_pkg("range-v3", "0.12.0", "rev123"),
}};
std::vector<Recipe> recipes = {recipe("range-v3")};
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
auto out = flake_nix(in);
REQUIRE(out.find("nixpkgs_range_v3_0_12_0.url = "
"\"github:NixOS/nixpkgs/rev123\";") != std::string::npos);
}