From d4c352a402aec1682625144c0440ca31c232ee74 Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 12 Nov 2026 02:15:26 +0000 Subject: tests: add edge cases of flakes A new test, edge-cases, covers paths that work but have no scenario yet: a flake given by an indirect reference, which the target fetches by its locked reference; a remote flake in a subdirectory; a flake in a subdirectory drawing inputs from sources; a dirty Git work tree; a flake whose lock file needs updating; drawing inputs from a source that isn't a directory; nested names of missing inputs; several sources sharing one flake; and an input locked to a path that the target has. The deploying machine gets a registry entry for the indirect reference. Resolving it fails if the global registry can't be downloaded, which it can't in the VM, and disabling the global registry with flake-registry needs flakes, so the deploying machine enables them, like any deploying machine using indirect references does. The target doesn't, so checks for flake support on targets still mean something. --- README.md | 12 ++++--- tests/default.nix | 5 +-- tests/fixtures.sh | 39 +++++++++++++++++++++++ tests/scenarios.nix | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/vm.nix | 6 ++++ 5 files changed, 147 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 35788cd..e6a8981 100644 --- a/README.md +++ b/README.md @@ -576,11 +576,13 @@ flakes, as long as the inputs drawing from them are declared with ## Tests -`tests/` contains NixOS VM tests for populate: one test per source type, and -one for features spanning sources, such as flake inputs from sources. Each -test deploys every combination of a source type's options to the deploying -machine itself, to a remote target as `root`, and to a remote target using -`sudo`, and checks the result after deploying and after deploying again. +`tests/` contains NixOS VM tests for populate: one test per source type, one +for features spanning sources, such as flake inputs from sources, one for edge +cases of flakes, such as unusual lock files, and one for the arguments +`writeDeploy` passes to `nixos-rebuild`. The tests deploy every combination +of a source type's options to the deploying machine itself, to a remote target +as `root`, and to a remote target using `sudo`, and check the result after +deploying and after deploying again. The tests require KVM and nixpkgs with `pkgs.testers.runNixOSTest` (23.05 or later), and use `` by default. diff --git a/tests/default.nix b/tests/default.nix index 1f3de5c..4d0e256 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,5 +1,6 @@ -# NixOS VM tests for populate, one test per source type, one for features -# spanning sources, and one for writeDeploy's arguments to nixos-rebuild. Build all with nix-build tests, or one with, +# NixOS VM tests for populate: one test per source type, one for features +# spanning sources, one for edge cases of flakes, and one for writeDeploy's +# arguments to nixos-rebuild. Build all with nix-build tests, or one with, # e.g., nix-build tests -A flake. { pkgs ? import ../pkgs {} }: diff --git a/tests/fixtures.sh b/tests/fixtures.sh index eb62a61..9608fcd 100755 --- a/tests/fixtures.sh +++ b/tests/fixtures.sh @@ -108,6 +108,45 @@ NIX (cd /fixtures/usegone && nix flake lock) rm -R /fixtures/gone +# kropsremote: a registry entry for remote, so it has an indirect reference. +nix registry add kropsremote "$url/remote.tar.gz" + +# monoin: a Git work tree with a flake in subdirectory sub that has an input. +mkdir -p /fixtures/monoin/sub +echo monoin > /fixtures/monoin/README +cat > /fixtures/monoin/sub/flake.nix < /fixtures/dirty/untracked + +# unlocked: a flake whose lock file is missing. +mkdir /fixtures/unlocked +cat > /fixtures/unlocked/flake.nix < /fixtures/uselocal/flake.nix <<'NIX' +{ + inputs.a.url = "path:/fixtures/alt"; + outputs = { a, ... }: { msg = "uselocal:${a.who}"; }; +} +NIX +(cd /fixtures/uselocal && nix flake lock) + # noflake: a directory without flake.nix. mkdir /fixtures/noflake echo noflake > /fixtures/noflake/README diff --git a/tests/scenarios.nix b/tests/scenarios.nix index d8081d3..ef80d05 100644 --- a/tests/scenarios.nix +++ b/tests/scenarios.nix @@ -692,4 +692,96 @@ assert !(krops.isLocalTarget (targets.remote "/")); }) ]) targetKinds; + # Edge cases of the flake source types and of generated flakes: unusual lock + # files, references, and changes between deploys. + edge-cases = concatMap (targetKind: let + s = name: args: scenario ({ + name = "${name}-${targetKind}"; + inherit targetKind; + isolate = true; + } // args); + copying = targetKind != "sudo"; + fetchOnTarget = !copying; + roots = n: '' + expect_eq roots "$(ls "$T"/.populate/flake-inputs/x 2>/dev/null | wc -l)" ${toString n} + ''; + in [ + # The registry entry kropsremote, which fixtures.sh adds on the deploying + # machine, points at ${url}/remote.tar.gz. The deploying machine has no + # global registry, which it couldn't download. + (s "indirect" { + phases = twice { + sources.x.flake = { flake = "kropsremote"; inherit fetchOnTarget; }; + check = checkFlake { attr = "msg"; expected = "remote:dep+inner"; }; + }; + }) + (s "remote-subdir" { + phases = twice { + sources.x.flake = { flake = "${url}/mono.tar.gz?dir=sub"; inherit fetchOnTarget; }; + check = checkFlake { attr = "who"; expected = "sub"; subdir = true; }; + }; + }) + (s "subdir-inputs" { + phases = twice { + sources = alt // { + x.flake = { + flake = "git+file:/fixtures/monoin?dir=sub"; inherit fetchOnTarget; + inputs.dep.follows = "alt"; + }; + }; + check = checkFlake { attr = "msg"; expected = "monoin:alt"; subdir = true; generated = true; }; + }; + }) + (s "dirty" { + phases = twice { + sources.x.flake = { flake = "/fixtures/dirty"; inherit fetchOnTarget; }; + check = checkFlake { attr = "msg"; expected = "dirty:dep+inner,sub"; } + '' + test ! -e "$T"/x/untracked || fail "untracked file delivered" + ''; + }; + }) + (s "unlocked" { + phases = [{ + sources.x.flake = { flake = "/fixtures/unlocked"; inherit fetchOnTarget; }; + expect.fail = "requires lock file changes"; + }]; + }) + (s "notdir" { + phases = [{ + sources = { + x.file = { path = "/fixtures/filesrc"; inputs.dep.follows = "p"; }; + p.pipe = "/fixtures/pipe"; + }; + expect.fail = "error: source p is not a directory"; + }]; + }) + (s "noinput-nested" { + phases = [{ + sources.x.flake-input = { flake = "/fixtures/top"; name = "dep/nope"; inherit fetchOnTarget; }; + expect.fail = "error: flake /fixtures/top has no input dep/nope"; + }]; + }) + (s "shared" { + phases = twice { + sources = { + x.flake = { flake = "/fixtures/top"; inherit fetchOnTarget; }; + dep.flake-input = { flake = "/fixtures/top"; name = "dep"; inherit fetchOnTarget; }; + sub.flake-input = { flake = "/fixtures/top"; name = "sub"; fetchOnTarget = true; }; + }; + check = checkFlake { attr = "msg"; expected = "top:dep+inner,sub"; } + '' + expect_eq dep "$(nix_eval "$T"/dep#greeting)" dep+inner + expect_eq sub "$(nix_eval "$T"/sub#who)" sub + ''; + }; + }) + ] ++ lib.optional (targetKind == "local") + # The target has the path the input is locked to. + (s "uselocal" { + phases = twice { + sources.x.flake = { flake = "/fixtures/uselocal"; fetchOnTarget = true; }; + check = checkFlake { attr = "msg"; expected = "uselocal:alt"; }; + }; + }) + ) targetKinds; + } diff --git a/tests/vm.nix b/tests/vm.nix index 78b861b..d808dd2 100644 --- a/tests/vm.nix +++ b/tests/vm.nix @@ -64,6 +64,12 @@ in pkgs.testers.runNixOSTest { pkgs.age pkgs.gnupg pkgs.pass pkgs.passage ]; networking.firewall.allowedTCPPorts = [ 8000 9418 ]; + # Resolving indirect references fails if the global registry can't be + # downloaded, which it can't in the VM. Disabling it needs flakes, and a + # deploying machine using indirect references has them anyway. The + # target doesn't, so its checks for flake support still mean something. + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + nix.settings.flake-registry = ""; programs.ssh.extraConfig = '' StrictHostKeyChecking no UserKnownHostsFile /dev/null -- cgit v1.3.1