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