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 ]
53 ++ lib.optionals (!config.skipTypeCheck) [ hostPkgs.mypy ];
54 buildInputs = [ testDriver ];
55 testScript = config.testScriptString;
56 preferLocalBuild = true;
57 passthru = config.passthru;
58 meta = config.meta // {
59 mainProgram = "nixos-test-driver";
60 };
61 }
62 ''
63 mkdir -p $out/bin
64
65 vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
66
67 ${lib.optionalString (!config.skipTypeCheck) ''
68 # prepend type hints so the test script can be type checked with mypy
69 cat "${../test-script-prepend.py}" >> testScriptWithTypes
70 echo "${builtins.toString machineNames}" >> testScriptWithTypes
71 echo "${builtins.toString vlanNames}" >> testScriptWithTypes
72 echo -n "$testScript" >> testScriptWithTypes
73
74 echo "Running type check (enable/disable: config.skipTypeCheck)"
75 echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipTypeCheck"
76
77 mypy --no-implicit-optional \
78 --pretty \
79 --no-color-output \
80 testScriptWithTypes
81 ''}
82
83 echo -n "$testScript" >> $out/test-script
84
85 ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver
86
87 ${testDriver}/bin/generate-driver-symbols
88 ${lib.optionalString (!config.skipLint) ''
89 echo "Linting test script (enable/disable: config.skipLint)"
90 echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipLint"
91
92 PYFLAKES_BUILTINS="$(
93 echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
94 cat ${lib.escapeShellArg "driver-symbols"}
95 )" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
96 ''}
97
98 # set defaults through environment
99 # see: ./test-driver/test-driver.py argparse implementation
100 wrapProgram $out/bin/nixos-test-driver \
101 --set startScripts "''${vmStartScripts[*]}" \
102 --set testScript "$out/test-script" \
103 --set globalTimeout "${toString config.globalTimeout}" \
104 --set vlans '${toString vlans}' \
105 ${lib.escapeShellArgs (
106 lib.concatMap (arg: [
107 "--add-flags"
108 arg
109 ]) config.extraDriverArgs
110 )}
111 '';
112
113in
114{
115 options = {
116
117 driver = mkOption {
118 description = "Package containing a script that runs the test.";
119 type = types.package;
120 defaultText = literalMD "set by the test framework";
121 };
122
123 hostPkgs = mkOption {
124 description = "Nixpkgs attrset used outside the nodes.";
125 type = types.raw;
126 example = lib.literalExpression ''
127 import nixpkgs { inherit system config overlays; }
128 '';
129 };
130
131 qemu.package = mkOption {
132 description = "Which qemu package to use for the virtualisation of [{option}`nodes`](#test-opt-nodes).";
133 type = types.package;
134 default = hostPkgs.qemu_test;
135 defaultText = "hostPkgs.qemu_test";
136 };
137
138 globalTimeout = mkOption {
139 description = ''
140 A global timeout for the complete test, expressed in seconds.
141 Beyond that timeout, every resource will be killed and released and the test will fail.
142
143 By default, we use a 1 hour timeout.
144 '';
145 type = types.int;
146 default = 60 * 60;
147 example = 10 * 60;
148 };
149
150 enableOCR = mkOption {
151 description = ''
152 Whether to enable Optical Character Recognition functionality for
153 testing graphical programs. See [`Machine objects`](#ssec-machine-objects).
154 '';
155 type = types.bool;
156 default = false;
157 };
158
159 extraPythonPackages = mkOption {
160 description = ''
161 Python packages to add to the test driver.
162
163 The argument is a Python package set, similar to `pkgs.pythonPackages`.
164 '';
165 example = lib.literalExpression ''
166 p: [ p.numpy ]
167 '';
168 type = types.functionTo (types.listOf types.package);
169 default = ps: [ ];
170 };
171
172 extraDriverArgs = mkOption {
173 description = ''
174 Extra arguments to pass to the test driver.
175
176 They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`.
177 '';
178 type = types.listOf types.str;
179 default = [ ];
180 };
181
182 skipLint = mkOption {
183 type = types.bool;
184 default = false;
185 description = ''
186 Do not run the linters. This may speed up your iteration cycle, but it is not something you should commit.
187 '';
188 };
189
190 skipTypeCheck = mkOption {
191 type = types.bool;
192 default = false;
193 description = ''
194 Disable type checking. This must not be enabled for new NixOS tests.
195
196 This may speed up your iteration cycle, unless you're working on the [{option}`testScript`](#test-opt-testScript).
197 '';
198 };
199 };
200
201 config = {
202 _module.args = {
203 hostPkgs =
204 # Comment is in nixos/modules/misc/nixpkgs.nix
205 lib.mkOverride lib.modules.defaultOverridePriority config.hostPkgs.__splicedPackages;
206 };
207
208 driver = withChecks driver;
209
210 # make available on the test runner
211 passthru.driver = config.driver;
212 };
213}