aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/populate/tests/default.nix
blob: 7b37bb32ee1c01ff84eb5594a4846a17d6b40f5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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 <nixpkgs> {}, 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