nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
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 concatMapStringsSep
20 isList
21 isAttrs
22 substring
23 attrValues
24 concatLists
25 const
26 elem
27 foldl'
28 generators
29 id
30 mapAttrs
31 trace
32 ;
33in
34
35rec {
36
37 # -- TRACING --
38
39 /**
40 Conditionally trace the supplied message, based on a predicate.
41
42 # Inputs
43
44 `pred`
45
46 : Predicate to check
47
48 `msg`
49
50 : Message that should be traced
51
52 `x`
53
54 : Value to return
55
56 # Type
57
58 ```
59 traceIf :: bool -> string -> a -> a
60 ```
61
62 # Examples
63 :::{.example}
64 ## `lib.debug.traceIf` usage example
65
66 ```nix
67 traceIf true "hello" 3
68 trace: hello
69 => 3
70 ```
71
72 :::
73 */
74 traceIf =
75 pred: msg: x:
76 if pred then trace msg x else x;
77
78 /**
79 Trace the supplied value after applying a function to it, and
80 return the original value.
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 = f: x: trace (f x) x;
111
112 /**
113 Trace the supplied value and return it.
114
115 # Inputs
116
117 `x`
118
119 : Value to trace and return
120
121 # Type
122
123 ```
124 traceVal :: a -> a
125 ```
126
127 # Examples
128 :::{.example}
129 ## `lib.debug.traceVal` usage example
130
131 ```nix
132 traceVal 42
133 # trace: 42
134 => 42
135 ```
136
137 :::
138 */
139 traceVal = traceValFn id;
140
141 /**
142 `builtins.trace`, but the value is `builtins.deepSeq`ed first.
143
144 # Inputs
145
146 `x`
147
148 : The value to trace
149
150 `y`
151
152 : The value to return
153
154 # Type
155
156 ```
157 traceSeq :: a -> b -> b
158 ```
159
160 # Examples
161 :::{.example}
162 ## `lib.debug.traceSeq` usage example
163
164 ```nix
165 trace { a.b.c = 3; } null
166 trace: { a = <CODE>; }
167 => null
168 traceSeq { a.b.c = 3; } null
169 trace: { a = { b = { c = 3; }; }; }
170 => null
171 ```
172
173 :::
174 */
175 traceSeq = x: y: trace (builtins.deepSeq x x) y;
176
177 /**
178 Like `traceSeq`, but only evaluate down to depth n.
179 This is very useful because lots of `traceSeq` usages
180 lead to an infinite recursion.
181
182 # Inputs
183
184 `depth`
185
186 : 1\. Function argument
187
188 `x`
189
190 : 2\. Function argument
191
192 `y`
193
194 : 3\. Function argument
195
196 # Type
197
198 ```
199 traceSeqN :: Int -> a -> b -> b
200 ```
201
202 # Examples
203 :::{.example}
204 ## `lib.debug.traceSeqN` usage example
205
206 ```nix
207 traceSeqN 2 { a.b.c = 3; } null
208 trace: { a = { b = {…}; }; }
209 => null
210 ```
211
212 :::
213 */
214 traceSeqN =
215 depth: x: y:
216 let
217 snip =
218 v:
219 if isList v then
220 noQuotes "[…]" v
221 else if isAttrs v then
222 noQuotes "{…}" v
223 else
224 v;
225 noQuotes = str: v: {
226 __pretty = const str;
227 val = v;
228 };
229 modify =
230 n: fn: v:
231 if (n == 0) then
232 fn v
233 else if isList v then
234 map (modify (n - 1) fn) v
235 else if isAttrs v then
236 mapAttrs (const (modify (n - 1) fn)) v
237 else
238 v;
239 in
240 trace (generators.toPretty { allowPrettyValues = true; } (modify depth snip x)) y;
241
242 /**
243 A combination of `traceVal` and `traceSeq` that applies a
244 provided function to the value to be traced after `deepSeq`ing
245 it.
246
247 # Inputs
248
249 `f`
250
251 : Function to apply
252
253 `v`
254
255 : Value to trace
256 */
257 traceValSeqFn = f: v: traceValFn f (builtins.deepSeq v v);
258
259 /**
260 A combination of `traceVal` and `traceSeq`.
261
262 # Inputs
263
264 `v`
265
266 : Value to trace
267 */
268 traceValSeq = traceValSeqFn id;
269
270 /**
271 A combination of `traceVal` and `traceSeqN` that applies a
272 provided function to the value to be traced.
273
274 # Inputs
275
276 `f`
277
278 : Function to apply
279
280 `depth`
281
282 : 2\. Function argument
283
284 `v`
285
286 : Value to trace
287 */
288 traceValSeqNFn =
289 f: depth: v:
290 traceSeqN depth (f v) v;
291
292 /**
293 A combination of `traceVal` and `traceSeqN`.
294
295 # Inputs
296
297 `depth`
298
299 : 1\. Function argument
300
301 `v`
302
303 : Value to trace
304 */
305 traceValSeqN = traceValSeqNFn id;
306
307 /**
308 Trace the input and output of a function `f` named `name`,
309 both down to `depth`.
310
311 This is useful for adding around a function call,
312 to see the before/after of values as they are transformed.
313
314 # Inputs
315
316 `depth`
317
318 : 1\. Function argument
319
320 `name`
321
322 : 2\. Function argument
323
324 `f`
325
326 : 3\. Function argument
327
328 `v`
329
330 : 4\. Function argument
331
332 # Examples
333 :::{.example}
334 ## `lib.debug.traceFnSeqN` usage example
335
336 ```nix
337 traceFnSeqN 2 "id" (x: x) { a.b.c = 3; }
338 trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; }
339 => { a.b.c = 3; }
340 ```
341
342 :::
343 */
344 traceFnSeqN =
345 depth: name: f: v:
346 let
347 res = f v;
348 in
349 lib.traceSeqN (depth + 1) {
350 fn = name;
351 from = v;
352 to = res;
353 } res;
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 # Inputs
379
380 `tests`
381
382 : Tests to run
383
384 # Type
385
386 ```
387 runTests :: {
388 tests = [ String ];
389 ${testName} :: {
390 expr :: a;
391 expected :: a;
392 };
393 }
394 ->
395 [
396 {
397 name :: String;
398 expected :: a;
399 result :: a;
400 }
401 ]
402 ```
403
404 # Examples
405 :::{.example}
406 ## `lib.debug.runTests` usage example
407
408 ```nix
409 runTests {
410 testAndOk = {
411 expr = lib.and true false;
412 expected = false;
413 };
414 testAndFail = {
415 expr = lib.and true false;
416 expected = true;
417 };
418 }
419 ->
420 [
421 {
422 name = "testAndFail";
423 expected = true;
424 result = false;
425 }
426 ]
427 ```
428
429 :::
430 */
431 runTests =
432 tests:
433 concatLists (
434 attrValues (
435 mapAttrs (
436 name: test:
437 let
438 testsToRun = if tests ? tests then tests.tests else [ ];
439 in
440 if
441 (substring 0 4 name == "test" || elem name testsToRun)
442 && ((testsToRun == [ ]) || elem name tests.tests)
443 && (test.expr != test.expected)
444
445 then
446 [
447 {
448 inherit name;
449 expected = test.expected;
450 result = test.expr;
451 }
452 ]
453 else
454 [ ]
455 ) tests
456 )
457 );
458
459 /**
460 Pretty-print a list of test failures.
461
462 This takes an attribute set containing `failures` (a list of test failures
463 produced by `runTests`) and pretty-prints each failing test, before
464 throwing an error containing the raw test data as JSON.
465
466 If the input list is empty, `null` is returned.
467
468 # Inputs
469
470 `failures`
471
472 : A list of test failures (produced `runTests`), each containing `name`,
473 `expected`, and `result` attributes.
474
475 # Type
476
477 ```
478 throwTestFailures :: {
479 failures = [
480 {
481 name :: String;
482 expected :: a;
483 result :: a;
484 }
485 ];
486 }
487 ->
488 null
489 ```
490
491 # Examples
492 :::{.example}
493
494 ## `lib.debug.throwTestFailures` usage example
495
496 ```nix
497 throwTestFailures {
498 failures = [
499 {
500 name = "testDerivation";
501 expected = derivation {
502 name = "a";
503 builder = "bash";
504 system = "x86_64-linux";
505 };
506 result = derivation {
507 name = "b";
508 builder = "bash";
509 system = "x86_64-linux";
510 };
511 }
512 ];
513 }
514 ->
515 trace: FAIL testDerivation:
516 Expected: <derivation a>
517 Result: <derivation b>
518
519 error:
520 … while evaluating the file '...':
521
522 … caused by explicit throw
523 at /nix/store/.../lib/debug.nix:528:7:
524 527| in
525 528| throw (
526 | ^
527 529| builtins.seq traceFailures (
528
529 error: 1 tests failed:
530 - testDerivation
531
532 [{"expected":"/nix/store/xh7kyqp69mxkwspmi81a94m9xx74r8dr-a","name":"testDerivation","result":"/nix/store/503l84nir4zw57d1shfhai25bxxn16c6-b"}]
533 null
534 ```
535
536 :::
537 */
538 throwTestFailures =
539 {
540 failures,
541 description ? "tests",
542 ...
543 }:
544 if failures == [ ] then
545 null
546 else
547 let
548 toPretty =
549 value:
550 # Thanks to @Ma27 for this:
551 #
552 # > The `unsafeDiscardStringContext` is useful when the `toPretty`
553 # > stumbles upon a derivation that would be realized without it (I
554 # > ran into the problem when writing a test for a flake helper where
555 # > I creating a bunch of "mock" derivations for different systems
556 # > and Nix then tried to build those when the error-string got
557 # > forced).
558 #
559 # See: https://github.com/NixOS/nixpkgs/pull/416207#discussion_r2145942389
560 builtins.unsafeDiscardStringContext (generators.toPretty { allowPrettyValues = true; } value);
561
562 failureToPretty = failure: ''
563 FAIL ${toPretty failure.name}:
564 Expected:
565 ${toPretty failure.expected}
566
567 Result:
568 ${toPretty failure.result}
569 '';
570
571 traceFailures = foldl' (_accumulator: failure: traceVal (failureToPretty failure)) null failures;
572 in
573 throw (
574 builtins.seq traceFailures (
575 "${builtins.toString (builtins.length failures)} ${description} failed:\n- "
576 + (concatMapStringsSep "\n- " (failure: failure.name) failures)
577 + "\n\n"
578 + builtins.toJSON failures
579 )
580 );
581
582 /**
583 Create a test assuming that list elements are `true`.
584
585 # Inputs
586
587 `expr`
588
589 : 1\. Function argument
590
591 # Examples
592 :::{.example}
593 ## `lib.debug.testAllTrue` usage example
594
595 ```nix
596 { testX = allTrue [ true ]; }
597 ```
598
599 :::
600 */
601 testAllTrue = expr: {
602 inherit expr;
603 expected = map (x: true) expr;
604 };
605}