1{
2 config,
3 lib,
4 hostPkgs,
5 ...
6}:
7let
8 inherit (lib) mkOption types literalMD;
9
10 # Reifies and correctly wraps the python test driver for
11 # the respective qemu version and with or without ocr support
12 testDriver = hostPkgs.callPackage ../test-driver {
13 inherit (config) enableOCR extraPythonPackages;
14 qemu_pkg = config.qemu.package;
15 imagemagick_light = hostPkgs.imagemagick_light.override { inherit (hostPkgs) libtiff; };
16 tesseract4 = hostPkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
17 };
18
19 vlans = map (
20 m: (m.virtualisation.vlans ++ (lib.mapAttrsToList (_: v: v.vlan) m.virtualisation.interfaces))
21 ) (lib.attrValues config.nodes);
22 vms = map (m: m.system.build.vm) (lib.attrValues config.nodes);
23
24 nodeHostNames =
25 let
26 nodesList = map (c: c.system.name) (lib.attrValues config.nodes);
27 in
28 nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
29
30 pythonizeName =
31 name:
32 let
33 head = lib.substring 0 1 name;
34 tail = lib.substring 1 (-1) name;
35 in
36 (if builtins.match "[A-z_]" head == null then "_" else head)
37 + lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
38
39 uniqueVlans = lib.unique (builtins.concatLists vlans);
40 vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
41 pythonizedNames = map pythonizeName nodeHostNames;
42 machineNames = map (name: "${name}: Machine;") pythonizedNames;
43
44 withChecks = lib.warnIf config.skipLint "Linting is disabled";
45
46 driver =
47 hostPkgs.runCommand "nixos-test-driver-${config.name}"
48 {
49 # inherit testName; TODO (roberth): need this?
50 nativeBuildInputs = [
51 hostPkgs.makeWrapper
52 ] ++ lib.optionals (!config.skipTypeCheck) [ hostPkgs.mypy ];
53 buildInputs = [ testDriver ];
54 testScript = config.testScriptString;
55 preferLocalBuild = true;
56 passthru = config.passthru;
57 meta = config.meta // {
58 mainProgram = "nixos-test-driver";
59 };
60 }
61 ''
62 mkdir -p $out/bin
63
64 vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
65
66 ${lib.optionalString (!config.skipTypeCheck) ''
67 # prepend type hints so the test script can be type checked with mypy
68 cat "${../test-script-prepend.py}" >> testScriptWithTypes
69 echo "${builtins.toString machineNames}" >> testScriptWithTypes
70 echo "${builtins.toString vlanNames}" >> testScriptWithTypes
71 echo -n "$testScript" >> testScriptWithTypes
72
73 echo "Running type check (enable/disable: config.skipTypeCheck)"
74 echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipTypeCheck"
75
76 mypy --no-implicit-optional \
77 --pretty \
78 --no-color-output \
79 testScriptWithTypes
80 ''}
81
82 echo -n "$testScript" >> $out/test-script
83
84 ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver
85
86 ${testDriver}/bin/generate-driver-symbols
87 ${lib.optionalString (!config.skipLint) ''
88 echo "Linting test script (enable/disable: config.skipLint)"
89 echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipLint"
90
91 PYFLAKES_BUILTINS="$(
92 echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
93 < ${lib.escapeShellArg "driver-symbols"}
94 )" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
95 ''}
96
97 # set defaults through environment
98 # see: ./test-driver/test-driver.py argparse implementation
99 wrapProgram $out/bin/nixos-test-driver \
100 --set startScripts "''${vmStartScripts[*]}" \
101 --set testScript "$out/test-script" \
102 --set globalTimeout "${toString config.globalTimeout}" \
103 --set vlans '${toString vlans}' \
104 ${lib.escapeShellArgs (
105 lib.concatMap (arg: [
106 "--add-flags"
107 arg
108 ]) config.extraDriverArgs
109 )}
110 '';
111
112in
113{
114 options = {
115
116 driver = mkOption {
117 description = "Package containing a script that runs the test.";
118 type = types.package;
119 defaultText = literalMD "set by the test framework";
120 };
121
122 hostPkgs = mkOption {
123 description = "Nixpkgs attrset used outside the nodes.";
124 type = types.raw;
125 example = lib.literalExpression ''
126 import nixpkgs { inherit system config overlays; }
127 '';
128 };
129
130 qemu.package = mkOption {
131 description = "Which qemu package to use for the virtualisation of [{option}`nodes`](#test-opt-nodes).";
132 type = types.package;
133 default = hostPkgs.qemu_test;
134 defaultText = "hostPkgs.qemu_test";
135 };
136
137 globalTimeout = mkOption {
138 description = ''
139 A global timeout for the complete test, expressed in seconds.
140 Beyond that timeout, every resource will be killed and released and the test will fail.
141
142 By default, we use a 1 hour timeout.
143 '';
144 type = types.int;
145 default = 60 * 60;
146 example = 10 * 60;
147 };
148
149 enableOCR = mkOption {
150 description = ''
151 Whether to enable Optical Character Recognition functionality for
152 testing graphical programs. See [`Machine objects`](#ssec-machine-objects).
153 '';
154 type = types.bool;
155 default = false;
156 };
157
158 extraPythonPackages = mkOption {
159 description = ''
160 Python packages to add to the test driver.
161
162 The argument is a Python package set, similar to `pkgs.pythonPackages`.
163 '';
164 example = lib.literalExpression ''
165 p: [ p.numpy ]
166 '';
167 type = types.functionTo (types.listOf types.package);
168 default = ps: [ ];
169 };
170
171 extraDriverArgs = mkOption {
172 description = ''
173 Extra arguments to pass to the test driver.
174
175 They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`.
176 '';
177 type = types.listOf types.str;
178 default = [ ];
179 };
180
181 skipLint = mkOption {
182 type = types.bool;
183 default = false;
184 description = ''
185 Do not run the linters. This may speed up your iteration cycle, but it is not something you should commit.
186 '';
187 };
188
189 skipTypeCheck = mkOption {
190 type = types.bool;
191 default = false;
192 description = ''
193 Disable type checking. This must not be enabled for new NixOS tests.
194
195 This may speed up your iteration cycle, unless you're working on the [{option}`testScript`](#test-opt-testScript).
196 '';
197 };
198 };
199
200 config = {
201 _module.args = {
202 hostPkgs =
203 # Comment is in nixos/modules/misc/nixpkgs.nix
204 lib.mkOverride lib.modules.defaultOverridePriority config.hostPkgs.__splicedPackages;
205 };
206
207 driver = withChecks driver;
208
209 # make available on the test runner
210 passthru.driver = config.driver;
211 };
212}