summary refs log tree commit diff
path: root/lib/debug.nix
diff options
context:
space:
mode:
Diffstat (limited to 'lib/debug.nix')
-rw-r--r--lib/debug.nix291
1 files changed, 291 insertions, 0 deletions
diff --git a/lib/debug.nix b/lib/debug.nix
new file mode 100644
index 00000000000..e3ca3352397
--- /dev/null
+++ b/lib/debug.nix
@@ -0,0 +1,291 @@
+/* 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
+   */
+  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 --
+
+  /* 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: 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; };
+
+
+  # -- 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`)." )
+    (concatStringsSep "; " (map (x: "${x}=") (attrNames a)));
+
+  showVal =
+    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." )
+    (mapAttrs (a: v: 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
+      ));
+}