From dd166e5a4d4349241aa3408c8d7f7340cb0a90fb Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 14 Nov 2026 00:33:20 +0000 Subject: tests: populate's tests of its own, krops' using them populate's tests are in pkgs/populate/tests, independent of krops. They run the program, with the sources as JSON from populate's Nix library, and target strings for the remote targets, leaving the user and whether the target is local to populate, so they cover its command line too. Phases expecting populate to refuse its arguments, e.g. a source name or a target path that can't be used, expect the program's refusal, with refused, the message, since nothing checks them during evaluation there. builder.nix is what builds the scenarios, which krops' tests use as well, with a function running the phases, and whether that checks the arguments during evaluation. vm.nix takes a module for both machines, for what else the scenarios run, and fixtures.nix and smoke.nix are what both use of the fixtures and the smoke test. krops' tests are deploy, writeDeploy's arguments to nixos-rebuild, with the stand-in for nixos-rebuild in the module, and populate, populate's scenarios that expect refusals, which pkgs.populate makes evaluation fail with, and those of populate's smoke test, all through pkgs.populate with target attribute sets as mkTarget returns them. -A only selects from all of populate's scenarios and krops' own. --- pkgs/populate/tests/default.nix | 135 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 pkgs/populate/tests/default.nix (limited to 'pkgs/populate/tests/default.nix') diff --git a/pkgs/populate/tests/default.nix b/pkgs/populate/tests/default.nix new file mode 100644 index 0000000..7b37bb3 --- /dev/null +++ b/pkgs/populate/tests/default.nix @@ -0,0 +1,135 @@ +# NixOS VM tests for populate: one test per source type, one for features +# spanning sources, one for edge cases of flakes, and one for staying inside +# the target directory. Each runs the program, with the sources as JSON +# from populate's Nix library, and target strings, which leave the user and +# whether the target is local to populate to decide. Build all with +# nix-build in this directory, or one with, e.g., nix-build -A flake. The +# exhaustive tests add the combinations of options that no code path tells +# apart from the ones the others cover; build them with nix-build -A +# exhaustive, or one with, e.g., nix-build -A exhaustive.flake. The smoke +# test runs a few scenarios covering each source type and each kind of +# target in a single VM test, for checking changes quickly; build it with +# nix-build -A smoke. nix-build -A only --argstr only REGEX runs the +# scenarios whose names match the regular expression REGEX in a single VM +# test, e.g. --argstr only 'lock-hooks-.*'. +{ pkgs ? import {}, only ? null }: + +let + inherit (pkgs) lib; + + populate-unwrapped = pkgs.callPackage ../. {}; + populateLib = import ../lib { inherit lib; }; + + inherit (import ./fixtures.nix) gitHead gitFirst; + + remoteHost = "target"; + + # rsync versions the scenarios depending on rsync's version run with. + # callPackage adds override and overrideDerivation, which aren't versions. + rsyncs = lib.filterAttrs + (_: lib.isDerivation) + (pkgs.callPackage ./rsync.nix {}); + + # A script running populate for a phase, with the tools it runs in front + # of PATH, and the sources on a file descriptor of their own, so the + # commands populate runs keep standard input. Store paths in the sources + # stay dependencies of the script. + runWith = rsync: { + source, backup, force, prune, deploy, targetKind, targetPath, + }: + if deploy != null then throw "populate's tests don't deploy" else + pkgs.writers.writeDash "populate" /* sh */ '' + set -efu + PATH=${lib.makeBinPath (with pkgs; [ + coreutils findutils git nix openssh pass passage rsync + ])}''${PATH:+:$PATH} + export PATH + exec ${populate-unwrapped}/bin/populate ${lib.escapeShellArgs ( + lib.optional backup "--backup" ++ + lib.optional force "--force" ++ + lib.optional prune "--prune" ++ + [ "/dev/fd/3" ] ++ + { + local = []; + remote = [ "root@${remoteHost}" ]; + sudo = [ "deploy@${remoteHost}" "--sudo" ]; + }.${targetKind} ++ + [ "--path" targetPath ] + )} 3<<'EOF' + ${builtins.toJSON source} + EOF + ''; + + # The scenarios, with populate using rsync. + scenariosWith = rsync: import ./scenarios.nix { + inherit lib; + inherit (populateLib) evalSource; + run = runWith rsync; + checksDuringEvaluation = false; + url = "http://deployer:8000"; + gitUrl = "git://deployer/gitsrc.git"; + inherit remoteHost; + agePath = "${pkgs.age}/bin/age"; + inherit gitHead gitFirst; + rsyncVersion = rsync.version; + }; + scenarios = scenariosWith pkgs.rsync; + + vm = name: scenarios: rsync: import ./vm.nix { + inherit pkgs gitHead scenarios rsync; + name = "populate-${name}"; + }; + + # VM tests of those of the groups of scenarios that are, or aren't, + # exhaustive, named after the groups with prefix in front. + testsOf = exhaustive: prefix: lib.mapAttrs + (name: scenarios: vm (prefix + name) scenarios pkgs.rsync) + (lib.filterAttrs (_: scenarios: scenarios != []) + (builtins.mapAttrs + (_: builtins.filter (scenario: scenario.exhaustive == exhaustive)) + scenarios)); + + allScenarios = lib.concatLists (builtins.attrValues scenarios); + + # The scenarios named names, failing if one is missing. + named = what: names: let + selected = builtins.filter (scenario: builtins.elem scenario.name names) allScenarios; + missing = lib.subtractLists (map (scenario: scenario.name) selected) names; + in + if missing != [] then + throw "tests: ${what} names missing scenarios: ${toString missing}" + else + selected; + + # The scenarios whose names match the regular expression only. + onlyScenarios = let + selected = builtins.filter + (scenario: builtins.match only scenario.name != null) + allScenarios; + in + if only == null then + throw "tests: -A only needs --argstr only REGEX" + else if selected == [] then + throw "tests: no scenario's name matches ${builtins.toJSON only}" + else + selected; +in + testsOf false "" + # Not built by nix-build either, which runs these scenarios anyway. + // { + smoke.smoke = vm "smoke" (named "smoke test" (import ./smoke.nix)) pkgs.rsync; + only.only = vm "only" onlyScenarios pkgs.rsync; + } + # An attribute set without recurseForDerivations, so nix-build doesn't + # build them. + // { exhaustive = testsOf true "exhaustive-"; } + // lib.mapAttrs' + (version: rsync: { + name = "rsync-${builtins.replaceStrings [ "." ] [ "-" ] version}"; + value = vm "rsync-${version}" + (builtins.filter + (scenario: lib.hasInfix "insecureLinks" scenario.name) + (scenariosWith rsync).file) + rsync; + }) + rsyncs -- cgit v1.3.1