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 (lib)
18 isInt
19 attrNames
20 isList
21 isAttrs
22 substring
23 addErrorContext
24 attrValues
25 concatLists
26 concatStringsSep
27 const
28 elem
29 generators
30 head
31 id
32 isDerivation
33 isFunction
34 mapAttrs
35 trace;
36in
37
38rec {
39
40 # -- TRACING --
41
42 /* Conditionally trace the supplied message, based on a predicate.
43
44 Type: traceIf :: bool -> string -> a -> a
45
46 Example:
47 traceIf true "hello" 3
48 trace: hello
49 => 3
50 */
51 traceIf =
52 # Predicate to check
53 pred:
54 # Message that should be traced
55 msg:
56 # Value to return
57 x: if pred then trace msg x else x;
58
59 /* Trace the supplied value after applying a function to it, and
60 return the original value.
61
62 Type: traceValFn :: (a -> b) -> a -> a
63
64 Example:
65 traceValFn (v: "mystring ${v}") "foo"
66 trace: mystring foo
67 => "foo"
68 */
69 traceValFn =
70 # Function to apply
71 f:
72 # Value to trace and return
73 x: trace (f x) x;
74
75 /* Trace the supplied value and return it.
76
77 Type: traceVal :: a -> a
78
79 Example:
80 traceVal 42
81 # trace: 42
82 => 42
83 */
84 traceVal = traceValFn id;
85
86 /* `builtins.trace`, but the value is `builtins.deepSeq`ed first.
87
88 Type: traceSeq :: a -> b -> b
89
90 Example:
91 trace { a.b.c = 3; } null
92 trace: { a = <CODE>; }
93 => null
94 traceSeq { a.b.c = 3; } null
95 trace: { a = { b = { c = 3; }; }; }
96 => null
97 */
98 traceSeq =
99 # The value to trace
100 x:
101 # The value to return
102 y: trace (builtins.deepSeq x x) y;
103
104 /* Like `traceSeq`, but only evaluate down to depth n.
105 This is very useful because lots of `traceSeq` usages
106 lead to an infinite recursion.
107
108 Example:
109 traceSeqN 2 { a.b.c = 3; } null
110 trace: { a = { b = {…}; }; }
111 => null
112 */
113 traceSeqN = depth: x: y:
114 let snip = v: if isList v then noQuotes "[…]" v
115 else if isAttrs v then noQuotes "{…}" v
116 else v;
117 noQuotes = str: v: { __pretty = const str; val = v; };
118 modify = n: fn: v: if (n == 0) then fn v
119 else if isList v then map (modify (n - 1) fn) v
120 else if isAttrs v then mapAttrs
121 (const (modify (n - 1) fn)) v
122 else v;
123 in trace (generators.toPretty { allowPrettyValues = true; }
124 (modify depth snip x)) y;
125
126 /* A combination of `traceVal` and `traceSeq` that applies a
127 provided function to the value to be traced after `deepSeq`ing
128 it.
129 */
130 traceValSeqFn =
131 # Function to apply
132 f:
133 # Value to trace
134 v: traceValFn f (builtins.deepSeq v v);
135
136 /* A combination of `traceVal` and `traceSeq`. */
137 traceValSeq = traceValSeqFn id;
138
139 /* A combination of `traceVal` and `traceSeqN` that applies a
140 provided function to the value to be traced. */
141 traceValSeqNFn =
142 # Function to apply
143 f:
144 depth:
145 # Value to trace
146 v: traceSeqN depth (f v) v;
147
148 /* A combination of `traceVal` and `traceSeqN`. */
149 traceValSeqN = traceValSeqNFn id;
150
151 /* Trace the input and output of a function `f` named `name`,
152 both down to `depth`.
153
154 This is useful for adding around a function call,
155 to see the before/after of values as they are transformed.
156
157 Example:
158 traceFnSeqN 2 "id" (x: x) { a.b.c = 3; }
159 trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; }
160 => { a.b.c = 3; }
161 */
162 traceFnSeqN = depth: name: f: v:
163 let res = f v;
164 in lib.traceSeqN
165 (depth + 1)
166 {
167 fn = name;
168 from = v;
169 to = res;
170 }
171 res;
172
173
174 # -- TESTING --
175
176 /* Evaluate a set of tests. A test is an attribute set `{expr,
177 expected}`, denoting an expression and its expected result. The
178 result is a list of failed tests, each represented as `{name,
179 expected, actual}`, denoting the attribute name of the failing
180 test and its expected and actual results.
181
182 Used for regression testing of the functions in lib; see
183 tests.nix for an example. Only tests having names starting with
184 "test" are run.
185
186 Add attr { tests = ["testName"]; } to run these tests only.
187 */
188 runTests =
189 # Tests to run
190 tests: concatLists (attrValues (mapAttrs (name: test:
191 let testsToRun = if tests ? tests then tests.tests else [];
192 in if (substring 0 4 name == "test" || elem name testsToRun)
193 && ((testsToRun == []) || elem name tests.tests)
194 && (test.expr != test.expected)
195
196 then [ { inherit name; expected = test.expected; result = test.expr; } ]
197 else [] ) tests));
198
199 /* Create a test assuming that list elements are `true`.
200
201 Example:
202 { testX = allTrue [ true ]; }
203 */
204 testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };
205
206
207 # -- DEPRECATED --
208
209 traceShowVal = x: trace (showVal x) x;
210 traceShowValMarked = str: x: trace (str + showVal x) x;
211
212 attrNamesToStr = a:
213 trace ( "Warning: `attrNamesToStr` is deprecated "
214 + "and will be removed in the next release. "
215 + "Please use more specific concatenation "
216 + "for your uses (`lib.concat(Map)StringsSep`)." )
217 (concatStringsSep "; " (map (x: "${x}=") (attrNames a)));
218
219 showVal =
220 trace ( "Warning: `showVal` is deprecated "
221 + "and will be removed in the next release, "
222 + "please use `traceSeqN`" )
223 (let
224 modify = v:
225 let pr = f: { __pretty = f; val = v; };
226 in if isDerivation v then pr
227 (drv: "<δ:${drv.name}:${concatStringsSep ","
228 (attrNames drv)}>")
229 else if [] == v then pr (const "[]")
230 else if isList v then pr (l: "[ ${go (head l)}, … ]")
231 else if isAttrs v then pr
232 (a: "{ ${ concatStringsSep ", " (attrNames a)} }")
233 else v;
234 go = x: generators.toPretty
235 { allowPrettyValues = true; }
236 (modify x);
237 in go);
238
239 traceXMLVal = x:
240 trace ( "Warning: `traceXMLVal` is deprecated "
241 + "and will be removed in the next release. "
242 + "Please use `traceValFn builtins.toXML`." )
243 (trace (builtins.toXML x) x);
244 traceXMLValMarked = str: x:
245 trace ( "Warning: `traceXMLValMarked` is deprecated "
246 + "and will be removed in the next release. "
247 + "Please use `traceValFn (x: str + builtins.toXML x)`." )
248 (trace (str + builtins.toXML x) x);
249
250 # trace the arguments passed to function and its result
251 # maybe rewrite these functions in a traceCallXml like style. Then one function is enough
252 traceCall = n: f: a: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a));
253 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));
254 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));
255
256 traceValIfNot = c: x:
257 trace ( "Warning: `traceValIfNot` is deprecated "
258 + "and will be removed in the next release. "
259 + "Please use `if/then/else` and `traceValSeq 1`.")
260 (if c x then true else traceSeq (showVal x) false);
261
262
263 addErrorContextToAttrs = attrs:
264 trace ( "Warning: `addErrorContextToAttrs` is deprecated "
265 + "and will be removed in the next release. "
266 + "Please use `builtins.addErrorContext` directly." )
267 (mapAttrs (a: v: addErrorContext "while evaluating ${a}" v) attrs);
268
269 # example: (traceCallXml "myfun" id 3) will output something like
270 # calling myfun arg 1: 3 result: 3
271 # this forces deep evaluation of all arguments and the result!
272 # note: if result doesn't evaluate you'll get no trace at all (FIXME)
273 # args should be printed in any case
274 traceCallXml = a:
275 trace ( "Warning: `traceCallXml` is deprecated "
276 + "and will be removed in the next release. "
277 + "Please complain if you use the function regularly." )
278 (if !isInt a then
279 traceCallXml 1 "calling ${a}\n"
280 else
281 let nr = a;
282 in (str: expr:
283 if isFunction expr then
284 (arg:
285 traceCallXml (builtins.add 1 nr) "${str}\n arg ${builtins.toString nr} is \n ${builtins.toXML (builtins.seq arg arg)}" (expr arg)
286 )
287 else
288 let r = builtins.seq expr expr;
289 in trace "${str}\n result:\n${builtins.toXML r}" r
290 ));
291}