1/**
2 Collection of functions useful for debugging
3 broken nix expressions.
4
5 * `trace`-like functions take two values, print
6 the first to stderr and return the second.
7 * `traceVal`-like functions take one argument
8 which both printed and returned.
9 * `traceSeq`-like functions fully evaluate their
10 traced value before printing (not just to “weak
11 head normal form” like trace does by default).
12 * Functions that end in `-Fn` take an additional
13 function as their first argument, which is applied
14 to the traced value before it is printed.
15*/
16{ lib }:
17let
18 inherit (lib)
19 isList
20 isAttrs
21 substring
22 attrValues
23 concatLists
24 const
25 elem
26 generators
27 id
28 mapAttrs
29 trace;
30in
31
32rec {
33
34 # -- TRACING --
35
36 /**
37 Conditionally trace the supplied message, based on a predicate.
38
39
40 # Inputs
41
42 `pred`
43
44 : Predicate to check
45
46 `msg`
47
48 : Message that should be traced
49
50 `x`
51
52 : Value to return
53
54 # Type
55
56 ```
57 traceIf :: bool -> string -> a -> a
58 ```
59
60 # Examples
61 :::{.example}
62 ## `lib.debug.traceIf` usage example
63
64 ```nix
65 traceIf true "hello" 3
66 trace: hello
67 => 3
68 ```
69
70 :::
71 */
72 traceIf =
73 pred:
74 msg:
75 x: if pred then trace msg x else x;
76
77 /**
78 Trace the supplied value after applying a function to it, and
79 return the original value.
80
81
82 # Inputs
83
84 `f`
85
86 : Function to apply
87
88 `x`
89
90 : Value to trace and return
91
92 # Type
93
94 ```
95 traceValFn :: (a -> b) -> a -> a
96 ```
97
98 # Examples
99 :::{.example}
100 ## `lib.debug.traceValFn` usage example
101
102 ```nix
103 traceValFn (v: "mystring ${v}") "foo"
104 trace: mystring foo
105 => "foo"
106 ```
107
108 :::
109 */
110 traceValFn =
111 f:
112 x: trace (f x) x;
113
114 /**
115 Trace the supplied value and return it.
116
117 # Inputs
118
119 `x`
120
121 : Value to trace and return
122
123 # Type
124
125 ```
126 traceVal :: a -> a
127 ```
128
129 # Examples
130 :::{.example}
131 ## `lib.debug.traceVal` usage example
132
133 ```nix
134 traceVal 42
135 # trace: 42
136 => 42
137 ```
138
139 :::
140 */
141 traceVal = traceValFn id;
142
143 /**
144 `builtins.trace`, but the value is `builtins.deepSeq`ed first.
145
146
147 # Inputs
148
149 `x`
150
151 : The value to trace
152
153 `y`
154
155 : The value to return
156
157 # Type
158
159 ```
160 traceSeq :: a -> b -> b
161 ```
162
163 # Examples
164 :::{.example}
165 ## `lib.debug.traceSeq` usage example
166
167 ```nix
168 trace { a.b.c = 3; } null
169 trace: { a = <CODE>; }
170 => null
171 traceSeq { a.b.c = 3; } null
172 trace: { a = { b = { c = 3; }; }; }
173 => null
174 ```
175
176 :::
177 */
178 traceSeq =
179 x:
180 y: trace (builtins.deepSeq x x) y;
181
182 /**
183 Like `traceSeq`, but only evaluate down to depth n.
184 This is very useful because lots of `traceSeq` usages
185 lead to an infinite recursion.
186
187
188 # Inputs
189
190 `depth`
191
192 : 1\. Function argument
193
194 `x`
195
196 : 2\. Function argument
197
198 `y`
199
200 : 3\. Function argument
201
202 # Type
203
204 ```
205 traceSeqN :: Int -> a -> b -> b
206 ```
207
208 # Examples
209 :::{.example}
210 ## `lib.debug.traceSeqN` usage example
211
212 ```nix
213 traceSeqN 2 { a.b.c = 3; } null
214 trace: { a = { b = {…}; }; }
215 => null
216 ```
217
218 :::
219 */
220 traceSeqN = depth: x: y:
221 let snip = v: if isList v then noQuotes "[…]" v
222 else if isAttrs v then noQuotes "{…}" v
223 else v;
224 noQuotes = str: v: { __pretty = const str; val = v; };
225 modify = n: fn: v: if (n == 0) then fn v
226 else if isList v then map (modify (n - 1) fn) v
227 else if isAttrs v then mapAttrs
228 (const (modify (n - 1) fn)) v
229 else v;
230 in trace (generators.toPretty { allowPrettyValues = true; }
231 (modify depth snip x)) y;
232
233 /**
234 A combination of `traceVal` and `traceSeq` that applies a
235 provided function to the value to be traced after `deepSeq`ing
236 it.
237
238
239 # Inputs
240
241 `f`
242
243 : Function to apply
244
245 `v`
246
247 : Value to trace
248 */
249 traceValSeqFn =
250 f:
251 v: traceValFn f (builtins.deepSeq v v);
252
253 /**
254 A combination of `traceVal` and `traceSeq`.
255
256 # Inputs
257
258 `v`
259
260 : Value to trace
261
262 */
263 traceValSeq = traceValSeqFn id;
264
265 /**
266 A combination of `traceVal` and `traceSeqN` that applies a
267 provided function to the value to be traced.
268
269
270 # Inputs
271
272 `f`
273
274 : Function to apply
275
276 `depth`
277
278 : 2\. Function argument
279
280 `v`
281
282 : Value to trace
283 */
284 traceValSeqNFn =
285 f:
286 depth:
287 v: traceSeqN depth (f v) v;
288
289 /**
290 A combination of `traceVal` and `traceSeqN`.
291
292 # Inputs
293
294 `depth`
295
296 : 1\. Function argument
297
298 `v`
299
300 : Value to trace
301 */
302 traceValSeqN = traceValSeqNFn id;
303
304 /**
305 Trace the input and output of a function `f` named `name`,
306 both down to `depth`.
307
308 This is useful for adding around a function call,
309 to see the before/after of values as they are transformed.
310
311
312 # Inputs
313
314 `depth`
315
316 : 1\. Function argument
317
318 `name`
319
320 : 2\. Function argument
321
322 `f`
323
324 : 3\. Function argument
325
326 `v`
327
328 : 4\. Function argument
329
330
331 # Examples
332 :::{.example}
333 ## `lib.debug.traceFnSeqN` usage example
334
335 ```nix
336 traceFnSeqN 2 "id" (x: x) { a.b.c = 3; }
337 trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; }
338 => { a.b.c = 3; }
339 ```
340
341 :::
342 */
343 traceFnSeqN = depth: name: f: v:
344 let res = f v;
345 in lib.traceSeqN
346 (depth + 1)
347 {
348 fn = name;
349 from = v;
350 to = res;
351 }
352 res;
353
354
355 # -- TESTING --
356
357 /**
358 Evaluates a set of tests.
359
360 A test is an attribute set `{expr, expected}`,
361 denoting an expression and its expected result.
362
363 The result is a `list` of __failed tests__, each represented as
364 `{name, expected, result}`,
365
366 - expected
367 - What was passed as `expected`
368 - result
369 - The actual `result` of the test
370
371 Used for regression testing of the functions in lib; see
372 tests.nix for more examples.
373
374 Important: Only attributes that start with `test` are executed.
375
376 - If you want to run only a subset of the tests add the attribute `tests = ["testName"];`
377
378
379 # Inputs
380
381 `tests`
382
383 : Tests to run
384
385 # Type
386
387 ```
388 runTests :: {
389 tests = [ String ];
390 ${testName} :: {
391 expr :: a;
392 expected :: a;
393 };
394 }
395 ->
396 [
397 {
398 name :: String;
399 expected :: a;
400 result :: a;
401 }
402 ]
403 ```
404
405 # Examples
406 :::{.example}
407 ## `lib.debug.runTests` usage example
408
409 ```nix
410 runTests {
411 testAndOk = {
412 expr = lib.and true false;
413 expected = false;
414 };
415 testAndFail = {
416 expr = lib.and true false;
417 expected = true;
418 };
419 }
420 ->
421 [
422 {
423 name = "testAndFail";
424 expected = true;
425 result = false;
426 }
427 ]
428 ```
429
430 :::
431 */
432 runTests =
433 tests: concatLists (attrValues (mapAttrs (name: test:
434 let testsToRun = if tests ? tests then tests.tests else [];
435 in if (substring 0 4 name == "test" || elem name testsToRun)
436 && ((testsToRun == []) || elem name tests.tests)
437 && (test.expr != test.expected)
438
439 then [ { inherit name; expected = test.expected; result = test.expr; } ]
440 else [] ) tests));
441
442 /**
443 Create a test assuming that list elements are `true`.
444
445
446 # Inputs
447
448 `expr`
449
450 : 1\. Function argument
451
452
453 # Examples
454 :::{.example}
455 ## `lib.debug.testAllTrue` usage example
456
457 ```nix
458 { testX = allTrue [ true ]; }
459 ```
460
461 :::
462 */
463 testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };
464}