diff --git a/lib/debug.nix b/lib/debug.nix index c2226f16c6a3..8dac6bb727a0 100644 --- a/lib/debug.nix +++ b/lib/debug.nix @@ -16,6 +16,7 @@ { lib }: let inherit (lib) + concatMapStringsSep isList isAttrs substring @@ -23,6 +24,7 @@ let concatLists const elem + foldl' generators id mapAttrs @@ -454,6 +456,129 @@ rec { ) ); + /** + Pretty-print a list of test failures. + + This takes an attribute set containing `failures` (a list of test failures + produced by `runTests`) and pretty-prints each failing test, before + throwing an error containing the raw test data as JSON. + + If the input list is empty, `null` is returned. + + # Inputs + + `failures` + + : A list of test failures (produced `runTests`), each containing `name`, + `expected`, and `result` attributes. + + # Type + + ``` + throwTestFailures :: { + failures = [ + { + name :: String; + expected :: a; + result :: a; + } + ]; + } + -> + null + ``` + + # Examples + :::{.example} + + ## `lib.debug.throwTestFailures` usage example + + ```nix + throwTestFailures { + failures = [ + { + name = "testDerivation"; + expected = derivation { + name = "a"; + builder = "bash"; + system = "x86_64-linux"; + }; + result = derivation { + name = "b"; + builder = "bash"; + system = "x86_64-linux"; + }; + } + ]; + } + -> + trace: FAIL testDerivation: + Expected: + Result: + + error: + … while evaluating the file '...': + + … caused by explicit throw + at /nix/store/.../lib/debug.nix:528:7: + 527| in + 528| throw ( + | ^ + 529| builtins.seq traceFailures ( + + error: 1 tests failed: + - testDerivation + + [{"expected":"/nix/store/xh7kyqp69mxkwspmi81a94m9xx74r8dr-a","name":"testDerivation","result":"/nix/store/503l84nir4zw57d1shfhai25bxxn16c6-b"}] + null + ``` + + ::: + */ + throwTestFailures = + { + failures, + description ? "tests", + ... + }: + if failures == [ ] then + null + else + let + toPretty = + value: + # Thanks to @Ma27 for this: + # + # > The `unsafeDiscardStringContext` is useful when the `toPretty` + # > stumbles upon a derivation that would be realized without it (I + # > ran into the problem when writing a test for a flake helper where + # > I creating a bunch of "mock" derivations for different systems + # > and Nix then tried to build those when the error-string got + # > forced). + # + # See: https://github.com/NixOS/nixpkgs/pull/416207#discussion_r2145942389 + builtins.unsafeDiscardStringContext (generators.toPretty { allowPrettyValues = true; } value); + + failureToPretty = failure: '' + FAIL ${toPretty failure.name}: + Expected: + ${toPretty failure.expected} + + Result: + ${toPretty failure.result} + ''; + + traceFailures = foldl' (_accumulator: failure: traceVal (failureToPretty failure)) null failures; + in + throw ( + builtins.seq traceFailures ( + "${builtins.toString (builtins.length failures)} ${description} failed:\n- " + + (concatMapStringsSep "\n- " (failure: failure.name) failures) + + "\n\n" + + builtins.toJSON failures + ) + ); + /** Create a test assuming that list elements are `true`. diff --git a/lib/path/tests/unit.nix b/lib/path/tests/unit.nix index fa2e004e9c3a..e3e7ebceeb53 100644 --- a/lib/path/tests/unit.nix +++ b/lib/path/tests/unit.nix @@ -15,7 +15,7 @@ let # This is not allowed generally, but we're in the tests here, so we'll allow ourselves. storeDirPath = /. + builtins.storeDir; - cases = lib.runTests { + failures = lib.runTests { # Test examples from the lib.path.append documentation testAppendExample1 = { expr = append /foo "bar/baz"; @@ -326,7 +326,6 @@ let }; }; in -if cases == [ ] then - "Unit tests successful" -else - throw "Path unit tests failed: ${lib.generators.toPretty { } cases}" +lib.debug.throwTestFailures { + inherit failures; +} diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 2ec670262649..e4f2222b7685 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -4741,8 +4741,6 @@ runTests { expected = "/non-existent/this/does/not/exist/for/real/please-dont-mess-with-your-local-fs/default.nix"; }; - # Tests for cross index utilities - testRenameCrossIndexFrom = { expr = lib.renameCrossIndexFrom "pkgs" { pkgsBuildBuild = "dummy-build-build"; @@ -4819,4 +4817,31 @@ runTests { }; }; + testThrowTestFailuresEmpty = { + expr = lib.debug.throwTestFailures { + failures = [ ]; + }; + + expected = null; + }; + + testThrowTestFailures = testingThrow ( + lib.debug.throwTestFailures { + failures = [ + { + name = "testDerivation"; + expected = builtins.derivation { + name = "a"; + builder = "bash"; + system = "x86_64-linux"; + }; + result = builtins.derivation { + name = "b"; + builder = "bash"; + system = "x86_64-linux"; + }; + } + ]; + } + ); } diff --git a/pkgs/test/kernel.nix b/pkgs/test/kernel.nix index 8c4aecf462d3..e5107930a00b 100644 --- a/pkgs/test/kernel.nix +++ b/pkgs/test/kernel.nix @@ -73,6 +73,7 @@ let }; in -lib.optional (failures != [ ]) ( - throw "The following kernel unit tests failed: ${lib.generators.toPretty { } failures}" -) +lib.debug.throwTestFailures { + inherit failures; + description = "kernel unit tests"; +} diff --git a/pkgs/test/systemd/nixos/default.nix b/pkgs/test/systemd/nixos/default.nix index 8547c19478f4..1590fd1a47fc 100644 --- a/pkgs/test/systemd/nixos/default.nix +++ b/pkgs/test/systemd/nixos/default.nix @@ -52,6 +52,7 @@ let }; in -lib.optional (failures != [ ]) ( - throw "The following systemd unit tests failed: ${lib.generators.toPretty { } failures}" -) +lib.debug.throwTestFailures { + inherit failures; + description = "systemd unit tests"; +}