1/* Collection of functions useful for debugging
2 broken nix expressions.
3
4 * `trace`-like functions take two values, print
5 the first to stderr and return the second.
6 * `traceVal`-like functions take one argument
7 which both printed and returned.
8 * `traceSeq`-like functions fully evaluate their
9 traced value before printing (not just to “weak
10 head normal form” like trace does by default).
11 * Functions that end in `-Fn` take an additional
12 function as their first argument, which is applied
13 to the traced value before it is printed.
14*/
15{ lib }:
16let
17 inherit (builtins) trace isAttrs isList isInt
18 head substring attrNames;
19 inherit (lib) id elem isFunction;
20in
21
22rec {
23
24 # -- TRACING --
25
26 /* Trace msg, but only if pred is true.
27
28 Example:
29 traceIf true "hello" 3
30 trace: hello
31 => 3
32 */
33 traceIf = pred: msg: x: if pred then trace msg x else x;
34
35 /* Trace the value and also return it.
36
37 Example:
38 traceValFn (v: "mystring ${v}") "foo"
39 trace: mystring foo
40 => "foo"
41 */
42 traceValFn = f: x: trace (f x) x;
43 traceVal = traceValFn id;
44
45 /* `builtins.trace`, but the value is `builtins.deepSeq`ed first.
46
47 Example:
48 trace { a.b.c = 3; } null
49 trace: { a = <CODE>; }
50 => null
51 traceSeq { a.b.c = 3; } null
52 trace: { a = { b = { c = 3; }; }; }
53 => null
54 */
55 traceSeq = x: y: trace (builtins.deepSeq x x) y;
56
57 /* Like `traceSeq`, but only evaluate down to depth n.
58 This is very useful because lots of `traceSeq` usages
59 lead to an infinite recursion.
60
61 Example:
62 traceSeqN 2 { a.b.c = 3; } null
63 trace: { a = { b = {…}; }; }
64 => null
65 */
66 traceSeqN = depth: x: y: with lib;
67 let snip = v: if isList v then noQuotes "[…]" v
68 else if isAttrs v then noQuotes "{…}" v
69 else v;
70 noQuotes = str: v: { __pretty = const str; val = v; };
71 modify = n: fn: v: if (n == 0) then fn v
72 else if isList v then map (modify (n - 1) fn) v
73 else if isAttrs v then mapAttrs
74 (const (modify (n - 1) fn)) v
75 else v;
76 in trace (generators.toPretty { allowPrettyValues = true; }
77 (modify depth snip x)) y;
78
79 /* A combination of `traceVal` and `traceSeq` */
80 traceValSeqFn = f: v: traceValFn f (builtins.deepSeq v v);
81 traceValSeq = traceValSeqFn id;
82
83 /* A combination of `traceVal` and `traceSeqN`. */
84 traceValSeqNFn = f: depth: v: traceSeqN depth (f v) v;
85 traceValSeqN = traceValSeqNFn id;
86
87
88 # -- TESTING --
89
90 /* Evaluate a set of tests. A test is an attribute set {expr,
91 expected}, denoting an expression and its expected result. The
92 result is a list of failed tests, each represented as {name,
93 expected, actual}, denoting the attribute name of the failing
94 test and its expected and actual results. Used for regression
95 testing of the functions in lib; see tests.nix for an example.
96 Only tests having names starting with "test" are run.
97 Add attr { tests = ["testName"]; } to run these test only
98 */
99 runTests = tests: lib.concatLists (lib.attrValues (lib.mapAttrs (name: test:
100 let testsToRun = if tests ? tests then tests.tests else [];
101 in if (substring 0 4 name == "test" || elem name testsToRun)
102 && ((testsToRun == []) || elem name tests.tests)
103 && (test.expr != test.expected)
104
105 then [ { inherit name; expected = test.expected; result = test.expr; } ]
106 else [] ) tests));
107
108 # create a test assuming that list elements are true
109 # usage: { testX = allTrue [ true ]; }
110 testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };
111
112
113 # -- DEPRECATED --
114
115 traceShowVal = x: trace (showVal x) x;
116 traceShowValMarked = str: x: trace (str + showVal x) x;
117
118 attrNamesToStr = a:
119 trace ( "Warning: `attrNamesToStr` is deprecated "
120 + "and will be removed in the next release. "
121 + "Please use more specific concatenation "
122 + "for your uses (`lib.concat(Map)StringsSep`)." )
123 (lib.concatStringsSep "; " (map (x: "${x}=") (attrNames a)));
124
125 showVal = with lib;
126 trace ( "Warning: `showVal` is deprecated "
127 + "and will be removed in the next release, "
128 + "please use `traceSeqN`" )
129 (let
130 modify = v:
131 let pr = f: { __pretty = f; val = v; };
132 in if isDerivation v then pr
133 (drv: "<δ:${drv.name}:${concatStringsSep ","
134 (attrNames drv)}>")
135 else if [] == v then pr (const "[]")
136 else if isList v then pr (l: "[ ${go (head l)}, … ]")
137 else if isAttrs v then pr
138 (a: "{ ${ concatStringsSep ", " (attrNames a)} }")
139 else v;
140 go = x: generators.toPretty
141 { allowPrettyValues = true; }
142 (modify x);
143 in go);
144
145 traceXMLVal = x:
146 trace ( "Warning: `traceXMLVal` is deprecated "
147 + "and will be removed in the next release. "
148 + "Please use `traceValFn builtins.toXML`." )
149 (trace (builtins.toXML x) x);
150 traceXMLValMarked = str: x:
151 trace ( "Warning: `traceXMLValMarked` is deprecated "
152 + "and will be removed in the next release. "
153 + "Please use `traceValFn (x: str + builtins.toXML x)`." )
154 (trace (str + builtins.toXML x) x);
155
156 # trace the arguments passed to function and its result
157 # maybe rewrite these functions in a traceCallXml like style. Then one function is enough
158 traceCall = n: f: a: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a));
159 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));
160 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));
161
162 traceValIfNot = c: x:
163 trace ( "Warning: `traceValIfNot` is deprecated "
164 + "and will be removed in the next release. "
165 + "Please use `if/then/else` and `traceValSeq 1`.")
166 (if c x then true else traceSeq (showVal x) false);
167
168
169 addErrorContextToAttrs = attrs:
170 trace ( "Warning: `addErrorContextToAttrs` is deprecated "
171 + "and will be removed in the next release. "
172 + "Please use `builtins.addErrorContext` directly." )
173 (lib.mapAttrs (a: v: lib.addErrorContext "while evaluating ${a}" v) attrs);
174
175 # example: (traceCallXml "myfun" id 3) will output something like
176 # calling myfun arg 1: 3 result: 3
177 # this forces deep evaluation of all arguments and the result!
178 # note: if result doesn't evaluate you'll get no trace at all (FIXME)
179 # args should be printed in any case
180 traceCallXml = a:
181 trace ( "Warning: `traceCallXml` is deprecated "
182 + "and will be removed in the next release. "
183 + "Please complain if you use the function regularly." )
184 (if !isInt a then
185 traceCallXml 1 "calling ${a}\n"
186 else
187 let nr = a;
188 in (str: expr:
189 if isFunction expr then
190 (arg:
191 traceCallXml (builtins.add 1 nr) "${str}\n arg ${builtins.toString nr} is \n ${builtins.toXML (builtins.seq arg arg)}" (expr arg)
192 )
193 else
194 let r = builtins.seq expr expr;
195 in trace "${str}\n result:\n${builtins.toXML r}" r
196 ));
197}