typioca: 2.7.0 -> 2.8.0
[NixPkgs.git] / lib / debug.nix
blob97e87acccf0efd9a1d06894ba242a3d7ba662c18
1 /* Collection of functions useful for debugging
2    broken nix expressions.
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.
15 { lib }:
16 let
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;
31 rec {
33   # -- TRACING --
35   /* Conditionally trace the supplied message, based on a predicate.
37      Type: traceIf :: bool -> string -> a -> a
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;
52   /* Trace the supplied value after applying a function to it, and
53      return the original value.
55      Type: traceValFn :: (a -> b) -> a -> a
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;
68   /* Trace the supplied value and return it.
70      Type: traceVal :: a -> a
72      Example:
73        traceVal 42
74        # trace: 42
75        => 42
76   */
77   traceVal = traceValFn id;
79   /* `builtins.trace`, but the value is `builtins.deepSeq`ed first.
81      Type: traceSeq :: a -> b -> b
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;
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.
101      Example:
102        traceSeqN 2 { a.b.c = 3; } null
103        trace: { a = { b = {…}; }; }
104        => null
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;
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);
131   /* A combination of `traceVal` and `traceSeq`. */
132   traceValSeq = traceValSeqFn id;
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;
143   /* A combination of `traceVal` and `traceSeqN`. */
144   traceValSeqN = traceValSeqNFn id;
146   /* Trace the input and output of a function `f` named `name`,
147   both down to `depth`.
149   This is useful for adding around a function call,
150   to see the before/after of values as they are transformed.
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;
169   # -- TESTING --
171   /* Evaluates a set of tests.
173      A test is an attribute set `{expr, expected}`,
174      denoting an expression and its expected result.
176      The result is a `list` of __failed tests__, each represented as
177      `{name, expected, result}`,
179      - expected
180        - What was passed as `expected`
181      - result
182        - The actual `result` of the test
184      Used for regression testing of the functions in lib; see
185      tests.nix for more examples.
187      Important: Only attributes that start with `test` are executed.
189      - If you want to run only a subset of the tests add the attribute `tests = ["testName"];`
191     Example:
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      ]
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)
237       then [ { inherit name; expected = test.expected; result = test.expr; } ]
238       else [] ) tests));
240   /* Create a test assuming that list elements are `true`.
242      Example:
243        { testX = allTrue [ true ]; }
244   */
245   testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };