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