aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/lib/debug.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/lib/debug.nix')
-rw-r--r--nixpkgs/lib/debug.nix253
1 files changed, 253 insertions, 0 deletions
diff --git a/nixpkgs/lib/debug.nix b/nixpkgs/lib/debug.nix
new file mode 100644
index 00000000000..2879f72ed2b
--- /dev/null
+++ b/nixpkgs/lib/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 (builtins) trace isAttrs isList isInt
+ head substring attrNames;
+ inherit (lib) id elem isFunction;
+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
+ */
+ traceSeqN = depth: x: y: with lib;
+ 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;
+
+
+ # -- TESTING --
+
+ /* Evaluate 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, actual}`, denoting the attribute name of the failing
+ test and its expected and actual results.
+
+ Used for regression testing of the functions in lib; see
+ tests.nix for an example. Only tests having names starting with
+ "test" are run.
+
+ Add attr { tests = ["testName"]; } to run these tests only.
+ */
+ runTests =
+ # Tests to run
+ tests: lib.concatLists (lib.attrValues (lib.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; };
+
+
+ # -- DEPRECATED --
+
+ traceShowVal = x: trace (showVal x) x;
+ traceShowValMarked = str: x: trace (str + showVal x) x;
+
+ attrNamesToStr = a:
+ trace ( "Warning: `attrNamesToStr` is deprecated "
+ + "and will be removed in the next release. "
+ + "Please use more specific concatenation "
+ + "for your uses (`lib.concat(Map)StringsSep`)." )
+ (lib.concatStringsSep "; " (map (x: "${x}=") (attrNames a)));
+
+ showVal = with lib;
+ trace ( "Warning: `showVal` is deprecated "
+ + "and will be removed in the next release, "
+ + "please use `traceSeqN`" )
+ (let
+ modify = v:
+ let pr = f: { __pretty = f; val = v; };
+ in if isDerivation v then pr
+ (drv: "<δ:${drv.name}:${concatStringsSep ","
+ (attrNames drv)}>")
+ else if [] == v then pr (const "[]")
+ else if isList v then pr (l: "[ ${go (head l)}, … ]")
+ else if isAttrs v then pr
+ (a: "{ ${ concatStringsSep ", " (attrNames a)} }")
+ else v;
+ go = x: generators.toPretty
+ { allowPrettyValues = true; }
+ (modify x);
+ in go);
+
+ traceXMLVal = x:
+ trace ( "Warning: `traceXMLVal` is deprecated "
+ + "and will be removed in the next release. "
+ + "Please use `traceValFn builtins.toXML`." )
+ (trace (builtins.toXML x) x);
+ traceXMLValMarked = str: x:
+ trace ( "Warning: `traceXMLValMarked` is deprecated "
+ + "and will be removed in the next release. "
+ + "Please use `traceValFn (x: str + builtins.toXML x)`." )
+ (trace (str + builtins.toXML x) x);
+
+ # trace the arguments passed to function and its result
+ # maybe rewrite these functions in a traceCallXml like style. Then one function is enough
+ traceCall = n: f: a: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a));
+ traceCall2 = n: f: a: b: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b));
+ traceCall3 = n: f: a: b: c: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b) (t "arg 3" c));
+
+ traceValIfNot = c: x:
+ trace ( "Warning: `traceValIfNot` is deprecated "
+ + "and will be removed in the next release. "
+ + "Please use `if/then/else` and `traceValSeq 1`.")
+ (if c x then true else traceSeq (showVal x) false);
+
+
+ addErrorContextToAttrs = attrs:
+ trace ( "Warning: `addErrorContextToAttrs` is deprecated "
+ + "and will be removed in the next release. "
+ + "Please use `builtins.addErrorContext` directly." )
+ (lib.mapAttrs (a: v: lib.addErrorContext "while evaluating ${a}" v) attrs);
+
+ # example: (traceCallXml "myfun" id 3) will output something like
+ # calling myfun arg 1: 3 result: 3
+ # this forces deep evaluation of all arguments and the result!
+ # note: if result doesn't evaluate you'll get no trace at all (FIXME)
+ # args should be printed in any case
+ traceCallXml = a:
+ trace ( "Warning: `traceCallXml` is deprecated "
+ + "and will be removed in the next release. "
+ + "Please complain if you use the function regularly." )
+ (if !isInt a then
+ traceCallXml 1 "calling ${a}\n"
+ else
+ let nr = a;
+ in (str: expr:
+ if isFunction expr then
+ (arg:
+ traceCallXml (builtins.add 1 nr) "${str}\n arg ${builtins.toString nr} is \n ${builtins.toXML (builtins.seq arg arg)}" (expr arg)
+ )
+ else
+ let r = builtins.seq expr expr;
+ in trace "${str}\n result:\n${builtins.toXML r}" r
+ ));
+}