Compare commits

..

14 commits
v1.2.1 ... main

Author SHA1 Message Date
9f9fcb8534 Merge branch 'feat/vm-tests' into 'main'
feat: add support for running NixOS VM tests more easily

See merge request TECHNOFAB/nixtest!10
2026-01-26 16:24:15 +01:00
c64e0cce0c
feat: add support for running NixOS VM tests more easily 2026-01-20 20:11:18 +01:00
f8fa0846ce Merge branch 'renovate/lock-file-maintenance' into 'main'
chore(deps): lock file maintenance

See merge request TECHNOFAB/nixtest!9
2026-01-01 17:52:46 +01:00
Renovate Bot
e25fb0c609 chore(deps): lock file maintenance 2026-01-01 14:47:37 +00:00
b87e38847d Merge branch 'renovate/github.com-jedib0t-go-pretty-v6-6.x' into 'main'
fix(deps): update module github.com/jedib0t/go-pretty/v6 to v6.7.8

See merge request TECHNOFAB/nixtest!8
2025-12-23 19:23:24 +01:00
Renovate Bot
98250aa7e7 fix(deps): update module github.com/jedib0t/go-pretty/v6 to v6.7.8 2025-12-23 14:09:03 +00:00
fc2b64839c Merge branch 'renovate/github.com-sergi-go-diff-1.x' into 'main'
fix(deps): update module github.com/sergi/go-diff to v1.4.0

See merge request TECHNOFAB/nixtest!6
2025-12-15 09:46:30 +01:00
Renovate Bot
b0988954c7 fix(deps): update module github.com/sergi/go-diff to v1.4.0 2025-12-15 08:09:53 +00:00
a71f897a65 Merge branch 'renovate/github.com-jedib0t-go-pretty-v6-6.x' into 'main'
fix(deps): update module github.com/jedib0t/go-pretty/v6 to v6.7.7

See merge request TECHNOFAB/nixtest!5
2025-12-15 08:35:52 +01:00
Renovate Bot
056851d6c6 fix(deps): update module github.com/jedib0t/go-pretty/v6 to v6.7.7 2025-12-15 07:08:30 +00:00
54d3b534d6 Merge branch 'renovate/github.com-spf13-pflag-1.x' into 'main'
fix(deps): update module github.com/spf13/pflag to v1.0.10

See merge request TECHNOFAB/nixtest!4
2025-12-15 07:17:57 +01:00
Renovate Bot
be32005bc7 fix(deps): update module github.com/spf13/pflag to v1.0.10 2025-12-15 00:07:32 +00:00
c202e41681 Merge branch 'renovate/github.com-stretchr-testify-1.x' into 'main'
fix(deps): update module github.com/stretchr/testify to v1.11.1

See merge request TECHNOFAB/nixtest!7
2025-12-15 00:47:37 +01:00
Renovate Bot
dab1e2ef6f fix(deps): update module github.com/stretchr/testify to v1.11.1 2025-12-14 23:15:53 +00:00
9 changed files with 172 additions and 47 deletions

View file

