Compare commits

...

9 commits
1.1.0 ... main

22 changed files with 236 additions and 152 deletions

View file

@ -2,7 +2,7 @@
[![built with nix](https://img.shields.io/static/v1?logo=nixos&logoColor=white&label=&message=Built%20with%20Nix&color=41439a)](https://builtwithnix.org)
[![pipeline status](https://gitlab.com/TECHNOFAB/nixtest/badges/main/pipeline.svg)](https://gitlab.com/TECHNOFAB/nixtest/-/commits/main)
![License: MIT](https://img.shields.io/gitlab/license/technofab/nix-gitlab-ci)
![License: MIT](https://img.shields.io/gitlab/license/technofab/nixtest)
[![Latest Release](https://gitlab.com/TECHNOFAB/nixtest/-/badges/release.svg)](https://gitlab.com/TECHNOFAB/nixtest/-/releases)
[![Support me](https://img.shields.io/badge/Support-me-green)](https://tec.tf/#support)
[![Docs](https://img.shields.io/badge/Read-Docs-green)](https://nixtest.projects.tf)

View file

@ -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 {

View file

@ -4,7 +4,7 @@
Usage of nixtest:
--junit string Path to generate JUNIT report to, leave empty to disable
--no-color Disable coloring
--pure Unset all env vars before running script tests
--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")
-f, --tests string Path to JSON file containing tests (required)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

1
docs/images/logo.svg Executable file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><rect width="100" height="100" fill="#222" rx="12"/><rect width="100" height="100" fill="url(#a)" rx="12"/><path fill="#3FB950" d="M18 54.7a3 3 0 0 1 4.3 0l17 17c.5.4 1 .4 1.5 0L82.5 30a3 3 0 0 1 4.2 0l2 1.9a3 3 0 0 1 0 4.2L46.2 78.7l-4 4a3 3 0 0 1-4.2 0L16 60.9a3 3 0 0 1 0-4.3l2-1.9Z"/><path fill="url(#b)" d="m89 66.6-5.3 9.6H71l6.3 11-2.7 4.8h-5.5l-9-15.8H53l9.6-9.6H89Zm-76-4.8 1 1.2 6 5.9-3.5 6-5.5-9.4 2-3.7Zm3-5 .1-.2-.1.2Zm11.4-1.1-3-3.1a6 6 0 0 0-8.5 0l-1.8 1.8H4.7L2 49.6l2.7-4.8h18l6.5-11.2H40l-12.6 22ZM62.8 18l6.4-11h5.4l2.7 4.8-9 15.7 4.4 7.9-6.9 6.9L45.8 7 56.5 7l6.2 11.1Z"/><path fill="url(#c)" d="m54.9 91.9-10.7.1-3.1-5.5a6 6 0 0 0 3.2-1.7l4-4 .2-.2L55 92Zm-19.1-7.2L31.7 92h-5.3l-2.8-4.8L29 78l6.9 6.8ZM47 78l-.8.8-4 4-.1.1.1-.1 4-4 .8-.8ZM40.2 72Zm.5-.2Zm.3-4.5-1 .9-11.7-11.7 3.3-6L41 67.3Zm54.3-22.6 2.7 4.7-2.7 4.8H77.5l-6.3 11.2h-7.6l20.8-20.7h11Zm-54.8-22 12.7.1 5.4 9.5H12l5.3-9.5h12.5l-6.2-11L26.2 7h5.5l8.8 15.8Zm44.3 3.4a6 6 0 0 0-2.4.4l1.3-2.4 1.1 2Z"/><defs><linearGradient id="b" x1="76.7" x2="59.1" y1="47.2" y2="17.1" gradientUnits="userSpaceOnUse"><stop offset=".2" stop-color="#7EB1DD"/></linearGradient><linearGradient id="c" x1="63.4" x2="81.3" y1="70.3" y2="40.6" gradientUnits="userSpaceOnUse"><stop offset="1" stop-color="#5277C3"/></linearGradient><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="matrix(0 50 -50 0 50 50)" gradientUnits="userSpaceOnUse"><stop offset=".5" stop-color="#091A3D"/><stop offset="1" stop-color="#000819"/></radialGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

3
docs/options.md Normal file
View file

@ -0,0 +1,3 @@
# Options
{% include 'options.md' %}

15
docs/style.css Normal file
View file

@ -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;
}

View file

@ -96,10 +96,10 @@ 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:
#

22
flake.lock generated
View file

@ -169,21 +169,6 @@
"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": [
@ -255,11 +240,11 @@
"nix-mkdocs": {
"locked": {
"dir": "lib",
"lastModified": 1745841841,
"narHash": "sha256-297zPQbUlc7ZAYDoaD6mCmQxCC3Tr4YOKekRF1ArZ7g=",
"lastModified": 1757055638,
"narHash": "sha256-KHYSkEreFe4meXzSdEbknC/HwaQSNClQkc8vzHlAsMM=",
"owner": "technofab",
"repo": "nixmkdocs",
"rev": "c7e3c3b13ded25818e9789938387bba6f2cde690",
"rev": "7840a5febdbeaf2da90babf6c94b3d0929d2bf74",
"type": "gitlab"
},
"original": {
@ -368,7 +353,6 @@
"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",

109
flake.nix
View file

@ -17,6 +17,7 @@
perSystem = {
lib,
pkgs,
self',
config,
...
}: {
@ -36,9 +37,12 @@
};
devenv.shells.default = {
containers = pkgs.lib.mkForce {};
packages = with pkgs; [gopls gore go-junit-report];
packages = with pkgs; [gore go-junit-report];
languages.go.enable = true;
languages.go = {
enable = true;
enableHardeningWorkaround = true;
};
pre-commit.hooks = {
treefmt = {
@ -62,81 +66,48 @@
};
};
doc = {
docs."default".config = {
path = ./docs;
deps = pp: [
pp.mkdocs-material
(pp.callPackage inputs.mkdocs-material-umami {})
];
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 self'.packages.optionsDocs;
};
config = {
site_name = "Nixtest";
site_url = "https://nixtest.projects.tf";
repo_name = "TECHNOFAB/nixtest";
repo_url = "https://gitlab.com/TECHNOFAB/nixtest";
edit_uri = "edit/main/docs/";
extra_css = ["style.css"];
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";
logo = "images/logo.svg";
favicon = "images/logo.svg";
};
}
{
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";}
{"Reference" = "reference.md";}
{"CLI" = "cli.md";}
{"Example Configs" = "examples.md";}
{"Options" = "options.md";}
];
markdown_extensions = [
"pymdownx.superfences"
"admonition"
];
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 :)";
}
];
};
};
};
};
@ -209,7 +180,8 @@
packages = let
ntlib = import ./lib {inherit pkgs lib;};
in {
doclib = inputs.nix-mkdocs.lib {inherit lib pkgs;};
in rec {
default = pkgs.callPackage ./package.nix {};
tests = ntlib.mkNixtest {
modules = ntlib.autodiscover {dir = ./tests;};
@ -217,6 +189,24 @@
inherit pkgs ntlib;
};
};
optionsDoc = doclib.mkOptionDocs {
module = {
_module.args.pkgs = pkgs;
imports = [
ntlib.module
];
};
roots = [
{
url = "https://gitlab.com/TECHNOFAB/nixtest/-/blob/main/lib";
path = toString ./lib;
}
];
};
optionsDocs = pkgs.runCommand "options-docs" {} ''
mkdir -p $out
ln -s ${optionsDoc} $out/options.md
'';
};
};
};
@ -232,7 +222,6 @@
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 = {

2
go.mod
View file

@ -1,6 +1,6 @@
module gitlab.com/technofab/nixtest
go 1.24.2
go 1.23.0
require (
github.com/akedrou/textdiff v0.1.0

View file

@ -16,7 +16,7 @@ type AppConfig struct {
JunitPath string
UpdateSnapshots bool
SkipPattern string
PureEnv bool
ImpureEnv bool
NoColor bool
}
@ -29,7 +29,7 @@ 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")

View file

@ -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)
}
}

View file

@ -3,6 +3,7 @@ package nix
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
@ -14,7 +15,7 @@ import (
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

View file

@ -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)

View file

@ -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)

View file

@ -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 }

View file

@ -39,7 +39,11 @@ in rec {
}: let
files = builtins.readDir dir;
matchingFiles = builtins.filter (name: builtins.match pattern name != null) (builtins.attrNames files);
imports = map (file: /${dir}/${file}) matchingFiles;
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

View file

@ -3,7 +3,18 @@
lib,
...
}: let
inherit (lib) mkOptionType mkOption types;
inherit
(lib)
mkOptionType
mkOption
types
filterAttrs
isType
removePrefix
assertMsg
generators
literalExpression
;
nixtest-lib = import ./default.nix {inherit pkgs lib;};
@ -16,14 +27,26 @@
unset = {
_type = "unset";
};
isUnset = lib.isType "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
lib.filterAttrs (name: value: (!isUnset value)) filteredAttrs
filterAttrs (name: value: (!isUnset value)) filteredAttrs
else if builtins.isList value
then builtins.filter (elem: !isUnset elem) (map filterUnset value)
else value;
@ -35,25 +58,31 @@
...
}: {
options = {
pos = mkOption {
type = types.either types.attrs unsetType;
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 = lib.removePrefix testsBase val.file;
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 lib.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 lib.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 lib.assertMsg (
assert assertMsg (
let
actualIsUnset = isUnset config.actual;
actualDrvIsUnset = isUnset config.actualDrv;
@ -66,43 +95,66 @@
};
name = mkOption {
type = types.str;
description = ''
Name of this test.
'';
};
description = mkOption {
type = types.either types.str unsetType;
default = unset;
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 = mkOption {
expected = mkUnsetOption {
type = types.anything;
default = unset;
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 lib.generators.toPretty {} val;
else generators.toPretty {} val;
};
actual = mkOption {
actual = mkUnsetOption {
type = types.anything;
default = unset;
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 lib.generators.toPretty {} val;
else generators.toPretty {} val;
};
actualDrv = mkOption {
type = types.either types.package unsetType;
default = unset;
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 = mkOption {
type = types.either types.str unsetType;
default = unset;
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
@ -122,11 +174,19 @@
options = {
name = mkOption {
type = types.str;
description = ''
Name of the suite, uses attrset name by default.
'';
default = name;
defaultText = literalExpression name;
};
pos = mkOption {
type = types.either types.attrs unsetType;
default = unset;
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 {
@ -136,20 +196,30 @@
inherit testsBase;
};
});
description = ''
Define tests of this suite here.
'';
default = [];
};
};
};
nixtestSubmodule = {config, ...}: {
_file = ./module.nix;
options = {
base = mkOption {
description = "Base directory of the tests, will be removed from the test file path";
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 {
@ -159,12 +229,22 @@
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 {

View file

@ -35,16 +35,6 @@ function assert_file_not_contains() {
}
}
function tmpdir() {
dir=$(mktemp -d)
trap "rm -rf $dir" EXIT
echo -n "$dir"
}
function tmpfile() {
file=$(mktemp)
trap "rm -f $file" EXIT
echo -n "$file"
}
function run() {
output=$($@ 2>&1)
exit_code=$?

View file

@ -13,6 +13,10 @@
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
''
@ -20,6 +24,9 @@
${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"
'';
}
{
@ -29,7 +36,7 @@
binary =
(ntlib.mkBinary {
nixtests = "stub";
extraParams = "--pure";
extraParams = "--impure";
})
+ "/bin/nixtests:run";
in
@ -38,7 +45,7 @@
${ntlib.helpers.path [pkgs.gnugrep]}
${ntlib.helpers.scriptHelpers}
assert_file_contains ${binary} "nixtest" "should contain nixtest"
assert_file_contains ${binary} "--pure" "should contain --pure arg"
assert_file_contains ${binary} "--impure" "should contain --impure arg"
assert_file_contains ${binary} "--tests=stub" "should contain --tests arg"
run "${binary} --help"
@ -63,21 +70,23 @@
in
# sh
''
${ntlib.helpers.path [pkgs.gnugrep pkgs.mktemp]}
${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
TMPDIR=$(tmpdir)
# start without nix & env binaries to expect errors
run "${binary} --pure --junit=$TMPDIR/junit.xml"
run "${binary} --junit=junit.xml"
assert "$exit_code -eq 2" "should exit 2"
assert "-f $TMPDIR/junit.xml" "should create junit.xml"
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} --pure --junit=$TMPDIR/junit2.xml"
run "${binary} --junit=junit2.xml"
assert "$exit_code -eq 2" "should exit 2"
assert "-f $TMPDIR/junit2.xml" "should create junit2.xml"
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"