push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
{
config,
extendModules,
lib,
...
}:
let
inherit (lib) mkOption types;
unsafeGetAttrPosStringOr =
default: name: value:
let
p = builtins.unsafeGetAttrPos name value;
in
if p == null then default else p.file + ":" + toString p.line + ":" + toString p.column;
in
{
options = {
result = mkOption {
internal = true;
default = config;
};
};
config = {
# Docs: nixos/doc/manual/development/writing-nixos-tests.section.md
/**
See https://nixos.org/manual/nixos/unstable#sec-override-nixos-test
*/
passthru.extend =
args@{
modules,
specialArgs ? { },
}:
(extendModules {
inherit specialArgs;
modules = map (lib.setDefaultModuleLocation (
unsafeGetAttrPosStringOr "<test.extend module>" "modules" args
)) modules;
}).config.test;
};
}

View File

@@ -0,0 +1,37 @@
{ lib }:
let
evalTest =
module:
lib.evalModules {
modules = testModules ++ [ module ];
class = "nixosTest";
};
runTest =
module:
(evalTest (
{ config, ... }:
{
imports = [ module ];
result = config.test;
}
)).config.result;
testModules = [
./call-test.nix
./driver.nix
./interactive.nix
./legacy.nix
./meta.nix
./name.nix
./network.nix
./nodes.nix
./pkgs.nix
./run.nix
./testScript.nix
];
in
{
inherit evalTest runTest testModules;
}

View File

