diff --git a/.envrc b/.envrc
index f990172..565a52a 100644
--- a/.envrc
+++ b/.envrc
@@ -1 +1,2 @@
-use flake . --impure --accept-flake-config
+source $(fetchurl https://gitlab.com/rensa-nix/direnv/-/raw/v0.3.0/direnvrc "sha256-u7+KEz684NnIZ+Vh5x5qLrt8rKdnUNexewBoeTcEVHQ=")
+use ren //repo/devShells/default
diff --git a/.gitignore b/.gitignore
index e1f66c8..f5db43c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,3 @@
-.direnv/
-.devenv/
result
-.pre-commit-config.yaml
*.xml
cover.*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6253297..7d70f67 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,5 @@
+# Generated by soonix, DO NOT EDIT
include:
- - component: gitlab.com/TECHNOFAB/nix-gitlab-ci/nix-gitlab-ci@2.0.0
- inputs:
- version: 2.0.0
+- component: gitlab.com/TECHNOFAB/nix-gitlab-ci/nix-gitlab-ci@3.1.2
+ inputs:
+ version: 3.1.2
diff --git a/.gitlab/renovate.json5 b/.gitlab/renovate.json5
new file mode 100644
index 0000000..67d4092
--- /dev/null
+++ b/.gitlab/renovate.json5
@@ -0,0 +1,23 @@
+{
+ "extends": [
+ "config:recommended"
+ ],
+ "gitlabci": {
+ "enabled": false
+ },
+ "lockFileMaintenance": {
+ "enabled": true,
+ "extends": [
+ "schedule:monthly"
+ ]
+ },
+ "nix": {
+ "enabled": true
+ },
+ "postUpgradeTasks": {
+ "commands": [
+ "nix-portable nix run .#update-package",
+ "nix-portable nix run .#soonix:update"
+ ]
+ }
+}
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..4e58a63
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,7 @@
+Copyright 2025 TECHNOFAB
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 657bbed..f3c2b85 100644
--- a/README.md
+++ b/README.md
@@ -2,19 +2,23 @@
[](https://builtwithnix.org)
[](https://gitlab.com/TECHNOFAB/nixtest/-/commits/main)
-
+
[](https://gitlab.com/TECHNOFAB/nixtest/-/releases)
[](https://tec.tf/#support)
[](https://nixtest.projects.tf)
Flexible test runner for testing Nix code, written in Go.
+[](https://asciinema.org/a/VS8uIiEQiPRtXbOreJqrxO7X6)
+
## Features
- Snapshot, Unit (equal checks) and Script-Tests (unit tests with assertions you could say)
- Supports testing against raw Nix code or derivation output
- Simple and easy to read summary of test results
+ 
- Junit report support (eg. for displaying the results in GitLab etc.)
+ 
## Usage
diff --git a/cmd/nixtest/main.go b/cmd/nixtest/main.go
index 5f141c1..5e71553 100644
--- a/cmd/nixtest/main.go
+++ b/cmd/nixtest/main.go
@@ -3,14 +3,14 @@ package main
import (
"os"
- "gitlab.com/technofab/nixtest/internal/config"
- appnix "gitlab.com/technofab/nixtest/internal/nix"
- "gitlab.com/technofab/nixtest/internal/report/console"
- "gitlab.com/technofab/nixtest/internal/report/junit"
- "gitlab.com/technofab/nixtest/internal/runner"
- appsnap "gitlab.com/technofab/nixtest/internal/snapshot"
- "gitlab.com/technofab/nixtest/internal/types"
- "gitlab.com/technofab/nixtest/internal/util"
+ "gitlab.com/TECHNOFAB/nixtest/internal/config"
+ appnix "gitlab.com/TECHNOFAB/nixtest/internal/nix"
+ "gitlab.com/TECHNOFAB/nixtest/internal/report/console"
+ "gitlab.com/TECHNOFAB/nixtest/internal/report/junit"
+ "gitlab.com/TECHNOFAB/nixtest/internal/runner"
+ appsnap "gitlab.com/TECHNOFAB/nixtest/internal/snapshot"
+ "gitlab.com/TECHNOFAB/nixtest/internal/types"
+ "gitlab.com/TECHNOFAB/nixtest/internal/util"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@@ -60,7 +60,7 @@ func main() {
SnapshotDir: appCfg.SnapshotDir,
UpdateSnapshots: appCfg.UpdateSnapshots,
SkipPattern: appCfg.SkipPattern,
- PureEnv: appCfg.PureEnv,
+ ImpureEnv: appCfg.ImpureEnv,
}
testRunner, err := runner.New(runnerCfg, nixService, snapshotService)
if err != nil {
diff --git a/docs/cli.md b/docs/cli.md
index 9c69c9c..fd882a7 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -3,10 +3,11 @@
```sh title="nix run .#nixtests:run -- --help"
Usage of nixtest:
--junit string Path to generate JUNIT report to, leave empty to disable
- --pure Unset all env vars before running script tests
- --skip string Regular expression to skip (e.g., 'test-.*|.*-b')
+ --no-color Disable coloring
+ --impure Don\'t unset all env vars before running script tests
+ -s, --skip string Regular expression to skip tests (e.g., 'test-.*|.*-b')
--snapshot-dir string Directory where snapshots are stored (default "./snapshots")
- --tests string Path to JSON file containing tests
- --update-snapshots Update all snapshots
- --workers int Amount of tests to run in parallel (default 4)
+ -f, --tests string Path to JSON file containing tests (required)
+ -u, --update-snapshots Update all snapshots
+ -w, --workers int Amount of tests to run in parallel (default 4)
```
diff --git a/docs/examples.md b/docs/examples.md
index 4e3ad41..c61c754 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -1,4 +1,6 @@
# Example Configs
+- [nixtest itself](https://gitlab.com/TECHNOFAB/nixtest)
+ see `flake.nix` and `tests/`
- [TECHNOFAB/nix-gitlab-ci](https://gitlab.com/TECHNOFAB/nix-gitlab-ci)
- see tests/
+ see `tests/`
diff --git a/docs/images/favicon.png b/docs/images/favicon.png
deleted file mode 100755
index 4203f2a..0000000
Binary files a/docs/images/favicon.png and /dev/null differ
diff --git a/docs/images/gitlab_junit_screenshot.png b/docs/images/gitlab_junit_screenshot.png
new file mode 100644
index 0000000..03696af
Binary files /dev/null and b/docs/images/gitlab_junit_screenshot.png differ
diff --git a/docs/images/logo.png b/docs/images/logo.png
deleted file mode 100755
index b8ff5f8..0000000
Binary files a/docs/images/logo.png and /dev/null differ
diff --git a/docs/images/logo.svg b/docs/images/logo.svg
new file mode 100755
index 0000000..93213ef
--- /dev/null
+++ b/docs/images/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/images/summary_screenshot.png b/docs/images/summary_screenshot.png
new file mode 100644
index 0000000..af60a6d
Binary files /dev/null and b/docs/images/summary_screenshot.png differ
diff --git a/docs/index.md b/docs/index.md
index 37124d9..f9fc7b5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -7,4 +7,6 @@ Flexible test runner for testing Nix code, written in Go.
- Snapshot, Unit (equal checks) and Script-Tests (unit tests with assertions you could say)
- Supports testing against raw Nix code or derivation output
- Simple and easy to read summary of test results
+ 
- Junit report support (eg. for displaying the results in GitLab etc.)
+ 
diff --git a/docs/options.md b/docs/options.md
new file mode 100644
index 0000000..4ca74a4
--- /dev/null
+++ b/docs/options.md
@@ -0,0 +1,3 @@
+# Options
+
+{% include 'options.md' %}
diff --git a/docs/reference.md b/docs/reference.md
new file mode 100644
index 0000000..fe9d7cb
--- /dev/null
+++ b/docs/reference.md
@@ -0,0 +1,53 @@
+# Reference
+
+## `flakeModule`
+
+The `flakeModule` for [flake-parts](https://flake.parts).
+
+## `lib`
+
+### `module`
+
+The nix module for validation of inputs etc.
+Used internally by `mkNixtestConfig`.
+
+### `autodiscover`
+
+```nix
+autodiscover {
+ dir,
+ pattern ? ".*_test.nix",
+}
+```
+
+Finds all test files in `dir` matching `pattern`.
+Returns a list of modules (can be passed to `mkNixtest`'s `modules` arg).
+
+### `mkNixtestConfig`
+
+```nix
+mkNixtestConfig {
+ modules,
+ args ? {},
+}
+```
+
+Evaluates the test `modules`.
+`args` are passed to the modules using `_module.args = args`.
+
+**Noteworthy attributes**:
+
+- `app`: nixtest wrapper
+- `finalConfigJson`: derivation containing the tests json file
+
+### `mkNixtest`
+
+```nix
+mkNixtest {
+ modules,
+ args ? {},
+}
+```
+
+Creates the nixtest wrapper, using the tests in `modules`.
+Basically `(mkNixtestConfig ).app`.
diff --git a/docs/style.css b/docs/style.css
new file mode 100644
index 0000000..b2ae4ff
--- /dev/null
+++ b/docs/style.css
@@ -0,0 +1,15 @@
+.md-header__button.md-logo {
+ margin: 0;
+ padding-top: .2rem;
+ padding-bottom: .2rem;
+}
+
+[dir="ltr"] .md-header__title {
+ margin-left: 0;
+}
+
+.md-header__button.md-logo img,
+.md-header__button.md-logo svg {
+ height: 2rem;
+}
+
diff --git a/docs/usage.md b/docs/usage.md
index 2c72e4a..ee7598c 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -2,6 +2,9 @@
## Flake Module
+The easiest way to use Nixtest is probably using the flakeModule.
+Just import `nixtest.flakeModule`, then define suites and tests in `perSystem`:
+
```nix
{
inputs.nixtest.url = "gitlab:TECHNOFAB/nixtest?dir=lib";
@@ -34,10 +37,25 @@
## Library
-You can also integrate nixtest in your own workflow by using the lib functions directly.
-Check out `flakeModule.nix` to see how it's used there.
+You can also use the lib directly, like this for example:
-
+```nix
+packages.tests = ntlib.mkNixtest {
+ modules = ntlib.autodiscover {dir = ./tests;};
+ args = {
+ inherit pkgs ntlib;
+ };
+};
+```
+
+This will auto-discover all test files ending with `_test.nix`.
+See [reference](reference.md) for all params to `autodiscover`.
+
+`ntlib` can be defined like this:
+
+```nix
+ntlib = inputs.nixtests.lib {inherit pkgs;};
+```
## Define Tests
@@ -78,22 +96,30 @@ Examples:
name = "script-test";
type = "script";
script =
- # there are two modes, "default"/"impure" and "pure"
+ # there are two modes, "default"/"pure" and "impure"
# in impure mode all env variables etc. from your current session are kept
- # and are available to the test
- # to make it more reproducible and cleaner, use --pure to switch to pure
+ # and are available to the test (using --impure).
+ # to make it more reproducible and cleaner, the default is pure
# mode which will unset all env variables before running the test. That
# requires you to set PATH yourself then:
+ #
+ # ''
+ # export PATH="${lib.makeBinPath [pkgs.gnugrep]}"
+ # grep -q "test" ${builtins.toFile "test" "test"}
+ # '';
+ #
+ # you can also use the helpers to make it nicer to read:
''
- export PATH="${lib.makeBinPath [pkgs.gnugrep]}"
- grep -q "test" ${builtins.toFile "test" "test"}
+ ${ntlib.helpers.path [pkgs.gnugrep]}
+ ${ntlib.helpers.scriptHelpers} # this adds helpers like assert etc.
+ assert_file_contains ${builtins.toFile "test" "test"} "test" "file should contain 'test'"
'';
}
{
name = "pretty-test";
# by default it uses json to serialize and compare the values. Derivations
# and functions don't really work that way though, so you can also use
- # "pretty" to use lib.generators.pretty
+ # "pretty" to use lib.generators.toPretty
format = "pretty";
# you can also set the pos here
pos = __curPos;
@@ -102,3 +128,7 @@ Examples:
}
]
```
+
+!!! note
+
+ for more examples see [examples](./examples.md)
diff --git a/flake.lock b/flake.lock
index 09366e0..6d5a6ab 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,281 +1,12 @@
{
"nodes": {
- "cachix": {
- "inputs": {
- "devenv": [
- "devenv"
- ],
- "flake-compat": [
- "devenv"
- ],
- "git-hooks": [
- "devenv"
- ],
- "nixpkgs": "nixpkgs"
- },
- "locked": {
- "lastModified": 1742042642,
- "narHash": "sha256-D0gP8srrX0qj+wNYNPdtVJsQuFzIng3q43thnHXQ/es=",
- "owner": "cachix",
- "repo": "cachix",
- "rev": "a624d3eaf4b1d225f918de8543ed739f2f574203",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "ref": "latest",
- "repo": "cachix",
- "type": "github"
- }
- },
- "devenv": {
- "inputs": {
- "cachix": "cachix",
- "flake-compat": "flake-compat",
- "git-hooks": "git-hooks",
- "nix": "nix",
- "nixpkgs": "nixpkgs_3"
- },
- "locked": {
- "lastModified": 1746189866,
- "narHash": "sha256-3sTvuSVBFcXbqg26Qcw/ENJ1s36jtzEcZ0mHqLqvWRA=",
- "owner": "cachix",
- "repo": "devenv",
- "rev": "5fc592d45dd056035e0fd5000893a21609c35526",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "devenv",
- "type": "github"
- }
- },
- "flake-compat": {
- "flake": false,
- "locked": {
- "lastModified": 1733328505,
- "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-parts": {
- "inputs": {
- "nixpkgs-lib": [
- "devenv",
- "nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1712014858,
- "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "flake-parts_2": {
- "inputs": {
- "nixpkgs-lib": "nixpkgs-lib"
- },
- "locked": {
- "lastModified": 1743550720,
- "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "c621e8422220273271f52058f618c94e405bb0f5",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "git-hooks": {
- "inputs": {
- "flake-compat": [
- "devenv"
- ],
- "gitignore": "gitignore",
- "nixpkgs": [
- "devenv",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1742649964,
- "narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "type": "github"
- }
- },
- "gitignore": {
- "inputs": {
- "nixpkgs": [
- "devenv",
- "git-hooks",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1709087332,
- "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "type": "github"
- }
- },
- "libgit2": {
- "flake": false,
- "locked": {
- "lastModified": 1697646580,
- "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
- "owner": "libgit2",
- "repo": "libgit2",
- "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
- "type": "github"
- },
- "original": {
- "owner": "libgit2",
- "repo": "libgit2",
- "type": "github"
- }
- },
- "mkdocs-material-umami": {
- "locked": {
- "lastModified": 1745840856,
- "narHash": "sha256-1Ad1JTMQMP6YsoIKAA+SBCE15qWrYkGue9/lXOLnu9I=",
- "owner": "technofab",
- "repo": "mkdocs-material-umami",
- "rev": "3ac9b194450f6b779c37b8d16fec640198e5cd0a",
- "type": "gitlab"
- },
- "original": {
- "owner": "technofab",
- "repo": "mkdocs-material-umami",
- "type": "gitlab"
- }
- },
- "nix": {
- "inputs": {
- "flake-compat": [
- "devenv"
- ],
- "flake-parts": "flake-parts",
- "libgit2": "libgit2",
- "nixpkgs": "nixpkgs_2",
- "nixpkgs-23-11": [
- "devenv"
- ],
- "nixpkgs-regression": [
- "devenv"
- ],
- "pre-commit-hooks": [
- "devenv"
- ]
- },
- "locked": {
- "lastModified": 1745930071,
- "narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
- "owner": "domenkozar",
- "repo": "nix",
- "rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
- "type": "github"
- },
- "original": {
- "owner": "domenkozar",
- "ref": "devenv-2.24",
- "repo": "nix",
- "type": "github"
- }
- },
- "nix-devtools": {
- "locked": {
- "dir": "lib",
- "lastModified": 1739971859,
- "narHash": "sha256-DaY11jX7Lraw7mRUIsgPsO+aSkkewQe2D+WMZORTNPE=",
- "owner": "technofab",
- "repo": "nix-devtools",
- "rev": "b4f059657de5ac2569afd69a8f042614d309e6bb",
- "type": "gitlab"
- },
- "original": {
- "dir": "lib",
- "owner": "technofab",
- "repo": "nix-devtools",
- "type": "gitlab"
- }
- },
- "nix-gitlab-ci": {
- "locked": {
- "dir": "lib",
- "lastModified": 1746973171,
- "narHash": "sha256-q/LhPZlhJB2gXZ5BfgU1Wep/1x1y9Sct3/JU8A2fzjg=",
- "owner": "technofab",
- "repo": "nix-gitlab-ci",
- "rev": "dca2d724c155799e537a898cb9f948f8afae4921",
- "type": "gitlab"
- },
- "original": {
- "dir": "lib",
- "owner": "technofab",
- "ref": "2.0.1",
- "repo": "nix-gitlab-ci",
- "type": "gitlab"
- }
- },
- "nix-mkdocs": {
- "locked": {
- "dir": "lib",
- "lastModified": 1745841841,
- "narHash": "sha256-297zPQbUlc7ZAYDoaD6mCmQxCC3Tr4YOKekRF1ArZ7g=",
- "owner": "technofab",
- "repo": "nixmkdocs",
- "rev": "c7e3c3b13ded25818e9789938387bba6f2cde690",
- "type": "gitlab"
- },
- "original": {
- "dir": "lib",
- "owner": "technofab",
- "repo": "nixmkdocs",
- "type": "gitlab"
- }
- },
"nixpkgs": {
"locked": {
- "lastModified": 1733212471,
- "narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
+ "lastModified": 1765472234,
+ "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
+ "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
"type": "github"
},
"original": {
@@ -287,11 +18,11 @@
},
"nixpkgs-lib": {
"locked": {
- "lastModified": 1743296961,
- "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
+ "lastModified": 1754184128,
+ "narHash": "sha256-AjhoyBL4eSyXf01Bmc6DiuaMrJRNdWopmdnMY0Pa/M0=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
- "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
+ "rev": "02e72200e6d56494f4a7c0da8118760736e41b60",
"type": "github"
},
"original": {
@@ -300,114 +31,30 @@
"type": "github"
}
},
- "nixpkgs_2": {
+ "ren": {
+ "inputs": {
+ "nixpkgs-lib": "nixpkgs-lib"
+ },
"locked": {
- "lastModified": 1717432640,
- "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
- "type": "github"
+ "dir": "lib",
+ "lastModified": 1758738378,
+ "narHash": "sha256-NjzqdvQCDDdObEBH8x/vdhbdhrIB+N9E570uCdksGHY=",
+ "owner": "rensa-nix",
+ "repo": "core",
+ "rev": "abe19f9f13aff41de2b63304545c87d193d19ef4",
+ "type": "gitlab"
},
"original": {
- "owner": "NixOS",
- "ref": "release-24.05",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs_3": {
- "locked": {
- "lastModified": 1733477122,
- "narHash": "sha256-qamMCz5mNpQmgBwc8SB5tVMlD5sbwVIToVZtSxMph9s=",
- "owner": "cachix",
- "repo": "devenv-nixpkgs",
- "rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "ref": "rolling",
- "repo": "devenv-nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs_4": {
- "locked": {
- "lastModified": 1746152631,
- "narHash": "sha256-zBuvmL6+CUsk2J8GINpyy8Hs1Zp4PP6iBWSmZ4SCQ/s=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "032bc6539bd5f14e9d0c51bd79cfe9a055b094c3",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs_5": {
- "locked": {
- "lastModified": 1745377448,
- "narHash": "sha256-jhZDfXVKdD7TSEGgzFJQvEEZ2K65UMiqW5YJ2aIqxMA=",
- "owner": "nixos",
- "repo": "nixpkgs",
- "rev": "507b63021ada5fee621b6ca371c4fca9ca46f52c",
- "type": "github"
- },
- "original": {
- "owner": "nixos",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
+ "dir": "lib",
+ "owner": "rensa-nix",
+ "repo": "core",
+ "type": "gitlab"
}
},
"root": {
"inputs": {
- "devenv": "devenv",
- "flake-parts": "flake-parts_2",
- "mkdocs-material-umami": "mkdocs-material-umami",
- "nix-devtools": "nix-devtools",
- "nix-gitlab-ci": "nix-gitlab-ci",
- "nix-mkdocs": "nix-mkdocs",
- "nixpkgs": "nixpkgs_4",
- "systems": "systems",
- "treefmt-nix": "treefmt-nix"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1689347949,
- "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
- "owner": "nix-systems",
- "repo": "default-linux",
- "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default-linux",
- "type": "github"
- }
- },
- "treefmt-nix": {
- "inputs": {
- "nixpkgs": "nixpkgs_5"
- },
- "locked": {
- "lastModified": 1746216483,
- "narHash": "sha256-4h3s1L/kKqt3gMDcVfN8/4v2jqHrgLIe4qok4ApH5x4=",
- "owner": "numtide",
- "repo": "treefmt-nix",
- "rev": "29ec5026372e0dec56f890e50dbe4f45930320fd",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "treefmt-nix",
- "type": "github"
+ "nixpkgs": "nixpkgs",
+ "ren": "ren"
}
}
},
diff --git a/flake.nix b/flake.nix
index bcc7b54..fec8334 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,334 +1,39 @@
{
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ ren.url = "gitlab:rensa-nix/core?dir=lib";
+ };
+
outputs = {
- flake-parts,
- systems,
+ ren,
+ self,
...
} @ inputs:
- flake-parts.lib.mkFlake {inherit inputs;} {
- imports = [
- inputs.devenv.flakeModule
- inputs.treefmt-nix.flakeModule
- inputs.nix-gitlab-ci.flakeModule
- inputs.nix-devtools.flakeModule
- inputs.nix-mkdocs.flakeModule
- ./lib/flakeModule.nix
+ ren.buildWith
+ {
+ inherit inputs;
+ cellsFrom = ./nix;
+ transformInputs = system: i:
+ i
+ // {
+ pkgs = import i.nixpkgs {inherit system;};
+ };
+ cellBlocks = with ren.blocks; [
+ (simple "devShells")
+ (simple "ci")
+ (simple "tests")
+ (simple "packages")
+ (simple "docs")
+ (simple "soonix")
+ ];
+ }
+ {
+ packages = ren.select self [
+ ["repo" "ci" "packages"]
+ ["repo" "tests"]
+ ["packages" "packages"]
+ ["repo" "docs"]
+ ["repo" "soonix" "packages"]
];
- systems = import systems;
- flake = {};
- perSystem = {
- lib,
- pkgs,
- config,
- ...
- }: {
- treefmt = {
- projectRootFile = "flake.nix";
- programs = {
- alejandra.enable = true;
- mdformat.enable = true;
- gofmt.enable = true;
- };
- settings.formatter.mdformat.command = let
- pkg = pkgs.python3.withPackages (p: [
- p.mdformat
- p.mdformat-mkdocs
- ]);
- in "${pkg}/bin/mdformat";
- };
- devenv.shells.default = {
- containers = pkgs.lib.mkForce {};
- packages = with pkgs; [gopls gore go-junit-report];
-
- languages.go.enable = true;
-
- pre-commit.hooks = {
- treefmt = {
- enable = true;
- packageOverrides.treefmt = config.treefmt.build.wrapper;
- };
- convco.enable = true;
- };
-
- task = {
- enable = true;
- alias = ",";
- tasks = {
- "test" = {
- cmds = [
- "go test -v -coverprofile cover.out ./..."
- "go tool cover -html cover.out -o cover.html"
- ];
- };
- };
- };
- };
-
- nixtest = {
- skip = "skip.*d";
- suites = {
- "suite-one" = {
- pos = __curPos;
- tests = [
- {
- name = "test-one";
- # required to figure out file and line, but optional
- expected = 1;
- actual = 1;
- }
- {
- name = "fail";
- expected = 0;
- actual = "meow";
- }
- {
- name = "snapshot-test";
- type = "snapshot";
- actual = "test";
- }
- {
- name = "test-snapshot-drv";
- type = "snapshot";
- actualDrv = pkgs.runCommand "test-snapshot" {} ''
- echo '"snapshot drv"' > $out
- '';
- }
- {
- name = "test-error-drv";
- expected = null;
- actualDrv = pkgs.runCommand "test-error-drv" {} ''
- echo "This works, but its better to just write 'fail' to \$out and expect 'success' or sth."
- exit 1
- '';
- }
- {
- name = "test-script";
- type = "script";
- script = ''
- echo Test something here
- # required in pure mode:
- export PATH="${lib.makeBinPath [pkgs.gnugrep]}"
- grep -q "test" ${builtins.toFile "test" "test"}
- '';
- }
- ];
- };
- "other-suite".tests = [
- {
- name = "obj-snapshot";
- type = "snapshot";
- pos = __curPos;
- actual = {hello = "world";};
- }
- {
- name = "pretty-snapshot";
- type = "snapshot";
- format = "pretty";
- pos = __curPos;
- actual = {
- example = args: {};
- example2 = {
- drv = pkgs.hello;
- };
- };
- }
- {
- name = "pretty-unit";
- format = "pretty";
- pos = __curPos;
- expected = pkgs.hello;
- actual = pkgs.hello;
- }
- {
- name = "test-drv";
- pos = __curPos;
- expected = {a = "b";};
- actualDrv = pkgs.runCommand "test-something" {} ''
- echo "Simulating taking some time"
- sleep 1
- echo '{"a":"b"}' > $out
- '';
- }
- {
- name = "skipped";
- expected = null;
- actual = null;
- }
- ];
- };
- };
-
- doc = {
- path = ./docs;
- deps = pp: [
- pp.mkdocs-material
- (pp.callPackage inputs.mkdocs-material-umami {})
- ];
- config = {
- site_name = "Nixtest";
- repo_name = "TECHNOFAB/nixtest";
- repo_url = "https://gitlab.com/TECHNOFAB/nixtest";
- edit_uri = "edit/main/docs/";
- theme = {
- name = "material";
- features = ["content.code.copy" "content.action.edit"];
- icon.repo = "simple/gitlab";
- logo = "images/logo.png";
- favicon = "images/favicon.png";
- palette = [
- {
- scheme = "default";
- media = "(prefers-color-scheme: light)";
- primary = "green";
- accent = "light green";
- toggle = {
- icon = "material/brightness-7";
- name = "Switch to dark mode";
- };
- }
- {
- scheme = "slate";
- media = "(prefers-color-scheme: dark)";
- primary = "green";
- accent = "light green";
- toggle = {
- icon = "material/brightness-4";
- name = "Switch to light mode";
- };
- }
- ];
- };
- plugins = ["search" "material-umami"];
- nav = [
- {"Introduction" = "index.md";}
- {"Usage" = "usage.md";}
- {"CLI" = "cli.md";}
- {"Example Configs" = "examples.md";}
- ];
- markdown_extensions = [
- "pymdownx.superfences"
- ];
- extra.analytics = {
- provider = "umami";
- site_id = "716d1869-9342-4b62-a770-e15d2d5c807d";
- src = "https://analytics.tf/umami";
- domains = "nixtest.projects.tf";
- feedback = {
- title = "Was this page helpful?";
- ratings = [
- {
- icon = "material/thumb-up-outline";
- name = "This page is helpful";
- data = "good";
- note = "Thanks for your feedback!";
- }
- {
- icon = "material/thumb-down-outline";
- name = "This page could be improved";
- data = "bad";
- note = "Thanks for your feedback! Please leave feedback by creating an issue :)";
- }
- ];
- };
- };
- };
- };
-
- ci = {
- stages = ["test" "build" "deploy"];
- jobs = {
- "test" = {
- stage = "test";
- script = [
- "nix run .#nixtests:run -- --junit=junit.xml"
- ];
- allow_failure = true;
- artifacts = {
- when = "always";
- reports.junit = "junit.xml";
- };
- };
- "test:go" = {
- stage = "test";
- nix.deps = with pkgs; [go go-junit-report gocover-cobertura];
- variables = {
- GOPATH = "$CI_PROJECT_DIR/.go";
- GOCACHE = "$CI_PROJECT_DIR/.go/pkg/mod";
- };
- script = [
- "go test -coverprofile=coverage.out -v 2>&1 ./... | go-junit-report -set-exit-code > report.xml"
- "go tool cover -func coverage.out"
- "gocover-cobertura < coverage.out > coverage.xml"
- ];
- allow_failure = true;
- coverage = "/\(statements\)(?:\s+)?(\d+(?:\.\d+)?%)/";
- cache.paths = [".go/pkg/mod/"];
- artifacts = {
- when = "always";
- reports = {
- junit = "report.xml";
- coverage_report = {
- coverage_format = "cobertura";
- path = "coverage.xml";
- };
- };
- };
- };
- "docs" = {
- stage = "build";
- script = [
- # sh
- ''
- nix build .#docs:default
- mkdir -p public
- cp -r result/. public/
- ''
- ];
- artifacts.paths = ["public"];
- };
- "pages" = {
- nix.enable = false;
- image = "alpine:latest";
- stage = "deploy";
- script = ["true"];
- artifacts.paths = ["public"];
- rules = [
- {
- "if" = "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH";
- }
- ];
- };
- };
- };
-
- packages.default = pkgs.callPackage ./package.nix {};
- };
};
-
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
-
- # flake & devenv related
- flake-parts.url = "github:hercules-ci/flake-parts";
- systems.url = "github:nix-systems/default-linux";
- devenv.url = "github:cachix/devenv";
- treefmt-nix.url = "github:numtide/treefmt-nix";
- nix-gitlab-ci.url = "gitlab:technofab/nix-gitlab-ci/2.0.1?dir=lib";
- nix-devtools.url = "gitlab:technofab/nix-devtools?dir=lib";
- nix-mkdocs.url = "gitlab:technofab/nixmkdocs?dir=lib";
- mkdocs-material-umami.url = "gitlab:technofab/mkdocs-material-umami";
- };
-
- nixConfig = {
- extra-substituters = [
- "https://cache.nixos.org/"
- "https://nix-community.cachix.org"
- "https://devenv.cachix.org"
- ];
-
- extra-trusted-public-keys = [
- "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
- "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
- "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
- ];
- };
}
diff --git a/go.mod b/go.mod
index 4cb8b73..b0e27d6 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,12 @@
-module gitlab.com/technofab/nixtest
+module gitlab.com/TECHNOFAB/nixtest
-go 1.24.2
+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.7
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
)
diff --git a/go.sum b/go.sum
index 84d30d0..ffb4c39 100644
--- a/go.sum
+++ b/go.sum
@@ -7,6 +7,8 @@ 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/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 +34,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=
diff --git a/internal/config/config.go b/internal/config/config.go
index f0b41e6..61427fb 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -1,6 +1,9 @@
package config
import (
+ "fmt"
+ "os"
+
"github.com/jedib0t/go-pretty/v6/text"
"github.com/rs/zerolog/log"
flag "github.com/spf13/pflag"
@@ -13,7 +16,7 @@ type AppConfig struct {
JunitPath string
UpdateSnapshots bool
SkipPattern string
- PureEnv bool
+ ImpureEnv bool
NoColor bool
}
@@ -26,11 +29,18 @@ func Load() AppConfig {
flag.StringVar(&cfg.JunitPath, "junit", "", "Path to generate JUNIT report to, leave empty to disable")
flag.BoolVarP(&cfg.UpdateSnapshots, "update-snapshots", "u", false, "Update all snapshots")
flag.StringVarP(&cfg.SkipPattern, "skip", "s", "", "Regular expression to skip tests (e.g., 'test-.*|.*-b')")
- flag.BoolVar(&cfg.PureEnv, "pure", false, "Unset all env vars before running script tests")
+ flag.BoolVar(&cfg.ImpureEnv, "impure", false, "Don't unset all env vars before running script tests")
flag.BoolVar(&cfg.NoColor, "no-color", false, "Disable coloring")
+ helpRequested := flag.BoolP("help", "h", false, "Show this menu")
flag.Parse()
+ if *helpRequested {
+ fmt.Println("Usage of nixtest:")
+ flag.PrintDefaults()
+ os.Exit(0)
+ }
+
if cfg.TestsFile == "" {
log.Panic().Msg("Tests file path (-f or --tests) is required.")
}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index c864d90..249dd60 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -64,7 +64,7 @@ func TestLoad_CustomValues(t *testing.T) {
"--junit", "report.xml",
"-u",
"--skip", "specific-test",
- "--pure",
+ "--impure",
"--no-color",
}
pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) // Reset flags
@@ -83,7 +83,7 @@ func TestLoad_CustomValues(t *testing.T) {
if cfg.SkipPattern != "specific-test" {
t.Errorf("SkipPattern: got %s, want specific-test", cfg.SkipPattern)
}
- if !cfg.PureEnv {
- t.Errorf("PureEnv: got %v, want true", cfg.PureEnv)
+ if !cfg.ImpureEnv {
+ t.Errorf("ImpureEnv: got %v, want true", cfg.ImpureEnv)
}
}
diff --git a/internal/nix/service.go b/internal/nix/service.go
index c0ec851..a3551d3 100644
--- a/internal/nix/service.go
+++ b/internal/nix/service.go
@@ -3,18 +3,19 @@ package nix
import (
"bytes"
"encoding/json"
+ "fmt"
"os"
"os/exec"
"strings"
- apperrors "gitlab.com/technofab/nixtest/internal/errors"
+ apperrors "gitlab.com/TECHNOFAB/nixtest/internal/errors"
)
// Service defines operations related to Nix
type Service interface {
BuildDerivation(derivation string) (string, error)
BuildAndParseJSON(derivation string) (any, error)
- BuildAndRunScript(derivation string, pureEnv bool) (exitCode int, stdout string, stderr string, err error)
+ BuildAndRunScript(derivation string, impureEnv bool) (exitCode int, stdout string, stderr string, err error)
}
type DefaultService struct {
@@ -72,21 +73,29 @@ func (s *DefaultService) BuildAndParseJSON(derivation string) (any, error) {
}
// BuildAndRunScript builds a derivation and runs it as a script
-func (s *DefaultService) BuildAndRunScript(derivation string, pureEnv bool) (exitCode int, stdout string, stderr string, err error) {
+func (s *DefaultService) BuildAndRunScript(derivation string, impureEnv bool) (exitCode int, stdout string, stderr string, err error) {
exitCode = -1
path, err := s.BuildDerivation(derivation)
if err != nil {
return exitCode, "", "", err
}
+ // run scripts in a temporary directory
+ tempDir, err := os.MkdirTemp("", "nixtest-script-")
+ if err != nil {
+ return exitCode, "", "", &apperrors.ScriptExecutionError{Path: path, Err: fmt.Errorf("failed to create temporary directory: %w", err)}
+ }
+ defer os.RemoveAll(tempDir)
+
var cmdArgs []string
- if pureEnv {
- cmdArgs = append([]string{"env", "-i"}, "bash", path)
- } else {
+ if impureEnv {
cmdArgs = []string{"bash", path}
+ } else {
+ cmdArgs = append([]string{"env", "-i"}, "bash", path)
}
cmd := s.commandExecutor(cmdArgs[0], cmdArgs[1:]...)
+ cmd.Dir = tempDir
var outBuf, errBuf bytes.Buffer
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
diff --git a/internal/nix/service_test.go b/internal/nix/service_test.go
index 3fee50a..781a8cd 100644
--- a/internal/nix/service_test.go
+++ b/internal/nix/service_test.go
@@ -10,7 +10,7 @@ import (
"strings"
"testing"
- apperrors "gitlab.com/technofab/nixtest/internal/errors"
+ apperrors "gitlab.com/TECHNOFAB/nixtest/internal/errors"
)
func TestHelperProcess(t *testing.T) {
@@ -232,7 +232,7 @@ func TestDefaultService_BuildAndRunScript(t *testing.T) {
tests := []struct {
name string
derivation string
- pureEnv bool
+ impureEnv bool
mockBuildDrvOutput string
mockBuildDrvError string
mockBuildDrvExitCode string
@@ -252,7 +252,7 @@ func TestDefaultService_BuildAndRunScript(t *testing.T) {
0, "Hello", "ErrOut", false, nil, "",
},
{
- "Success pure", "script.drv#sh", true, mockScriptPath, "", "0",
+ "Success impure", "script.drv#sh", true, mockScriptPath, "", "0",
"Hello", "ErrOut", "0",
0, "Hello", "ErrOut", false, nil, "",
},
@@ -277,7 +277,7 @@ func TestDefaultService_BuildAndRunScript(t *testing.T) {
os.Setenv("MOCK_SCRIPT_STDERR", tt.mockScriptStderr)
os.Setenv("MOCK_SCRIPT_EXIT_CODE", tt.mockScriptExitCode)
- exitCode, stdout, stderr, err := service.BuildAndRunScript(tt.derivation, tt.pureEnv)
+ exitCode, stdout, stderr, err := service.BuildAndRunScript(tt.derivation, tt.impureEnv)
if (err != nil) != tt.wantErr {
t.Fatalf("BuildAndRunScript() error = %v, wantErr %v", err, tt.wantErr)
diff --git a/internal/report/console/console.go b/internal/report/console/console.go
index 959741c..efca37f 100644
--- a/internal/report/console/console.go
+++ b/internal/report/console/console.go
@@ -11,8 +11,8 @@ import (
"github.com/jedib0t/go-pretty/v6/text"
"github.com/rs/zerolog/log"
"github.com/sergi/go-diff/diffmatchpatch"
- "gitlab.com/technofab/nixtest/internal/types"
- "gitlab.com/technofab/nixtest/internal/util"
+ "gitlab.com/TECHNOFAB/nixtest/internal/types"
+ "gitlab.com/TECHNOFAB/nixtest/internal/util"
)
// PrintErrors prints error messages for failed tests
diff --git a/internal/report/console/console_test.go b/internal/report/console/console_test.go
index bbe9a6a..d562de4 100644
--- a/internal/report/console/console_test.go
+++ b/internal/report/console/console_test.go
@@ -12,7 +12,7 @@ import (
"time"
"github.com/jedib0t/go-pretty/v6/text"
- "gitlab.com/technofab/nixtest/internal/types"
+ "gitlab.com/TECHNOFAB/nixtest/internal/types"
)
// captureOutput captures stdout and stderr during the execution of a function
diff --git a/internal/report/junit/junit.go b/internal/report/junit/junit.go
index a376600..be2fcb3 100644
--- a/internal/report/junit/junit.go
+++ b/internal/report/junit/junit.go
@@ -7,8 +7,8 @@ import (
"strings"
"time"
- "gitlab.com/technofab/nixtest/internal/types"
- "gitlab.com/technofab/nixtest/internal/util"
+ "gitlab.com/TECHNOFAB/nixtest/internal/types"
+ "gitlab.com/TECHNOFAB/nixtest/internal/util"
)
type JUnitReport struct {
diff --git a/internal/report/junit/junit_test.go b/internal/report/junit/junit_test.go
index fbca0aa..afccbb5 100644
--- a/internal/report/junit/junit_test.go
+++ b/internal/report/junit/junit_test.go
@@ -9,7 +9,7 @@ import (
"testing"
"time"
- "gitlab.com/technofab/nixtest/internal/types"
+ "gitlab.com/TECHNOFAB/nixtest/internal/types"
)
func formatDurationSeconds(d time.Duration) string {
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index 6f250d1..8ccb8df 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -9,10 +9,10 @@ import (
"time"
"github.com/rs/zerolog/log"
- "gitlab.com/technofab/nixtest/internal/nix"
- "gitlab.com/technofab/nixtest/internal/snapshot"
- "gitlab.com/technofab/nixtest/internal/types"
- "gitlab.com/technofab/nixtest/internal/util"
+ "gitlab.com/TECHNOFAB/nixtest/internal/nix"
+ "gitlab.com/TECHNOFAB/nixtest/internal/snapshot"
+ "gitlab.com/TECHNOFAB/nixtest/internal/types"
+ "gitlab.com/TECHNOFAB/nixtest/internal/util"
)
// Runner executes tests based on provided specifications and configuration
@@ -32,7 +32,7 @@ type Config struct {
SnapshotDir string
UpdateSnapshots bool
SkipPattern string
- PureEnv bool
+ ImpureEnv bool
}
func New(cfg Config, nixService nix.Service, snapService snapshot.Service) (*Runner, error) {
@@ -181,7 +181,7 @@ func (r *Runner) handleUnitTest(result *types.TestResult, spec types.TestSpec, a
// handleScriptTest processes script type tests
func (r *Runner) handleScriptTest(result *types.TestResult, spec types.TestSpec) {
- exitCode, stdout, stderrStr, err := r.nixService.BuildAndRunScript(spec.Script, r.config.PureEnv)
+ exitCode, stdout, stderrStr, err := r.nixService.BuildAndRunScript(spec.Script, r.config.ImpureEnv)
if err != nil {
result.Status = types.StatusError
result.ErrorMessage = fmt.Sprintf("[system] failed to run script derivation %s: %v", spec.Script, err)
diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go
index 1415dd8..582c2c1 100644
--- a/internal/runner/runner_test.go
+++ b/internal/runner/runner_test.go
@@ -9,8 +9,8 @@ import (
"testing"
"time"
- apperrors "gitlab.com/technofab/nixtest/internal/errors"
- "gitlab.com/technofab/nixtest/internal/types"
+ apperrors "gitlab.com/TECHNOFAB/nixtest/internal/errors"
+ "gitlab.com/TECHNOFAB/nixtest/internal/types"
)
// --- Mock Service Implementations ---
@@ -18,7 +18,7 @@ import (
type mockNixService struct {
BuildDerivationFunc func(derivation string) (string, error)
BuildAndParseJSONFunc func(derivation string) (any, error)
- BuildAndRunScriptFunc func(derivation string, pureEnv bool) (exitCode int, stdout string, stderr string, err error)
+ BuildAndRunScriptFunc func(derivation string, impureEnv bool) (exitCode int, stdout string, stderr string, err error)
}
func (m *mockNixService) BuildDerivation(d string) (string, error) {
@@ -253,7 +253,7 @@ func TestRunner_executeTest(t *testing.T) {
spec: types.TestSpec{Name: "ScriptSuccess", Type: types.TestTypeScript, Script: "script.sh"},
runnerConfig: Config{},
setupMockServices: func(t *testing.T, mNix *mockNixService, mSnap *mockSnapshotService, s types.TestSpec, c Config) {
- mNix.BuildAndRunScriptFunc = func(derivation string, pureEnv bool) (int, string, string, error) {
+ mNix.BuildAndRunScriptFunc = func(derivation string, impureEnv bool) (int, string, string, error) {
return 0, "stdout", "stderr", nil
}
},
@@ -264,7 +264,7 @@ func TestRunner_executeTest(t *testing.T) {
spec: types.TestSpec{Name: "ScriptFail", Type: types.TestTypeScript, Script: "script.sh"},
runnerConfig: Config{},
setupMockServices: func(t *testing.T, mNix *mockNixService, mSnap *mockSnapshotService, s types.TestSpec, c Config) {
- mNix.BuildAndRunScriptFunc = func(derivation string, pureEnv bool) (int, string, string, error) {
+ mNix.BuildAndRunScriptFunc = func(derivation string, impureEnv bool) (int, string, string, error) {
return 1, "out on fail", "err on fail", nil
}
},
@@ -313,7 +313,7 @@ func TestRunner_RunTests(t *testing.T) {
mockSnapSvc := &mockSnapshotService{}
mockNixSvc.BuildAndParseJSONFunc = func(derivation string) (any, error) { return "parsed", nil }
- mockNixSvc.BuildAndRunScriptFunc = func(derivation string, pureEnv bool) (int, string, string, error) { return 0, "", "", nil }
+ mockNixSvc.BuildAndRunScriptFunc = func(derivation string, impureEnv bool) (int, string, string, error) { return 0, "", "", nil }
mockSnapSvc.StatFunc = func(name string) (os.FileInfo, error) { return mockFileInfo{}, nil }
mockSnapSvc.LoadFileFunc = func(filePath string) (any, error) { return "snapshot", nil }
mockSnapSvc.CreateFileFunc = func(filePath string, data any) error { return nil }
diff --git a/internal/snapshot/service.go b/internal/snapshot/service.go
index 932ece8..72f8802 100644
--- a/internal/snapshot/service.go
+++ b/internal/snapshot/service.go
@@ -7,8 +7,8 @@ import (
"path/filepath"
"strings"
- apperrors "gitlab.com/technofab/nixtest/internal/errors"
- "gitlab.com/technofab/nixtest/internal/util"
+ apperrors "gitlab.com/TECHNOFAB/nixtest/internal/errors"
+ "gitlab.com/TECHNOFAB/nixtest/internal/util"
)
// Service defines operations related to test snapshots
diff --git a/internal/snapshot/service_test.go b/internal/snapshot/service_test.go
index 9a1f4bb..b82b070 100644
--- a/internal/snapshot/service_test.go
+++ b/internal/snapshot/service_test.go
@@ -9,7 +9,7 @@ import (
"strings"
"testing"
- apperrors "gitlab.com/technofab/nixtest/internal/errors"
+ apperrors "gitlab.com/TECHNOFAB/nixtest/internal/errors"
)
func TestDefaultService_GetPath(t *testing.T) {
diff --git a/internal/util/util.go b/internal/util/util.go
index 63e7252..9916a75 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -8,7 +8,7 @@ import (
"github.com/akedrou/textdiff"
"github.com/akedrou/textdiff/myers"
- apperrors "gitlab.com/technofab/nixtest/internal/errors"
+ apperrors "gitlab.com/TECHNOFAB/nixtest/internal/errors"
)
func ComputeDiff(expected, actual string) (string, error) {
diff --git a/internal/util/util_test.go b/internal/util/util_test.go
index 0928c06..fa76a20 100644
--- a/internal/util/util_test.go
+++ b/internal/util/util_test.go
@@ -8,7 +8,7 @@ import (
"strings"
"testing"
- apperrors "gitlab.com/technofab/nixtest/internal/errors"
+ apperrors "gitlab.com/TECHNOFAB/nixtest/internal/errors"
)
func TestComputeDiff(t *testing.T) {
diff --git a/lib/default.nix b/lib/default.nix
index e8b7f97..ec71f09 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -1,62 +1,70 @@
{
pkgs,
lib ? pkgs.lib,
- self ? "",
...
-}: {
- mkTest = {
- type ? "unit",
- name,
- description ? "",
- format ? "json",
- expected ? null,
- actual ? null,
- actualDrv ? null,
- script ? null,
- pos ? null,
+}: let
+ inherit (lib) evalModules toList;
+in rec {
+ helpers = import ./testHelpers.nix {inherit lib;};
+
+ mkBinary = {
+ nixtests,
+ extraParams,
}: let
- fileRelative = lib.removePrefix ((toString self) + "/") pos.file;
- actual' =
- if format == "json"
- then actual
- else lib.generators.toPretty {} actual;
- expected' =
- if format == "json"
- then expected
- else lib.generators.toPretty {} expected;
+ program = pkgs.callPackage ../package.nix {};
in
- assert lib.assertMsg (!(type == "script" && script == null)) "test ${name} has type 'script' but no script was passed"; {
- inherit type name description;
- actual = actual';
- expected = expected';
- # discard string context, otherwise it's being built instantly which we don't want
- actualDrv = builtins.unsafeDiscardStringContext (actualDrv.drvPath or "");
- script =
- if script != null
- then
- builtins.unsafeDiscardStringContext
- (pkgs.writeShellScript "nixtest-${name}" ''
- # show which line failed the test
- set -x
- ${script}
- '').drvPath
- else null;
- pos =
- if pos == null
- then ""
- else "${fileRelative}:${toString pos.line}";
+ (pkgs.writeShellScriptBin "nixtests:run" ''
+ ${program}/bin/nixtest --tests=${nixtests} ${extraParams} "$@"
+ '')
+ // {
+ tests = nixtests;
};
- mkSuite = name: tests: {
- inherit name tests;
- };
+
exportSuites = suites: let
suitesList =
if builtins.isList suites
then suites
else [suites];
- testsMapped = builtins.toJSON suitesList;
+ suitesMapped = builtins.toJSON suitesList;
in
pkgs.runCommand "tests.json" {} ''
- echo '${testsMapped}' > $out
+ echo '${suitesMapped}' > $out
'';
+
+ module = import ./module.nix {inherit lib pkgs;};
+
+ autodiscover = {
+ dir,
+ pattern ? ".*_test.nix",
+ }: let
+ files = builtins.readDir dir;
+ matchingFiles = builtins.filter (name: builtins.match pattern name != null) (builtins.attrNames files);
+ imports = map (file:
+ if builtins.isString dir
+ then (builtins.unsafeDiscardStringContext dir) + "/${file}"
+ else /${dir}/${file})
+ matchingFiles;
+ in {
+ inherit imports;
+ # automatically set the base so test filepaths are easier to read
+ config.base = builtins.toString dir + "/";
+ };
+
+ mkNixtestConfig = {
+ modules,
+ args ? {},
+ ...
+ }:
+ (evalModules {
+ modules =
+ (toList modules)
+ ++ [
+ module
+ {
+ _module.args = args;
+ }
+ ];
+ }).config;
+
+ mkNixtest = args: (mkNixtestConfig args).app;
}
diff --git a/lib/flakeModule.nix b/lib/flakeModule.nix
index 6068aea..1d7e406 100644
--- a/lib/flakeModule.nix
+++ b/lib/flakeModule.nix
@@ -15,56 +15,14 @@ in {
nixtests-lib = import ./. {inherit pkgs self;};
in {
options.nixtest = mkOption {
- type = types.submodule ({...}: {
- options = {
- skip = mkOption {
- type = types.str;
- default = "";
- description = "Which tests to skip (regex)";
- };
- suites = mkOption {
- type = types.attrsOf (types.submodule {
- options = {
- tests = mkOption {
- type = types.listOf types.attrs;
- default = [];
- };
- pos = mkOption {
- type = types.nullOr types.attrs;
- default = null;
- };
- };
- });
- default = {};
- };
- };
- });
+ type = types.submodule (nixtests-lib.module);
default = {};
};
+ config.nixtest.base = toString self + "/";
- config.legacyPackages = rec {
- "nixtests" = let
- suites = map (suiteName: let
- suite = builtins.getAttr suiteName config.nixtest.suites;
- in
- nixtests-lib.mkSuite
- suiteName
- (map (test:
- nixtests-lib.mkTest ({
- # default pos to suite's pos if given
- pos = suite.pos;
- }
- // test))
- suite.tests))
- (builtins.attrNames config.nixtest.suites);
- in
- nixtests-lib.exportSuites suites;
- "nixtests:run" = let
- program = pkgs.callPackage ./../package.nix {};
- in
- pkgs.writeShellScriptBin "nixtests:run" ''
- ${program}/bin/nixtest --tests=${nixtests} --skip="${config.nixtest.skip}" "$@"
- '';
+ config.legacyPackages = {
+ "nixtests" = config.nixtest.finalConfigJson;
+ "nixtests:run" = config.nixtest.app;
};
}
);
diff --git a/lib/module.nix b/lib/module.nix
new file mode 100644
index 0000000..1600c50
--- /dev/null
+++ b/lib/module.nix
@@ -0,0 +1,268 @@
+{
+ pkgs,
+ lib,
+ ...
+}: let
+ inherit
+ (lib)
+ mkOptionType
+ mkOption
+ types
+ filterAttrs
+ isType
+ removePrefix
+ assertMsg
+ generators
+ literalExpression
+ ;
+
+ nixtest-lib = import ./default.nix {inherit pkgs lib;};
+
+ unsetType = mkOptionType {
+ name = "unset";
+ description = "unset";
+ descriptionClass = "noun";
+ check = value: true;
+ };
+ unset = {
+ _type = "unset";
+ };
+ isUnset = isType "unset";
+ unsetOr = typ:
+ (types.either unsetType typ)
+ // {
+ inherit (typ) description getSubOptions;
+ };
+ mkUnsetOption = opts:
+ mkOption (opts
+ // {
+ type = unsetOr opts.type;
+ default = opts.default or unset;
+ defaultText = literalExpression "unset";
+ });
+
+ filterUnset = value:
+ if builtins.isAttrs value && !builtins.hasAttr "_type" value
+ then let
+ filteredAttrs = builtins.mapAttrs (n: v: filterUnset v) value;
+ in
+ filterAttrs (name: value: (!isUnset value)) filteredAttrs
+ else if builtins.isList value
+ then builtins.filter (elem: !isUnset elem) (map filterUnset value)
+ else value;
+
+ testsSubmodule = {
+ config,
+ testsBase,
+ pos,
+ ...
+ }: {
+ options = {
+ pos = mkUnsetOption {
+ type = types.attrs;
+ description = ''
+ Position of test, use `__curPos` for automatic insertion of current position.
+ '';
+ default = pos;
+ apply = val:
+ if isUnset val
+ then val
+ else let
+ fileRelative = removePrefix testsBase val.file;
+ in "${fileRelative}:${toString val.line}";
+ };
+ type = mkOption {
+ type = types.enum ["unit" "snapshot" "script"];
+ description = ''
+ Type of test, has to be one of "unit", "snapshot" or "script".
+ '';
+ default = "unit";
+ apply = value:
+ 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)
+ "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)
+ )
+ "test '${config.name}' as type 'unit' requires only 'actual' OR 'actualDrv' to be set"; value;
+ };
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Name of this test.
+ '';
+ };
+ description = mkUnsetOption {
+ type = types.str;
+ description = ''
+ Short description of the test.
+ '';
+ };
+ format = mkOption {
+ type = types.enum ["json" "pretty"];
+ description = ''
+ Which format to use for serializing arbitrary values.
+ Required since this config is serialized to JSON for passing it to Nixtest, so no Nix-values can be used directly.
+
+ - `json`: serializes the data to json using `builtins.toJSON`
+ - `pretty`: serializes the data to a "pretty" format using `lib.generators.toPretty`
+ '';
+ default = "json";
+ };
+ expected = mkUnsetOption {
+ type = types.anything;
+ description = ''
+ Expected value of the test. Remember, the values are serialized (see [here](#suitesnametestsformat)).
+ '';
+ apply = val:
+ if isUnset val || config.format == "json"
+ then val
+ else generators.toPretty {} val;
+ };
+ actual = mkUnsetOption {
+ type = types.anything;
+ description = ''
+ Actual value of the test. Remember, the values are serialized (see [here](#suitesnametestsformat)).
+ '';
+ apply = val:
+ if isUnset val || config.format == "json"
+ then val
+ else generators.toPretty {} val;
+ };
+ actualDrv = mkUnsetOption {
+ type = types.package;
+ description = ''
+ Actual value of the test, but as a derivation.
+ Nixtest will build this derivation when running the test, then compare the contents of the
+ resulting file to the [`expected`](#suitesnametestsexpected) value.
+ '';
+ apply = val:
+ # keep unset value
+ if isUnset val
+ then val
+ else builtins.unsafeDiscardStringContext (val.drvPath or "");
+ };
+ script = mkUnsetOption {
+ type = types.str;
+ description = ''
+ Script to run for the test.
+ Nixtest will run this, failing the test if it exits with a non-zero exit code.
+ '';
+ apply = val:
+ if isUnset val
+ then val
+ else
+ builtins.unsafeDiscardStringContext
+ (pkgs.writeShellScript "nixtest-${config.name}" val).drvPath;
+ };
+ };
+ };
+
+ suitesSubmodule = {
+ name,
+ config,
+ testsBase,
+ ...
+ }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Name of the suite, uses attrset name by default.
+ '';
+ default = name;
+ defaultText = literalExpression name;
+ };
+ pos = mkUnsetOption {
+ type = types.attrs;
+ description = ''
+ Position for tests, use `__curPos` for automatic insertion of current position.
+ This will set `pos` for every test of this suite, useful if the suite's tests are all in a single file.
+ '';
+ example = literalExpression "__curPos";
+ };
+ tests = mkOption {
+ type = types.listOf (types.submoduleWith {
+ modules = [testsSubmodule];
+ specialArgs = {
+ inherit (config) pos;
+ inherit testsBase;
+ };
+ });
+ description = ''
+ Define tests of this suite here.
+ '';
+ default = [];
+ };
+ };
+ };
+
+ nixtestSubmodule = {config, ...}: {
+ _file = ./module.nix;
+ options = {
+ base = mkOption {
+ type = types.str;
+ description = ''
+ Base directory of the tests, will be removed from the test file path.
+ This makes it possible to show the relative path from the git repo, instead of ugly Nix store paths.
+ '';
+ default = "";
+ };
+ skip = mkOption {
+ type = types.str;
+ description = ''
+ Tests to skip, is passed to Nixtest's `--skip` param.
+ '';
+ default = "";
+ };
+ suites = mkOption {
+ type = types.attrsOf (types.submoduleWith {
+ modules = [suitesSubmodule];
+ specialArgs = {
+ testsBase = config.base;
+ };
+ });
+ description = ''
+ 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 = [
+ {
+ name = "Some Test";
+ }
+ ];
+ };
+ };
+
+ finalConfigJson = mkOption {
+ internal = true;
+ type = types.package;
+ };
+ app = mkOption {
+ internal = true;
+ type = types.package;
+ };
+ };
+ config = {
+ finalConfigJson = nixtest-lib.exportSuites config.suites;
+ app = nixtest-lib.mkBinary {
+ nixtests = config.finalConfigJson;
+ extraParams = ''--skip="${config.skip}"'';
+ };
+ };
+ };
+in
+ nixtestSubmodule
diff --git a/lib/scriptHelpers.sh b/lib/scriptHelpers.sh
new file mode 100644
index 0000000..b1e3514
--- /dev/null
+++ b/lib/scriptHelpers.sh
@@ -0,0 +1,41 @@
+output=
+exit_code=
+
+function assert() {
+ test $1 || { echo "Assertion '$1' failed: $2" >&2; exit 1; }
+}
+function assert_eq() {
+ assert "$1 -eq $2" "$3"
+}
+function assert_not_eq() {
+ assert "$1 -ne $2" "$3"
+}
+function assert_contains() {
+ echo "$1" | grep -q -- "$2" || {
+ echo "Assertion failed: $3. $1 does not contain $2" >&2;
+ exit 1;
+ }
+}
+function assert_not_contains() {
+ echo "$1" | grep -q -- "$2" && {
+ echo "Assertion failed: $3. $1 does contain $2" >&2;
+ exit 1;
+ }
+}
+function assert_file_contains() {
+ grep -q -- "$2" $1 || {
+ echo "Assertion failed: $3. $1 does not contain $2" >&2;
+ exit 1;
+ }
+}
+function assert_file_not_contains() {
+ grep -q -- "$2" $1 && {
+ echo "Assertion failed: $3. $1 does contain $2" >&2;
+ exit 1;
+ }
+}
+
+function run() {
+ output=$($@ 2>&1)
+ exit_code=$?
+}
diff --git a/lib/testHelpers.nix b/lib/testHelpers.nix
new file mode 100644
index 0000000..c86a6a8
--- /dev/null
+++ b/lib/testHelpers.nix
@@ -0,0 +1,7 @@
+{lib, ...}: {
+ path = pkgs: "export PATH=${lib.makeBinPath pkgs}";
+ pathAdd = pkgs: "export PATH=$PATH:${lib.makeBinPath pkgs}";
+ scriptHelpers = builtins.readFile ./scriptHelpers.sh;
+ toJsonFile = any: builtins.toFile "actual" (builtins.unsafeDiscardStringContext (builtins.toJSON any));
+ toPrettyFile = any: builtins.toFile "actual" (lib.generators.toPretty {} any);
+}
diff --git a/nix/packages/packages.nix b/nix/packages/packages.nix
new file mode 100644
index 0000000..9174657
--- /dev/null
+++ b/nix/packages/packages.nix
@@ -0,0 +1,8 @@
+{inputs, ...}: let
+ inherit (inputs) self pkgs;
+in {
+ nixtest = pkgs.callPackage "${self}/package.nix" {};
+ update-package = pkgs.writeShellScriptBin "update-package" ''
+ ${pkgs.nix-update}/bin/nix-update nixtest --flake --version skip
+ '';
+}
diff --git a/nix/repo/ci.nix b/nix/repo/ci.nix
new file mode 100644
index 0000000..010c4ad
--- /dev/null
+++ b/nix/repo/ci.nix
@@ -0,0 +1,85 @@
+{inputs, ...}: let
+ inherit (inputs) pkgs cilib;
+in
+ cilib.mkCI {
+ pipelines."default" = {
+ stages = ["test" "build" "deploy"];
+ jobs = {
+ "test:lib" = {
+ stage = "test";
+ script = [
+ "nix run .#tests -- --junit=junit.xml"
+ ];
+ allow_failure = true;
+ artifacts = {
+ when = "always";
+ reports.junit = "junit.xml";
+ };
+ };
+ "test:go" = {
+ stage = "test";
+ nix.deps = with pkgs; [gcc go go-junit-report gocover-cobertura];
+ variables = {
+ GOPATH = "$CI_PROJECT_DIR/.go";
+ GOCACHE = "$CI_PROJECT_DIR/.go/pkg/mod";
+ };
+ script = [
+ # sh
+ ''
+ set +e
+ go test -coverprofile=coverage.out -v 2>&1 ./... | go-junit-report -set-exit-code > report.xml
+ TEST_EXIT_CODE=$?
+ go tool cover -func coverage.out
+ gocover-cobertura < coverage.out > coverage.xml
+
+ exit $TEST_EXIT_CODE
+ ''
+ ];
+ allow_failure = true;
+ coverage = "/\(statements\)(?:\s+)?(\d+(?:\.\d+)?%)/";
+ cache.paths = [".go/pkg/mod/"];
+ artifacts = {
+ when = "always";
+ reports = {
+ junit = "report.xml";
+ coverage_report = {
+ coverage_format = "cobertura";
+ path = "coverage.xml";
+ };
+ };
+ };
+ };
+ "build" = {
+ stage = "build";
+ script = [
+ # sh
+ "nix build .#nixtest"
+ ];
+ };
+ "docs" = {
+ stage = "build";
+ script = [
+ # sh
+ ''
+ nix build .#docs:default
+ mkdir -p public
+ cp -r result/. public/
+ ''
+ ];
+ artifacts.paths = ["public"];
+ };
+ "pages" = {
+ nix.enable = false;
+ image = "alpine:latest";
+ stage = "deploy";
+ script = ["true"];
+ artifacts.paths = ["public"];
+ rules = [
+ {
+ "if" = "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH";
+ }
+ ];
+ };
+ };
+ };
+ }
diff --git a/nix/repo/devShells.nix b/nix/repo/devShells.nix
new file mode 100644
index 0000000..1f425c9
--- /dev/null
+++ b/nix/repo/devShells.nix
@@ -0,0 +1,33 @@
+{
+ inputs,
+ cell,
+ ...
+}: let
+ inherit (inputs) pkgs devshell treefmt;
+ inherit (cell) soonix;
+in {
+ default = devshell.mkShell {
+ imports = [soonix.devshellModule];
+ packages = with pkgs; [
+ (treefmt.mkWrapper pkgs {
+ programs = {
+ alejandra.enable = true;
+ mdformat.enable = true;
+ gofmt.enable = true;
+ };
+ settings.formatter.mdformat.command = let
+ pkg = pkgs.python3.withPackages (p: [
+ p.mdformat
+ p.mdformat-mkdocs
+ ]);
+ in "${pkg}/bin/mdformat";
+ })
+ gcc
+ go
+ gopls
+ delve
+ go-junit-report
+ gocover-cobertura
+ ];
+ };
+}
diff --git a/nix/repo/docs.nix b/nix/repo/docs.nix
new file mode 100644
index 0000000..f8aea5c
--- /dev/null
+++ b/nix/repo/docs.nix
@@ -0,0 +1,64 @@
+{inputs, ...}: let
+ inherit (inputs) pkgs doclib ntlib;
+
+ optionsDoc = doclib.mkOptionDocs {
+ module = ntlib.module;
+ roots = [
+ {
+ url = "https://gitlab.com/TECHNOFAB/nixtest/-/blob/main/lib";
+ path = "${inputs.self}/lib";
+ }
+ ];
+ };
+ optionsDocs = pkgs.runCommand "options-docs" {} ''
+ mkdir -p $out
+ ln -s ${optionsDoc} $out/options.md
+ '';
+in
+ (doclib.mkDocs {
+ docs."default" = {
+ base = "${inputs.self}";
+ path = "${inputs.self}/docs";
+ material = {
+ enable = true;
+ colors = {
+ primary = "green";
+ accent = "light green";
+ };
+ umami = {
+ enable = true;
+ src = "https://analytics.tf/umami";
+ siteId = "716d1869-9342-4b62-a770-e15d2d5c807d";
+ domains = ["nixtest.projects.tf"];
+ };
+ };
+ macros = {
+ enable = true;
+ includeDir = toString optionsDocs;
+ };
+ config = {
+ site_name = "Nixtest";
+ site_url = "https://nixtest.projects.tf";
+ repo_name = "TECHNOFAB/nixtest";
+ repo_url = "https://gitlab.com/TECHNOFAB/nixtest";
+ extra_css = ["style.css"];
+ theme = {
+ logo = "images/logo.svg";
+ icon.repo = "simple/gitlab";
+ favicon = "images/logo.svg";
+ };
+ nav = [
+ {"Introduction" = "index.md";}
+ {"Usage" = "usage.md";}
+ {"Reference" = "reference.md";}
+ {"CLI" = "cli.md";}
+ {"Example Configs" = "examples.md";}
+ {"Options" = "options.md";}
+ ];
+ markdown_extensions = [
+ "pymdownx.superfences"
+ "admonition"
+ ];
+ };
+ };
+ }).packages
diff --git a/nix/repo/flake.lock b/nix/repo/flake.lock
new file mode 100644
index 0000000..930798e
--- /dev/null
+++ b/nix/repo/flake.lock
@@ -0,0 +1,100 @@
+{
+ "nodes": {
+ "devshell-lib": {
+ "locked": {
+ "dir": "lib",
+ "lastModified": 1758204313,
+ "narHash": "sha256-ainbY0Oajb1HMdvy+A8QxF/P5qwcbEzJGEY5pzKdDdc=",
+ "owner": "rensa-nix",
+ "repo": "devshell",
+ "rev": "7d0c4bc78d9f017a739b0c7eb2f4e563118353e6",
+ "type": "gitlab"
+ },
+ "original": {
+ "dir": "lib",
+ "owner": "rensa-nix",
+ "repo": "devshell",
+ "type": "gitlab"
+ }
+ },
+ "nix-gitlab-ci-lib": {
+ "locked": {
+ "dir": "lib",
+ "lastModified": 1765444672,
+ "narHash": "sha256-B0cMjRs9P50ym9Le0VUcRN69Yy6tbV13MXq81tTTEus=",
+ "owner": "TECHNOFAB",
+ "repo": "nix-gitlab-ci",
+ "rev": "8f88a53b5479773cd626420362631bc1da99e677",
+ "type": "gitlab"
+ },
+ "original": {
+ "dir": "lib",
+ "owner": "TECHNOFAB",
+ "ref": "3.1.2",
+ "repo": "nix-gitlab-ci",
+ "type": "gitlab"
+ }
+ },
+ "nixmkdocs-lib": {
+ "locked": {
+ "dir": "lib",
+ "lastModified": 1763481845,
+ "narHash": "sha256-Bp0+9rDmlPWMcnKqGx+BG4+o5KO8FuDAOvXRnXrm3Fo=",
+ "owner": "TECHNOFAB",
+ "repo": "nixmkdocs",
+ "rev": "73d59093df94a894d25bc4bf71880b6f00faa62f",
+ "type": "gitlab"
+ },
+ "original": {
+ "dir": "lib",
+ "owner": "TECHNOFAB",
+ "repo": "nixmkdocs",
+ "type": "gitlab"
+ }
+ },
+ "root": {
+ "inputs": {
+ "devshell-lib": "devshell-lib",
+ "nix-gitlab-ci-lib": "nix-gitlab-ci-lib",
+ "nixmkdocs-lib": "nixmkdocs-lib",
+ "soonix-lib": "soonix-lib",
+ "treefmt-nix": "treefmt-nix"
+ }
+ },
+ "soonix-lib": {
+ "locked": {
+ "dir": "lib",
+ "lastModified": 1763323017,
+ "narHash": "sha256-MJyg37d+VMfRoFiVUj16FW+zkEwQXbgK9LoFF/SHoxA=",
+ "owner": "TECHNOFAB",
+ "repo": "soonix",
+ "rev": "078034b01e4eaf1f9436d46721f7cbe0d96eb8b4",
+ "type": "gitlab"
+ },
+ "original": {
+ "dir": "lib",
+ "owner": "TECHNOFAB",
+ "repo": "soonix",
+ "type": "gitlab"
+ }
+ },
+ "treefmt-nix": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1762938485,
+ "narHash": "sha256-AlEObg0syDl+Spi4LsZIBrjw+snSVU4T8MOeuZJUJjM=",
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "rev": "5b4ee75aeefd1e2d5a1cc43cf6ba65eba75e83e4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/nix/repo/flake.nix b/nix/repo/flake.nix
new file mode 100644
index 0000000..7452c07
--- /dev/null
+++ b/nix/repo/flake.nix
@@ -0,0 +1,22 @@
+{
+ inputs = {
+ devshell-lib.url = "gitlab:rensa-nix/devshell?dir=lib";
+ soonix-lib.url = "gitlab:TECHNOFAB/soonix?dir=lib";
+ nix-gitlab-ci-lib.url = "gitlab:TECHNOFAB/nix-gitlab-ci/3.1.2?dir=lib";
+ nixmkdocs-lib.url = "gitlab:TECHNOFAB/nixmkdocs?dir=lib";
+ treefmt-nix = {
+ url = "github:numtide/treefmt-nix";
+ flake = false;
+ };
+ };
+ outputs = i:
+ i
+ // {
+ devshell = i.devshell-lib.lib {inherit (i.parent) pkgs;};
+ soonix = i.soonix-lib.lib {inherit (i.parent) pkgs;};
+ cilib = i.nix-gitlab-ci-lib.lib {inherit (i.parent) pkgs;};
+ doclib = i.nixmkdocs-lib.lib {inherit (i.parent) pkgs;};
+ ntlib = import "${i.parent.self}/lib" {inherit (i.parent) pkgs;};
+ treefmt = import i.treefmt-nix;
+ };
+}
diff --git a/nix/repo/soonix.nix b/nix/repo/soonix.nix
new file mode 100644
index 0000000..9eaab59
--- /dev/null
+++ b/nix/repo/soonix.nix
@@ -0,0 +1,34 @@
+{
+ inputs,
+ cell,
+ ...
+}: let
+ inherit (inputs) soonix;
+ inherit (cell) ci;
+in
+ (soonix.make {
+ hooks = {
+ ci = ci.soonix;
+ renovate = {
+ output = ".gitlab/renovate.json5";
+ data = {
+ extends = ["config:recommended"];
+ postUpgradeTasks.commands = [
+ "nix-portable nix run .#update-package"
+ "nix-portable nix run .#soonix:update"
+ ];
+ lockFileMaintenance = {
+ enabled = true;
+ extends = ["schedule:monthly"];
+ };
+ nix.enabled = true;
+ gitlabci.enabled = false;
+ };
+ hook = {
+ mode = "copy";
+ gitignore = false;
+ };
+ opts.format = "json";
+ };
+ };
+ }).config
diff --git a/nix/repo/tests.nix b/nix/repo/tests.nix
new file mode 100644
index 0000000..9e2e8fb
--- /dev/null
+++ b/nix/repo/tests.nix
@@ -0,0 +1,10 @@
+{inputs, ...}: let
+ inherit (inputs) pkgs ntlib;
+in {
+ tests = ntlib.mkNixtest {
+ modules = ntlib.autodiscover {dir = "${inputs.self}/tests";};
+ args = {
+ inherit pkgs ntlib;
+ };
+ };
+}
diff --git a/package.nix b/package.nix
index cce5967..b930d30 100644
--- a/package.nix
+++ b/package.nix
@@ -4,7 +4,8 @@
...
}:
buildGoModule {
- name = "nixtest";
+ pname = "nixtest";
+ version = "latest";
src =
# filter everything except for cmd/ and go.mod, go.sum
with lib.fileset;
@@ -18,6 +19,6 @@ buildGoModule {
];
};
subPackages = ["cmd/nixtest"];
- vendorHash = "sha256-6kARJgngmXielUoXukYdAA0QHk1mwLRvgKJhx+v1iSo=";
+ vendorHash = "sha256-6CJ1noKOcNW0lHCe0hZse7+Dcvq+A02yvAKqq+dawE8=";
meta.mainProgram = "nixtest";
}
diff --git a/snapshots/pretty-snapshot.snap.json b/snapshots/pretty-snapshot.snap.json
index d7ee4ed..6349f86 100644
--- a/snapshots/pretty-snapshot.snap.json
+++ b/snapshots/pretty-snapshot.snap.json
@@ -1 +1 @@
-"{\n example = \u003cfunction\u003e;\n example2 = {\n drv = \u003cderivation hello-2.12.1\u003e;\n };\n}"
\ No newline at end of file
+"{\n example = \u003cfunction\u003e;\n example2 = {\n drv = \u003cderivation hello-2.12.2\u003e;\n };\n}"
diff --git a/tests/fixtures/sample_test.nix b/tests/fixtures/sample_test.nix
new file mode 100644
index 0000000..dcc2fb1
--- /dev/null
+++ b/tests/fixtures/sample_test.nix
@@ -0,0 +1,97 @@
+{
+ lib,
+ pkgs,
+ ...
+}: {
+ skip = "skip.*d";
+ suites = {
+ "suite-one" = {
+ # required to figure out file and line, but optional
+ pos = __curPos;
+ tests = [
+ {
+ name = "test-one";
+ expected = 1;
+ actual = 1;
+ }
+ {
+ name = "fail";
+ expected = 0;
+ actual = "meow";
+ }
+ {
+ name = "snapshot-test";
+ type = "snapshot";
+ actual = "test";
+ }
+ {
+ name = "test-snapshot-drv";
+ type = "snapshot";
+ actualDrv = pkgs.runCommand "test-snapshot" {} ''
+ echo '"snapshot drv"' > $out
+ '';
+ }
+ {
+ name = "test-error-drv";
+ expected = null;
+ actualDrv = pkgs.runCommand "test-error-drv" {} ''
+ echo "This works, but its better to just write 'fail' to \$out and expect 'success' or sth."
+ exit 1
+ '';
+ }
+ {
+ name = "test-script";
+ type = "script";
+ script = ''
+ echo Test something here
+ # required in pure mode:
+ export PATH="${lib.makeBinPath [pkgs.gnugrep]}"
+ grep -q "test" ${builtins.toFile "test" "test"}
+ '';
+ }
+ ];
+ };
+ "other-suite".tests = [
+ {
+ name = "obj-snapshot";
+ type = "snapshot";
+ pos = __curPos;
+ actual = {hello = "world";};
+ }
+ {
+ name = "pretty-snapshot";
+ type = "snapshot";
+ format = "pretty";
+ pos = __curPos;
+ actual = {
+ example = args: {};
+ example2 = {
+ drv = pkgs.hello;
+ };
+ };
+ }
+ {
+ name = "pretty-unit";
+ format = "pretty";
+ pos = __curPos;
+ expected = pkgs.hello;
+ actual = pkgs.hello;
+ }
+ {
+ name = "test-drv";
+ pos = __curPos;
+ expected = {a = "b";};
+ actualDrv = pkgs.runCommand "test-something" {} ''
+ echo "Simulating taking some time"
+ sleep 1
+ echo '{"a":"b"}' > $out
+ '';
+ }
+ {
+ name = "skipped";
+ expected = null;
+ actual = null;
+ }
+ ];
+ };
+}
diff --git a/tests/lib_test.nix b/tests/lib_test.nix
new file mode 100644
index 0000000..85a3c7a
--- /dev/null
+++ b/tests/lib_test.nix
@@ -0,0 +1,99 @@
+{
+ pkgs,
+ ntlib,
+ ...
+}: {
+ suites."Lib Tests" = {
+ pos = __curPos;
+ tests = [
+ {
+ name = "autodiscovery";
+ type = "script";
+ script = let
+ actual = ntlib.helpers.toPrettyFile (ntlib.autodiscover {
+ dir = ./fixtures;
+ });
+ # tests if strings with store path context work
+ actualDirString = ntlib.helpers.toPrettyFile (ntlib.autodiscover {
+ dir = "${./fixtures}";
+ });
+ in
+ # sh
+ ''
+ ${ntlib.helpers.path [pkgs.gnugrep]}
+ ${ntlib.helpers.scriptHelpers}
+ assert_file_contains ${actual} "sample_test.nix" "should find sample_test.nix"
+ assert_file_contains ${actual} "base = \"/nix/store/.*-source/tests/fixtures/\"" "should set base to fixtures dir"
+
+ assert_file_contains ${actualDirString} "sample_test.nix" "should find sample_test.nix"
+ assert_file_contains ${actualDirString} "base = \"/nix/store/.*-fixtures/\"" "should set base to fixtures dir"
+ '';
+ }
+ {
+ name = "binary";
+ type = "script";
+ script = let
+ binary =
+ (ntlib.mkBinary {
+ nixtests = "stub";
+ extraParams = "--impure";
+ })
+ + "/bin/nixtests:run";
+ in
+ # sh
+ ''
+ ${ntlib.helpers.path [pkgs.gnugrep]}
+ ${ntlib.helpers.scriptHelpers}
+ assert_file_contains ${binary} "nixtest" "should contain nixtest"
+ assert_file_contains ${binary} "--impure" "should contain --impure arg"
+ assert_file_contains ${binary} "--tests=stub" "should contain --tests arg"
+
+ run "${binary} --help"
+ assert_eq $exit_code 0 "should exit 0"
+ assert_contains "$output" "Usage of nixtest" "should show help"
+
+ run "${binary}"
+ assert_eq $exit_code 1 "should exit 1"
+ assert_contains "$output" "Tests file does not exist"
+ '';
+ }
+ {
+ name = "full run with fixtures";
+ type = "script";
+ script = let
+ binary =
+ (ntlib.mkNixtest {
+ modules = ntlib.autodiscover {dir = ./fixtures;};
+ args = {inherit pkgs;};
+ })
+ + "/bin/nixtests:run";
+ in
+ # sh
+ ''
+ ${ntlib.helpers.path [pkgs.gnugrep pkgs.mktemp pkgs.coreutils]}
+ ${ntlib.helpers.scriptHelpers}
+ export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
+ export NIX_SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
+ cp -r ${./../snapshots} snapshots
+
+ # start without nix & env binaries to expect errors
+ run "${binary} --junit=junit.xml"
+ assert "$exit_code -eq 2" "should exit 2"
+ assert "-f junit.xml" "should create junit.xml"
+ assert_contains "$output" "executable file not found" "nix should not be found in pure mode"
+
+ # now add required deps
+ ${ntlib.helpers.pathAdd [pkgs.nix pkgs.coreutils]}
+ run "${binary} --junit=junit2.xml"
+ assert "$exit_code -eq 2" "should exit 2"
+ 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" "ERROR" "should contain an error"
+ assert_contains "$output" "SKIP" "should contain a skip"
+ '';
+ }
+ ];
+ };
+}