at 21.11-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 20 mkTestDriver = 21 let 22 testDriverScript = ./test-driver/test-driver.py; 23 in 24 qemu_pkg: stdenv.mkDerivation { 25 name = "nixos-test-driver"; 26 27 nativeBuildInputs = [ makeWrapper ]; 28 buildInputs = [ (python3.withPackages (p: [ p.ptpython p.colorama ])) ]; 29 checkInputs = with python3Packages; [ pylint black mypy ]; 30 31 dontUnpack = true; 32 33 preferLocalBuild = true; 34 35 buildPhase = '' 36 python <<EOF 37 from pydoc import importfile 38 with open('driver-exports', 'w') as fp: 39 fp.write(','.join(dir(importfile('${testDriverScript}')))) 40 EOF 41 ''; 42 43 doCheck = true; 44 checkPhase = '' 45 mypy --disallow-untyped-defs \ 46 --no-implicit-optional \ 47 --ignore-missing-imports ${testDriverScript} 48 pylint --errors-only ${testDriverScript} 49 black --check --diff ${testDriverScript} 50 ''; 51 52 installPhase = 53 '' 54 mkdir -p $out/bin 55 cp ${testDriverScript} $out/bin/nixos-test-driver 56 chmod u+x $out/bin/nixos-test-driver 57 # TODO: copy user script part into this file (append) 58 59 wrapProgram $out/bin/nixos-test-driver \ 60 --prefix PATH : "${lib.makeBinPath [ qemu_pkg vde2 netpbm coreutils ]}" \ 61 62 install -m 0644 -vD driver-exports $out/nix-support/driver-exports 63 ''; 64 }; 65 66 # Run an automated test suite in the given virtual network. 67 runTests = { 68 # the script that runs the network 69 driver, 70 # a source position in the format of builtins.unsafeGetAttrPos 71 # for meta.position 72 pos, 73 }: 74 stdenv.mkDerivation { 75 name = "vm-test-run-${driver.testName}"; 76 77 requiredSystemFeatures = [ "kvm" "nixos-test" ]; 78 79 buildCommand = 80 '' 81 mkdir -p $out 82 83 LOGFILE=/dev/null tests='exec(os.environ["testScript"])' ${driver}/bin/nixos-test-driver 84 ''; 85 86 passthru = driver.passthru // { 87 inherit driver; 88 }; 89 90 inherit pos; 91 }; 92 93 94 makeTest = 95 { testScript 96 , enableOCR ? false 97 , name ? "unnamed" 98 # Skip linting (mainly intended for faster dev cycles) 99 , skipLint ? false 100 , passthru ? {} 101 , # For meta.position 102 pos ? # position used in error messages and for meta.position 103 (if t.meta.description or null != null 104 then builtins.unsafeGetAttrPos "description" t.meta 105 else builtins.unsafeGetAttrPos "testScript" t) 106 , ... 107 } @ t: 108 let 109 # A standard store path to the vm monitor is built like this: 110 # /tmp/nix-build-vm-test-run-$name.drv-0/vm-state-machine/monitor 111 # The max filename length of a unix domain socket is 108 bytes. 112 # This means $name can at most be 50 bytes long. 113 maxTestNameLen = 50; 114 testNameLen = builtins.stringLength name; 115 116 117 118 ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; }; 119 120 imagemagick_tiff = imagemagick_light.override { inherit libtiff; }; 121 122 # Generate convenience wrappers for running the test driver 123 # interactively with the specified network, and for starting the 124 # VMs from the command line. 125 mkDriver = qemu_pkg: 126 let 127 build-vms = import ./build-vms.nix { 128 inherit system pkgs minimal specialArgs; 129 extraConfigurations = extraConfigurations ++ (pkgs.lib.optional (qemu_pkg != null) 130 { 131 virtualisation.qemu.package = qemu_pkg; 132 } 133 ); 134 }; 135 136 # FIXME: get this pkg from the module system 137 testDriver = mkTestDriver (if qemu_pkg == null then pkgs.qemu_test else qemu_pkg); 138 139 nodes = build-vms.buildVirtualNetwork ( 140 t.nodes or (if t ? machine then { machine = t.machine; } else { }) 141 ); 142 vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes); 143 vms = map (m: m.config.system.build.vm) (lib.attrValues nodes); 144 145 testScript' = 146 # Call the test script with the computed nodes. 147 if lib.isFunction testScript 148 then testScript { inherit nodes; } 149 else testScript; 150 151 testDriverName = with builtins; 152 if testNameLen > maxTestNameLen then 153 abort 154 ("The name of the test '${name}' must not be longer than ${toString maxTestNameLen} " + 155 "it's currently ${toString testNameLen} characters long.") 156 else 157 "nixos-test-driver-${name}"; 158 in 159 lib.warnIf skipLint "Linting is disabled" (runCommand testDriverName 160 { 161 nativeBuildInputs = [ makeWrapper ]; 162 testScript = testScript'; 163 preferLocalBuild = true; 164 testName = name; 165 passthru = passthru // { 166 inherit nodes; 167 }; 168 } 169 '' 170 mkdir -p $out/bin 171 172 echo -n "$testScript" > $out/test-script 173 ${lib.optionalString (!skipLint) '' 174 PYFLAKES_BUILTINS="$( 175 echo -n ${lib.escapeShellArg (lib.concatStringsSep "," nodeHostNames)}, 176 < ${lib.escapeShellArg "${testDriver}/nix-support/driver-exports"} 177 )" ${python3Packages.pyflakes}/bin/pyflakes $out/test-script 178 ''} 179 180 ln -s ${testDriver}/bin/nixos-test-driver $out/bin/ 181 vms=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done)) 182 wrapProgram $out/bin/nixos-test-driver \ 183 --add-flags "''${vms[*]}" \ 184 ${lib.optionalString enableOCR 185 "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \ 186 --run "export testScript=\"\$(${coreutils}/bin/cat $out/test-script)\"" \ 187 --set VLANS '${toString vlans}' 188 ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms 189 wrapProgram $out/bin/nixos-run-vms \ 190 --add-flags "''${vms[*]}" \ 191 ${lib.optionalString enableOCR "--prefix PATH : '${ocrProg}/bin'"} \ 192 --set tests 'start_all(); join_all();' \ 193 --set VLANS '${toString vlans}' \ 194 ${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"} 195 ''); # " 196 197 passMeta = drv: drv // lib.optionalAttrs (t ? meta) { 198 meta = (drv.meta or { }) // t.meta; 199 }; 200 201 driver = mkDriver null; 202 driverInteractive = mkDriver pkgs.qemu; 203 204 test = passMeta (runTests { inherit driver pos; }); 205 206 nodeNames = builtins.attrNames driver.nodes; 207 invalidNodeNames = lib.filter 208 (node: builtins.match "^[A-z_]([A-z0-9_]+)?$" node == null) 209 nodeNames; 210 211 nodeHostNames = map (c: c.config.system.name) (lib.attrValues driver.nodes); 212 213 in 214 if lib.length invalidNodeNames > 0 then 215 throw '' 216 Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})! 217 All machines are referenced as python variables in the testing framework which will break the 218 script when special characters are used. 219 220 Please stick to alphanumeric chars and underscores as separation. 221 '' 222 else 223 test // { 224 inherit test driver driverInteractive; 225 inherit (driver) nodes; 226 }; 227 228 runInMachine = 229 { drv 230 , machine 231 , preBuild ? "" 232 , postBuild ? "" 233 , qemu ? pkgs.qemu_test 234 , ... # ??? 235 }: 236 let 237 build-vms = import ./build-vms.nix { 238 inherit system pkgs minimal specialArgs extraConfigurations; 239 }; 240 241 vm = build-vms.buildVM { } 242 [ 243 machine 244 { 245 key = "run-in-machine"; 246 networking.hostName = "client"; 247 nix.readOnlyStore = false; 248 virtualisation.writableStore = false; 249 } 250 ]; 251 252 buildrunner = writeText "vm-build" '' 253 source $1 254 255 ${coreutils}/bin/mkdir -p $TMPDIR 256 cd $TMPDIR 257 258 exec $origBuilder $origArgs 259 ''; 260 261 testScript = '' 262 start_all() 263 client.wait_for_unit("multi-user.target") 264 ${preBuild} 265 client.succeed("env -i ${bash}/bin/bash ${buildrunner} /tmp/xchg/saved-env >&2") 266 ${postBuild} 267 client.succeed("sync") # flush all data before pulling the plug 268 ''; 269 270 vmRunCommand = writeText "vm-run" '' 271 xchg=vm-state-client/xchg 272 ${coreutils}/bin/mkdir $out 273 ${coreutils}/bin/mkdir -p $xchg 274 275 for i in $passAsFile; do 276 i2=''${i}Path 277 _basename=$(${coreutils}/bin/basename ''${!i2}) 278 ${coreutils}/bin/cp ''${!i2} $xchg/$_basename 279 eval $i2=/tmp/xchg/$_basename 280 ${coreutils}/bin/ls -la $xchg 281 done 282 283 unset i i2 _basename 284 export | ${gnugrep}/bin/grep -v '^xchg=' > $xchg/saved-env 285 unset xchg 286 287 export tests='${testScript}' 288 ${mkTestDriver qemu}/bin/nixos-test-driver --keep-vm-state ${vm.config.system.build.vm}/bin/run-*-vm 289 ''; # */ 290 291 in 292 lib.overrideDerivation drv (attrs: { 293 requiredSystemFeatures = [ "kvm" ]; 294 builder = "${bash}/bin/sh"; 295 args = [ "-e" vmRunCommand ]; 296 origArgs = attrs.args; 297 origBuilder = attrs.builder; 298 }); 299 300 301 runInMachineWithX = { require ? [ ], ... } @ args: 302 let 303 client = 304 { ... }: 305 { 306 inherit require; 307 imports = [ 308 ../tests/common/auto.nix 309 ]; 310 virtualisation.memorySize = 1024; 311 services.xserver.enable = true; 312 test-support.displayManager.auto.enable = true; 313 services.xserver.displayManager.defaultSession = "none+icewm"; 314 services.xserver.windowManager.icewm.enable = true; 315 }; 316 in 317 runInMachine ({ 318 machine = client; 319 preBuild = 320 '' 321 client.wait_for_x() 322 ''; 323 } // args); 324 325 326 simpleTest = as: (makeTest as).test; 327 328}