@@ -0,0 +1,213 @@
{
config,
lib,
hostPkgs,
...
}:
let
inherit (lib) mkOption types literalMD;
# Reifies and correctly wraps the python test driver for
# the respective qemu version and with or without ocr support
testDriver = hostPkgs.callPackage ../test-driver {
inherit (config) enableOCR extraPythonPackages;
qemu_pkg = config.qemu.package;
imagemagick_light = hostPkgs.imagemagick_light.override { inherit (hostPkgs) libtiff; };
tesseract4 = hostPkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
};
vlans = map (
m: (m.virtualisation.vlans ++ (lib.mapAttrsToList (_: v: v.vlan) m.virtualisation.interfaces))
) (lib.attrValues config.nodes);
vms = map (m: m.system.build.vm) (lib.attrValues config.nodes);
nodeHostNames =
let
nodesList = map (c: c.system.name) (lib.attrValues config.nodes);
in
nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
pythonizeName =
name:
let
head = lib.substring 0 1 name;
tail = lib.substring 1 (-1) name;
in
(if builtins.match "[A-z_]" head == null then "_" else head)
+ lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
uniqueVlans = lib.unique (builtins.concatLists vlans);
vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
pythonizedNames = map pythonizeName nodeHostNames;
machineNames = map (name: "${name}: Machine;") pythonizedNames;
withChecks = lib.warnIf config.skipLint "Linting is disabled";
driver =
hostPkgs.runCommand "nixos-test-driver-${config.name}"
{
# inherit testName; TODO (roberth): need this?
nativeBuildInputs = [
hostPkgs.makeWrapper
]
++ lib.optionals (!config.skipTypeCheck) [ hostPkgs.mypy ];
buildInputs = [ testDriver ];
testScript = config.testScriptString;
preferLocalBuild = true;
passthru = config.passthru;
meta = config.meta // {
mainProgram = "nixos-test-driver";
};
}
''
mkdir -p $out/bin
vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
${lib.optionalString (!config.skipTypeCheck) ''
# prepend type hints so the test script can be type checked with mypy
cat "${../test-script-prepend.py}" >> testScriptWithTypes
echo "${builtins.toString machineNames}" >> testScriptWithTypes
echo "${builtins.toString vlanNames}" >> testScriptWithTypes
echo -n "$testScript" >> testScriptWithTypes
echo "Running type check (enable/disable: config.skipTypeCheck)"
echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipTypeCheck"
mypy --no-implicit-optional \
--pretty \
--no-color-output \
testScriptWithTypes
''}
echo -n "$testScript" >> $out/test-script
ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver
${testDriver}/bin/generate-driver-symbols
${lib.optionalString (!config.skipLint) ''
echo "Linting test script (enable/disable: config.skipLint)"
echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipLint"
PYFLAKES_BUILTINS="$(
echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
cat ${lib.escapeShellArg "driver-symbols"}
)" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
''}
# set defaults through environment
# see: ./test-driver/test-driver.py argparse implementation
wrapProgram $out/bin/nixos-test-driver \
--set startScripts "''${vmStartScripts[*]}" \
--set testScript "$out/test-script" \
--set globalTimeout "${toString config.globalTimeout}" \
--set vlans '${toString vlans}' \
${lib.escapeShellArgs (
lib.concatMap (arg: [
"--add-flags"
arg
]) config.extraDriverArgs
)}
'';
in
{
options = {
driver = mkOption {
description = "Package containing a script that runs the test.";
type = types.package;
defaultText = literalMD "set by the test framework";
};
hostPkgs = mkOption {
description = "Nixpkgs attrset used outside the nodes.";
type = types.raw;
example = lib.literalExpression ''
import nixpkgs { inherit system config overlays; }
'';
};
qemu.package = mkOption {
description = "Which qemu package to use for the virtualisation of [{option}`nodes`](#test-opt-nodes).";
type = types.package;
default = hostPkgs.qemu_test;
defaultText = "hostPkgs.qemu_test";
};
globalTimeout = mkOption {
description = ''
A global timeout for the complete test, expressed in seconds.
Beyond that timeout, every resource will be killed and released and the test will fail.
By default, we use a 1 hour timeout.
'';
type = types.int;
default = 60 * 60;
example = 10 * 60;
};
enableOCR = mkOption {
description = ''
Whether to enable Optical Character Recognition functionality for
testing graphical programs. See [`Machine objects`](#ssec-machine-objects).
'';
type = types.bool;
default = false;
};
extraPythonPackages = mkOption {
description = ''
Python packages to add to the test driver.
The argument is a Python package set, similar to `pkgs.pythonPackages`.
'';
example = lib.literalExpression ''
p: [ p.numpy ]
'';
type = types.functionTo (types.listOf types.package);
default = ps: [ ];
};
extraDriverArgs = mkOption {
description = ''
Extra arguments to pass to the test driver.
They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`.
'';
type = types.listOf types.str;
default = [ ];
};
skipLint = mkOption {
type = types.bool;
default = false;
description = ''
Do not run the linters. This may speed up your iteration cycle, but it is not something you should commit.
'';
};
skipTypeCheck = mkOption {
type = types.bool;
default = false;
description = ''
Disable type checking. This must not be enabled for new NixOS tests.
This may speed up your iteration cycle, unless you're working on the [{option}`testScript`](#test-opt-testScript).
'';
};
};
config = {
_module.args = {
hostPkgs =
# Comment is in nixos/modules/misc/nixpkgs.nix
lib.mkOverride lib.modules.defaultOverridePriority config.hostPkgs.__splicedPackages;
};
driver = withChecks driver;
# make available on the test runner
passthru.driver = config.driver;
};
}

View File

@@ -0,0 +1,51 @@
{
config,
lib,
moduleType,
hostPkgs,
...
}:
let
inherit (lib) mkOption types;
in
{
options = {
interactive = mkOption {
description = ''
Tests [can be run interactively](#sec-running-nixos-tests-interactively)
using the program in the test derivation's `.driverInteractive` attribute.
When they are, the configuration will include anything set in this submodule.
You can set any top-level test option here.
Example test module:
```nix
{ config, lib, ... }: {
nodes.rabbitmq = {
services.rabbitmq.enable = true;
};
# When running interactively ...
interactive.nodes.rabbitmq = {
# ... enable the web ui.
services.rabbitmq.managementPlugin.enable = true;
};
}
```
For details, see the section about [running tests interactively](#sec-running-nixos-tests-interactively).
'';
type = moduleType;
visible = "shallow";
};
};
config = {
interactive.qemu.package = hostPkgs.qemu;
interactive.extraDriverArgs = [ "--interactive" ];
passthru.driverInteractive = config.interactive.driver;
};
}

View File

@@ -0,0 +1,31 @@
{
config,
options,
lib,
...
}:
let
inherit (lib) mkIf mkOption types;
in
{
# This needs options.warnings and options.assertions, which we don't have (yet?).
# imports = [
# (lib.mkRenamedOptionModule [ "machine" ] [ "nodes" "machine" ])
# (lib.mkRemovedOptionModule [ "minimal" ] "The minimal kernel module was removed as it was broken and not used any more in nixpkgs.")
# ];
options = {
machine = mkOption {
internal = true;
type = types.raw;
};
};
config = {
nodes = mkIf options.machine.isDefined (
lib.warn
"In test `${config.name}': The `machine' attribute in NixOS tests (pkgs.nixosTest / make-test-python.nix / testing-python.nix / makeTest) is deprecated. Please set the equivalent `nodes.machine'."
{ inherit (config) machine; }
);
};
}

View File

@@ -0,0 +1,62 @@
{ lib, ... }:
let
inherit (lib) types mkOption literalMD;
in
{
options = {
meta = mkOption {
description = ''
The [`meta`](https://nixos.org/manual/nixpkgs/stable/#chap-meta) attributes that will be set on the returned derivations.
Not all [`meta`](https://nixos.org/manual/nixpkgs/stable/#chap-meta) attributes are supported, but more can be added as desired.
'';
apply = lib.filterAttrs (k: v: v != null);
type = types.submodule (
{ config, ... }:
{
options = {
maintainers = mkOption {
type = types.listOf types.raw;
default = [ ];
description = ''
The [list of maintainers](https://nixos.org/manual/nixpkgs/stable/#var-meta-maintainers) for this test.
'';
};
timeout = mkOption {
type = types.nullOr types.int;
default = 3600; # 1 hour
description = ''
The [{option}`test`](#test-opt-test)'s [`meta.timeout`](https://nixos.org/manual/nixpkgs/stable/#var-meta-timeout) in seconds.
'';
};
broken = mkOption {
type = types.bool;
default = false;
description = ''
Sets the [`meta.broken`](https://nixos.org/manual/nixpkgs/stable/#var-meta-broken) attribute on the [{option}`test`](#test-opt-test) derivation.
'';
};
platforms = mkOption {
type = types.listOf types.raw;
default = lib.platforms.linux ++ lib.platforms.darwin;
description = ''
Sets the [`meta.platforms`](https://nixos.org/manual/nixpkgs/stable/#var-meta-platforms) attribute on the [{option}`test`](#test-opt-test) derivation.
'';
};
hydraPlatforms = mkOption {
type = types.listOf types.raw;
# Ideally this would default to `platforms` again:
# default = config.platforms;
default = lib.platforms.linux;
defaultText = literalMD "`lib.platforms.linux` only, as the `hydra.nixos.org` build farm does not currently support virtualisation on Darwin.";
description = ''
Sets the [`meta.hydraPlatforms`](https://nixos.org/manual/nixpkgs/stable/#var-meta-hydraPlatforms) attribute on the [{option}`test`](#test-opt-test) derivation.
'';
};
};
}
);
default = { };
};
};
}

View File

@@ -0,0 +1,14 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
options.name = mkOption {
description = ''
The name of the test.
This is used in the derivation names of the [{option}`driver`](#test-opt-driver) and [{option}`test`](#test-opt-test) runner.
'';
type = types.str;
};
}

View File

@@ -0,0 +1,182 @@
{ lib, nodes, ... }:
let
inherit (lib)
attrNames
concatMap
concatMapStrings
flip
forEach
head
listToAttrs
mkDefault
mkOption
nameValuePair
optionalString
range
toLower
types
zipListsWith
zipLists
;
nodeNumbers = listToAttrs (zipListsWith nameValuePair (attrNames nodes) (range 1 254));
networkModule =
{
config,
nodes,
pkgs,
...
}:
let
qemu-common = import ../qemu-common.nix { inherit lib pkgs; };
# Convert legacy VLANs to named interfaces and merge with explicit interfaces.
vlansNumbered = forEach (zipLists config.virtualisation.vlans (range 1 255)) (v: {
name = "eth${toString v.snd}";
vlan = v.fst;
assignIP = true;
});
explicitInterfaces = lib.mapAttrsToList (n: v: v // { name = n; }) config.virtualisation.interfaces;
interfaces = vlansNumbered ++ explicitInterfaces;
interfacesNumbered = zipLists interfaces (range 1 255);
# Automatically assign IP addresses to requested interfaces.
assignIPs = lib.filter (i: i.assignIP) interfaces;
ipInterfaces = forEach assignIPs (
i:
nameValuePair i.name {
ipv4.addresses = [
{
address = "192.168.${toString i.vlan}.${toString config.virtualisation.test.nodeNumber}";
prefixLength = 24;
}
];
ipv6.addresses = [
{
address = "2001:db8:${toString i.vlan}::${toString config.virtualisation.test.nodeNumber}";
prefixLength = 64;
}
];
}
);
qemuOptions = lib.flatten (
forEach interfacesNumbered (
{ fst, snd }: qemu-common.qemuNICFlags snd fst.vlan config.virtualisation.test.nodeNumber
)
);
udevRules = forEach interfacesNumbered (
{ fst, snd }:
# MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive.
''SUBSYSTEM=="net",ACTION=="add",ATTR{address}=="${toLower (qemu-common.qemuNicMac fst.vlan config.virtualisation.test.nodeNumber)}",NAME="${fst.name}"''
);
networkConfig = {
networking.hostName = mkDefault config.virtualisation.test.nodeName;
networking.interfaces = listToAttrs ipInterfaces;
networking.primaryIPAddress =
optionalString (ipInterfaces != [ ])
(head (head ipInterfaces).value.ipv4.addresses).address;
networking.primaryIPv6Address =
optionalString (ipInterfaces != [ ])
(head (head ipInterfaces).value.ipv6.addresses).address;
# Put the IP addresses of all VMs in this machine's
# /etc/hosts file. If a machine has multiple
# interfaces, use the IP address corresponding to
# the first interface (i.e. the first network in its
# virtualisation.vlans option).
networking.extraHosts = flip concatMapStrings (attrNames nodes) (
m':
let
config = nodes.${m'};
hostnames =
optionalString (
config.networking.domain != null
) "${config.networking.hostName}.${config.networking.domain} "
+ "${config.networking.hostName}\n";
in
optionalString (
config.networking.primaryIPAddress != ""
) "${config.networking.primaryIPAddress} ${hostnames}"
+ optionalString (
config.networking.primaryIPv6Address != ""
) "${config.networking.primaryIPv6Address} ${hostnames}"
);
virtualisation.qemu.options = qemuOptions;
boot.initrd.services.udev.rules = concatMapStrings (x: x + "\n") udevRules;
};
in
{
key = "network-interfaces";
config = networkConfig // {
# Expose the networkConfig items for tests like nixops
# that need to recreate the network config.
system.build.networkConfig = networkConfig;
};
};
nodeNumberModule = (
regular@{ config, name, ... }:
{
options = {
virtualisation.test.nodeName = mkOption {
internal = true;
default = name;
# We need to force this in specialisations, otherwise it'd be
# readOnly = true;
description = ''
The `name` in `nodes.<name>`; stable across `specialisations`.
'';
};
virtualisation.test.nodeNumber = mkOption {
internal = true;
type = types.int;
readOnly = true;
default = nodeNumbers.${config.virtualisation.test.nodeName};
description = ''
A unique number assigned for each node in `nodes`.
'';
};
# specialisations override the `name` module argument,
# so we push the real `virtualisation.test.nodeName`.
specialisation = mkOption {
type = types.attrsOf (
types.submodule {
options.configuration = mkOption {
type = types.submoduleWith {
modules = [
{
config.virtualisation.test.nodeName =
# assert regular.config.virtualisation.test.nodeName != "configuration";
regular.config.virtualisation.test.nodeName;
}
];
};
};
}
);
};
};
}
);
in
{
config = {
extraBaseModules = {
imports = [
networkModule
nodeNumberModule
];
};
};
}

View File

@@ -0,0 +1,40 @@
# A module containing the base imports and overrides that
# are always applied in NixOS VM tests, unconditionally,
# even in `inheritParentConfig = false` specialisations.
{ lib, ... }:
let
inherit (lib) mkDefault mkForce;
in
{
imports = [
../../modules/virtualisation/qemu-vm.nix
../../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs
{
key = "no-manual";
documentation.nixos.enable = false;
}
{
key = "no-revision";
# Make the revision metadata constant, in order to avoid needless retesting.
# The human version (e.g. 21.05-pre) is left as is, because it is useful
# for external modules that test with e.g. testers.nixosTest and rely on that
# version number.
config.system.nixos = {
revision = mkForce "constant-nixos-revision";
versionSuffix = mkForce "test";
label = mkForce "test";
};
}
(
{ config, ... }:
{
# Don't pull in switch-to-configuration by default, except when specialisations or early boot shenanigans are involved.
# This is mostly a Hydra optimization, so we don't rebuild all the tests every time switch-to-configuration-ng changes.
key = "no-switch-to-configuration";
system.switch.enable = mkDefault (
config.isSpecialisation || config.specialisation != { } || config.virtualisation.installBootLoader
);
}
)
];
}

260
nixos/lib/testing/nodes.nix Normal file
View File

@@ -0,0 +1,260 @@
testModuleArgs@{
config,
lib,
hostPkgs,
nodes,
options,
...
}:
let
inherit (lib)
literalExpression
literalMD
mapAttrs
mkDefault
mkIf
mkMerge
mkOption
mkForce
optional
optionalAttrs
types
;
inherit (hostPkgs.stdenv) hostPlatform;
guestSystem =
if hostPlatform.isLinux then
hostPlatform.system
else
let
hostToGuest = {
"x86_64-darwin" = "x86_64-linux";
"aarch64-darwin" = "aarch64-linux";
};
supportedHosts = lib.concatStringsSep ", " (lib.attrNames hostToGuest);
message = "NixOS Test: don't know which VM guest system to pair with VM host system: ${hostPlatform.system}. Perhaps you intended to run the tests on a Linux host, or one of the following systems that may run NixOS tests: ${supportedHosts}";
in
hostToGuest.${hostPlatform.system} or (throw message);
baseOS = import ../eval-config.nix {
inherit lib;
system = null; # use modularly defined system
inherit (config.node) specialArgs;
modules = [ config.defaults ];
baseModules = (import ../../modules/module-list.nix) ++ [
./nixos-test-base.nix
{
key = "nodes";
_module.args.nodes = config.nodesCompat;
}
(
{ config, ... }:
{
virtualisation.qemu.package = testModuleArgs.config.qemu.package;
virtualisation.host.pkgs = hostPkgs;
}
)
(
{ options, ... }:
{
key = "nodes.nix-pkgs";
config = optionalAttrs (!config.node.pkgsReadOnly) (
mkIf (!options.nixpkgs.pkgs.isDefined) {
# TODO: switch to nixpkgs.hostPlatform and make sure containers-imperative test still evaluates.
nixpkgs.system = guestSystem;
}
);
}
)
testModuleArgs.config.extraBaseModules
];
};
# TODO (lib): Dedup with run.nix, add to lib/options.nix
mkOneUp = opt: f: lib.mkOverride (opt.highestPrio - 1) (f opt.value);
in
{
options = {
sshBackdoor = {
enable = mkOption {
default = config.enableDebugHook;
defaultText = lib.literalExpression "config.enableDebugHook";
type = types.bool;
description = "Whether to turn on the VSOCK-based access to all VMs. This provides an unauthenticated access intended for debugging.";
};
vsockOffset = mkOption {
default = 2;
type = types.ints.between 2 4294967296;
description = ''
This field is only relevant when multiple users run the (interactive)
driver outside the sandbox and with the SSH backdoor activated.
The typical symptom for this being a problem are error messages like this:
`vhost-vsock: unable to set guest cid: Address already in use`
This option allows to assign an offset to each vsock number to
resolve this.
This is a 32bit number. The lowest possible vsock number is `3`
(i.e. with the lowest node number being `1`, this is 2+1).
'';
};
};
node.type = mkOption {
type = types.raw;
default = baseOS.type;
internal = true;
};
nodes = mkOption {
type = types.lazyAttrsOf config.node.type;
visible = "shallow";
description = ''
An attribute set of NixOS configuration modules.
The configurations are augmented by the [`defaults`](#test-opt-defaults) option.
They are assigned network addresses according to the `nixos/lib/testing/network.nix` module.
A few special options are available, that aren't in a plain NixOS configuration. See [Configuring the nodes](#sec-nixos-test-nodes)
'';
};
defaults = mkOption {
description = ''
NixOS configuration that is applied to all [{option}`nodes`](#test-opt-nodes).
'';
type = types.deferredModule;
default = { };
};
extraBaseModules = mkOption {
description = ''
NixOS configuration that, like [{option}`defaults`](#test-opt-defaults), is applied to all [{option}`nodes`](#test-opt-nodes) and can not be undone with [`specialisation.<name>.inheritParentConfig`](https://search.nixos.org/options?show=specialisation.%3Cname%3E.inheritParentConfig&from=0&size=50&sort=relevance&type=packages&query=specialisation).
'';
type = types.deferredModule;
default = { };
};
node.pkgs = mkOption {
description = ''
The Nixpkgs to use for the nodes.
Setting this will make the `nixpkgs.*` options read-only, to avoid mistakenly testing with a Nixpkgs configuration that diverges from regular use.
'';
type = types.nullOr types.pkgs;
default = null;
defaultText = literalMD ''
`null`, so construct `pkgs` according to the `nixpkgs.*` options as usual.
'';
};
node.pkgsReadOnly = mkOption {
description = ''
Whether to make the `nixpkgs.*` options read-only. This is only relevant when [`node.pkgs`](#test-opt-node.pkgs) is set.
Set this to `false` when any of the [`nodes`](#test-opt-nodes) needs to configure any of the `nixpkgs.*` options. This will slow down evaluation of your test a bit.
'';
type = types.bool;
default = config.node.pkgs != null;
defaultText = literalExpression ''node.pkgs != null'';
};
node.specialArgs = mkOption {
type = types.lazyAttrsOf types.raw;
default = { };
description = ''
An attribute set of arbitrary values that will be made available as module arguments during the resolution of module `imports`.
Note that it is not possible to override these from within the NixOS configurations. If you argument is not relevant to `imports`, consider setting {option}`defaults._module.args.<name>` instead.
'';
};
nodesCompat = mkOption {
internal = true;
description = ''
Basically `_module.args.nodes`, but with backcompat and warnings added.
This will go away.
'';
};
};
config = {
_module.args.nodes = config.nodesCompat;
nodesCompat = mapAttrs (
name: config:
config
// {
config =
lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2211)
"Module argument `nodes.${name}.config` is deprecated. Use `nodes.${name}` instead."
config;
}
) config.nodes;
passthru.nodes = config.nodesCompat;
extraDriverArgs = mkIf config.sshBackdoor.enable [
"--dump-vsocks=${toString config.sshBackdoor.vsockOffset}"
];
defaults = mkMerge [
(mkIf config.node.pkgsReadOnly {
nixpkgs.pkgs = config.node.pkgs;
imports = [ ../../modules/misc/nixpkgs/read-only.nix ];
})
(mkIf config.sshBackdoor.enable (
let
inherit (config.sshBackdoor) vsockOffset;
in
{ config, ... }:
{
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "yes";
PermitEmptyPasswords = "yes";
};
};
security.pam.services.sshd = {
allowNullPassword = true;
};
virtualisation.qemu.options = [
"-device vhost-vsock-pci,guest-cid=${
toString (config.virtualisation.test.nodeNumber + vsockOffset)
}"
];
}
))
];
# Docs: nixos/doc/manual/development/writing-nixos-tests.section.md
/**
See https://nixos.org/manual/nixos/unstable#sec-override-nixos-test
*/
passthru.extendNixOS =
{
module,
specialArgs ? { },
}:
config.passthru.extend {
modules = [
{
extraBaseModules = module;
node.specialArgs = mkOneUp options.node.specialArgs (_: specialArgs);
}
];
};
};
}

View File

@@ -0,0 +1,20 @@
{
config,
lib,
hostPkgs,
...
}:
{
config = {
# default pkgs for use in VMs
_module.args.pkgs =
# TODO: deprecate it everywhere; not just on darwin. Throw on darwin?
lib.warnIf hostPkgs.stdenv.hostPlatform.isDarwin
"Do not use the `pkgs` module argument in tests you want to run on darwin. It is ambiguous, and many tests are broken because of it. If you need to use a package on the VM host, use `hostPkgs`. Otherwise, use `config.node.pkgs`, or `config.nodes.<name>.nixpkgs.pkgs`."
hostPkgs;
defaults = {
# TODO: a module to set a shared pkgs, if options.nixpkgs.* is untouched by user (highestPrio) */
};
};
}

156
nixos/lib/testing/run.nix Normal file
View File

@@ -0,0 +1,156 @@
{
config,
hostPkgs,
lib,
options,
...
}:
let
inherit (lib) types mkOption;
inherit (hostPkgs.stdenv.hostPlatform) isDarwin isLinux;
# TODO (lib): Also use lib equivalent in nodes.nix
/**
Create a module system definition that overrides an existing option from a different module evaluation.
Type: Option a -> (a -> a) -> Definition a
*/
mkOneUp =
/**
Option from an existing module evaluation, e.g.
- `(lib.evalModules ...).options.x` when invoking `evalModules` again,
- or `{ options, ... }:` when invoking `extendModules`.
*/
opt:
/**
Function from the old value to the new definition, which will be wrapped with `mkOverride`.
*/
f:
lib.mkOverride (opt.highestPrio - 1) (f opt.value);
in
{
options = {
passthru = mkOption {
type = types.lazyAttrsOf types.raw;
description = ''
Attributes to add to the returned derivations,
which are not necessarily part of the build.
This is a bit like doing `drv // { myAttr = true; }` (which would be lost by `overrideAttrs`).
It does not change the actual derivation, but adds the attribute nonetheless, so that
consumers of what would be `drv` have more information.
'';
};
enableDebugHook = lib.mkEnableOption "" // {
description = ''
Halt test execution after any test fail and provide the possibility to
hook into the sandbox to connect with either the test driver via
`telnet localhost 4444` or with the VMs via SSH and vsocks (see also
`sshBackdoor.enable`).
'';
};
rawTestDerivation = mkOption {
type = types.package;
description = ''
Unfiltered version of `test`, for troubleshooting the test framework and `testBuildFailure` in the test framework's test suite.
This is not intended for general use. Use `test` instead.
'';
internal = true;
};
rawTestDerivationArg = mkOption {
type = types.functionTo types.raw;
description = ''
Argument passed to `mkDerivation` to create the `rawTestDerivation`.
'';
};
test = mkOption {
type = types.package;
# TODO: can the interactive driver be configured to access the network?
description = ''
Derivation that runs the test as its "build" process.
This implies that NixOS tests run isolated from the network, making them
more dependable.
'';
};
};
config = {
rawTestDerivation = hostPkgs.stdenv.mkDerivation config.rawTestDerivationArg;
rawTestDerivationArg =
finalAttrs:
assert lib.assertMsg (
config.sshBackdoor.enable -> isLinux
) "The SSH backdoor is not supported for macOS host systems!";
assert lib.assertMsg (
config.enableDebugHook -> isLinux
) "The debugging hook is not supported for macOS host systems!";
{
name = "vm-test-run-${config.name}";
requiredSystemFeatures = [
"nixos-test"
]
++ lib.optional isLinux "kvm"
++ lib.optional isDarwin "apple-virt";
nativeBuildInputs = lib.optionals config.enableDebugHook [
hostPkgs.openssh
hostPkgs.inetutils
];
buildCommand = ''
mkdir -p $out
# effectively mute the XMLLogger
export LOGFILE=/dev/null
${lib.optionalString config.enableDebugHook ''
ln -sf \
${hostPkgs.systemd}/lib/systemd/ssh_config.d/20-systemd-ssh-proxy.conf \
ssh_config
''}
${config.driver}/bin/nixos-test-driver \
-o $out \
${lib.optionalString config.enableDebugHook "--debug-hook=${hostPkgs.breakpointHook.attach}"}
'';
passthru = config.passthru;
meta = config.meta;
};
test = lib.lazyDerivation {
# lazyDerivation improves performance when only passthru items and/or meta are used.
derivation = config.rawTestDerivation;
inherit (config) passthru meta;
};
# useful for inspection (debugging / exploration)
passthru.config = config;
/**
For discoverTests only. Deprecated. Will be removed when discoverTests can be removed from NixOS all-tests.nix.
*/
passthru.test = config.test;
# Docs: nixos/doc/manual/development/writing-nixos-tests.section.md
/**
See https://nixos.org/manual/nixos/unstable#sec-override-nixos-test
*/
passthru.overrideTestDerivation =
f:
config.passthru.extend {
modules = [
{
rawTestDerivationArg = mkOneUp options.rawTestDerivationArg (lib.extends (lib.toExtension f));
}
];
};
};
}

