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