nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at release-25.11 605 lines 11 kB view raw
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}