View File

@@ -0,0 +1,93 @@
testModuleArgs@{
config,
lib,
hostPkgs,
nodes,
moduleType,
...
}:
let
inherit (lib) mkOption types;
inherit (types) either str functionTo;
in
{
options = {
testScript = mkOption {
type = either str (functionTo str);
description = ''
A series of python declarations and statements that you write to perform
the test.
'';
};
testScriptString = mkOption {
type = str;
readOnly = true;
internal = true;
};
includeTestScriptReferences = mkOption {
type = types.bool;
default = true;
internal = true;
};
withoutTestScriptReferences = mkOption {
type = moduleType;
description = ''
A parallel universe where the testScript is invalid and has no references.
'';
internal = true;
visible = false;
};
};
config = {
withoutTestScriptReferences.includeTestScriptReferences = false;
withoutTestScriptReferences.testScript = lib.mkForce "testscript omitted";
testScriptString =
if lib.isFunction config.testScript then
config.testScript {
nodes = lib.mapAttrs (
k: v:
if v.virtualisation.useNixStoreImage then
# prevent infinite recursion when testScript would
# reference v's toplevel
config.withoutTestScriptReferences.nodesCompat.${k}
else
# reuse memoized config
v
) config.nodesCompat;
}
else
config.testScript;
defaults =
{ config, name, ... }:
{
# Make sure all derivations referenced by the test
# script are available on the nodes. When the store is
# accessed through 9p, this isn't important, since
# everything in the store is available to the guest,
# but when building a root image it is, as all paths
# that should be available to the guest has to be
# copied to the image.
virtualisation.additionalPaths =
lib.optional
# A testScript may evaluate nodes, which has caused
# infinite recursions. The demand cycle involves:
# testScript -->
# nodes -->
# toplevel -->
# additionalPaths -->
# hasContext testScript' -->
# testScript (ad infinitum)
# If we don't need to build an image, we can break this
# cycle by short-circuiting when useNixStoreImage is false.
(
config.virtualisation.useNixStoreImage
&& builtins.hasContext testModuleArgs.config.testScriptString
&& testModuleArgs.config.includeTestScriptReferences
)
(hostPkgs.writeStringReferencesToFile testModuleArgs.config.testScriptString);
};
};
}