2020-06-27 22:56:58 +02:00
|
|
|
|
{ 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
|
2021-01-29 20:48:00 +01:00
|
|
|
|
inherit (depot.nix.yants)
|
|
|
|
|
sum
|
|
|
|
|
struct
|
|
|
|
|
string
|
|
|
|
|
any
|
|
|
|
|
defun
|
|
|
|
|
list
|
|
|
|
|
drv
|
2021-01-31 13:41:32 +01:00
|
|
|
|
bool
|
2021-01-29 20:48:00 +01:00
|
|
|
|
;
|
|
|
|
|
|
2021-03-26 11:40:26 +01:00
|
|
|
|
bins = depot.nix.getBins pkgs.coreutils [ "printf" ]
|
2021-11-23 20:23:13 +01:00
|
|
|
|
// depot.nix.getBins pkgs.s6-portable-utils [ "s6-touch" "s6-false" "s6-cat" ];
|
2020-06-27 22:56:58 +02:00
|
|
|
|
|
2021-01-31 13:41:32 +01:00
|
|
|
|
# Returns true if the given expression throws when `deepSeq`-ed
|
|
|
|
|
throws = expr:
|
|
|
|
|
!(builtins.tryEval (builtins.deepSeq expr { })).success;
|
|
|
|
|
|
2020-06-27 22:56:58 +02:00
|
|
|
|
# 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;
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-31 13:41:32 +01:00
|
|
|
|
AssertErrorContext =
|
|
|
|
|
sum "AssertErrorContext" {
|
|
|
|
|
not-equal = struct "not-equal" {
|
|
|
|
|
left = any;
|
|
|
|
|
right = any;
|
|
|
|
|
};
|
|
|
|
|
should-throw = struct "should-throw" {
|
|
|
|
|
expr = any;
|
|
|
|
|
};
|
2021-01-31 19:22:35 +01:00
|
|
|
|
unexpected-throw = struct "unexpected-throw" { };
|
2021-01-31 13:41:32 +01:00
|
|
|
|
};
|
|
|
|
|
|
2020-06-27 22:56:58 +02:00
|
|
|
|
# The result of an assert,
|
|
|
|
|
# either it’s true (yep) or false (nope).
|
2021-01-31 13:41:32 +01:00
|
|
|
|
# If it's nope we return an additional context
|
|
|
|
|
# attribute which gives details on the failure
|
|
|
|
|
# depending on the type of assert performed.
|
2020-06-27 22:56:58 +02:00
|
|
|
|
AssertResult =
|
|
|
|
|
sum "AssertResult" {
|
|
|
|
|
yep = struct "yep" {
|
|
|
|
|
test = string;
|
|
|
|
|
};
|
2021-01-31 13:41:32 +01:00
|
|
|
|
nope = struct "nope" {
|
2020-06-27 22:56:58 +02:00
|
|
|
|
test = string;
|
2021-01-31 13:41:32 +01:00
|
|
|
|
context = AssertErrorContext;
|
2021-01-29 20:53:26 +01:00
|
|
|
|
};
|
2020-06-27 22:56:58 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# 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;
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-31 13:41:32 +01:00
|
|
|
|
# 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
|
2020-06-27 22:56:58 +02:00
|
|
|
|
then { yep = { test = desc; }; }
|
2021-01-31 13:41:32 +01:00
|
|
|
|
else {
|
|
|
|
|
nope = {
|
2020-06-27 22:56:58 +02:00
|
|
|
|
test = desc;
|
2021-01-31 13:41:32 +01:00
|
|
|
|
inherit context;
|
2020-06-27 22:56:58 +02:00
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2021-01-31 13:41:32 +01:00
|
|
|
|
# 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));
|
|
|
|
|
|
2021-01-29 20:53:26 +01:00
|
|
|
|
# assert that the expression throws when `deepSeq`-ed
|
|
|
|
|
assertThrows = defun [ string any AssertResult ]
|
|
|
|
|
(desc: expr:
|
2021-01-31 13:41:32 +01:00
|
|
|
|
let
|
|
|
|
|
context = { should-throw = { inherit expr; }; };
|
|
|
|
|
in
|
|
|
|
|
assertBoolContext context desc (throws expr));
|
2021-01-29 20:53:26 +01:00
|
|
|
|
|
2021-01-31 19:22:35 +01:00
|
|
|
|
# assert that the expression does not throw when `deepSeq`-ed
|
|
|
|
|
assertDoesNotThrow = defun [ string any AssertResult ]
|
|
|
|
|
(desc: expr:
|
|
|
|
|
assertBoolContext { unexpected-throw = { }; } desc (!(throws expr)));
|
|
|
|
|
|
2020-06-27 22:56:58 +02:00
|
|
|
|
# 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.
|
2021-01-29 20:48:00 +01:00
|
|
|
|
runTestsuite = defun [ string (list ItResult) drv ]
|
2020-06-27 22:56:58 +02:00
|
|
|
|
(name: itResults:
|
|
|
|
|
let
|
2021-09-15 00:17:27 +02:00
|
|
|
|
goodAss = ass: AssertResult.match ass {
|
|
|
|
|
yep = _: true;
|
|
|
|
|
nope = _: false;
|
2020-06-27 22:56:58 +02:00
|
|
|
|
};
|
2021-09-15 00:17:27 +02:00
|
|
|
|
res = partitionTests
|
|
|
|
|
(it:
|
|
|
|
|
(partitionTests goodAss it.asserts).err == [ ]
|
|
|
|
|
)
|
|
|
|
|
itResults;
|
2021-09-11 19:25:39 +02:00
|
|
|
|
prettyRes = lib.generators.toPretty { } res;
|
2020-06-27 22:56:58 +02:00
|
|
|
|
in
|
|
|
|
|
if res.err == [ ]
|
2021-01-29 20:48:00 +01:00
|
|
|
|
then
|
|
|
|
|
depot.nix.runExecline.local "testsuite-${name}-successful" { } [
|
|
|
|
|
"importas"
|
|
|
|
|
"out"
|
|
|
|
|
"out"
|
2021-09-11 19:25:39 +02:00
|
|
|
|
# force derivation to rebuild if test case list changes
|
2021-11-23 20:23:13 +01:00
|
|
|
|
"ifelse"
|
|
|
|
|
[ bins.s6-false ]
|
|
|
|
|
[
|
|
|
|
|
bins.printf
|
|
|
|
|
""
|
|
|
|
|
(builtins.hashString "sha512" prettyRes)
|
|
|
|
|
]
|
2021-01-29 20:48:00 +01:00
|
|
|
|
"if"
|
|
|
|
|
[ bins.printf "%s\n" "testsuite ${name} successful!" ]
|
2021-03-26 11:40:26 +01:00
|
|
|
|
bins.s6-touch
|
|
|
|
|
"$out"
|
2021-01-29 20:48:00 +01:00
|
|
|
|
]
|
2021-11-23 20:23:13 +01:00
|
|
|
|
else
|
|
|
|
|
depot.nix.runExecline.local "testsuite-${name}-failed"
|
|
|
|
|
{
|
|
|
|
|
stdin = prettyRes + "\n";
|
|
|
|
|
} [
|
2021-01-29 20:48:00 +01:00
|
|
|
|
"importas"
|
|
|
|
|
"out"
|
|
|
|
|
"out"
|
2021-11-23 20:23:13 +01:00
|
|
|
|
"if"
|
|
|
|
|
[ bins.printf "%s\n" "testsuite ${name} failed!" ]
|
|
|
|
|
"if"
|
|
|
|
|
[ bins.s6-cat ]
|
2021-01-29 20:48:00 +01:00
|
|
|
|
"exit"
|
|
|
|
|
"1"
|
|
|
|
|
]);
|
2020-06-27 22:56:58 +02:00
|
|
|
|
|
|
|
|
|
in
|
|
|
|
|
{
|
|
|
|
|
inherit
|
|
|
|
|
assertEq
|
2021-01-29 20:53:26 +01:00
|
|
|
|
assertThrows
|
2021-01-31 19:22:35 +01:00
|
|
|
|
assertDoesNotThrow
|
2020-06-27 22:56:58 +02:00
|
|
|
|
it
|
|
|
|
|
runTestsuite
|
|
|
|
|
;
|
|
|
|
|
}
|