at 24.05-pre 6.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 isList 19 isAttrs 20 substring 21 attrValues 22 concatLists 23 const 24 elem 25 generators 26 id 27 mapAttrs 28 trace; 29in 30 31rec { 32 33 # -- TRACING -- 34 35 /* Conditionally trace the supplied message, based on a predicate. 36 37 Type: traceIf :: bool -> string -> a -> a 38 39 Example: 40 traceIf true "hello" 3 41 trace: hello 42 => 3 43 */ 44 traceIf = 45 # Predicate to check 46 pred: 47 # Message that should be traced 48 msg: 49 # Value to return 50 x: if pred then trace msg x else x; 51 52 /* Trace the supplied value after applying a function to it, and 53 return the original value. 54 55 Type: traceValFn :: (a -> b) -> a -> a 56 57 Example: 58 traceValFn (v: "mystring ${v}") "foo" 59 trace: mystring foo 60 => "foo" 61 */ 62 traceValFn = 63 # Function to apply 64 f: 65 # Value to trace and return 66 x: trace (f x) x; 67 68 /* Trace the supplied value and return it. 69 70 Type: traceVal :: a -> a 71 72 Example: 73 traceVal 42 74 # trace: 42 75 => 42 76 */ 77 traceVal = traceValFn id; 78 79 /* `builtins.trace`, but the value is `builtins.deepSeq`ed first. 80 81 Type: traceSeq :: a -> b -> b 82 83 Example: 84 trace { a.b.c = 3; } null 85 trace: { a = <CODE>; } 86 => null 87 traceSeq { a.b.c = 3; } null 88 trace: { a = { b = { c = 3; }; }; } 89 => null 90 */ 91 traceSeq = 92 # The value to trace 93 x: 94 # The value to return 95 y: trace (builtins.deepSeq x x) y; 96 97 /* Like `traceSeq`, but only evaluate down to depth n. 98 This is very useful because lots of `traceSeq` usages 99 lead to an infinite recursion. 100 101 Example: 102 traceSeqN 2 { a.b.c = 3; } null 103 trace: { a = { b = {}; }; } 104 => null 105 106 Type: traceSeqN :: Int -> a -> b -> b 107 */ 108 traceSeqN = depth: x: y: 109 let snip = v: if isList v then noQuotes "[]" v 110 else if isAttrs v then noQuotes "{}" v 111 else v; 112 noQuotes = str: v: { __pretty = const str; val = v; }; 113 modify = n: fn: v: if (n == 0) then fn v 114 else if isList v then map (modify (n - 1) fn) v 115 else if isAttrs v then mapAttrs 116 (const (modify (n - 1) fn)) v 117 else v; 118 in trace (generators.toPretty { allowPrettyValues = true; } 119 (modify depth snip x)) y; 120 121 /* A combination of `traceVal` and `traceSeq` that applies a 122 provided function to the value to be traced after `deepSeq`ing 123 it. 124 */ 125 traceValSeqFn = 126 # Function to apply 127 f: 128 # Value to trace 129 v: traceValFn f (builtins.deepSeq v v); 130 131 /* A combination of `traceVal` and `traceSeq`. */ 132 traceValSeq = traceValSeqFn id; 133 134 /* A combination of `traceVal` and `traceSeqN` that applies a 135 provided function to the value to be traced. */ 136 traceValSeqNFn = 137 # Function to apply 138 f: 139 depth: 140 # Value to trace 141 v: traceSeqN depth (f v) v; 142 143 /* A combination of `traceVal` and `traceSeqN`. */ 144 traceValSeqN = traceValSeqNFn id; 145 146 /* Trace the input and output of a function `f` named `name`, 147 both down to `depth`. 148 149 This is useful for adding around a function call, 150 to see the before/after of values as they are transformed. 151 152 Example: 153 traceFnSeqN 2 "id" (x: x) { a.b.c = 3; } 154 trace: { fn = "id"; from = { a.b = {}; }; to = { a.b = {}; }; } 155 => { a.b.c = 3; } 156 */ 157 traceFnSeqN = depth: name: f: v: 158 let res = f v; 159 in lib.traceSeqN 160 (depth + 1) 161 { 162 fn = name; 163 from = v; 164 to = res; 165 } 166 res; 167 168 169 # -- TESTING -- 170 171 /* Evaluates a set of tests. 172 173 A test is an attribute set `{expr, expected}`, 174 denoting an expression and its expected result. 175 176 The result is a `list` of __failed tests__, each represented as 177 `{name, expected, result}`, 178 179 - expected 180 - What was passed as `expected` 181 - result 182 - The actual `result` of the test 183 184 Used for regression testing of the functions in lib; see 185 tests.nix for more examples. 186 187 Important: Only attributes that start with `test` are executed. 188 189 - If you want to run only a subset of the tests add the attribute `tests = ["testName"];` 190 191 Example: 192 193 runTests { 194 testAndOk = { 195 expr = lib.and true false; 196 expected = false; 197 }; 198 testAndFail = { 199 expr = lib.and true false; 200 expected = true; 201 }; 202 } 203 -> 204 [ 205 { 206 name = "testAndFail"; 207 expected = true; 208 result = false; 209 } 210 ] 211 212 Type: 213 runTests :: { 214 tests = [ String ]; 215 ${testName} :: { 216 expr :: a; 217 expected :: a; 218 }; 219 } 220 -> 221 [ 222 { 223 name :: String; 224 expected :: a; 225 result :: a; 226 } 227 ] 228 */ 229 runTests = 230 # Tests to run 231 tests: concatLists (attrValues (mapAttrs (name: test: 232 let testsToRun = if tests ? tests then tests.tests else []; 233 in if (substring 0 4 name == "test" || elem name testsToRun) 234 && ((testsToRun == []) || elem name tests.tests) 235 && (test.expr != test.expected) 236 237 then [ { inherit name; expected = test.expected; result = test.expr; } ] 238 else [] ) tests)); 239 240 /* Create a test assuming that list elements are `true`. 241 242 Example: 243 { testX = allTrue [ true ]; } 244 */ 245 testAllTrue = expr: { inherit expr; expected = map (x: true) expr; }; 246}