Release NixOS 23.11
[NixPkgs.git] / lib / tests / misc.nix
blob8f4a37149d92c419a31a91de4676f855dc6bea9b
1 /*
2 Nix evaluation tests for various lib functions.
4 Since these tests are implemented with Nix evaluation, error checking is limited to what `builtins.tryEval` can detect, which is `throw`'s and `abort`'s, without error messages.
5 If you need to test error messages or more complex evaluations, see ./modules.sh, ./sources.sh or ./filesystem.sh as examples.
7 To run these tests:
9   [nixpkgs]$ nix-instantiate --eval --strict lib/tests/misc.nix
11 If the resulting list is empty, all tests passed.
12 Alternatively, to run all `lib` tests:
14   [nixpkgs]$ nix-build lib/tests/release.nix
16 with import ../default.nix;
18 let
19   testingThrow = expr: {
20     expr = (builtins.tryEval (builtins.seq expr "didn't throw"));
21     expected = { success = false; value = false; };
22   };
23   testingEval = expr: {
24     expr = (builtins.tryEval expr).success;
25     expected = true;
26   };
27   testingDeepThrow = expr: testingThrow (builtins.deepSeq expr expr);
29   testSanitizeDerivationName = { name, expected }:
30   let
31     drv = derivation {
32       name = strings.sanitizeDerivationName name;
33       builder = "x";
34       system = "x";
35     };
36   in {
37     # Evaluate the derivation so an invalid name would be caught
38     expr = builtins.seq drv.drvPath drv.name;
39     inherit expected;
40   };
44 runTests {
46 # CUSTOMIZATION
48   testFunctionArgsMakeOverridable = {
49     expr = functionArgs (makeOverridable ({ a, b, c ? null}: {}));
50     expected = { a = false; b = false; c = true; };
51   };
53   testFunctionArgsMakeOverridableOverride = {
54     expr = functionArgs (makeOverridable ({ a, b, c ? null }: {}) { a = 1; b = 2; }).override;
55     expected = { a = false; b = false; c = true; };
56   };
58 # TRIVIAL
60   testId = {
61     expr = id 1;
62     expected = 1;
63   };
65   testConst = {
66     expr = const 2 3;
67     expected = 2;
68   };
70   testPipe = {
71     expr = pipe 2 [
72       (x: x + 2) # 2 + 2 = 4
73       (x: x * 2) # 4 * 2 = 8
74     ];
75     expected = 8;
76   };
78   testPipeEmpty = {
79     expr = pipe 2 [];
80     expected = 2;
81   };
83   testPipeStrings = {
84     expr = pipe [ 3 4 ] [
85       (map toString)
86       (map (s: s + "\n"))
87       concatStrings
88     ];
89     expected = ''
90       3
91       4
92     '';
93   };
95   /*
96   testOr = {
97     expr = or true false;
98     expected = true;
99   };
100   */
102   testAnd = {
103     expr = and true false;
104     expected = false;
105   };
107   testFix = {
108     expr = fix (x: {a = if x ? a then "a" else "b";});
109     expected = {a = "a";};
110   };
112   testComposeExtensions = {
113     expr = let obj = makeExtensible (self: { foo = self.bar; });
114                f = self: super: { bar = false; baz = true; };
115                g = self: super: { bar = super.baz or false; };
116                f_o_g = composeExtensions f g;
117                composed = obj.extend f_o_g;
118            in composed.foo;
119     expected = true;
120   };
122   testComposeManyExtensions0 = {
123     expr = let obj = makeExtensible (self: { foo = true; });
124                emptyComposition = composeManyExtensions [];
125                composed = obj.extend emptyComposition;
126            in composed.foo;
127     expected = true;
128   };
130   testComposeManyExtensions =
131     let f = self: super: { bar = false; baz = true; };
132         g = self: super: { bar = super.baz or false; };
133         h = self: super: { qux = super.bar or false; };
134         obj = makeExtensible (self: { foo = self.qux; });
135     in {
136     expr = let composition = composeManyExtensions [f g h];
137                composed = obj.extend composition;
138            in composed.foo;
139     expected = (obj.extend (composeExtensions f (composeExtensions g h))).foo;
140   };
142   testBitAnd = {
143     expr = (bitAnd 3 10);
144     expected = 2;
145   };
147   testBitOr = {
148     expr = (bitOr 3 10);
149     expected = 11;
150   };
152   testBitXor = {
153     expr = (bitXor 3 10);
154     expected = 9;
155   };
157   testToHexString = {
158     expr = toHexString 250;
159     expected = "FA";
160   };
162   testToBaseDigits = {
163     expr = toBaseDigits 2 6;
164     expected = [ 1 1 0 ];
165   };
167   testFunctionArgsFunctor = {
168     expr = functionArgs { __functor = self: { a, b }: null; };
169     expected = { a = false; b = false; };
170   };
172   testFunctionArgsSetFunctionArgs = {
173     expr = functionArgs (setFunctionArgs (args: args.x) { x = false; });
174     expected = { x = false; };
175   };
177 # STRINGS
179   testConcatMapStrings = {
180     expr = concatMapStrings (x: x + ";") ["a" "b" "c"];
181     expected = "a;b;c;";
182   };
184   testConcatStringsSep = {
185     expr = concatStringsSep "," ["a" "b" "c"];
186     expected = "a,b,c";
187   };
189   testConcatLines = {
190     expr = concatLines ["a" "b" "c"];
191     expected = "a\nb\nc\n";
192   };
194   testReplicateString = {
195     expr = strings.replicate 5 "hello";
196     expected = "hellohellohellohellohello";
197   };
199   testSplitStringsSimple = {
200     expr = strings.splitString "." "a.b.c.d";
201     expected = [ "a" "b" "c" "d" ];
202   };
204   testSplitStringsEmpty = {
205     expr = strings.splitString "." "a..b";
206     expected = [ "a" "" "b" ];
207   };
209   testSplitStringsOne = {
210     expr = strings.splitString ":" "a.b";
211     expected = [ "a.b" ];
212   };
214   testSplitStringsNone = {
215     expr = strings.splitString "." "";
216     expected = [ "" ];
217   };
219   testSplitStringsFirstEmpty = {
220     expr = strings.splitString "/" "/a/b/c";
221     expected = [ "" "a" "b" "c" ];
222   };
224   testSplitStringsLastEmpty = {
225     expr = strings.splitString ":" "2001:db8:0:0042::8a2e:370:";
226     expected = [ "2001" "db8" "0" "0042" "" "8a2e" "370" "" ];
227   };
229   testSplitStringsRegex = {
230     expr = strings.splitString "\\[{}]()^$?*+|." "A\\[{}]()^$?*+|.B";
231     expected = [ "A" "B" ];
232   };
234   testSplitStringsDerivation = {
235     expr = take 3  (strings.splitString "/" (derivation {
236       name = "name";
237       builder = "builder";
238       system = "system";
239     }));
240     expected = ["" "nix" "store"];
241   };
243   testSplitVersionSingle = {
244     expr = versions.splitVersion "1";
245     expected = [ "1" ];
246   };
248   testSplitVersionDouble = {
249     expr = versions.splitVersion "1.2";
250     expected = [ "1" "2" ];
251   };
253   testSplitVersionTriple = {
254     expr = versions.splitVersion "1.2.3";
255     expected = [ "1" "2" "3" ];
256   };
258   testPadVersionLess = {
259     expr = versions.pad 3 "1.2";
260     expected = "1.2.0";
261   };
263   testPadVersionLessExtra = {
264     expr = versions.pad 3 "1.3-rc1";
265     expected = "1.3.0-rc1";
266   };
268   testPadVersionMore = {
269     expr = versions.pad 3 "1.2.3.4";
270     expected = "1.2.3";
271   };
273   testIsStorePath =  {
274     expr =
275       let goodPath =
276             "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11";
277       in {
278         storePath = isStorePath goodPath;
279         storePathDerivation = isStorePath (import ../.. { system = "x86_64-linux"; }).hello;
280         storePathAppendix = isStorePath
281           "${goodPath}/bin/python";
282         nonAbsolute = isStorePath (concatStrings (tail (stringToCharacters goodPath)));
283         asPath = isStorePath (/. + goodPath);
284         otherPath = isStorePath "/something/else";
285         otherVals = {
286           attrset = isStorePath {};
287           list = isStorePath [];
288           int = isStorePath 42;
289         };
290       };
291     expected = {
292       storePath = true;
293       storePathDerivation = true;
294       storePathAppendix = false;
295       nonAbsolute = false;
296       asPath = true;
297       otherPath = false;
298       otherVals = {
299         attrset = false;
300         list = false;
301         int = false;
302       };
303     };
304   };
306   testEscapeXML = {
307     expr = escapeXML ''"test" 'test' < & >'';
308     expected = "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;";
309   };
311   testToShellVars = {
312     expr = ''
313       ${toShellVars {
314         STRing01 = "just a 'string'";
315         _array_ = [ "with" "more strings" ];
316         assoc."with some" = ''
317           strings
318           possibly newlines
319         '';
320         drv = {
321           outPath = "/drv";
322           foo = "ignored attribute";
323         };
324         path = /path;
325         stringable = {
326           __toString = _: "hello toString";
327           bar = "ignored attribute";
328         };
329       }}
330     '';
331     expected = ''
332       STRing01='just a '\'''string'\''''
333       declare -a _array_=('with' 'more strings')
334       declare -A assoc=(['with some']='strings
335       possibly newlines
336       ')
337       drv='/drv'
338       path='/path'
339       stringable='hello toString'
340     '';
341   };
343   testHasInfixFalse = {
344     expr = hasInfix "c" "abde";
345     expected = false;
346   };
348   testHasInfixTrue = {
349     expr = hasInfix "c" "abcde";
350     expected = true;
351   };
353   testHasInfixDerivation = {
354     expr = hasInfix "hello" (import ../.. { system = "x86_64-linux"; }).hello;
355     expected = true;
356   };
358   testHasInfixPath = {
359     expr = hasInfix "tests" ./.;
360     expected = true;
361   };
363   testHasInfixPathStoreDir = {
364     expr = hasInfix builtins.storeDir ./.;
365     expected = true;
366   };
368   testHasInfixToString = {
369     expr = hasInfix "a" { __toString = _: "a"; };
370     expected = true;
371   };
373   testRemovePrefixExample1 = {
374     expr = removePrefix "foo." "foo.bar.baz";
375     expected = "bar.baz";
376   };
377   testRemovePrefixExample2 = {
378     expr = removePrefix "xxx" "foo.bar.baz";
379     expected = "foo.bar.baz";
380   };
381   testRemovePrefixEmptyPrefix = {
382     expr = removePrefix "" "foo";
383     expected = "foo";
384   };
385   testRemovePrefixEmptyString = {
386     expr = removePrefix "foo" "";
387     expected = "";
388   };
389   testRemovePrefixEmptyBoth = {
390     expr = removePrefix "" "";
391     expected = "";
392   };
394   testNormalizePath = {
395     expr = strings.normalizePath "//a/b//c////d/";
396     expected = "/a/b/c/d/";
397   };
399   testCharToInt = {
400     expr = strings.charToInt "A";
401     expected = 65;
402   };
404   testEscapeC = {
405     expr = strings.escapeC [ " " ] "Hello World";
406     expected = "Hello\\x20World";
407   };
409   testEscapeURL = testAllTrue [
410     ("" == strings.escapeURL "")
411     ("Hello" == strings.escapeURL "Hello")
412     ("Hello%20World" == strings.escapeURL "Hello World")
413     ("Hello%2FWorld" == strings.escapeURL "Hello/World")
414     ("42%25" == strings.escapeURL "42%")
415     ("%20%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%09%3A%2F%40%24%27%28%29%2A%2C%3B" == strings.escapeURL " ?&=#+%!<>#\"{}|\\^[]`\t:/@$'()*,;")
416   ];
418   testToInt = testAllTrue [
419     # Naive
420     (123 == toInt "123")
421     (0 == toInt "0")
422     # Whitespace Padding
423     (123 == toInt " 123")
424     (123 == toInt "123 ")
425     (123 == toInt " 123 ")
426     (123 == toInt "   123   ")
427     (0 == toInt " 0")
428     (0 == toInt "0 ")
429     (0 == toInt " 0 ")
430     (-1 == toInt "-1")
431     (-1 == toInt " -1 ")
432   ];
434   testToIntFails = testAllTrue [
435     ( builtins.tryEval (toInt "") == { success = false; value = false; } )
436     ( builtins.tryEval (toInt "123 123") == { success = false; value = false; } )
437     ( builtins.tryEval (toInt "0 123") == { success = false; value = false; } )
438     ( builtins.tryEval (toInt " 0d ") == { success = false; value = false; } )
439     ( builtins.tryEval (toInt " 1d ") == { success = false; value = false; } )
440     ( builtins.tryEval (toInt " d0 ") == { success = false; value = false; } )
441     ( builtins.tryEval (toInt "00") == { success = false; value = false; } )
442     ( builtins.tryEval (toInt "01") == { success = false; value = false; } )
443     ( builtins.tryEval (toInt "002") == { success = false; value = false; } )
444     ( builtins.tryEval (toInt " 002 ") == { success = false; value = false; } )
445     ( builtins.tryEval (toInt " foo ") == { success = false; value = false; } )
446     ( builtins.tryEval (toInt " foo 123 ") == { success = false; value = false; } )
447     ( builtins.tryEval (toInt " foo123 ") == { success = false; value = false; } )
448   ];
450   testToIntBase10 = testAllTrue [
451     # Naive
452     (123 == toIntBase10 "123")
453     (0 == toIntBase10 "0")
454     # Whitespace Padding
455     (123 == toIntBase10 " 123")
456     (123 == toIntBase10 "123 ")
457     (123 == toIntBase10 " 123 ")
458     (123 == toIntBase10 "   123   ")
459     (0 == toIntBase10 " 0")
460     (0 == toIntBase10 "0 ")
461     (0 == toIntBase10 " 0 ")
462     # Zero Padding
463     (123 == toIntBase10 "0123")
464     (123 == toIntBase10 "0000123")
465     (0 == toIntBase10 "000000")
466     # Whitespace and Zero Padding
467     (123 == toIntBase10 " 0123")
468     (123 == toIntBase10 "0123 ")
469     (123 == toIntBase10 " 0123 ")
470     (123 == toIntBase10 " 0000123")
471     (123 == toIntBase10 "0000123 ")
472     (123 == toIntBase10 " 0000123 ")
473     (0 == toIntBase10 " 000000")
474     (0 == toIntBase10 "000000 ")
475     (0 == toIntBase10 " 000000 ")
476     (-1 == toIntBase10 "-1")
477     (-1 == toIntBase10 " -1 ")
478   ];
480   testToIntBase10Fails = testAllTrue [
481     ( builtins.tryEval (toIntBase10 "") == { success = false; value = false; } )
482     ( builtins.tryEval (toIntBase10 "123 123") == { success = false; value = false; } )
483     ( builtins.tryEval (toIntBase10 "0 123") == { success = false; value = false; } )
484     ( builtins.tryEval (toIntBase10 " 0d ") == { success = false; value = false; } )
485     ( builtins.tryEval (toIntBase10 " 1d ") == { success = false; value = false; } )
486     ( builtins.tryEval (toIntBase10 " d0 ") == { success = false; value = false; } )
487     ( builtins.tryEval (toIntBase10 " foo ") == { success = false; value = false; } )
488     ( builtins.tryEval (toIntBase10 " foo 123 ") == { success = false; value = false; } )
489     ( builtins.tryEval (toIntBase10 " foo 00123 ") == { success = false; value = false; } )
490     ( builtins.tryEval (toIntBase10 " foo00123 ") == { success = false; value = false; } )
491   ];
493 # LISTS
495   testFilter = {
496     expr = filter (x: x != "a") ["a" "b" "c" "a"];
497     expected = ["b" "c"];
498   };
500   testFold =
501     let
502       f = op: fold: fold op 0 (range 0 100);
503       # fold with associative operator
504       assoc = f builtins.add;
505       # fold with non-associative operator
506       nonAssoc = f builtins.sub;
507     in {
508       expr = {
509         assocRight = assoc foldr;
510         # right fold with assoc operator is same as left fold
511         assocRightIsLeft = assoc foldr == assoc foldl;
512         nonAssocRight = nonAssoc foldr;
513         nonAssocLeft = nonAssoc foldl;
514         # with non-assoc operator the fold results are not the same
515         nonAssocRightIsNotLeft = nonAssoc foldl != nonAssoc foldr;
516         # fold is an alias for foldr
517         foldIsRight = nonAssoc fold == nonAssoc foldr;
518       };
519       expected = {
520         assocRight = 5050;
521         assocRightIsLeft = true;
522         nonAssocRight = 50;
523         nonAssocLeft = (-5050);
524         nonAssocRightIsNotLeft = true;
525         foldIsRight = true;
526       };
527     };
529   testFoldl'Empty = {
530     expr = foldl' (acc: el: abort "operation not called") 0 [ ];
531     expected = 0;
532   };
534   testFoldl'IntegerAdding = {
535     expr = foldl' (acc: el: acc + el) 0 [ 1 2 3 ];
536     expected = 6;
537   };
539   # The accumulator isn't forced deeply
540   testFoldl'NonDeep = {
541     expr = take 3 (foldl'
542       (acc: el: [ el ] ++ acc)
543       [ (abort "unevaluated list entry") ]
544       [ 1 2 3 ]);
545     expected = [ 3 2 1 ];
546   };
548   # Compared to builtins.foldl', lib.foldl' evaluates the first accumulator strictly too
549   testFoldl'StrictInitial = {
550     expr = (builtins.tryEval (foldl' (acc: el: el) (throw "hello") [])).success;
551     expected = false;
552   };
554   # Make sure we don't get a stack overflow for large lists
555   # This number of elements would notably cause a stack overflow if it was implemented without the `foldl'` builtin
556   testFoldl'Large = {
557     expr = foldl' (acc: el: acc + el) 0 (range 0 100000);
558     expected = 5000050000;
559   };
561   testTake = testAllTrue [
562     ([] == (take 0 [  1 2 3 ]))
563     ([1] == (take 1 [  1 2 3 ]))
564     ([ 1 2 ] == (take 2 [  1 2 3 ]))
565     ([ 1 2 3 ] == (take 3 [  1 2 3 ]))
566     ([ 1 2 3 ] == (take 4 [  1 2 3 ]))
567   ];
569   testListHasPrefixExample1 = {
570     expr = lists.hasPrefix [ 1 2 ] [ 1 2 3 4 ];
571     expected = true;
572   };
573   testListHasPrefixExample2 = {
574     expr = lists.hasPrefix [ 0 1 ] [ 1 2 3 4 ];
575     expected = false;
576   };
577   testListHasPrefixLazy = {
578     expr = lists.hasPrefix [ 1 ] [ 1 (abort "lib.lists.hasPrefix is not lazy") ];
579     expected = true;
580   };
581   testListHasPrefixEmptyPrefix = {
582     expr = lists.hasPrefix [ ] [ 1 2 ];
583     expected = true;
584   };
585   testListHasPrefixEmptyList = {
586     expr = lists.hasPrefix [ 1 2 ] [ ];
587     expected = false;
588   };
590   testListRemovePrefixExample1 = {
591     expr = lists.removePrefix [ 1 2 ] [ 1 2 3 4 ];
592     expected = [ 3 4 ];
593   };
594   testListRemovePrefixExample2 = {
595     expr = (builtins.tryEval (lists.removePrefix [ 0 1 ] [ 1 2 3 4 ])).success;
596     expected = false;
597   };
598   testListRemovePrefixEmptyPrefix = {
599     expr = lists.removePrefix [ ] [ 1 2 ];
600     expected = [ 1 2 ];
601   };
602   testListRemovePrefixEmptyList = {
603     expr = (builtins.tryEval (lists.removePrefix [ 1 2 ] [ ])).success;
604     expected = false;
605   };
607   testFoldAttrs = {
608     expr = foldAttrs (n: a: [n] ++ a) [] [
609     { a = 2; b = 7; }
610     { a = 3;        c = 8; }
611     ];
612     expected = { a = [ 2 3 ]; b = [7]; c = [8];};
613   };
615   testListCommonPrefixExample1 = {
616     expr = lists.commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ];
617     expected = [ 1 2 ];
618   };
619   testListCommonPrefixExample2 = {
620     expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ];
621     expected = [ 1 2 3 ];
622   };
623   testListCommonPrefixExample3 = {
624     expr = lists.commonPrefix [ 1 2 3 ] [ 4 5 6 ];
625     expected = [ ];
626   };
627   testListCommonPrefixEmpty = {
628     expr = lists.commonPrefix [ ] [ 1 2 3 ];
629     expected = [ ];
630   };
631   testListCommonPrefixSame = {
632     expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 ];
633     expected = [ 1 2 3 ];
634   };
635   testListCommonPrefixLazy = {
636     expr = lists.commonPrefix [ 1 ] [ 1 (abort "lib.lists.commonPrefix shouldn't evaluate this")];
637     expected = [ 1 ];
638   };
639   # This would stack overflow if `commonPrefix` were implemented using recursion
640   testListCommonPrefixLong =
641     let
642       longList = genList (n: n) 100000;
643     in {
644       expr = lists.commonPrefix longList longList;
645       expected = longList;
646     };
648   testSort = {
649     expr = sort builtins.lessThan [ 40 2 30 42 ];
650     expected = [2 30 40 42];
651   };
653   testReplicate = {
654     expr = replicate 3 "a";
655     expected = ["a" "a" "a"];
656   };
658   testToIntShouldConvertStringToInt = {
659     expr = toInt "27";
660     expected = 27;
661   };
663   testToIntShouldThrowErrorIfItCouldNotConvertToInt = {
664     expr = builtins.tryEval (toInt "\"foo\"");
665     expected = { success = false; value = false; };
666   };
668   testHasAttrByPathTrue = {
669     expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; };
670     expected = true;
671   };
673   testHasAttrByPathFalse = {
674     expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; };
675     expected = false;
676   };
678   testFindFirstIndexExample1 = {
679     expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ];
680     expected = 1;
681   };
683   testFindFirstIndexExample2 = {
684     expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ 1 6 4 ];
685     expected = "a very specific default";
686   };
688   testFindFirstIndexEmpty = {
689     expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null [];
690     expected = null;
691   };
693   testFindFirstIndexSingleMatch = {
694     expr = lists.findFirstIndex (x: x == 5) null [ 5 ];
695     expected = 0;
696   };
698   testFindFirstIndexSingleDefault = {
699     expr = lists.findFirstIndex (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
700     expected = null;
701   };
703   testFindFirstIndexNone = {
704     expr = builtins.tryEval (lists.findFirstIndex (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
705     expected = { success = false; value = false; };
706   };
708   # Makes sure that the implementation doesn't cause a stack overflow
709   testFindFirstIndexBig = {
710     expr = lists.findFirstIndex (x: x == 1000000) null (range 0 1000000);
711     expected = 1000000;
712   };
714   testFindFirstIndexLazy = {
715     expr = lists.findFirstIndex (x: x == 1) null [ 1 (abort "list elements after the match must not be evaluated") ];
716     expected = 0;
717   };
719   testFindFirstExample1 = {
720     expr = lists.findFirst (x: x > 3) 7 [ 1 6 4 ];
721     expected = 6;
722   };
724   testFindFirstExample2 = {
725     expr = lists.findFirst (x: x > 9) 7 [ 1 6 4 ];
726     expected = 7;
727   };
729   testAllUnique_true = {
730     expr = allUnique [ 3 2 4 1 ];
731     expected = true;
732   };
733   testAllUnique_false = {
734     expr = allUnique [ 3 2 3 4 ];
735     expected = false;
736   };
738 # ATTRSETS
740   testConcatMapAttrs = {
741     expr = concatMapAttrs
742       (name: value: {
743         ${name} = value;
744         ${name + value} = value;
745       })
746       {
747         foo = "bar";
748         foobar = "baz";
749       };
750     expected = {
751       foo = "bar";
752       foobar = "baz";
753       foobarbaz = "baz";
754     };
755   };
757   # code from example
758   testFoldlAttrs = {
759     expr = {
760       example = foldlAttrs
761         (acc: name: value: {
762           sum = acc.sum + value;
763           names = acc.names ++ [ name ];
764         })
765         { sum = 0; names = [ ]; }
766         {
767           foo = 1;
768           bar = 10;
769         };
770       # should just return the initial value
771       emptySet = foldlAttrs (throw "function not needed") 123 { };
772       # should just evaluate to the last value
773       valuesNotNeeded = foldlAttrs (acc: _name: _v: acc) 3 { z = throw "value z not needed"; a = throw "value a not needed"; };
774       # the accumulator doesnt have to be an attrset it can be as trivial as being just a number or string
775       trivialAcc = foldlAttrs (acc: _name: v: acc * 10 + v) 1 { z = 1; a = 2; };
776     };
777     expected = {
778       example = {
779         sum = 11;
780         names = [ "bar" "foo" ];
781       };
782       emptySet = 123;
783       valuesNotNeeded = 3;
784       trivialAcc = 121;
785     };
786   };
789   testMergeAttrsListExample1 = {
790     expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ];
791     expected = { a = 0; b = 1; c = 2; d = 3; };
792   };
793   testMergeAttrsListExample2 = {
794     expr = attrsets.mergeAttrsList [ { a = 0; } { a = 1; } ];
795     expected = { a = 1; };
796   };
797   testMergeAttrsListExampleMany =
798     let
799       list = genList (n:
800         listToAttrs (genList (m:
801           let
802             # Integer divide n by two to create duplicate attributes
803             str = "halfn${toString (n / 2)}m${toString m}";
804           in
805           nameValuePair str str
806         ) 100)
807       ) 100;
808     in {
809       expr = attrsets.mergeAttrsList list;
810       expected = foldl' mergeAttrs { } list;
811     };
813   # code from the example
814   testRecursiveUpdateUntil = {
815     expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {
816       # first attribute set
817       foo.bar = 1;
818       foo.baz = 2;
819       bar = 3;
820     } {
821       #second attribute set
822       foo.bar = 1;
823       foo.quz = 2;
824       baz = 4;
825     };
826     expected = {
827       foo.bar = 1; # 'foo.*' from the second set
828       foo.quz = 2; #
829       bar = 3;     # 'bar' from the first set
830       baz = 4;     # 'baz' from the second set
831     };
832   };
834   testOverrideExistingEmpty = {
835     expr = overrideExisting {} { a = 1; };
836     expected = {};
837   };
839   testOverrideExistingDisjoint = {
840     expr = overrideExisting { b = 2; } { a = 1; };
841     expected = { b = 2; };
842   };
844   testOverrideExistingOverride = {
845     expr = overrideExisting { a = 3; b = 2; } { a = 1; };
846     expected = { a = 1; b = 2; };
847   };
849   testListAttrsReverse = let
850     exampleAttrs = {foo=1; bar="asdf"; baz = [1 3 3 7]; fnord=null;};
851     exampleSingletonList = [{name="foo"; value=1;}];
852   in {
853     expr = {
854       isReverseToListToAttrs = builtins.listToAttrs (attrsToList exampleAttrs) == exampleAttrs;
855       isReverseToAttrsToList = attrsToList (builtins.listToAttrs exampleSingletonList) == exampleSingletonList;
856       testDuplicatePruningBehaviour = attrsToList (builtins.listToAttrs [{name="a"; value=2;} {name="a"; value=1;}]);
857     };
858     expected = {
859       isReverseToAttrsToList = true;
860       isReverseToListToAttrs = true;
861       testDuplicatePruningBehaviour = [{name="a"; value=2;}];
862     };
863   };
865   testAttrsToListsCanDealWithFunctions = testingEval (
866     attrsToList { someFunc= a: a + 1;}
867   );
869 # GENERATORS
870 # these tests assume attributes are converted to lists
871 # in alphabetical order
873   testMkKeyValueDefault = {
874     expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar";
875     expected = ''f\:oo:bar'';
876   };
878   testMkValueString = {
879     expr = let
880       vals = {
881         int = 42;
882         string = ''fo"o'';
883         bool = true;
884         bool2 = false;
885         null = null;
886         # float = 42.23; # floats are strange
887       };
888       in mapAttrs
889         (const (generators.mkValueStringDefault {}))
890         vals;
891     expected = {
892       int = "42";
893       string = ''fo"o'';
894       bool = "true";
895       bool2 = "false";
896       null = "null";
897       # float = "42.23" true false [ "bar" ] ]'';
898     };
899   };
901   testToKeyValue = {
902     expr = generators.toKeyValue {} {
903       key = "value";
904       "other=key" = "baz";
905     };
906     expected = ''
907       key=value
908       other\=key=baz
909     '';
910   };
912   testToINIEmpty = {
913     expr = generators.toINI {} {};
914     expected = "";
915   };
917   testToINIEmptySection = {
918     expr = generators.toINI {} { foo = {}; bar = {}; };
919     expected = ''
920       [bar]
922       [foo]
923     '';
924   };
926   testToINIDuplicateKeys = {
927     expr = generators.toINI { listsAsDuplicateKeys = true; } { foo.bar = true; baz.qux = [ 1 false ]; };
928     expected = ''
929       [baz]
930       qux=1
931       qux=false
933       [foo]
934       bar=true
935     '';
936   };
938   testToINIDefaultEscapes = {
939     expr = generators.toINI {} {
940       "no [ and ] allowed unescaped" = {
941         "and also no = in keys" = 42;
942       };
943     };
944     expected = ''
945       [no \[ and \] allowed unescaped]
946       and also no \= in keys=42
947     '';
948   };
950   testToINIDefaultFull = {
951     expr = generators.toINI {} {
952       "section 1" = {
953         attribute1 = 5;
954         x = "Me-se JarJar Binx";
955         # booleans are converted verbatim by default
956         boolean = false;
957       };
958       "foo[]" = {
959         "he\\h=he" = "this is okay";
960       };
961     };
962     expected = ''
963       [foo\[\]]
964       he\h\=he=this is okay
966       [section 1]
967       attribute1=5
968       boolean=false
969       x=Me-se JarJar Binx
970     '';
971   };
973   testToINIWithGlobalSectionEmpty = {
974     expr = generators.toINIWithGlobalSection {} {
975       globalSection = {
976       };
977       sections = {
978       };
979     };
980     expected = ''
981     '';
982   };
984   testToINIWithGlobalSectionGlobalEmptyIsTheSameAsToINI =
985     let
986       sections = {
987         "section 1" = {
988           attribute1 = 5;
989           x = "Me-se JarJar Binx";
990         };
991         "foo" = {
992           "he\\h=he" = "this is okay";
993         };
994       };
995     in {
996       expr =
997         generators.toINIWithGlobalSection {} {
998             globalSection = {};
999             sections = sections;
1000         };
1001       expected = generators.toINI {} sections;
1002   };
1004   testToINIWithGlobalSectionFull = {
1005     expr = generators.toINIWithGlobalSection {} {
1006       globalSection = {
1007         foo = "bar";
1008         test = false;
1009       };
1010       sections = {
1011         "section 1" = {
1012           attribute1 = 5;
1013           x = "Me-se JarJar Binx";
1014         };
1015         "foo" = {
1016           "he\\h=he" = "this is okay";
1017         };
1018       };
1019     };
1020     expected = ''
1021       foo=bar
1022       test=false
1024       [foo]
1025       he\h\=he=this is okay
1027       [section 1]
1028       attribute1=5
1029       x=Me-se JarJar Binx
1030     '';
1031   };
1033   testToGitINI = {
1034     expr = generators.toGitINI {
1035       user = {
1036         email = "user@example.org";
1037         name = "John Doe";
1038         signingKey = "00112233445566778899AABBCCDDEEFF";
1039       };
1040       gpg.program = "path-to-gpg";
1041       tag.gpgSign = true;
1042       include.path = "~/path/to/config.inc";
1043       includeIf."gitdif:~/src/dir".path = "~/path/to/conditional.inc";
1044       extra = {
1045         boolean = true;
1046         integer = 38;
1047         name = "value";
1048         subsection.value = "test";
1049       };};
1050     expected = ''
1051       [extra]
1052       ${"\t"}boolean = true
1053       ${"\t"}integer = 38
1054       ${"\t"}name = "value"
1056       [extra "subsection"]
1057       ${"\t"}value = "test"
1059       [gpg]
1060       ${"\t"}program = "path-to-gpg"
1062       [include]
1063       ${"\t"}path = "~/path/to/config.inc"
1065       [includeIf "gitdif:~/src/dir"]
1066       ${"\t"}path = "~/path/to/conditional.inc"
1068       [tag]
1069       ${"\t"}gpgSign = true
1071       [user]
1072       ${"\t"}email = "user@example.org"
1073       ${"\t"}name = "John Doe"
1074       ${"\t"}signingKey = "00112233445566778899AABBCCDDEEFF"
1075     '';
1076   };
1078   /* right now only invocation check */
1079   testToJSONSimple =
1080     let val = {
1081       foobar = [ "baz" 1 2 3 ];
1082     };
1083     in {
1084       expr = generators.toJSON {} val;
1085       # trivial implementation
1086       expected = builtins.toJSON val;
1087   };
1089   /* right now only invocation check */
1090   testToYAMLSimple =
1091     let val = {
1092       list = [ { one = 1; } { two = 2; } ];
1093       all = 42;
1094     };
1095     in {
1096       expr = generators.toYAML {} val;
1097       # trivial implementation
1098       expected = builtins.toJSON val;
1099   };
1101   testToPretty =
1102     let
1103       deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
1104     in {
1105     expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
1106       int = 42;
1107       float = 0.1337;
1108       bool = true;
1109       emptystring = "";
1110       string = "fn\${o}\"r\\d";
1111       newlinestring = "\n";
1112       path = /. + "/foo";
1113       null_ = null;
1114       function = x: x;
1115       functionArgs = { arg ? 4, foo }: arg;
1116       list = [ 3 4 function [ false ] ];
1117       emptylist = [];
1118       attrs = { foo = null; "foo b/ar" = "baz"; };
1119       emptyattrs = {};
1120       drv = deriv;
1121     };
1122     expected = rec {
1123       int = "42";
1124       float = "0.1337";
1125       bool = "true";
1126       emptystring = ''""'';
1127       string = ''"fn\''${o}\"r\\d"'';
1128       newlinestring = "\"\\n\"";
1129       path = "/foo";
1130       null_ = "null";
1131       function = "<function>";
1132       functionArgs = "<function, args: {arg?, foo}>";
1133       list = "[ 3 4 ${function} [ false ] ]";
1134       emptylist = "[ ]";
1135       attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }";
1136       emptyattrs = "{ }";
1137       drv = "<derivation ${deriv.name}>";
1138     };
1139   };
1141   testToPrettyLimit =
1142     let
1143       a.b = 1;
1144       a.c = a;
1145     in {
1146       expr = generators.toPretty { } (generators.withRecursion { throwOnDepthLimit = false; depthLimit = 2; } a);
1147       expected = "{\n  b = 1;\n  c = {\n    b = \"<unevaluated>\";\n    c = {\n      b = \"<unevaluated>\";\n      c = \"<unevaluated>\";\n    };\n  };\n}";
1148     };
1150   testToPrettyLimitThrow =
1151     let
1152       a.b = 1;
1153       a.c = a;
1154     in {
1155       expr = (builtins.tryEval
1156         (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))).success;
1157       expected = false;
1158     };
1160   testWithRecursionDealsWithFunctors =
1161     let
1162       functor = {
1163         __functor = self: { a, b, }: null;
1164       };
1165       a = {
1166         value = "1234";
1167         b = functor;
1168         c.d = functor;
1169       };
1170     in {
1171       expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a);
1172       expected = "{\n  b = <function, args: {a, b}>;\n  c = {\n    d = \"<unevaluated>\";\n  };\n  value = \"<unevaluated>\";\n}";
1173     };
1175   testToPrettyMultiline = {
1176     expr = mapAttrs (const (generators.toPretty { })) rec {
1177       list = [ 3 4 [ false ] ];
1178       attrs = { foo = null; bar.foo = "baz"; };
1179       newlinestring = "\n";
1180       multilinestring = ''
1181         hello
1182         ''${there}
1183         te'''st
1184       '';
1185       multilinestring' = ''
1186         hello
1187         there
1188         test'';
1189     };
1190     expected = rec {
1191       list = ''
1192         [
1193           3
1194           4
1195           [
1196             false
1197           ]
1198         ]'';
1199       attrs = ''
1200         {
1201           bar = {
1202             foo = "baz";
1203           };
1204           foo = null;
1205         }'';
1206       newlinestring = "''\n  \n''";
1207       multilinestring = ''
1208         '''
1209           hello
1210           '''''${there}
1211           te''''st
1212         ''''';
1213       multilinestring' = ''
1214         '''
1215           hello
1216           there
1217           test''''';
1219     };
1220   };
1222   testToPrettyAllowPrettyValues = {
1223     expr = generators.toPretty { allowPrettyValues = true; }
1224              { __pretty = v: "«" + v + "»"; val = "foo"; };
1225     expected  = "«foo»";
1226   };
1228   testToPlist =
1229     let
1230       deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
1231     in {
1232     expr = mapAttrs (const (generators.toPlist { })) {
1233       value = {
1234         nested.values = rec {
1235           int = 42;
1236           float = 0.1337;
1237           bool = true;
1238           emptystring = "";
1239           string = "fn\${o}\"r\\d";
1240           newlinestring = "\n";
1241           path = /. + "/foo";
1242           null_ = null;
1243           list = [ 3 4 "test" ];
1244           emptylist = [];
1245           attrs = { foo = null; "foo b/ar" = "baz"; };
1246           emptyattrs = {};
1247         };
1248       };
1249     };
1250     expected = { value = builtins.readFile ./test-to-plist-expected.plist; };
1251   };
1253   testToLuaEmptyAttrSet = {
1254     expr = generators.toLua {} {};
1255     expected = ''{}'';
1256   };
1258   testToLuaEmptyList = {
1259     expr = generators.toLua {} [];
1260     expected = ''{}'';
1261   };
1263   testToLuaListOfVariousTypes = {
1264     expr = generators.toLua {} [ null 43 3.14159 true ];
1265     expected = ''
1266       {
1267         nil,
1268         43,
1269         3.14159,
1270         true
1271       }'';
1272   };
1274   testToLuaString = {
1275     expr = generators.toLua {} ''double-quote (") and single quotes (')'';
1276     expected = ''"double-quote (\") and single quotes (')"'';
1277   };
1279   testToLuaAttrsetWithLuaInline = {
1280     expr = generators.toLua {} { x = generators.mkLuaInline ''"abc" .. "def"''; };
1281     expected = ''
1282       {
1283         ["x"] = ("abc" .. "def")
1284       }'';
1285   };
1287   testToLuaAttrsetWithSpaceInKey = {
1288     expr = generators.toLua {} { "some space and double-quote (\")" = 42; };
1289     expected = ''
1290       {
1291         ["some space and double-quote (\")"] = 42
1292       }'';
1293   };
1295   testToLuaWithoutMultiline = {
1296     expr = generators.toLua { multiline = false; } [ 41 43 ];
1297     expected = ''{ 41, 43 }'';
1298   };
1300   testToLuaEmptyBindings = {
1301     expr = generators.toLua { asBindings = true; } {};
1302     expected = "";
1303   };
1305   testToLuaBindings = {
1306     expr = generators.toLua { asBindings = true; } { x1 = 41; _y = { a = 43; }; };
1307     expected = ''
1308       _y = {
1309         ["a"] = 43
1310       }
1311       x1 = 41
1312     '';
1313   };
1315   testToLuaPartialTableBindings = {
1316     expr = generators.toLua { asBindings = true; } { "x.y" = 42; };
1317     expected = ''
1318       x.y = 42
1319     '';
1320   };
1322   testToLuaIndentedBindings = {
1323     expr = generators.toLua { asBindings = true; indent = "  "; } { x = { y = 42; }; };
1324     expected = "  x = {\n    [\"y\"] = 42\n  }\n";
1325   };
1327   testToLuaBindingsWithSpace = testingThrow (
1328     generators.toLua { asBindings = true; } { "with space" = 42; }
1329   );
1331   testToLuaBindingsWithLeadingDigit = testingThrow (
1332     generators.toLua { asBindings = true; } { "11eleven" = 42; }
1333   );
1335   testToLuaBasicExample = {
1336     expr = generators.toLua {} {
1337       cmd = [ "typescript-language-server" "--stdio" ];
1338       settings.workspace.library = generators.mkLuaInline ''vim.api.nvim_get_runtime_file("", true)'';
1339     };
1340     expected = ''
1341       {
1342         ["cmd"] = {
1343           "typescript-language-server",
1344           "--stdio"
1345         },
1346         ["settings"] = {
1347           ["workspace"] = {
1348             ["library"] = (vim.api.nvim_get_runtime_file("", true))
1349           }
1350         }
1351       }'';
1352   };
1354 # CLI
1356   testToGNUCommandLine = {
1357     expr = cli.toGNUCommandLine {} {
1358       data = builtins.toJSON { id = 0; };
1359       X = "PUT";
1360       retry = 3;
1361       retry-delay = null;
1362       url = [ "https://example.com/foo" "https://example.com/bar" ];
1363       silent = false;
1364       verbose = true;
1365     };
1367     expected = [
1368       "-X" "PUT"
1369       "--data" "{\"id\":0}"
1370       "--retry" "3"
1371       "--url" "https://example.com/foo"
1372       "--url" "https://example.com/bar"
1373       "--verbose"
1374     ];
1375   };
1377   testToGNUCommandLineShell = {
1378     expr = cli.toGNUCommandLineShell {} {
1379       data = builtins.toJSON { id = 0; };
1380       X = "PUT";
1381       retry = 3;
1382       retry-delay = null;
1383       url = [ "https://example.com/foo" "https://example.com/bar" ];
1384       silent = false;
1385       verbose = true;
1386     };
1388     expected = "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
1389   };
1391   testSanitizeDerivationNameLeadingDots = testSanitizeDerivationName {
1392     name = "..foo";
1393     expected = "foo";
1394   };
1396   testSanitizeDerivationNameUnicode = testSanitizeDerivationName {
1397     name = "fö";
1398     expected = "f-";
1399   };
1401   testSanitizeDerivationNameAscii = testSanitizeDerivationName {
1402     name = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
1403     expected = "-+--.-0123456789-=-?-ABCDEFGHIJKLMNOPQRSTUVWXYZ-_-abcdefghijklmnopqrstuvwxyz-";
1404   };
1406   testSanitizeDerivationNameTooLong = testSanitizeDerivationName {
1407     name = "This string is loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1408     expected = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1409   };
1411   testSanitizeDerivationNameTooLongWithInvalid = testSanitizeDerivationName {
1412     name = "Hello there aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&&&&&&&";
1413     expected = "there-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-";
1414   };
1416   testSanitizeDerivationNameEmpty = testSanitizeDerivationName {
1417     name = "";
1418     expected = "unknown";
1419   };
1421   testFreeformOptions = {
1422     expr =
1423       let
1424         submodule = { lib, ... }: {
1425           freeformType = lib.types.attrsOf (lib.types.submodule {
1426             options.bar = lib.mkOption {};
1427           });
1428           options.bar = lib.mkOption {};
1429         };
1431         module = { lib, ... }: {
1432           options.foo = lib.mkOption {
1433             type = lib.types.submodule submodule;
1434           };
1435         };
1437         options = (evalModules {
1438           modules = [ module ];
1439         }).options;
1441         locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
1442       in map (o: o.loc) locs;
1443     expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
1444   };
1446   testCartesianProductOfEmptySet = {
1447     expr = cartesianProductOfSets {};
1448     expected = [ {} ];
1449   };
1451   testCartesianProductOfOneSet = {
1452     expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
1453     expected = [ { a = 1; } { a = 2; } { a = 3; } ];
1454   };
1456   testCartesianProductOfTwoSets = {
1457     expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
1458     expected = [
1459       { a = 1; b = 10; }
1460       { a = 1; b = 20; }
1461     ];
1462   };
1464   testCartesianProductOfTwoSetsWithOneEmpty = {
1465     expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
1466     expected = [ ];
1467   };
1469   testCartesianProductOfThreeSets = {
1470     expr = cartesianProductOfSets {
1471       a = [   1   2   3 ];
1472       b = [  10  20  30 ];
1473       c = [ 100 200 300 ];
1474     };
1475     expected = [
1476       { a = 1; b = 10; c = 100; }
1477       { a = 1; b = 10; c = 200; }
1478       { a = 1; b = 10; c = 300; }
1480       { a = 1; b = 20; c = 100; }
1481       { a = 1; b = 20; c = 200; }
1482       { a = 1; b = 20; c = 300; }
1484       { a = 1; b = 30; c = 100; }
1485       { a = 1; b = 30; c = 200; }
1486       { a = 1; b = 30; c = 300; }
1488       { a = 2; b = 10; c = 100; }
1489       { a = 2; b = 10; c = 200; }
1490       { a = 2; b = 10; c = 300; }
1492       { a = 2; b = 20; c = 100; }
1493       { a = 2; b = 20; c = 200; }
1494       { a = 2; b = 20; c = 300; }
1496       { a = 2; b = 30; c = 100; }
1497       { a = 2; b = 30; c = 200; }
1498       { a = 2; b = 30; c = 300; }
1500       { a = 3; b = 10; c = 100; }
1501       { a = 3; b = 10; c = 200; }
1502       { a = 3; b = 10; c = 300; }
1504       { a = 3; b = 20; c = 100; }
1505       { a = 3; b = 20; c = 200; }
1506       { a = 3; b = 20; c = 300; }
1508       { a = 3; b = 30; c = 100; }
1509       { a = 3; b = 30; c = 200; }
1510       { a = 3; b = 30; c = 300; }
1511     ];
1512   };
1514   # The example from the showAttrPath documentation
1515   testShowAttrPathExample = {
1516     expr = showAttrPath [ "foo" "10" "bar" ];
1517     expected = "foo.\"10\".bar";
1518   };
1520   testShowAttrPathEmpty = {
1521     expr = showAttrPath [];
1522     expected = "<root attribute path>";
1523   };
1525   testShowAttrPathVarious = {
1526     expr = showAttrPath [
1527       "."
1528       "foo"
1529       "2"
1530       "a2-b"
1531       "_bc'de"
1532     ];
1533     expected = ''".".foo."2".a2-b._bc'de'';
1534   };
1536   testGroupBy = {
1537     expr = groupBy (n: toString (mod n 5)) (range 0 16);
1538     expected = {
1539       "0" = [ 0 5 10 15 ];
1540       "1" = [ 1 6 11 16 ];
1541       "2" = [ 2 7 12 ];
1542       "3" = [ 3 8 13 ];
1543       "4" = [ 4 9 14 ];
1544     };
1545   };
1547   testGroupBy' = {
1548     expr = groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ];
1549     expected = { false = 3; true = 12; };
1550   };
1552   # The example from the updateManyAttrsByPath documentation
1553   testUpdateManyAttrsByPathExample = {
1554     expr = updateManyAttrsByPath [
1555       {
1556         path = [ "a" "b" ];
1557         update = old: { d = old.c; };
1558       }
1559       {
1560         path = [ "a" "b" "c" ];
1561         update = old: old + 1;
1562       }
1563       {
1564         path = [ "x" "y" ];
1565         update = old: "xy";
1566       }
1567     ] { a.b.c = 0; };
1568     expected = { a = { b = { d = 1; }; }; x = { y = "xy"; }; };
1569   };
1571   # If there are no updates, the value is passed through
1572   testUpdateManyAttrsByPathNone = {
1573     expr = updateManyAttrsByPath [] "something";
1574     expected = "something";
1575   };
1577   # A single update to the root path is just like applying the function directly
1578   testUpdateManyAttrsByPathSingleIncrement = {
1579     expr = updateManyAttrsByPath [
1580       {
1581         path = [ ];
1582         update = old: old + 1;
1583       }
1584     ] 0;
1585     expected = 1;
1586   };
1588   # Multiple updates can be applied are done in order
1589   testUpdateManyAttrsByPathMultipleIncrements = {
1590     expr = updateManyAttrsByPath [
1591       {
1592         path = [ ];
1593         update = old: old + "a";
1594       }
1595       {
1596         path = [ ];
1597         update = old: old + "b";
1598       }
1599       {
1600         path = [ ];
1601         update = old: old + "c";
1602       }
1603     ] "";
1604     expected = "abc";
1605   };
1607   # If an update doesn't use the value, all previous updates are not evaluated
1608   testUpdateManyAttrsByPathLazy = {
1609     expr = updateManyAttrsByPath [
1610       {
1611         path = [ ];
1612         update = old: old + throw "nope";
1613       }
1614       {
1615         path = [ ];
1616         update = old: "untainted";
1617       }
1618     ] (throw "start");
1619     expected = "untainted";
1620   };
1622   # Deeply nested attributes can be updated without affecting others
1623   testUpdateManyAttrsByPathDeep = {
1624     expr = updateManyAttrsByPath [
1625       {
1626         path = [ "a" "b" "c" ];
1627         update = old: old + 1;
1628       }
1629     ] {
1630       a.b.c = 0;
1632       a.b.z = 0;
1633       a.y.z = 0;
1634       x.y.z = 0;
1635     };
1636     expected = {
1637       a.b.c = 1;
1639       a.b.z = 0;
1640       a.y.z = 0;
1641       x.y.z = 0;
1642     };
1643   };
1645   # Nested attributes are updated first
1646   testUpdateManyAttrsByPathNestedBeforehand = {
1647     expr = updateManyAttrsByPath [
1648       {
1649         path = [ "a" ];
1650         update = old: old // { x = old.b; };
1651       }
1652       {
1653         path = [ "a" "b" ];
1654         update = old: old + 1;
1655       }
1656     ] {
1657       a.b = 0;
1658     };
1659     expected = {
1660       a.b = 1;
1661       a.x = 1;
1662     };
1663   };
1665   ## Levenshtein distance functions and co.
1666   testCommonPrefixLengthEmpty = {
1667     expr = strings.commonPrefixLength "" "hello";
1668     expected = 0;
1669   };
1671   testCommonPrefixLengthSame = {
1672     expr = strings.commonPrefixLength "hello" "hello";
1673     expected = 5;
1674   };
1676   testCommonPrefixLengthDiffering = {
1677     expr = strings.commonPrefixLength "hello" "hey";
1678     expected = 2;
1679   };
1681   testCommonSuffixLengthEmpty = {
1682     expr = strings.commonSuffixLength "" "hello";
1683     expected = 0;
1684   };
1686   testCommonSuffixLengthSame = {
1687     expr = strings.commonSuffixLength "hello" "hello";
1688     expected = 5;
1689   };
1691   testCommonSuffixLengthDiffering = {
1692     expr = strings.commonSuffixLength "test" "rest";
1693     expected = 3;
1694   };
1696   testLevenshteinEmpty = {
1697     expr = strings.levenshtein "" "";
1698     expected = 0;
1699   };
1701   testLevenshteinOnlyAdd = {
1702     expr = strings.levenshtein "" "hello there";
1703     expected = 11;
1704   };
1706   testLevenshteinOnlyRemove = {
1707     expr = strings.levenshtein "hello there" "";
1708     expected = 11;
1709   };
1711   testLevenshteinOnlyTransform = {
1712     expr = strings.levenshtein "abcdef" "ghijkl";
1713     expected = 6;
1714   };
1716   testLevenshteinMixed = {
1717     expr = strings.levenshtein "kitchen" "sitting";
1718     expected = 5;
1719   };
1721   testLevenshteinAtMostZeroFalse = {
1722     expr = strings.levenshteinAtMost 0 "foo" "boo";
1723     expected = false;
1724   };
1726   testLevenshteinAtMostZeroTrue = {
1727     expr = strings.levenshteinAtMost 0 "foo" "foo";
1728     expected = true;
1729   };
1731   testLevenshteinAtMostOneFalse = {
1732     expr = strings.levenshteinAtMost 1 "car" "ct";
1733     expected = false;
1734   };
1736   testLevenshteinAtMostOneTrue = {
1737     expr = strings.levenshteinAtMost 1 "car" "cr";
1738     expected = true;
1739   };
1741   # We test levenshteinAtMost 2 particularly well because it uses a complicated
1742   # implementation
1743   testLevenshteinAtMostTwoIsEmpty = {
1744     expr = strings.levenshteinAtMost 2 "" "";
1745     expected = true;
1746   };
1748   testLevenshteinAtMostTwoIsZero = {
1749     expr = strings.levenshteinAtMost 2 "abcdef" "abcdef";
1750     expected = true;
1751   };
1753   testLevenshteinAtMostTwoIsOne = {
1754     expr = strings.levenshteinAtMost 2 "abcdef" "abddef";
1755     expected = true;
1756   };
1758   testLevenshteinAtMostTwoDiff0False = {
1759     expr = strings.levenshteinAtMost 2 "abcdef" "aczyef";
1760     expected = false;
1761   };
1763   testLevenshteinAtMostTwoDiff0Outer = {
1764     expr = strings.levenshteinAtMost 2 "abcdef" "zbcdez";
1765     expected = true;
1766   };
1768   testLevenshteinAtMostTwoDiff0DelLeft = {
1769     expr = strings.levenshteinAtMost 2 "abcdef" "bcdefz";
1770     expected = true;
1771   };
1773   testLevenshteinAtMostTwoDiff0DelRight = {
1774     expr = strings.levenshteinAtMost 2 "abcdef" "zabcde";
1775     expected = true;
1776   };
1778   testLevenshteinAtMostTwoDiff1False = {
1779     expr = strings.levenshteinAtMost 2 "abcdef" "bddez";
1780     expected = false;
1781   };
1783   testLevenshteinAtMostTwoDiff1DelLeft = {
1784     expr = strings.levenshteinAtMost 2 "abcdef" "bcdez";
1785     expected = true;
1786   };
1788   testLevenshteinAtMostTwoDiff1DelRight = {
1789     expr = strings.levenshteinAtMost 2 "abcdef" "zbcde";
1790     expected = true;
1791   };
1793   testLevenshteinAtMostTwoDiff2False = {
1794     expr = strings.levenshteinAtMost 2 "hello" "hxo";
1795     expected = false;
1796   };
1798   testLevenshteinAtMostTwoDiff2True = {
1799     expr = strings.levenshteinAtMost 2 "hello" "heo";
1800     expected = true;
1801   };
1803   testLevenshteinAtMostTwoDiff3 = {
1804     expr = strings.levenshteinAtMost 2 "hello" "ho";
1805     expected = false;
1806   };
1808   testLevenshteinAtMostThreeFalse = {
1809     expr = strings.levenshteinAtMost 3 "hello" "Holla!";
1810     expected = false;
1811   };
1813   testLevenshteinAtMostThreeTrue = {
1814     expr = strings.levenshteinAtMost 3 "hello" "Holla";
1815     expected = true;
1816   };
1818   # lazyDerivation
1820   testLazyDerivationIsLazyInDerivationForAttrNames = {
1821     expr = attrNames (lazyDerivation {
1822       derivation = throw "not lazy enough";
1823     });
1824     # It's ok to add attribute names here when lazyDerivation is improved
1825     # in accordance with its inline comments.
1826     expected = [ "drvPath" "meta" "name" "out" "outPath" "outputName" "outputs" "system" "type" ];
1827   };
1829   testLazyDerivationIsLazyInDerivationForPassthruAttr = {
1830     expr = (lazyDerivation {
1831       derivation = throw "not lazy enough";
1832       passthru.tests = "whatever is in tests";
1833     }).tests;
1834     expected = "whatever is in tests";
1835   };
1837   testLazyDerivationIsLazyInDerivationForPassthruAttr2 = {
1838     # passthru.tests is not a special case. It works for any attr.
1839     expr = (lazyDerivation {
1840       derivation = throw "not lazy enough";
1841       passthru.foo = "whatever is in foo";
1842     }).foo;
1843     expected = "whatever is in foo";
1844   };
1846   testLazyDerivationIsLazyInDerivationForMeta = {
1847     expr = (lazyDerivation {
1848       derivation = throw "not lazy enough";
1849       meta = "whatever is in meta";
1850     }).meta;
1851     expected = "whatever is in meta";
1852   };
1854   testLazyDerivationReturnsDerivationAttrs = let
1855     derivation = {
1856       type = "derivation";
1857       outputs = ["out"];
1858       out = "test out";
1859       outPath = "test outPath";
1860       outputName = "out";
1861       drvPath = "test drvPath";
1862       name = "test name";
1863       system = "test system";
1864       meta = "test meta";
1865     };
1866   in {
1867     expr = lazyDerivation { inherit derivation; };
1868     expected = derivation;
1869   };
1871   testTypeDescriptionInt = {
1872     expr = (with types; int).description;
1873     expected = "signed integer";
1874   };
1875   testTypeDescriptionListOfInt = {
1876     expr = (with types; listOf int).description;
1877     expected = "list of signed integer";
1878   };
1879   testTypeDescriptionListOfListOfInt = {
1880     expr = (with types; listOf (listOf int)).description;
1881     expected = "list of list of signed integer";
1882   };
1883   testTypeDescriptionListOfEitherStrOrBool = {
1884     expr = (with types; listOf (either str bool)).description;
1885     expected = "list of (string or boolean)";
1886   };
1887   testTypeDescriptionEitherListOfStrOrBool = {
1888     expr = (with types; either (listOf bool) str).description;
1889     expected = "(list of boolean) or string";
1890   };
1891   testTypeDescriptionEitherStrOrListOfBool = {
1892     expr = (with types; either str (listOf bool)).description;
1893     expected = "string or list of boolean";
1894   };
1895   testTypeDescriptionOneOfListOfStrOrBool = {
1896     expr = (with types; oneOf [ (listOf bool) str ]).description;
1897     expected = "(list of boolean) or string";
1898   };
1899   testTypeDescriptionOneOfListOfStrOrBoolOrNumber = {
1900     expr = (with types; oneOf [ (listOf bool) str number ]).description;
1901     expected = "(list of boolean) or string or signed integer or floating point number";
1902   };
1903   testTypeDescriptionEitherListOfBoolOrEitherStringOrNumber = {
1904     expr = (with types; either (listOf bool) (either str number)).description;
1905     expected = "(list of boolean) or string or signed integer or floating point number";
1906   };
1907   testTypeDescriptionEitherEitherListOfBoolOrStringOrNumber = {
1908     expr = (with types; either (either (listOf bool) str) number).description;
1909     expected = "(list of boolean) or string or signed integer or floating point number";
1910   };
1911   testTypeDescriptionEitherNullOrBoolOrString = {
1912     expr = (with types; either (nullOr bool) str).description;
1913     expected = "null or boolean or string";
1914   };
1915   testTypeDescriptionEitherListOfEitherBoolOrStrOrInt = {
1916     expr = (with types; either (listOf (either bool str)) int).description;
1917     expected = "(list of (boolean or string)) or signed integer";
1918   };
1919   testTypeDescriptionEitherIntOrListOrEitherBoolOrStr = {
1920     expr = (with types; either int (listOf (either bool str))).description;
1921     expected = "signed integer or list of (boolean or string)";
1922   };
1924 # Meta
1925   testGetExe'Output = {
1926     expr = getExe' {
1927       type = "derivation";
1928       out = "somelonghash";
1929       bin = "somelonghash";
1930     } "executable";
1931     expected = "somelonghash/bin/executable";
1932   };
1934   testGetExeOutput = {
1935     expr = getExe {
1936       type = "derivation";
1937       out = "somelonghash";
1938       bin = "somelonghash";
1939       meta.mainProgram = "mainProgram";
1940     };
1941     expected = "somelonghash/bin/mainProgram";
1942   };
1944   testGetExe'FailureFirstArg = testingThrow (
1945     getExe' "not a derivation" "executable"
1946   );
1948   testGetExe'FailureSecondArg = testingThrow (
1949     getExe' { type = "derivation"; } "dir/executable"
1950   );
1952   testPlatformMatch = {
1953     expr = meta.platformMatch { system = "x86_64-linux"; } "x86_64-linux";
1954     expected = true;
1955   };
1957   testPlatformMatchAttrs = {
1958     expr = meta.platformMatch (systems.elaborate "x86_64-linux") (systems.elaborate "x86_64-linux").parsed;
1959     expected = true;
1960   };
1962   testPlatformMatchNoMatch = {
1963     expr = meta.platformMatch { system = "x86_64-darwin"; } "x86_64-linux";
1964     expected = false;
1965   };
1967   testPlatformMatchMissingSystem = {
1968     expr = meta.platformMatch { } "x86_64-linux";
1969     expected = false;
1970   };