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}