
So far, we have used "black" for formatting the test code, which is rather strict and opinionated and when used inline in Nix expressions it creates all sorts of trouble. One of the main annoyances is that when using strings coming from Nix expressions (eg. store paths or option definitions from NixOS modules), completely unrelated changes could cause tests to fail, since eg. black wants lines to be broken. Another downside of enforcing a certain kind of formatting is that it makes the Nix expression code inconsistent because we're mixing two spaces of indentation (common in nixpkgs) with four spaces of indentation as defined in PEP-8. While this is perfectly fine for standalone Python files, it really looks ugly and inconsistent IMO when used within Nix strings. What we actually want though is a linter that catches problems early on before actually running the test, because this is *actually* helping in development because running the actual VM test takes much longer. This is the reason why I switched from black to pyflakes, because the latter actually has useful checks, eg. usage of undefined variables, invalid format arguments, duplicate arguments, shadowed loop vars and more. Signed-off-by: aszlig <aszlig@nix.build> Closes: https://github.com/NixOS/nixpkgs/issues/72964
312 lines
9.3 KiB
Nix
312 lines
9.3 KiB
Nix
{ system
|
|
, pkgs ? import ../.. { inherit system config; }
|
|
# Use a minimal kernel?
|
|
, minimal ? false
|
|
# Ignored
|
|
, config ? { }
|
|
# !!! See comment about args in lib/modules.nix
|
|
, specialArgs ? { }
|
|
# Modules to add to each VM
|
|
, extraConfigurations ? [ ]
|
|
}:
|
|
|
|
with pkgs;
|
|
|
|
rec {
|
|
|
|
inherit pkgs;
|
|
|
|
|
|
mkTestDriver =
|
|
let
|
|
testDriverScript = ./test-driver/test-driver.py;
|
|
in
|
|
qemu_pkg: stdenv.mkDerivation {
|
|
name = "nixos-test-driver";
|
|
|
|
nativeBuildInputs = [ makeWrapper ];
|
|
buildInputs = [ (python3.withPackages (p: [ p.ptpython ])) ];
|
|
checkInputs = with python3Packages; [ pylint black mypy ];
|
|
|
|
dontUnpack = true;
|
|
|
|
preferLocalBuild = true;
|
|
|
|
doCheck = true;
|
|
checkPhase = ''
|
|
mypy --disallow-untyped-defs \
|
|
--no-implicit-optional \
|
|
--ignore-missing-imports ${testDriverScript}
|
|
pylint --errors-only ${testDriverScript}
|
|
black --check --diff ${testDriverScript}
|
|
'';
|
|
|
|
installPhase =
|
|
''
|
|
mkdir -p $out/bin
|
|
cp ${testDriverScript} $out/bin/nixos-test-driver
|
|
chmod u+x $out/bin/nixos-test-driver
|
|
# TODO: copy user script part into this file (append)
|
|
|
|
wrapProgram $out/bin/nixos-test-driver \
|
|
--prefix PATH : "${lib.makeBinPath [ qemu_pkg vde2 netpbm coreutils ]}" \
|
|
'';
|
|
};
|
|
|
|
# Run an automated test suite in the given virtual network.
|
|
runTests = {
|
|
# the script that runs the network
|
|
driver,
|
|
# a source position in the format of builtins.unsafeGetAttrPos
|
|
# for meta.position
|
|
pos,
|
|
}:
|
|
stdenv.mkDerivation {
|
|
name = "vm-test-run-${driver.testName}";
|
|
|
|
requiredSystemFeatures = [ "kvm" "nixos-test" ];
|
|
|
|
buildCommand =
|
|
''
|
|
mkdir -p $out
|
|
|
|
LOGFILE=/dev/null tests='exec(os.environ["testScript"])' ${driver}/bin/nixos-test-driver
|
|
'';
|
|
|
|
passthru = driver.passthru;
|
|
|
|
inherit pos;
|
|
};
|
|
|
|
|
|
makeTest =
|
|
{ testScript
|
|
, enableOCR ? false
|
|
, name ? "unnamed"
|
|
# Skip linting (mainly intended for faster dev cycles)
|
|
, skipLint ? false
|
|
, passthru ? {}
|
|
, # For meta.position
|
|
pos ? # position used in error messages and for meta.position
|
|
(if t.meta.description or null != null
|
|
then builtins.unsafeGetAttrPos "description" t.meta
|
|
else builtins.unsafeGetAttrPos "testScript" t)
|
|
, ...
|
|
} @ t:
|
|
let
|
|
# A standard store path to the vm monitor is built like this:
|
|
# /tmp/nix-build-vm-test-run-$name.drv-0/vm-state-machine/monitor
|
|
# The max filename length of a unix domain socket is 108 bytes.
|
|
# This means $name can at most be 50 bytes long.
|
|
maxTestNameLen = 50;
|
|
testNameLen = builtins.stringLength name;
|
|
|
|
|
|
|
|
ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; };
|
|
|
|
imagemagick_tiff = imagemagick_light.override { inherit libtiff; };
|
|
|
|
# Generate convenience wrappers for running the test driver
|
|
# interactively with the specified network, and for starting the
|
|
# VMs from the command line.
|
|
mkDriver = qemu_pkg:
|
|
let
|
|
build-vms = import ./build-vms.nix {
|
|
inherit system pkgs minimal specialArgs;
|
|
extraConfigurations = extraConfigurations ++ (pkgs.lib.optional (qemu_pkg != null)
|
|
{
|
|
virtualisation.qemu.package = qemu_pkg;
|
|
}
|
|
);
|
|
};
|
|
|
|
# FIXME: get this pkg from the module system
|
|
testDriver = mkTestDriver (if qemu_pkg == null then pkgs.qemu_test else qemu_pkg);
|
|
|
|
nodes = build-vms.buildVirtualNetwork (
|
|
t.nodes or (if t ? machine then { machine = t.machine; } else { })
|
|
);
|
|
vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes);
|
|
vms = map (m: m.config.system.build.vm) (lib.attrValues nodes);
|
|
|
|
testScript' =
|
|
# Call the test script with the computed nodes.
|
|
if lib.isFunction testScript
|
|
then testScript { inherit nodes; }
|
|
else testScript;
|
|
|
|
testDriverName = with builtins;
|
|
if testNameLen > maxTestNameLen then
|
|
abort
|
|
("The name of the test '${name}' must not be longer than ${toString maxTestNameLen} " +
|
|
"it's currently ${toString testNameLen} characters long.")
|
|
else
|
|
"nixos-test-driver-${name}";
|
|
in
|
|
lib.warnIf skipLint "Linting is disabled" (runCommand testDriverName
|
|
{
|
|
buildInputs = [ makeWrapper ];
|
|
testScript = testScript';
|
|
preferLocalBuild = true;
|
|
testName = name;
|
|
passthru = passthru // {
|
|
inherit nodes;
|
|
};
|
|
}
|
|
''
|
|
mkdir -p $out/bin
|
|
|
|
echo -n "$testScript" > $out/test-script
|
|
${lib.optionalString (!skipLint) ''
|
|
${python3Packages.pyflakes}/bin/pyflakes $out/test-script
|
|
''}
|
|
|
|
ln -s ${testDriver}/bin/nixos-test-driver $out/bin/
|
|
vms=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
|
|
wrapProgram $out/bin/nixos-test-driver \
|
|
--add-flags "''${vms[*]}" \
|
|
${lib.optionalString enableOCR
|
|
"--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \
|
|
--run "export testScript=\"\$(${coreutils}/bin/cat $out/test-script)\"" \
|
|
--set VLANS '${toString vlans}'
|
|
ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms
|
|
wrapProgram $out/bin/nixos-run-vms \
|
|
--add-flags "''${vms[*]}" \
|
|
${lib.optionalString enableOCR "--prefix PATH : '${ocrProg}/bin'"} \
|
|
--set tests 'start_all(); join_all();' \
|
|
--set VLANS '${toString vlans}' \
|
|
${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"}
|
|
''); # "
|
|
|
|
passMeta = drv: drv // lib.optionalAttrs (t ? meta) {
|
|
meta = (drv.meta or { }) // t.meta;
|
|
};
|
|
|
|
driver = mkDriver null;
|
|
driverInteractive = mkDriver pkgs.qemu;
|
|
|
|
test = passMeta (runTests { inherit driver pos; });
|
|
|
|
nodeNames = builtins.attrNames driver.nodes;
|
|
invalidNodeNames = lib.filter
|
|
(node: builtins.match "^[A-z_]([A-z0-9_]+)?$" node == null)
|
|
nodeNames;
|
|
|
|
in
|
|
if lib.length invalidNodeNames > 0 then
|
|
throw ''
|
|
Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})!
|
|
All machines are referenced as python variables in the testing framework which will break the
|
|
script when special characters are used.
|
|
|
|
Please stick to alphanumeric chars and underscores as separation.
|
|
''
|
|
else
|
|
test // {
|
|
inherit test driver driverInteractive;
|
|
inherit (driver) nodes;
|
|
};
|
|
|
|
runInMachine =
|
|
{ drv
|
|
, machine
|
|
, preBuild ? ""
|
|
, postBuild ? ""
|
|
, qemu ? pkgs.qemu_test
|
|
, ... # ???
|
|
}:
|
|
let
|
|
build-vms = import ./build-vms.nix {
|
|
inherit system pkgs minimal specialArgs extraConfigurations;
|
|
};
|
|
|
|
vm = build-vms.buildVM { }
|
|
[
|
|
machine
|
|
{
|
|
key = "run-in-machine";
|
|
networking.hostName = "client";
|
|
nix.readOnlyStore = false;
|
|
virtualisation.writableStore = false;
|
|
}
|
|
];
|
|
|
|
buildrunner = writeText "vm-build" ''
|
|
source $1
|
|
|
|
${coreutils}/bin/mkdir -p $TMPDIR
|
|
cd $TMPDIR
|
|
|
|
exec $origBuilder $origArgs
|
|
'';
|
|
|
|
testScript = ''
|
|
start_all()
|
|
client.wait_for_unit("multi-user.target")
|
|
${preBuild}
|
|
client.succeed("env -i ${bash}/bin/bash ${buildrunner} /tmp/xchg/saved-env >&2")
|
|
${postBuild}
|
|
client.succeed("sync") # flush all data before pulling the plug
|
|
'';
|
|
|
|
vmRunCommand = writeText "vm-run" ''
|
|
xchg=vm-state-client/xchg
|
|
${coreutils}/bin/mkdir $out
|
|
${coreutils}/bin/mkdir -p $xchg
|
|
|
|
for i in $passAsFile; do
|
|
i2=''${i}Path
|
|
_basename=$(${coreutils}/bin/basename ''${!i2})
|
|
${coreutils}/bin/cp ''${!i2} $xchg/$_basename
|
|
eval $i2=/tmp/xchg/$_basename
|
|
${coreutils}/bin/ls -la $xchg
|
|
done
|
|
|
|
unset i i2 _basename
|
|
export | ${gnugrep}/bin/grep -v '^xchg=' > $xchg/saved-env
|
|
unset xchg
|
|
|
|
export tests='${testScript}'
|
|
${mkTestDriver qemu}/bin/nixos-test-driver --keep-vm-state ${vm.config.system.build.vm}/bin/run-*-vm
|
|
''; # */
|
|
|
|
in
|
|
lib.overrideDerivation drv (attrs: {
|
|
requiredSystemFeatures = [ "kvm" ];
|
|
builder = "${bash}/bin/sh";
|
|
args = [ "-e" vmRunCommand ];
|
|
origArgs = attrs.args;
|
|
origBuilder = attrs.builder;
|
|
});
|
|
|
|
|
|
runInMachineWithX = { require ? [ ], ... } @ args:
|
|
let
|
|
client =
|
|
{ ... }:
|
|
{
|
|
inherit require;
|
|
imports = [
|
|
../tests/common/auto.nix
|
|
];
|
|
virtualisation.memorySize = 1024;
|
|
services.xserver.enable = true;
|
|
test-support.displayManager.auto.enable = true;
|
|
services.xserver.displayManager.defaultSession = "none+icewm";
|
|
services.xserver.windowManager.icewm.enable = true;
|
|
};
|
|
in
|
|
runInMachine ({
|
|
machine = client;
|
|
preBuild =
|
|
''
|
|
client.wait_for_x()
|
|
'';
|
|
} // args);
|
|
|
|
|
|
simpleTest = as: (makeTest as).test;
|
|
|
|
}
|