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 pythonizeName = name:
25 let
26 head = lib.substring 0 1 name;
27 tail = lib.substring 1 (-1) name;
28 in
29 (if builtins.match "[A-z_]" head == null then "_" else head) +
30 lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
31
32 uniqueVlans = lib.unique (builtins.concatLists vlans);
33 vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
34 pythonizedNames = map pythonizeName nodeHostNames;
35 machineNames = map (name: "${name}: Machine;") pythonizedNames;
36
37 withChecks = lib.warnIf config.skipLint "Linting is disabled";
38
39 driver =
40 hostPkgs.runCommand "nixos-test-driver-${config.name}"
41 {
42 # inherit testName; TODO (roberth): need this?
43 nativeBuildInputs = [
44 hostPkgs.makeWrapper
45 ] ++ lib.optionals (!config.skipTypeCheck) [ hostPkgs.mypy ];
46 buildInputs = [ testDriver ];
47 testScript = config.testScriptString;
48 preferLocalBuild = true;
49 passthru = config.passthru;
50 meta = config.meta // {
51 mainProgram = "nixos-test-driver";
52 };
53 }
54 ''
55 mkdir -p $out/bin
56
57 vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
58
59 ${lib.optionalString (!config.skipTypeCheck) ''
60 # prepend type hints so the test script can be type checked with mypy
61 cat "${../test-script-prepend.py}" >> testScriptWithTypes
62 echo "${builtins.toString machineNames}" >> testScriptWithTypes
63 echo "${builtins.toString vlanNames}" >> testScriptWithTypes
64 echo -n "$testScript" >> testScriptWithTypes
65
66 cat -n testScriptWithTypes
67
68 mypy --no-implicit-optional \
69 --pretty \
70 --no-color-output \
71 testScriptWithTypes
72 ''}
73
74 echo -n "$testScript" >> $out/test-script
75
76 ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver
77
78 ${testDriver}/bin/generate-driver-symbols
79 ${lib.optionalString (!config.skipLint) ''
80 PYFLAKES_BUILTINS="$(
81 echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
82 < ${lib.escapeShellArg "driver-symbols"}
83 )" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
84 ''}
85
86 # set defaults through environment
87 # see: ./test-driver/test-driver.py argparse implementation
88 wrapProgram $out/bin/nixos-test-driver \
89 --set startScripts "''${vmStartScripts[*]}" \
90 --set testScript "$out/test-script" \
91 --set vlans '${toString vlans}' \
92 ${lib.escapeShellArgs (lib.concatMap (arg: ["--add-flags" arg]) config.extraDriverArgs)}
93 '';
94
95in
96{
97 options = {
98
99 driver = mkOption {
100 description = mdDoc "Package containing a script that runs the test.";
101 type = types.package;
102 defaultText = literalMD "set by the test framework";
103 };
104
105 hostPkgs = mkOption {
106 description = mdDoc "Nixpkgs attrset used outside the nodes.";
107 type = types.raw;
108 example = lib.literalExpression ''
109 import nixpkgs { inherit system config overlays; }
110 '';
111 };
112
113 qemu.package = mkOption {
114 description = mdDoc "Which qemu package to use for the virtualisation of [{option}`nodes`](#test-opt-nodes).";
115 type = types.package;
116 default = hostPkgs.qemu_test;
117 defaultText = "hostPkgs.qemu_test";
118 };
119
120 enableOCR = mkOption {
121 description = mdDoc ''
122 Whether to enable Optical Character Recognition functionality for
123 testing graphical programs. See [Machine objects](`ssec-machine-objects`).
124 '';
125 type = types.bool;
126 default = false;
127 };
128
129 extraPythonPackages = mkOption {
130 description = mdDoc ''
131 Python packages to add to the test driver.
132
133 The argument is a Python package set, similar to `pkgs.pythonPackages`.
134 '';
135 example = lib.literalExpression ''
136 p: [ p.numpy ]
137 '';
138 type = types.functionTo (types.listOf types.package);
139 default = ps: [ ];
140 };
141
142 extraDriverArgs = mkOption {
143 description = mdDoc ''
144 Extra arguments to pass to the test driver.
145
146 They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`.
147 '';
148 type = types.listOf types.str;
149 default = [];
150 };
151
152 skipLint = mkOption {
153 type = types.bool;
154 default = false;
155 description = mdDoc ''
156 Do not run the linters. This may speed up your iteration cycle, but it is not something you should commit.
157 '';
158 };
159
160 skipTypeCheck = mkOption {
161 type = types.bool;
162 default = false;
163 description = mdDoc ''
164 Disable type checking. This must not be enabled for new NixOS tests.
165
166 This may speed up your iteration cycle, unless you're working on the [{option}`testScript`](#test-opt-testScript).
167 '';
168 };
169 };
170
171 config = {
172 _module.args.hostPkgs = config.hostPkgs;
173
174 driver = withChecks driver;
175
176 # make available on the test runner
177 passthru.driver = config.driver;
178 };
179}