1{ config, lib, hostPkgs, ... }:
2let
3 inherit (lib) mkOption types literalMD mdDoc;
4
5 # Reifies and correctly wraps the python test driver for
6 # the respective qemu version and with or without ocr support
7 testDriver = hostPkgs.callPackage ../test-driver {
8 inherit (config) enableOCR extraPythonPackages;
9 qemu_pkg = config.qemu.package;
10 imagemagick_light = hostPkgs.imagemagick_light.override { inherit (hostPkgs) libtiff; };
11 tesseract4 = hostPkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
12 };
13
14
15 vlans = map (m: m.virtualisation.vlans) (lib.attrValues config.nodes);
16 vms = map (m: m.system.build.vm) (lib.attrValues config.nodes);
17
18 nodeHostNames =
19 let
20 nodesList = map (c: c.system.name) (lib.attrValues config.nodes);
21 in
22 nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
23
24 # TODO: This is an implementation error and needs fixing
25 # the testing famework cannot legitimately restrict hostnames further
26 # beyond RFC1035
27 invalidNodeNames = lib.filter
28 (node: builtins.match "^[A-z_]([A-z0-9_]+)?$" node == null)
29 nodeHostNames;
30
31 uniqueVlans = lib.unique (builtins.concatLists vlans);
32 vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
33 machineNames = map (name: "${name}: Machine;") nodeHostNames;
34
35 withChecks =
36 if lib.length invalidNodeNames > 0 then
37 throw ''
38 Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})!
39 All machines are referenced as python variables in the testing framework which will break the
40 script when special characters are used.
41
42 This is an IMPLEMENTATION ERROR and needs to be fixed. Meanwhile,
43 please stick to alphanumeric chars and underscores as separation.
44 ''
45 else
46 lib.warnIf config.skipLint "Linting is disabled";
47
48 driver =
49 hostPkgs.runCommand "nixos-test-driver-${config.name}"
50 {
51 # inherit testName; TODO (roberth): need this?
52 nativeBuildInputs = [
53 hostPkgs.makeWrapper
54 ] ++ lib.optionals (!config.skipTypeCheck) [ hostPkgs.mypy ];
55 buildInputs = [ testDriver ];
56 testScript = config.testScriptString;
57 preferLocalBuild = true;
58 passthru = config.passthru;
59 meta = config.meta // {
60 mainProgram = "nixos-test-driver";
61 };
62 }
63 ''
64 mkdir -p $out/bin
65
66 vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
67
68 ${lib.optionalString (!config.skipTypeCheck) ''
69 # prepend type hints so the test script can be type checked with mypy
70 cat "${../test-script-prepend.py}" >> testScriptWithTypes
71 echo "${builtins.toString machineNames}" >> testScriptWithTypes
72 echo "${builtins.toString vlanNames}" >> testScriptWithTypes
73 echo -n "$testScript" >> testScriptWithTypes
74
75 cat -n testScriptWithTypes
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 PYFLAKES_BUILTINS="$(
90 echo -n ${lib.escapeShellArg (lib.concatStringsSep "," nodeHostNames)},
91 < ${lib.escapeShellArg "driver-symbols"}
92 )" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
93 ''}
94
95 # set defaults through environment
96 # see: ./test-driver/test-driver.py argparse implementation
97 wrapProgram $out/bin/nixos-test-driver \
98 --set startScripts "''${vmStartScripts[*]}" \
99 --set testScript "$out/test-script" \
100 --set vlans '${toString vlans}' \
101 ${lib.escapeShellArgs (lib.concatMap (arg: ["--add-flags" arg]) config.extraDriverArgs)}
102 '';
103
104in
105{
106 options = {
107
108 driver = mkOption {
109 description = mdDoc "Package containing a script that runs the test.";
110 type = types.package;
111 defaultText = literalMD "set by the test framework";
112 };
113
114 hostPkgs = mkOption {
115 description = mdDoc "Nixpkgs attrset used outside the nodes.";
116 type = types.raw;
117 example = lib.literalExpression ''
118 import nixpkgs { inherit system config overlays; }
119 '';
120 };
121
122 qemu.package = mkOption {
123 description = mdDoc "Which qemu package to use for the virtualisation of [{option}`nodes`](#test-opt-nodes).";
124 type = types.package;
125 default = hostPkgs.qemu_test;
126 defaultText = "hostPkgs.qemu_test";
127 };
128
129 enableOCR = mkOption {
130 description = mdDoc ''
131 Whether to enable Optical Character Recognition functionality for
132 testing graphical programs. See [Machine objects](`ssec-machine-objects`).
133 '';
134 type = types.bool;
135 default = false;
136 };
137
138 extraPythonPackages = mkOption {
139 description = mdDoc ''
140 Python packages to add to the test driver.
141
142 The argument is a Python package set, similar to `pkgs.pythonPackages`.
143 '';
144 example = lib.literalExpression ''
145 p: [ p.numpy ]
146 '';
147 type = types.functionTo (types.listOf types.package);
148 default = ps: [ ];
149 };
150
151 extraDriverArgs = mkOption {
152 description = mdDoc ''
153 Extra arguments to pass to the test driver.
154
155 They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`.
156 '';
157 type = types.listOf types.str;
158 default = [];
159 };
160
161 skipLint = mkOption {
162 type = types.bool;
163 default = false;
164 description = mdDoc ''
165 Do not run the linters. This may speed up your iteration cycle, but it is not something you should commit.
166 '';
167 };
168
169 skipTypeCheck = mkOption {
170 type = types.bool;
171 default = false;
172 description = mdDoc ''
173 Disable type checking. This must not be enabled for new NixOS tests.
174
175 This may speed up your iteration cycle, unless you're working on the [{option}`testScript`](#test-opt-testScript).
176 '';
177 };
178 };
179
180 config = {
181 _module.args.hostPkgs = config.hostPkgs;
182
183 driver = withChecks driver;
184
185 # make available on the test runner
186 passthru.driver = config.driver;
187 };
188}