aboutsummaryrefslogtreecommitdiff
path: root/debug.nix
diff options
context:
space:
mode:
Diffstat (limited to 'debug.nix')
-rw-r--r--debug.nix253
1 files changed, 253 insertions, 0 deletions
diff --git a/debug.nix b/debug.nix
new file mode 100644
index 000000000..a851cd747
--- /dev/null
+++ b/debug.nix
@@ -0,0 +1,253 @@
+/* Collection of functions useful for debugging
+ broken nix expressions.
+
+ * `trace`-like functions take two values, print
+ the first to stderr and return the second.
+ * `traceVal`-like functions take one argument
+ which both printed and returned.
+ * `traceSeq`-like functions fully evaluate their
+ traced value before printing (not just to “weak
+ head normal form” like trace does by default).
+ * Functions that end in `-Fn` take an additional
+ function as their first argument, which is applied
+ to the traced value before it is printed.
+*/
+{ lib }:
+let
+ inherit (lib)
+ isInt
+ attrNames
+ isList
+ isAttrs
+ substring
+ addErrorContext
+ attrValues
+ concatLists
+ concatStringsSep
+ const
+ elem
+ generators
+ head
+ id
+ isDerivation
+ isFunction
+ mapAttrs
+ trace;
+in
+
+rec {
+
+ # -- TRACING --
+
+ /* Conditionally trace the supplied message, based on a predicate.
+
+ Type: traceIf :: bool -> string -> a -> a
+
+ Example:
+ traceIf true "hello" 3
+ trace: hello
+ => 3
+ */
+ traceIf =
+ # Predicate to check
+ pred:
+ # Message that should be traced
+ msg:
+ # Value to return
+ x: if pred then trace msg x else x;
+
+ /* Trace the supplied value after applying a function to it, and
+ return the original value.
+
+ Type: traceValFn :: (a -> b) -> a -> a
+
+ Example:
+ traceValFn (v: "mystring ${v}") "foo"
+ trace: mystring foo
+ => "foo"
+ */
+ traceValFn =
+ # Function to apply
+ f:
+ # Value to trace and return
+ x: trace (f x) x;
+
+ /* Trace the supplied value and return it.
+
+ Type: traceVal :: a -> a
+
+ Example:
+ traceVal 42
+ # trace: 42
+ => 42
+ */
+ traceVal = traceValFn id;
+
+ /* `builtins.trace`, but the value is `builtins.deepSeq`ed first.
+
+ Type: traceSeq :: a -> b -> b
+
+ Example:
+ trace { a.b.c = 3; } null
+ trace: { a = <CODE>; }
+ => null
+ traceSeq { a.b.c = 3; } null
+ trace: { a = { b = { c = 3; }; }; }
+ => null
+ */
+ traceSeq =
+ # The value to trace
+ x:
+ # The value to return
+ y: trace (builtins.deepSeq x x) y;
+
+ /* Like `traceSeq`, but only evaluate down to depth n.
+ This is very useful because lots of `traceSeq` usages
+ lead to an infinite recursion.
+
+ Example:
+ traceSeqN 2 { a.b.c = 3; } null
+ trace: { a = { b = {…}; }; }
+ => null
+
+ Type: traceSeqN :: Int -> a -> b -> b
+ */
+ traceSeqN = depth: x: y:
+ let snip = v: if isList v then noQuotes "[…]" v
+ else if isAttrs v then noQuotes "{…}" v
+ else v;
+ noQuotes = str: v: { __pretty = const str; val = v; };
+ modify = n: fn: v: if (n == 0) then fn v
+ else if isList v then map (modify (n - 1) fn) v
+ else if isAttrs v then mapAttrs
+ (const (modify (n - 1) fn)) v
+ else v;
+ in trace (generators.toPretty { allowPrettyValues = true; }
+ (modify depth snip x)) y;
+
+ /* A combination of `traceVal` and `traceSeq` that applies a
+ provided function to the value to be traced after `deepSeq`ing
+ it.
+ */
+ traceValSeqFn =
+ # Function to apply
+ f:
+ # Value to trace
+ v: traceValFn f (builtins.deepSeq v v);
+
+ /* A combination of `traceVal` and `traceSeq`. */
+ traceValSeq = traceValSeqFn id;
+
+ /* A combination of `traceVal` and `traceSeqN` that applies a
+ provided function to the value to be traced. */
+ traceValSeqNFn =
+ # Function to apply
+ f:
+ depth:
+ # Value to trace
+ v: traceSeqN depth (f v) v;
+
+ /* A combination of `traceVal` and `traceSeqN`. */
+ traceValSeqN = traceValSeqNFn id;
+
+ /* Trace the input and output of a function `f` named `name`,
+ both down to `depth`.
+
+ This is useful for adding around a function call,
+ to see the before/after of values as they are transformed.
+
+ Example:
+ traceFnSeqN 2 "id" (x: x) { a.b.c = 3; }
+ trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; }
+ => { a.b.c = 3; }
+ */
+ traceFnSeqN = depth: name: f: v:
+ let res = f v;
+ in lib.traceSeqN
+ (depth + 1)
+ {
+ fn = name;
+ from = v;
+ to = res;
+ }
+ res;
+
+
+ # -- TESTING --
+
+ /* Evaluates a set of tests.
+
+ A test is an attribute set `{expr, expected}`,
+ denoting an expression and its expected result.
+
+ The result is a `list` of __failed tests__, each represented as
+ `{name, expected, result}`,
+
+ - expected
+ - What was passed as `expected`
+ - result
+ - The actual `result` of the test
+
+ Used for regression testing of the functions in lib; see
+ tests.nix for more examples.
+
+ Important: Only attributes that start with `test` are executed.
+
+ - If you want to run only a subset of the tests add the attribute `tests = ["testName"];`
+
+ Example:
+
+ runTests {
+ testAndOk = {
+ expr = lib.and true false;
+ expected = false;
+ };
+ testAndFail = {
+ expr = lib.and true false;
+ expected = true;
+ };
+ }
+ ->
+ [
+ {
+ name = "testAndFail";
+ expected = true;
+ result = false;
+ }
+ ]
+
+ Type:
+ runTests :: {
+ tests = [ String ];
+ ${testName} :: {
+ expr :: a;
+ expected :: a;
+ };
+ }
+ ->
+ [
+ {
+ name :: String;
+ expected :: a;
+ result :: a;
+ }
+ ]
+ */
+ runTests =
+ # Tests to run
+ tests: concatLists (attrValues (mapAttrs (name: test:
+ let testsToRun = if tests ? tests then tests.tests else [];
+ in if (substring 0 4 name == "test" || elem name testsToRun)
+ && ((testsToRun == []) || elem name tests.tests)
+ && (test.expr != test.expected)
+
+ then [ { inherit name; expected = test.expected; result = test.expr; } ]
+ else [] ) tests));
+
+ /* Create a test assuming that list elements are `true`.
+
+ Example:
+ { testX = allTrue [ true ]; }
+ */
+ testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };
+}