tvl-depot/nix/runTestsuite/default.nix
Profpatsch d0b635f6f2 fix(nix/runTestsuite): use s6-portable-utils
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
2021-03-26 11:01:20 +00:00

185 lines
4.9 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{ 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 its 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
;
}