@ -64,6 +64,7 @@ There are currently 3 types of tests:
- `snapshot` -> snapshot testing, only needs `actual` and compares that to the snapshot
- `unit` -> equality checking, needs `expected` and `actual` or `actualDrv`
- `script` -> shell script test, needs `script`
- `vm` -> NixOS VM test, needs `vmConfig`
Examples:
@ -126,6 +127,23 @@ Examples:
expected = pkgs.hello;
actual = pkgs.hello;
}
{
name = "vm-test";
type = "vm";
# gets passed to pkgs.testers.nixosTest, so same params apply
# name gets automatically set, so thats not required
vmConfig = {
nodes.machine = {
services.nginx.enable = true;
};
testScript =
# py
''
machine.wait_for_unit("nginx.service")
machine.wait_for_open_port(80)
'';
};
}
]
```

12
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1765472234,
"narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"lastModified": 1767116409,
"narHash": "sha256-5vKw92l1GyTnjoLzEagJy5V5mDFck72LiQWZSOnSicw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"rev": "cad22e7d996aea55ecab064e84834289143e44a0",
"type": "github"
},
"original": {
@ -37,11 +37,11 @@
},
"locked": {
"dir": "lib",
"lastModified": 1758738378,
"narHash": "sha256-NjzqdvQCDDdObEBH8x/vdhbdhrIB+N9E570uCdksGHY=",
"lastModified": 1766497301,
"narHash": "sha256-W7WeOXMUZROMtbU1qQNWy/yai+k8gG09YACFQ7ImpsQ=",
"owner": "rensa-nix",
"repo": "core",
"rev": "abe19f9f13aff41de2b63304545c87d193d19ef4",
"rev": "e08c48b5db1052bfb8b8dad764e05decc1af893e",
"type": "gitlab"
},
"original": {

8
go.mod
View file

@ -5,8 +5,8 @@ go 1.23.0
require (
github.com/akedrou/textdiff v0.1.0
github.com/rs/zerolog v1.34.0
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
)
require (
@ -19,9 +19,9 @@ require (
)
require (
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/jedib0t/go-pretty/v6 v6.7.8
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/sergi/go-diff v1.3.1
github.com/sergi/go-diff v1.4.0
golang.org/x/sys v0.33.0 // indirect
)

10
go.sum
View file

@ -7,6 +7,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jedib0t/go-pretty/v6 v6.7.7 h1:Y1Id3lJ3k4UB8uwWWy3l8EVFnUlx5chR5+VbsofPNX0=
github.com/jedib0t/go-pretty/v6 v6.7.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jedib0t/go-pretty/v6 v6.7.8 h1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=
github.com/jedib0t/go-pretty/v6 v6.7.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -32,12 +36,18 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View file

@ -14,6 +14,7 @@
assertMsg
generators
literalExpression
xor
;
nixtest-lib = import ./default.nix {inherit pkgs lib;};
@ -72,25 +73,17 @@
in "${fileRelative}:${toString val.line}";
};
type = mkOption {
type = types.enum ["unit" "snapshot" "script"];
type = types.enum ["unit" "snapshot" "script" "vm"];
description = ''
Type of test, has to be one of "unit", "snapshot" or "script".
Type of test, has to be one of "unit", "snapshot", "script", or "vm".
'';
default = "unit";
apply = value:
assert assertMsg (value != "script" || !isUnset config.script)
assert assertMsg (value == "script" -> !isUnset config.script)
"test '${config.name}' as type 'script' requires 'script' to be set";
assert assertMsg (value != "unit" || !isUnset config.expected)
assert assertMsg (value == "unit" -> !isUnset config.expected)
"test '${config.name}' as type 'unit' requires 'expected' to be set";
assert assertMsg (
let
actualIsUnset = isUnset config.actual;
actualDrvIsUnset = isUnset config.actualDrv;
in
(value != "unit")
|| (!actualIsUnset && actualDrvIsUnset)
|| (actualIsUnset && !actualDrvIsUnset)
)
assert assertMsg (value == "unit" -> (xor (isUnset config.actual) (isUnset config.actualDrv)))
"test '${config.name}' as type 'unit' requires only 'actual' OR 'actualDrv' to be set"; value;
};
name = mkOption {
@ -162,6 +155,59 @@
builtins.unsafeDiscardStringContext
(pkgs.writeShellScript "nixtest-${config.name}" val).drvPath;
};
vmConfig = mkUnsetOption {
type = types.attrs;
description = ''
Configuration for `pkgs.testers.nixosText`.
'';
example = {
nodes.machine = {
services.nginx.enable = true;
};
testScript =
# py
''
machine.wait_for_unit("nginx.service")
machine.wait_for_open_port(80)
'';
};
};
finalConfig = mkOption {
internal = true;
type = types.attrs;
};
};
config = {
finalConfig = builtins.addErrorContext "[nixtest] while processing test ${config.name}" {
inherit (config) name expected actual actualDrv;
type =
if config.type == "vm"
then "script"
else config.type;
script =
if config.type == "vm"
then
assert assertMsg ((!isUnset config.vmConfig) && (config.vmConfig ? nodes) && (config.vmConfig ? testScript))
"test '${config.name}' as type 'vm' requires 'vmConfig' to be set and contain 'nodes' & 'testScript'"; let
inherit
(pkgs.testers.nixosTest (
{
name = "nixtest-vm-${config.name}";
}
// config.vmConfig
))
driver
;
in
builtins.unsafeDiscardStringContext
(pkgs.writeShellScript "nixtest-vm-${config.name}" ''
# use different TMPDIR to prevent race conditions:
# vde_switch: Could not bind to socket '/tmp/vde1.ctl/ctl': Address already in use
TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d) ${driver}/bin/nixos-test-driver
'').drvPath
else config.script;
};
};
};
@ -201,6 +247,17 @@
'';
default = [];
};
finalConfig = mkOption {
internal = true;
type = types.attrs;
};
};
config = {
finalConfig = builtins.addErrorContext "[nixtest] while processing suite ${config.name}" {
inherit (config) name;
tests = map (test: test.finalConfig) config.tests;
};
};
};
@ -233,11 +290,6 @@
Define your test suites here, every test belongs to a suite.
'';
default = {};
apply = suites:
map (
n: filterUnset (builtins.removeAttrs suites.${n} ["pos"])
)
(builtins.attrNames suites);
example = {
"Suite A".tests = [
{
@ -247,6 +299,10 @@
};
};
finalConfig = mkOption {
internal = true;
type = types.listOf types.attrs;
};
finalConfigJson = mkOption {
internal = true;
type = types.package;
@ -257,11 +313,18 @@
};
};
config = {
finalConfigJson = nixtest-lib.exportSuites config.suites;
app = nixtest-lib.mkBinary {
nixtests = config.finalConfigJson;
extraParams = ''--skip="${config.skip}"'';
};
finalConfig = map (suite: filterUnset suite.finalConfig) (builtins.attrValues config.suites);
finalConfigJson =
builtins.addErrorContext "[nixtest] while exporting suites"
(nixtest-lib.exportSuites config.finalConfig);
app =
(nixtest-lib.mkBinary {
nixtests = config.finalConfigJson;
extraParams = ''--skip="${config.skip}"'';
})
// {
rawTests = config.finalConfig;
};
};
};
in

24
nix/repo/flake.lock generated
View file

@ -3,11 +3,11 @@
"devshell-lib": {
"locked": {
"dir": "lib",
"lastModified": 1758204313,
"narHash": "sha256-ainbY0Oajb1HMdvy+A8QxF/P5qwcbEzJGEY5pzKdDdc=",
"lastModified": 1767274074,
"narHash": "sha256-h2grM9qoSnYdqN7K8+taeMuWC2umaN/c2FCBu48frlo=",
"owner": "rensa-nix",
"repo": "devshell",
"rev": "7d0c4bc78d9f017a739b0c7eb2f4e563118353e6",
"rev": "5508ced269ee40ff7f5261ee3b5bf5597f7cad5d",
"type": "gitlab"
},
"original": {
@ -38,11 +38,11 @@
"nixmkdocs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1763481845,
"narHash": "sha256-Bp0+9rDmlPWMcnKqGx+BG4+o5KO8FuDAOvXRnXrm3Fo=",
"lastModified": 1766404754,
"narHash": "sha256-EjBe6x6BT8ckPirMWhSf1GfaFxORYxR/Uu71FvSAm60=",
"owner": "TECHNOFAB",
"repo": "nixmkdocs",
"rev": "73d59093df94a894d25bc4bf71880b6f00faa62f",
"rev": "cfa9606eeeb9288e2799896d7d42b3d3860f9ccb",
"type": "gitlab"
},
"original": {
@ -64,11 +64,11 @@
"soonix-lib": {
"locked": {
"dir": "lib",
"lastModified": 1763323017,
"narHash": "sha256-MJyg37d+VMfRoFiVUj16FW+zkEwQXbgK9LoFF/SHoxA=",
"lastModified": 1767274116,
"narHash": "sha256-8+VeMokZHjOLs6fRUTj/9uxbMlHKDl384Tk6K8Qjm4k=",
"owner": "TECHNOFAB",
"repo": "soonix",
"rev": "078034b01e4eaf1f9436d46721f7cbe0d96eb8b4",
"rev": "56f281eea45bdcf29674adfa7962f14e490a6051",
"type": "gitlab"
},
"original": {
@ -81,11 +81,11 @@
"treefmt-nix": {
"flake": false,
"locked": {
"lastModified": 1762938485,
"narHash": "sha256-AlEObg0syDl+Spi4LsZIBrjw+snSVU4T8MOeuZJUJjM=",
"lastModified": 1767122417,
"narHash": "sha256-yOt/FTB7oSEKQH9EZMFMeuldK1HGpQs2eAzdS9hNS/o=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "5b4ee75aeefd1e2d5a1cc43cf6ba65eba75e83e4",
"rev": "dec15f37015ac2e774c84d0952d57fcdf169b54d",
"type": "github"
},
"original": {

View file

@ -19,6 +19,6 @@ buildGoModule {
];
};
subPackages = ["cmd/nixtest"];
vendorHash = "sha256-6kARJgngmXielUoXukYdAA0QHk1mwLRvgKJhx+v1iSo=";
vendorHash = "sha256-WF/lzu9lt9SR3WiA8LLWVT1OwpE3sIOtSqf4HMIMmE8=";
meta.mainProgram = "nixtest";
}

View file

@ -49,6 +49,39 @@
grep -q "test" ${builtins.toFile "test" "test"}
'';
}
{
name = "test-vm";
type = "vm";
vmConfig = {
nodes.machine = {pkgs, ...}: {
services.nginx = {
enable = true;
virtualHosts."localhost" = {
root = pkgs.writeTextDir "index.html" "Hello from nixtest VM!";
};
};
};
testScript =
# py
''
machine.wait_for_unit("nginx.service")
machine.wait_for_open_port(80)
machine.succeed("curl -f http://localhost | grep 'Hello from nixtest VM!'")
'';
};
}
{
name = "vm-fail";
type = "vm";
vmConfig = {
nodes.machine = {};
testScript =
# py
''
machine.succeed("curl -f http://localhost | grep 'Hello from nixtest VM!'")
'';
};
}
];
};
"other-suite".tests = [

View file

@ -89,9 +89,10 @@
assert "-f junit2.xml" "should create junit2.xml"
assert_not_contains "$output" "executable file not found" "nix should now exist"
assert_contains "$output" "suite-one" "should contain suite-one"
assert_contains "$output" "8/11 (1 SKIPPED)" "should be 8/11 total"
assert_contains "$output" "9/13 (1 SKIPPED)" "should be 9/13 total"
assert_contains "$output" "ERROR" "should contain an error"
assert_contains "$output" "SKIP" "should contain a skip"
assert_contains "$output" "RequestedAssertionFailed" "vm-fail test should fail"
'';
}
];