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 Type: traceSeqN :: Int -> a -> b -> b
114 */
115 traceSeqN = depth: x: y:
116 let snip = v: if isList v then noQuotes "[…]" v
117 else if isAttrs v then noQuotes "{…}" v
118 else v;
119 noQuotes = str: v: { __pretty = const str; val = v; };
120 modify = n: fn: v: if (n == 0) then fn v
121 else if isList v then map (modify (n - 1) fn) v
122 else if isAttrs v then mapAttrs
123 (const (modify (n - 1) fn)) v
124 else v;
125 in trace (generators.toPretty { allowPrettyValues = true; }
126 (modify depth snip x)) y;
127
128 /* A combination of `traceVal` and `traceSeq` that applies a
129 provided function to the value to be traced after `deepSeq`ing
130 it.
131 */
132 traceValSeqFn =
133 # Function to apply
134 f:
135 # Value to trace
136 v: traceValFn f (builtins.deepSeq v v);
137
138 /* A combination of `traceVal` and `traceSeq`. */
139 traceValSeq = traceValSeqFn id;
140
141 /* A combination of `traceVal` and `traceSeqN` that applies a
142 provided function to the value to be traced. */
143 traceValSeqNFn =
144 # Function to apply
145 f:
146 depth:
147 # Value to trace
148 v: traceSeqN depth (f v) v;
149
150 /* A combination of `traceVal` and `traceSeqN`. */
151 traceValSeqN = traceValSeqNFn id;
152
153 /* Trace the input and output of a function `f` named `name`,
154 both down to `depth`.
155
156 This is useful for adding around a function call,
157 to see the before/after of values as they are transformed.
158
159 Example:
160 traceFnSeqN 2 "id" (x: x) { a.b.c = 3; }
161 trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; }
162 => { a.b.c = 3; }
163 */
164 traceFnSeqN = depth: name: f: v:
165 let res = f v;
166 in lib.traceSeqN
167 (depth + 1)
168 {
169 fn = name;
170 from = v;
171 to = res;
172 }
173 res;
174
175
176 # -- TESTING --
177
178 /* Evaluates a set of tests.
179
180 A test is an attribute set `{expr, expected}`,
181 denoting an expression and its expected result.
182
183 The result is a `list` of __failed tests__, each represented as
184 `{name, expected, result}`,
185
186 - expected
187 - What was passed as `expected`
188 - result
189 - The actual `result` of the test
190
191 Used for regression testing of the functions in lib; see
192 tests.nix for more examples.
193
194 Important: Only attributes that start with `test` are executed.
195
196 - If you want to run only a subset of the tests add the attribute `tests = ["testName"];`
197
198 Example:
199
200 runTests {
201 testAndOk = {
202 expr = lib.and true false;
203 expected = false;
204 };
205 testAndFail = {
206 expr = lib.and true false;
207 expected = true;
208 };
209 }
210 ->
211 [
212 {
213 name = "testAndFail";
214 expected = true;
215 result = false;
216 }
217 ]
218
219 Type:
220 runTests :: {
221 tests = [ String ];
222 ${testName} :: {
223 expr :: a;
224 expected :: a;
225 };
226 }
227 ->
228 [
229 {
230 name :: String;
231 expected :: a;
232 result :: a;
233 }
234 ]
235 */
236 runTests =
237 # Tests to run
238 tests: concatLists (attrValues (mapAttrs (name: test:
239 let testsToRun = if tests ? tests then tests.tests else [];
240 in if (substring 0 4 name == "test" || elem name testsToRun)
241 && ((testsToRun == []) || elem name tests.tests)
242 && (test.expr != test.expected)
243
244 then [ { inherit name; expected = test.expected; result = test.expr; } ]
245 else [] ) tests));
246
247 /* Create a test assuming that list elements are `true`.
248
249 Example:
250 { testX = allTrue [ true ]; }
251 */
252 testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };
253}