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}