d0b635f6f2
Same as 221698c603dcb318c609b4d21cb2a9fada44a14c We had a bunch of instances of https://github.com/NixOS/nix/issues/2176, where nix would exit with a “killed by signal 9” error. According to Eelco in that issue, this is perfectly normal behaviour of course, and appears if the last command in a loop closes `stdout` or `stdin`, then the builder will SIGKILL it immediately. This is of course also a perfectly fine error message for that case. It turns out that mainly GNU coreutils exhibit this behaviour … Let’s see if using a more sane tool suite fixes that. Change-Id: Iaf9e542952ca36c02208a3f067f575ba978272b4 Reviewed-on: https://cl.tvl.fyi/c/depot/+/2663 Reviewed-by: Profpatsch <mail@profpatsch.de> Tested-by: BuildkiteCI
185 lines
4.9 KiB
Nix
185 lines
4.9 KiB
Nix
{ lib, pkgs, depot, ... }:
|
||
|
||
# Run a nix testsuite.
|
||
#
|
||
# The tests are simple assertions on the nix level,
|
||
# and can use derivation outputs if IfD is enabled.
|
||
#
|
||
# You build a testsuite by bundling assertions into
|
||
# “it”s and then bundling the “it”s into a testsuite.
|
||
#
|
||
# Running the testsuite will abort evaluation if
|
||
# any assertion fails.
|
||
#
|
||
# Example:
|
||
#
|
||
# runTestsuite "myFancyTestsuite" [
|
||
# (it "does an assertion" [
|
||
# (assertEq "42 is equal to 42" "42" "42")
|
||
# (assertEq "also 23" 23 23)
|
||
# ])
|
||
# (it "frmbls the brlbr" [
|
||
# (assertEq true false)
|
||
# ])
|
||
# ]
|
||
#
|
||
# will fail the second it group because true is not false.
|
||
|
||
let
|
||
inherit (depot.nix.yants)
|
||
sum
|
||
struct
|
||
string
|
||
any
|
||
defun
|
||
list
|
||
drv
|
||
bool
|
||
;
|
||
|
||
bins = depot.nix.getBins pkgs.coreutils [ "printf" ]
|
||
// depot.nix.getBins pkgs.s6-portable-utils [ "s6-touch" ];
|
||
|
||
# Returns true if the given expression throws when `deepSeq`-ed
|
||
throws = expr:
|
||
!(builtins.tryEval (builtins.deepSeq expr {})).success;
|
||
|
||
# rewrite the builtins.partition result
|
||
# to use `ok` and `err` instead of `right` and `wrong`.
|
||
partitionTests = pred: xs:
|
||
let res = builtins.partition pred xs;
|
||
in {
|
||
ok = res.right;
|
||
err = res.wrong;
|
||
};
|
||
|
||
AssertErrorContext =
|
||
sum "AssertErrorContext" {
|
||
not-equal = struct "not-equal" {
|
||
left = any;
|
||
right = any;
|
||
};
|
||
should-throw = struct "should-throw" {
|
||
expr = any;
|
||
};
|
||
unexpected-throw = struct "unexpected-throw" { };
|
||
};
|
||
|
||
# The result of an assert,
|
||
# either it’s true (yep) or false (nope).
|
||
# If it's nope we return an additional context
|
||
# attribute which gives details on the failure
|
||
# depending on the type of assert performed.
|
||
AssertResult =
|
||
sum "AssertResult" {
|
||
yep = struct "yep" {
|
||
test = string;
|
||
};
|
||
nope = struct "nope" {
|
||
test = string;
|
||
context = AssertErrorContext;
|
||
};
|
||
};
|
||
|
||
# Result of an it. An it is a bunch of asserts
|
||
# bundled up with a good description of what is tested.
|
||
ItResult =
|
||
struct "ItResult" {
|
||
it-desc = string;
|
||
asserts = list AssertResult;
|
||
};
|
||
|
||
# If the given boolean is true return a positive AssertResult.
|
||
# If the given boolean is false return a negative AssertResult
|
||
# with the provided AssertErrorContext describing the failure.
|
||
#
|
||
# This function is intended as a generic assert to implement
|
||
# more assert types and is not exposed to the user.
|
||
assertBoolContext = defun [ AssertErrorContext string bool AssertResult ]
|
||
(context: desc: res:
|
||
if res
|
||
then { yep = { test = desc; }; }
|
||
else { nope = {
|
||
test = desc;
|
||
inherit context;
|
||
};
|
||
});
|
||
|
||
# assert that left and right values are equal
|
||
assertEq = defun [ string any any AssertResult ]
|
||
(desc: left: right:
|
||
let
|
||
context = { not-equal = { inherit left right; }; };
|
||
in
|
||
assertBoolContext context desc (left == right));
|
||
|
||
# assert that the expression throws when `deepSeq`-ed
|
||
assertThrows = defun [ string any AssertResult ]
|
||
(desc: expr:
|
||
let
|
||
context = { should-throw = { inherit expr; }; };
|
||
in
|
||
assertBoolContext context desc (throws expr));
|
||
|
||
# assert that the expression does not throw when `deepSeq`-ed
|
||
assertDoesNotThrow = defun [ string any AssertResult ]
|
||
(desc: expr:
|
||
assertBoolContext { unexpected-throw = { }; } desc (!(throws expr)));
|
||
|
||
# Annotate a bunch of asserts with a descriptive name
|
||
it = desc: asserts: {
|
||
it-desc = desc;
|
||
inherit asserts;
|
||
};
|
||
|
||
# Run a bunch of its and check whether all asserts are yep.
|
||
# If not, abort evaluation with `throw`
|
||
# and print the result of the test suite.
|
||
#
|
||
# Takes a test suite name as first argument.
|
||
runTestsuite = defun [ string (list ItResult) drv ]
|
||
(name: itResults:
|
||
let
|
||
goodAss = ass: {
|
||
good = AssertResult.match ass {
|
||
yep = _: true;
|
||
nope = _: false;
|
||
};
|
||
x = ass;
|
||
};
|
||
goodIt = it: {
|
||
inherit (it) it-desc;
|
||
asserts = partitionTests (ass:
|
||
AssertResult.match ass {
|
||
yep = _: true;
|
||
nope = _: false;
|
||
}) it.asserts;
|
||
};
|
||
goodIts = partitionTests (it: (goodIt it).asserts.err == []);
|
||
res = goodIts itResults;
|
||
in
|
||
if res.err == []
|
||
then depot.nix.runExecline.local "testsuite-${name}-successful" {} [
|
||
"importas" "out" "out"
|
||
"if" [ bins.printf "%s\n" "testsuite ${name} successful!" ]
|
||
bins.s6-touch "$out"
|
||
]
|
||
else depot.nix.runExecline.local "testsuite-${name}-failed" {} [
|
||
"importas" "out" "out"
|
||
"if" [
|
||
bins.printf "%s\n%s\n"
|
||
"testsuite ${name} failed!"
|
||
(lib.generators.toPretty {} res)
|
||
]
|
||
"exit" "1"
|
||
]);
|
||
|
||
in {
|
||
inherit
|
||
assertEq
|
||
assertThrows
|
||
assertDoesNotThrow
|
||
it
|
||
runTestsuite
|
||
;
|
||
}
|