at 22.05-pre 10 kB view raw
1{ system 2, pkgs ? import ../.. { inherit system config; } 3 # Use a minimal kernel? 4, minimal ? false 5 # Ignored 6, config ? { } 7 # !!! See comment about args in lib/modules.nix 8, specialArgs ? { } 9 # Modules to add to each VM 10, extraConfigurations ? [ ] 11}: 12 13with pkgs; 14 15rec { 16 17 inherit pkgs; 18 19 # Reifies and correctly wraps the python test driver for 20 # the respective qemu version and with or without ocr support 21 pythonTestDriver = { 22 qemu_pkg ? pkgs.qemu_test 23 , enableOCR ? false 24 }: 25 let 26 name = "nixos-test-driver"; 27 testDriverScript = ./test-driver/test-driver.py; 28 ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; }; 29 imagemagick_tiff = imagemagick_light.override { inherit libtiff; }; 30 in stdenv.mkDerivation { 31 inherit name; 32 33 nativeBuildInputs = [ makeWrapper ]; 34 buildInputs = [ (python3.withPackages (p: [ p.ptpython p.colorama ])) ]; 35 checkInputs = with python3Packages; [ pylint black mypy ]; 36 37 dontUnpack = true; 38 39 preferLocalBuild = true; 40 41 buildPhase = '' 42 python <<EOF 43 from pydoc import importfile 44 with open('driver-symbols', 'w') as fp: 45 t = importfile('${testDriverScript}') 46 d = t.Driver([],[],"") 47 test_symbols = d.test_symbols() 48 fp.write(','.join(test_symbols.keys())) 49 EOF 50 ''; 51 52 doCheck = true; 53 checkPhase = '' 54 mypy --disallow-untyped-defs \ 55 --no-implicit-optional \ 56 --ignore-missing-imports ${testDriverScript} 57 pylint --errors-only ${testDriverScript} 58 black --check --diff ${testDriverScript} 59 ''; 60 61 installPhase = 62 '' 63 mkdir -p $out/bin 64 cp ${testDriverScript} $out/bin/nixos-test-driver 65 chmod u+x $out/bin/nixos-test-driver 66 # TODO: copy user script part into this file (append) 67 68 wrapProgram $out/bin/nixos-test-driver \ 69 --argv0 ${name} \ 70 --prefix PATH : "${lib.makeBinPath [ qemu_pkg vde2 netpbm coreutils socat ]}" \ 71 ${lib.optionalString enableOCR 72 "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \ 73 74 install -m 0644 -vD driver-symbols $out/nix-support/driver-symbols 75 ''; 76 }; 77 78 # Run an automated test suite in the given virtual network. 79 runTests = { driver, pos }: 80 stdenv.mkDerivation { 81 name = "vm-test-run-${driver.testName}"; 82 83 requiredSystemFeatures = [ "kvm" "nixos-test" ]; 84 85 buildCommand = 86 '' 87 mkdir -p $out 88 89 # effectively mute the XMLLogger 90 export LOGFILE=/dev/null 91 92 ${driver}/bin/nixos-test-driver 93 ''; 94 95 passthru = driver.passthru // { 96 inherit driver; 97 }; 98 99 inherit pos; # for better debugging 100 }; 101 102 # Generate convenience wrappers for running the test driver 103 # has vlans, vms and test script defaulted through env variables 104 # also instantiates test script with nodes, if it's a function (contract) 105 setupDriverForTest = { 106 testScript 107 , testName 108 , nodes 109 , qemu_pkg ? pkgs.qemu_test 110 , enableOCR ? false 111 , skipLint ? false 112 , passthru ? {} 113 }: 114 let 115 # FIXME: get this pkg from the module system 116 testDriver = pythonTestDriver { inherit qemu_pkg enableOCR;}; 117 118 testDriverName = 119 let 120 # A standard store path to the vm monitor is built like this: 121 # /tmp/nix-build-vm-test-run-$name.drv-0/vm-state-machine/monitor 122 # The max filename length of a unix domain socket is 108 bytes. 123 # This means $name can at most be 50 bytes long. 124 maxTestNameLen = 50; 125 testNameLen = builtins.stringLength testName; 126 in with builtins; 127 if testNameLen > maxTestNameLen then 128 abort 129 ("The name of the test '${testName}' must not be longer than ${toString maxTestNameLen} " + 130 "it's currently ${toString testNameLen} characters long.") 131 else 132 "nixos-test-driver-${testName}"; 133 134 vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes); 135 vms = map (m: m.config.system.build.vm) (lib.attrValues nodes); 136 137 nodeHostNames = let 138 nodesList = map (c: c.config.system.name) (lib.attrValues nodes); 139 in nodesList ++ lib.optional (lib.length nodesList == 1) "machine"; 140 141 # TODO: This is an implementation error and needs fixing 142 # the testing famework cannot legitimately restrict hostnames further 143 # beyond RFC1035 144 invalidNodeNames = lib.filter 145 (node: builtins.match "^[A-z_]([A-z0-9_]+)?$" node == null) 146 nodeHostNames; 147 148 testScript' = 149 # Call the test script with the computed nodes. 150 if lib.isFunction testScript 151 then testScript { inherit nodes; } 152 else testScript; 153 154 in 155 if lib.length invalidNodeNames > 0 then 156 throw '' 157 Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})! 158 All machines are referenced as python variables in the testing framework which will break the 159 script when special characters are used. 160 161 This is an IMPLEMENTATION ERROR and needs to be fixed. Meanwhile, 162 please stick to alphanumeric chars and underscores as separation. 163 '' 164 else lib.warnIf skipLint "Linting is disabled" (runCommand testDriverName 165 { 166 inherit testName; 167 nativeBuildInputs = [ makeWrapper ]; 168 testScript = testScript'; 169 preferLocalBuild = true; 170 passthru = passthru // { 171 inherit nodes; 172 }; 173 } 174 '' 175 mkdir -p $out/bin 176 177 vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done)) 178 echo -n "$testScript" > $out/test-script 179 ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver 180 181 ${lib.optionalString (!skipLint) '' 182 PYFLAKES_BUILTINS="$( 183 echo -n ${lib.escapeShellArg (lib.concatStringsSep "," nodeHostNames)}, 184 < ${lib.escapeShellArg "${testDriver}/nix-support/driver-symbols"} 185 )" ${python3Packages.pyflakes}/bin/pyflakes $out/test-script 186 ''} 187 188 # set defaults through environment 189 # see: ./test-driver/test-driver.py argparse implementation 190 wrapProgram $out/bin/nixos-test-driver \ 191 --set startScripts "''${vmStartScripts[*]}" \ 192 --set testScript "$out/test-script" \ 193 --set vlans '${toString vlans}' 194 ''); 195 196 # Make a full-blown test 197 makeTest = 198 { testScript 199 , enableOCR ? false 200 , name ? "unnamed" 201 # Skip linting (mainly intended for faster dev cycles) 202 , skipLint ? false 203 , passthru ? {} 204 , # For meta.position 205 pos ? # position used in error messages and for meta.position 206 (if t.meta.description or null != null 207 then builtins.unsafeGetAttrPos "description" t.meta 208 else builtins.unsafeGetAttrPos "testScript" t) 209 , ... 210 } @ t: 211 let 212 nodes = qemu_pkg: 213 let 214 testScript' = 215 # Call the test script with the computed nodes. 216 if lib.isFunction testScript 217 then testScript { nodes = nodes qemu_pkg; } 218 else testScript; 219 220 build-vms = import ./build-vms.nix { 221 inherit system lib pkgs minimal specialArgs; 222 extraConfigurations = extraConfigurations ++ [( 223 { config, ... }: 224 { 225 virtualisation.qemu.package = qemu_pkg; 226 227 # Make sure all derivations referenced by the test 228 # script are available on the nodes. When the store is 229 # accessed through 9p, this isn't important, since 230 # everything in the store is available to the guest, 231 # but when building a root image it is, as all paths 232 # that should be available to the guest has to be 233 # copied to the image. 234 virtualisation.additionalPaths = 235 lib.optional 236 # A testScript may evaluate nodes, which has caused 237 # infinite recursions. The demand cycle involves: 238 # testScript --> 239 # nodes --> 240 # toplevel --> 241 # additionalPaths --> 242 # hasContext testScript' --> 243 # testScript (ad infinitum) 244 # If we don't need to build an image, we can break this 245 # cycle by short-circuiting when useNixStoreImage is false. 246 (config.virtualisation.useNixStoreImage && builtins.hasContext testScript') 247 (pkgs.writeStringReferencesToFile testScript'); 248 249 # Ensure we do not use aliases. Ideally this is only set 250 # when the test framework is used by Nixpkgs NixOS tests. 251 nixpkgs.config.allowAliases = false; 252 } 253 )]; 254 }; 255 in 256 build-vms.buildVirtualNetwork ( 257 t.nodes or (if t ? machine then { machine = t.machine; } else { }) 258 ); 259 260 driver = setupDriverForTest { 261 inherit testScript enableOCR skipLint passthru; 262 testName = name; 263 qemu_pkg = pkgs.qemu_test; 264 nodes = nodes pkgs.qemu_test; 265 }; 266 driverInteractive = setupDriverForTest { 267 inherit testScript enableOCR skipLint passthru; 268 testName = name; 269 qemu_pkg = pkgs.qemu; 270 nodes = nodes pkgs.qemu; 271 }; 272 273 test = 274 let 275 passMeta = drv: drv // lib.optionalAttrs (t ? meta) { 276 meta = (drv.meta or { }) // t.meta; 277 }; 278 in passMeta (runTests { inherit driver pos; }); 279 280 in 281 test // { 282 inherit test driver driverInteractive nodes; 283 }; 284 285 abortForFunction = functionName: abort ''The ${functionName} function was 286 removed because it is not an essential part of the NixOS testing 287 infrastructure. It had no usage in NixOS or Nixpkgs and it had no designated 288 maintainer. You are free to reintroduce it by documenting it in the manual 289 and adding yourself as maintainer. It was removed in 290 https://github.com/NixOS/nixpkgs/pull/137013 291 ''; 292 293 runInMachine = abortForFunction "runInMachine"; 294 295 runInMachineWithX = abortForFunction "runInMachineWithX"; 296 297 simpleTest = as: (makeTest as).test; 298 299}