Merge pull request #270774 from emilytrau/libbacktrace-musl
[NixPkgs.git] / lib / tests / misc.nix
blob608af656d02c0cdfc8144d12ff20a72b3196c6d4
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   testSortOn = {
654     expr = sortOn stringLength [ "aa" "b" "cccc" ];
655     expected = [ "b" "aa" "cccc" ];
656   };
658   testSortOnEmpty = {
659     expr = sortOn (throw "nope") [ ];
660     expected = [ ];
661   };
663   testSortOnIncomparable = {
664     expr =
665       map
666         (x: x.f x.ok)
667         (sortOn (x: x.ok) [
668           { ok = 1; f = x: x; }
669           { ok = 3; f = x: x + 3; }
670           { ok = 2; f = x: x; }
671         ]);
672     expected = [ 1 2 6 ];
673   };
675   testReplicate = {
676     expr = replicate 3 "a";
677     expected = ["a" "a" "a"];
678   };
680   testToIntShouldConvertStringToInt = {
681     expr = toInt "27";
682     expected = 27;
683   };
685   testToIntShouldThrowErrorIfItCouldNotConvertToInt = {
686     expr = builtins.tryEval (toInt "\"foo\"");
687     expected = { success = false; value = false; };
688   };
690   testHasAttrByPathTrue = {
691     expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; };
692     expected = true;
693   };
695   testHasAttrByPathFalse = {
696     expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; };
697     expected = false;
698   };
700   testFindFirstIndexExample1 = {
701     expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ];
702     expected = 1;
703   };
705   testFindFirstIndexExample2 = {
706     expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ 1 6 4 ];
707     expected = "a very specific default";
708   };
710   testFindFirstIndexEmpty = {
711     expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null [];
712     expected = null;
713   };
715   testFindFirstIndexSingleMatch = {
716     expr = lists.findFirstIndex (x: x == 5) null [ 5 ];
717     expected = 0;
718   };
720   testFindFirstIndexSingleDefault = {
721     expr = lists.findFirstIndex (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
722     expected = null;
723   };
725   testFindFirstIndexNone = {
726     expr = builtins.tryEval (lists.findFirstIndex (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
727     expected = { success = false; value = false; };
728   };
730   # Makes sure that the implementation doesn't cause a stack overflow
731   testFindFirstIndexBig = {
732     expr = lists.findFirstIndex (x: x == 1000000) null (range 0 1000000);
733     expected = 1000000;
734   };
736   testFindFirstIndexLazy = {
737     expr = lists.findFirstIndex (x: x == 1) null [ 1 (abort "list elements after the match must not be evaluated") ];
738     expected = 0;
739   };
741   testFindFirstExample1 = {
742     expr = lists.findFirst (x: x > 3) 7 [ 1 6 4 ];
743     expected = 6;
744   };
746   testFindFirstExample2 = {
747     expr = lists.findFirst (x: x > 9) 7 [ 1 6 4 ];
748     expected = 7;
749   };
751   testAllUnique_true = {
752     expr = allUnique [ 3 2 4 1 ];
753     expected = true;
754   };
755   testAllUnique_false = {
756     expr = allUnique [ 3 2 3 4 ];
757     expected = false;
758   };
760 # ATTRSETS
762   testConcatMapAttrs = {
763     expr = concatMapAttrs
764       (name: value: {
765         ${name} = value;
766         ${name + value} = value;
767       })
768       {
769         foo = "bar";
770         foobar = "baz";
771       };
772     expected = {
773       foo = "bar";
774       foobar = "baz";
775       foobarbaz = "baz";
776     };
777   };
779   # code from example
780   testFoldlAttrs = {
781     expr = {
782       example = foldlAttrs
783         (acc: name: value: {
784           sum = acc.sum + value;
785           names = acc.names ++ [ name ];
786         })
787         { sum = 0; names = [ ]; }
788         {
789           foo = 1;
790           bar = 10;
791         };
792       # should just return the initial value
793       emptySet = foldlAttrs (throw "function not needed") 123 { };
794       # should just evaluate to the last value
795       valuesNotNeeded = foldlAttrs (acc: _name: _v: acc) 3 { z = throw "value z not needed"; a = throw "value a not needed"; };
796       # the accumulator doesnt have to be an attrset it can be as trivial as being just a number or string
797       trivialAcc = foldlAttrs (acc: _name: v: acc * 10 + v) 1 { z = 1; a = 2; };
798     };
799     expected = {
800       example = {
801         sum = 11;
802         names = [ "bar" "foo" ];
803       };
804       emptySet = 123;
805       valuesNotNeeded = 3;
806       trivialAcc = 121;
807     };
808   };
811   testMergeAttrsListExample1 = {
812     expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ];
813     expected = { a = 0; b = 1; c = 2; d = 3; };
814   };
815   testMergeAttrsListExample2 = {
816     expr = attrsets.mergeAttrsList [ { a = 0; } { a = 1; } ];
817     expected = { a = 1; };
818   };
819   testMergeAttrsListExampleMany =
820     let
821       list = genList (n:
822         listToAttrs (genList (m:
823           let
824             # Integer divide n by two to create duplicate attributes
825             str = "halfn${toString (n / 2)}m${toString m}";
826           in
827           nameValuePair str str
828         ) 100)
829       ) 100;
830     in {
831       expr = attrsets.mergeAttrsList list;
832       expected = foldl' mergeAttrs { } list;
833     };
835   # code from the example
836   testRecursiveUpdateUntil = {
837     expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {
838       # first attribute set
839       foo.bar = 1;
840       foo.baz = 2;
841       bar = 3;
842     } {
843       #second attribute set
844       foo.bar = 1;
845       foo.quz = 2;
846       baz = 4;
847     };
848     expected = {
849       foo.bar = 1; # 'foo.*' from the second set
850       foo.quz = 2; #
851       bar = 3;     # 'bar' from the first set
852       baz = 4;     # 'baz' from the second set
853     };
854   };
856   testMatchAttrsMatchingExact = {
857     expr = matchAttrs { cpu = { bits = 64; }; } { cpu = { bits = 64; }; };
858     expected = true;
859   };
861   testMatchAttrsMismatch = {
862     expr = matchAttrs { cpu = { bits = 128; }; } { cpu = { bits = 64; }; };
863     expected = false;
864   };
866   testMatchAttrsMatchingImplicit = {
867     expr = matchAttrs { cpu = { }; } { cpu = { bits = 64; }; };
868     expected = true;
869   };
871   testMatchAttrsMissingAttrs = {
872     expr = matchAttrs { cpu = {}; } { };
873     expected = false;
874   };
876   testOverrideExistingEmpty = {
877     expr = overrideExisting {} { a = 1; };
878     expected = {};
879   };
881   testOverrideExistingDisjoint = {
882     expr = overrideExisting { b = 2; } { a = 1; };
883     expected = { b = 2; };
884   };
886   testOverrideExistingOverride = {
887     expr = overrideExisting { a = 3; b = 2; } { a = 1; };
888     expected = { a = 1; b = 2; };
889   };
891   testListAttrsReverse = let
892     exampleAttrs = {foo=1; bar="asdf"; baz = [1 3 3 7]; fnord=null;};
893     exampleSingletonList = [{name="foo"; value=1;}];
894   in {
895     expr = {
896       isReverseToListToAttrs = builtins.listToAttrs (attrsToList exampleAttrs) == exampleAttrs;
897       isReverseToAttrsToList = attrsToList (builtins.listToAttrs exampleSingletonList) == exampleSingletonList;
898       testDuplicatePruningBehaviour = attrsToList (builtins.listToAttrs [{name="a"; value=2;} {name="a"; value=1;}]);
899     };
900     expected = {
901       isReverseToAttrsToList = true;
902       isReverseToListToAttrs = true;
903       testDuplicatePruningBehaviour = [{name="a"; value=2;}];
904     };
905   };
907   testAttrsToListsCanDealWithFunctions = testingEval (
908     attrsToList { someFunc= a: a + 1;}
909   );
911 # GENERATORS
912 # these tests assume attributes are converted to lists
913 # in alphabetical order
915   testMkKeyValueDefault = {
916     expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar";
917     expected = ''f\:oo:bar'';
918   };
920   testMkValueString = {
921     expr = let
922       vals = {
923         int = 42;
924         string = ''fo"o'';
925         bool = true;
926         bool2 = false;
927         null = null;
928         # float = 42.23; # floats are strange
929       };
930       in mapAttrs
931         (const (generators.mkValueStringDefault {}))
932         vals;
933     expected = {
934       int = "42";
935       string = ''fo"o'';
936       bool = "true";
937       bool2 = "false";
938       null = "null";
939       # float = "42.23" true false [ "bar" ] ]'';
940     };
941   };
943   testToKeyValue = {
944     expr = generators.toKeyValue {} {
945       key = "value";
946       "other=key" = "baz";
947     };
948     expected = ''
949       key=value
950       other\=key=baz
951     '';
952   };
954   testToINIEmpty = {
955     expr = generators.toINI {} {};
956     expected = "";
957   };
959   testToINIEmptySection = {
960     expr = generators.toINI {} { foo = {}; bar = {}; };
961     expected = ''
962       [bar]
964       [foo]
965     '';
966   };
968   testToINIDuplicateKeys = {
969     expr = generators.toINI { listsAsDuplicateKeys = true; } { foo.bar = true; baz.qux = [ 1 false ]; };
970     expected = ''
971       [baz]
972       qux=1
973       qux=false
975       [foo]
976       bar=true
977     '';
978   };
980   testToINIDefaultEscapes = {
981     expr = generators.toINI {} {
982       "no [ and ] allowed unescaped" = {
983         "and also no = in keys" = 42;
984       };
985     };
986     expected = ''
987       [no \[ and \] allowed unescaped]
988       and also no \= in keys=42
989     '';
990   };
992   testToINIDefaultFull = {
993     expr = generators.toINI {} {
994       "section 1" = {
995         attribute1 = 5;
996         x = "Me-se JarJar Binx";
997         # booleans are converted verbatim by default
998         boolean = false;
999       };
1000       "foo[]" = {
1001         "he\\h=he" = "this is okay";
1002       };
1003     };
1004     expected = ''
1005       [foo\[\]]
1006       he\h\=he=this is okay
1008       [section 1]
1009       attribute1=5
1010       boolean=false
1011       x=Me-se JarJar Binx
1012     '';
1013   };
1015   testToINIWithGlobalSectionEmpty = {
1016     expr = generators.toINIWithGlobalSection {} {
1017       globalSection = {
1018       };
1019       sections = {
1020       };
1021     };
1022     expected = ''
1023     '';
1024   };
1026   testToINIWithGlobalSectionGlobalEmptyIsTheSameAsToINI =
1027     let
1028       sections = {
1029         "section 1" = {
1030           attribute1 = 5;
1031           x = "Me-se JarJar Binx";
1032         };
1033         "foo" = {
1034           "he\\h=he" = "this is okay";
1035         };
1036       };
1037     in {
1038       expr =
1039         generators.toINIWithGlobalSection {} {
1040             globalSection = {};
1041             sections = sections;
1042         };
1043       expected = generators.toINI {} sections;
1044   };
1046   testToINIWithGlobalSectionFull = {
1047     expr = generators.toINIWithGlobalSection {} {
1048       globalSection = {
1049         foo = "bar";
1050         test = false;
1051       };
1052       sections = {
1053         "section 1" = {
1054           attribute1 = 5;
1055           x = "Me-se JarJar Binx";
1056         };
1057         "foo" = {
1058           "he\\h=he" = "this is okay";
1059         };
1060       };
1061     };
1062     expected = ''
1063       foo=bar
1064       test=false
1066       [foo]
1067       he\h\=he=this is okay
1069       [section 1]
1070       attribute1=5
1071       x=Me-se JarJar Binx
1072     '';
1073   };
1075   testToGitINI = {
1076     expr = generators.toGitINI {
1077       user = {
1078         email = "user@example.org";
1079         name = "John Doe";
1080         signingKey = "00112233445566778899AABBCCDDEEFF";
1081       };
1082       gpg.program = "path-to-gpg";
1083       tag.gpgSign = true;
1084       include.path = "~/path/to/config.inc";
1085       includeIf."gitdif:~/src/dir".path = "~/path/to/conditional.inc";
1086       extra = {
1087         boolean = true;
1088         integer = 38;
1089         name = "value";
1090         subsection.value = "test";
1091       };};
1092     expected = ''
1093       [extra]
1094       ${"\t"}boolean = true
1095       ${"\t"}integer = 38
1096       ${"\t"}name = "value"
1098       [extra "subsection"]
1099       ${"\t"}value = "test"
1101       [gpg]
1102       ${"\t"}program = "path-to-gpg"
1104       [include]
1105       ${"\t"}path = "~/path/to/config.inc"
1107       [includeIf "gitdif:~/src/dir"]
1108       ${"\t"}path = "~/path/to/conditional.inc"
1110       [tag]
1111       ${"\t"}gpgSign = true
1113       [user]
1114       ${"\t"}email = "user@example.org"
1115       ${"\t"}name = "John Doe"
1116       ${"\t"}signingKey = "00112233445566778899AABBCCDDEEFF"
1117     '';
1118   };
1120   /* right now only invocation check */
1121   testToJSONSimple =
1122     let val = {
1123       foobar = [ "baz" 1 2 3 ];
1124     };
1125     in {
1126       expr = generators.toJSON {} val;
1127       # trivial implementation
1128       expected = builtins.toJSON val;
1129   };
1131   /* right now only invocation check */
1132   testToYAMLSimple =
1133     let val = {
1134       list = [ { one = 1; } { two = 2; } ];
1135       all = 42;
1136     };
1137     in {
1138       expr = generators.toYAML {} val;
1139       # trivial implementation
1140       expected = builtins.toJSON val;
1141   };
1143   testToPretty =
1144     let
1145       deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
1146     in {
1147     expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
1148       int = 42;
1149       float = 0.1337;
1150       bool = true;
1151       emptystring = "";
1152       string = "fn\${o}\"r\\d";
1153       newlinestring = "\n";
1154       path = /. + "/foo";
1155       null_ = null;
1156       function = x: x;
1157       functionArgs = { arg ? 4, foo }: arg;
1158       list = [ 3 4 function [ false ] ];
1159       emptylist = [];
1160       attrs = { foo = null; "foo b/ar" = "baz"; };
1161       emptyattrs = {};
1162       drv = deriv;
1163     };
1164     expected = rec {
1165       int = "42";
1166       float = "0.1337";
1167       bool = "true";
1168       emptystring = ''""'';
1169       string = ''"fn\''${o}\"r\\d"'';
1170       newlinestring = "\"\\n\"";
1171       path = "/foo";
1172       null_ = "null";
1173       function = "<function>";
1174       functionArgs = "<function, args: {arg?, foo}>";
1175       list = "[ 3 4 ${function} [ false ] ]";
1176       emptylist = "[ ]";
1177       attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }";
1178       emptyattrs = "{ }";
1179       drv = "<derivation ${deriv.name}>";
1180     };
1181   };
1183   testToPrettyLimit =
1184     let
1185       a.b = 1;
1186       a.c = a;
1187     in {
1188       expr = generators.toPretty { } (generators.withRecursion { throwOnDepthLimit = false; depthLimit = 2; } a);
1189       expected = "{\n  b = 1;\n  c = {\n    b = \"<unevaluated>\";\n    c = {\n      b = \"<unevaluated>\";\n      c = \"<unevaluated>\";\n    };\n  };\n}";
1190     };
1192   testToPrettyLimitThrow =
1193     let
1194       a.b = 1;
1195       a.c = a;
1196     in {
1197       expr = (builtins.tryEval
1198         (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))).success;
1199       expected = false;
1200     };
1202   testWithRecursionDealsWithFunctors =
1203     let
1204       functor = {
1205         __functor = self: { a, b, }: null;
1206       };
1207       a = {
1208         value = "1234";
1209         b = functor;
1210         c.d = functor;
1211       };
1212     in {
1213       expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a);
1214       expected = "{\n  b = <function, args: {a, b}>;\n  c = {\n    d = \"<unevaluated>\";\n  };\n  value = \"<unevaluated>\";\n}";
1215     };
1217   testToPrettyMultiline = {
1218     expr = mapAttrs (const (generators.toPretty { })) rec {
1219       list = [ 3 4 [ false ] ];
1220       attrs = { foo = null; bar.foo = "baz"; };
1221       newlinestring = "\n";
1222       multilinestring = ''
1223         hello
1224         ''${there}
1225         te'''st
1226       '';
1227       multilinestring' = ''
1228         hello
1229         there
1230         test'';
1231     };
1232     expected = rec {
1233       list = ''
1234         [
1235           3
1236           4
1237           [
1238             false
1239           ]
1240         ]'';
1241       attrs = ''
1242         {
1243           bar = {
1244             foo = "baz";
1245           };
1246           foo = null;
1247         }'';
1248       newlinestring = "''\n  \n''";
1249       multilinestring = ''
1250         '''
1251           hello
1252           '''''${there}
1253           te''''st
1254         ''''';
1255       multilinestring' = ''
1256         '''
1257           hello
1258           there
1259           test''''';
1261     };
1262   };
1264   testToPrettyAllowPrettyValues = {
1265     expr = generators.toPretty { allowPrettyValues = true; }
1266              { __pretty = v: "«" + v + "»"; val = "foo"; };
1267     expected  = "«foo»";
1268   };
1270   testToPlist =
1271     let
1272       deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
1273     in {
1274     expr = mapAttrs (const (generators.toPlist { })) {
1275       value = {
1276         nested.values = rec {
1277           int = 42;
1278           float = 0.1337;
1279           bool = true;
1280           emptystring = "";
1281           string = "fn\${o}\"r\\d";
1282           newlinestring = "\n";
1283           path = /. + "/foo";
1284           null_ = null;
1285           list = [ 3 4 "test" ];
1286           emptylist = [];
1287           attrs = { foo = null; "foo b/ar" = "baz"; };
1288           emptyattrs = {};
1289         };
1290       };
1291     };
1292     expected = { value = builtins.readFile ./test-to-plist-expected.plist; };
1293   };
1295   testToLuaEmptyAttrSet = {
1296     expr = generators.toLua {} {};
1297     expected = ''{}'';
1298   };
1300   testToLuaEmptyList = {
1301     expr = generators.toLua {} [];
1302     expected = ''{}'';
1303   };
1305   testToLuaListOfVariousTypes = {
1306     expr = generators.toLua {} [ null 43 3.14159 true ];
1307     expected = ''
1308       {
1309         nil,
1310         43,
1311         3.14159,
1312         true
1313       }'';
1314   };
1316   testToLuaString = {
1317     expr = generators.toLua {} ''double-quote (") and single quotes (')'';
1318     expected = ''"double-quote (\") and single quotes (')"'';
1319   };
1321   testToLuaAttrsetWithLuaInline = {
1322     expr = generators.toLua {} { x = generators.mkLuaInline ''"abc" .. "def"''; };
1323     expected = ''
1324       {
1325         ["x"] = ("abc" .. "def")
1326       }'';
1327   };
1329   testToLuaAttrsetWithSpaceInKey = {
1330     expr = generators.toLua {} { "some space and double-quote (\")" = 42; };
1331     expected = ''
1332       {
1333         ["some space and double-quote (\")"] = 42
1334       }'';
1335   };
1337   testToLuaWithoutMultiline = {
1338     expr = generators.toLua { multiline = false; } [ 41 43 ];
1339     expected = ''{ 41, 43 }'';
1340   };
1342   testToLuaEmptyBindings = {
1343     expr = generators.toLua { asBindings = true; } {};
1344     expected = "";
1345   };
1347   testToLuaBindings = {
1348     expr = generators.toLua { asBindings = true; } { x1 = 41; _y = { a = 43; }; };
1349     expected = ''
1350       _y = {
1351         ["a"] = 43
1352       }
1353       x1 = 41
1354     '';
1355   };
1357   testToLuaPartialTableBindings = {
1358     expr = generators.toLua { asBindings = true; } { "x.y" = 42; };
1359     expected = ''
1360       x.y = 42
1361     '';
1362   };
1364   testToLuaIndentedBindings = {
1365     expr = generators.toLua { asBindings = true; indent = "  "; } { x = { y = 42; }; };
1366     expected = "  x = {\n    [\"y\"] = 42\n  }\n";
1367   };
1369   testToLuaBindingsWithSpace = testingThrow (
1370     generators.toLua { asBindings = true; } { "with space" = 42; }
1371   );
1373   testToLuaBindingsWithLeadingDigit = testingThrow (
1374     generators.toLua { asBindings = true; } { "11eleven" = 42; }
1375   );
1377   testToLuaBasicExample = {
1378     expr = generators.toLua {} {
1379       cmd = [ "typescript-language-server" "--stdio" ];
1380       settings.workspace.library = generators.mkLuaInline ''vim.api.nvim_get_runtime_file("", true)'';
1381     };
1382     expected = ''
1383       {
1384         ["cmd"] = {
1385           "typescript-language-server",
1386           "--stdio"
1387         },
1388         ["settings"] = {
1389           ["workspace"] = {
1390             ["library"] = (vim.api.nvim_get_runtime_file("", true))
1391           }
1392         }
1393       }'';
1394   };
1396 # CLI
1398   testToGNUCommandLine = {
1399     expr = cli.toGNUCommandLine {} {
1400       data = builtins.toJSON { id = 0; };
1401       X = "PUT";
1402       retry = 3;
1403       retry-delay = null;
1404       url = [ "https://example.com/foo" "https://example.com/bar" ];
1405       silent = false;
1406       verbose = true;
1407     };
1409     expected = [
1410       "-X" "PUT"
1411       "--data" "{\"id\":0}"
1412       "--retry" "3"
1413       "--url" "https://example.com/foo"
1414       "--url" "https://example.com/bar"
1415       "--verbose"
1416     ];
1417   };
1419   testToGNUCommandLineShell = {
1420     expr = cli.toGNUCommandLineShell {} {
1421       data = builtins.toJSON { id = 0; };
1422       X = "PUT";
1423       retry = 3;
1424       retry-delay = null;
1425       url = [ "https://example.com/foo" "https://example.com/bar" ];
1426       silent = false;
1427       verbose = true;
1428     };
1430     expected = "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
1431   };
1433   testSanitizeDerivationNameLeadingDots = testSanitizeDerivationName {
1434     name = "..foo";
1435     expected = "foo";
1436   };
1438   testSanitizeDerivationNameUnicode = testSanitizeDerivationName {
1439     name = "fö";
1440     expected = "f-";
1441   };
1443   testSanitizeDerivationNameAscii = testSanitizeDerivationName {
1444     name = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
1445     expected = "-+--.-0123456789-=-?-ABCDEFGHIJKLMNOPQRSTUVWXYZ-_-abcdefghijklmnopqrstuvwxyz-";
1446   };
1448   testSanitizeDerivationNameTooLong = testSanitizeDerivationName {
1449     name = "This string is loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1450     expected = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1451   };
1453   testSanitizeDerivationNameTooLongWithInvalid = testSanitizeDerivationName {
1454     name = "Hello there aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&&&&&&&";
1455     expected = "there-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-";
1456   };
1458   testSanitizeDerivationNameEmpty = testSanitizeDerivationName {
1459     name = "";
1460     expected = "unknown";
1461   };
1463   testFreeformOptions = {
1464     expr =
1465       let
1466         submodule = { lib, ... }: {
1467           freeformType = lib.types.attrsOf (lib.types.submodule {
1468             options.bar = lib.mkOption {};
1469           });
1470           options.bar = lib.mkOption {};
1471         };
1473         module = { lib, ... }: {
1474           options.foo = lib.mkOption {
1475             type = lib.types.submodule submodule;
1476           };
1477         };
1479         options = (evalModules {
1480           modules = [ module ];
1481         }).options;
1483         locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
1484       in map (o: o.loc) locs;
1485     expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
1486   };
1488   testCartesianProductOfEmptySet = {
1489     expr = cartesianProductOfSets {};
1490     expected = [ {} ];
1491   };
1493   testCartesianProductOfOneSet = {
1494     expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
1495     expected = [ { a = 1; } { a = 2; } { a = 3; } ];
1496   };
1498   testCartesianProductOfTwoSets = {
1499     expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
1500     expected = [
1501       { a = 1; b = 10; }
1502       { a = 1; b = 20; }
1503     ];
1504   };
1506   testCartesianProductOfTwoSetsWithOneEmpty = {
1507     expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
1508     expected = [ ];
1509   };
1511   testCartesianProductOfThreeSets = {
1512     expr = cartesianProductOfSets {
1513       a = [   1   2   3 ];
1514       b = [  10  20  30 ];
1515       c = [ 100 200 300 ];
1516     };
1517     expected = [
1518       { a = 1; b = 10; c = 100; }
1519       { a = 1; b = 10; c = 200; }
1520       { a = 1; b = 10; c = 300; }
1522       { a = 1; b = 20; c = 100; }
1523       { a = 1; b = 20; c = 200; }
1524       { a = 1; b = 20; c = 300; }
1526       { a = 1; b = 30; c = 100; }
1527       { a = 1; b = 30; c = 200; }
1528       { a = 1; b = 30; c = 300; }
1530       { a = 2; b = 10; c = 100; }
1531       { a = 2; b = 10; c = 200; }
1532       { a = 2; b = 10; c = 300; }
1534       { a = 2; b = 20; c = 100; }
1535       { a = 2; b = 20; c = 200; }
1536       { a = 2; b = 20; c = 300; }
1538       { a = 2; b = 30; c = 100; }
1539       { a = 2; b = 30; c = 200; }
1540       { a = 2; b = 30; c = 300; }
1542       { a = 3; b = 10; c = 100; }
1543       { a = 3; b = 10; c = 200; }
1544       { a = 3; b = 10; c = 300; }
1546       { a = 3; b = 20; c = 100; }
1547       { a = 3; b = 20; c = 200; }
1548       { a = 3; b = 20; c = 300; }
1550       { a = 3; b = 30; c = 100; }
1551       { a = 3; b = 30; c = 200; }
1552       { a = 3; b = 30; c = 300; }
1553     ];
1554   };
1556   # The example from the showAttrPath documentation
1557   testShowAttrPathExample = {
1558     expr = showAttrPath [ "foo" "10" "bar" ];
1559     expected = "foo.\"10\".bar";
1560   };
1562   testShowAttrPathEmpty = {
1563     expr = showAttrPath [];
1564     expected = "<root attribute path>";
1565   };
1567   testShowAttrPathVarious = {
1568     expr = showAttrPath [
1569       "."
1570       "foo"
1571       "2"
1572       "a2-b"
1573       "_bc'de"
1574     ];
1575     expected = ''".".foo."2".a2-b._bc'de'';
1576   };
1578   testGroupBy = {
1579     expr = groupBy (n: toString (mod n 5)) (range 0 16);
1580     expected = {
1581       "0" = [ 0 5 10 15 ];
1582       "1" = [ 1 6 11 16 ];
1583       "2" = [ 2 7 12 ];
1584       "3" = [ 3 8 13 ];
1585       "4" = [ 4 9 14 ];
1586     };
1587   };
1589   testGroupBy' = {
1590     expr = groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ];
1591     expected = { false = 3; true = 12; };
1592   };
1594   # The example from the updateManyAttrsByPath documentation
1595   testUpdateManyAttrsByPathExample = {
1596     expr = updateManyAttrsByPath [
1597       {
1598         path = [ "a" "b" ];
1599         update = old: { d = old.c; };
1600       }
1601       {
1602         path = [ "a" "b" "c" ];
1603         update = old: old + 1;
1604       }
1605       {
1606         path = [ "x" "y" ];
1607         update = old: "xy";
1608       }
1609     ] { a.b.c = 0; };
1610     expected = { a = { b = { d = 1; }; }; x = { y = "xy"; }; };
1611   };
1613   # If there are no updates, the value is passed through
1614   testUpdateManyAttrsByPathNone = {
1615     expr = updateManyAttrsByPath [] "something";
1616     expected = "something";
1617   };
1619   # A single update to the root path is just like applying the function directly
1620   testUpdateManyAttrsByPathSingleIncrement = {
1621     expr = updateManyAttrsByPath [
1622       {
1623         path = [ ];
1624         update = old: old + 1;
1625       }
1626     ] 0;
1627     expected = 1;
1628   };
1630   # Multiple updates can be applied are done in order
1631   testUpdateManyAttrsByPathMultipleIncrements = {
1632     expr = updateManyAttrsByPath [
1633       {
1634         path = [ ];
1635         update = old: old + "a";
1636       }
1637       {
1638         path = [ ];
1639         update = old: old + "b";
1640       }
1641       {
1642         path = [ ];
1643         update = old: old + "c";
1644       }
1645     ] "";
1646     expected = "abc";
1647   };
1649   # If an update doesn't use the value, all previous updates are not evaluated
1650   testUpdateManyAttrsByPathLazy = {
1651     expr = updateManyAttrsByPath [
1652       {
1653         path = [ ];
1654         update = old: old + throw "nope";
1655       }
1656       {
1657         path = [ ];
1658         update = old: "untainted";
1659       }
1660     ] (throw "start");
1661     expected = "untainted";
1662   };
1664   # Deeply nested attributes can be updated without affecting others
1665   testUpdateManyAttrsByPathDeep = {
1666     expr = updateManyAttrsByPath [
1667       {
1668         path = [ "a" "b" "c" ];
1669         update = old: old + 1;
1670       }
1671     ] {
1672       a.b.c = 0;
1674       a.b.z = 0;
1675       a.y.z = 0;
1676       x.y.z = 0;
1677     };
1678     expected = {
1679       a.b.c = 1;
1681       a.b.z = 0;
1682       a.y.z = 0;
1683       x.y.z = 0;
1684     };
1685   };
1687   # Nested attributes are updated first
1688   testUpdateManyAttrsByPathNestedBeforehand = {
1689     expr = updateManyAttrsByPath [
1690       {
1691         path = [ "a" ];
1692         update = old: old // { x = old.b; };
1693       }
1694       {
1695         path = [ "a" "b" ];
1696         update = old: old + 1;
1697       }
1698     ] {
1699       a.b = 0;
1700     };
1701     expected = {
1702       a.b = 1;
1703       a.x = 1;
1704     };
1705   };
1707   ## Levenshtein distance functions and co.
1708   testCommonPrefixLengthEmpty = {
1709     expr = strings.commonPrefixLength "" "hello";
1710     expected = 0;
1711   };
1713   testCommonPrefixLengthSame = {
1714     expr = strings.commonPrefixLength "hello" "hello";
1715     expected = 5;
1716   };
1718   testCommonPrefixLengthDiffering = {
1719     expr = strings.commonPrefixLength "hello" "hey";
1720     expected = 2;
1721   };
1723   testCommonSuffixLengthEmpty = {
1724     expr = strings.commonSuffixLength "" "hello";
1725     expected = 0;
1726   };
1728   testCommonSuffixLengthSame = {
1729     expr = strings.commonSuffixLength "hello" "hello";
1730     expected = 5;
1731   };
1733   testCommonSuffixLengthDiffering = {
1734     expr = strings.commonSuffixLength "test" "rest";
1735     expected = 3;
1736   };
1738   testLevenshteinEmpty = {
1739     expr = strings.levenshtein "" "";
1740     expected = 0;
1741   };
1743   testLevenshteinOnlyAdd = {
1744     expr = strings.levenshtein "" "hello there";
1745     expected = 11;
1746   };
1748   testLevenshteinOnlyRemove = {
1749     expr = strings.levenshtein "hello there" "";
1750     expected = 11;
1751   };
1753   testLevenshteinOnlyTransform = {
1754     expr = strings.levenshtein "abcdef" "ghijkl";
1755     expected = 6;
1756   };
1758   testLevenshteinMixed = {
1759     expr = strings.levenshtein "kitchen" "sitting";
1760     expected = 5;
1761   };
1763   testLevenshteinAtMostZeroFalse = {
1764     expr = strings.levenshteinAtMost 0 "foo" "boo";
1765     expected = false;
1766   };
1768   testLevenshteinAtMostZeroTrue = {
1769     expr = strings.levenshteinAtMost 0 "foo" "foo";
1770     expected = true;
1771   };
1773   testLevenshteinAtMostOneFalse = {
1774     expr = strings.levenshteinAtMost 1 "car" "ct";
1775     expected = false;
1776   };
1778   testLevenshteinAtMostOneTrue = {
1779     expr = strings.levenshteinAtMost 1 "car" "cr";
1780     expected = true;
1781   };
1783   # We test levenshteinAtMost 2 particularly well because it uses a complicated
1784   # implementation
1785   testLevenshteinAtMostTwoIsEmpty = {
1786     expr = strings.levenshteinAtMost 2 "" "";
1787     expected = true;
1788   };
1790   testLevenshteinAtMostTwoIsZero = {
1791     expr = strings.levenshteinAtMost 2 "abcdef" "abcdef";
1792     expected = true;
1793   };
1795   testLevenshteinAtMostTwoIsOne = {
1796     expr = strings.levenshteinAtMost 2 "abcdef" "abddef";
1797     expected = true;
1798   };
1800   testLevenshteinAtMostTwoDiff0False = {
1801     expr = strings.levenshteinAtMost 2 "abcdef" "aczyef";
1802     expected = false;
1803   };
1805   testLevenshteinAtMostTwoDiff0Outer = {
1806     expr = strings.levenshteinAtMost 2 "abcdef" "zbcdez";
1807     expected = true;
1808   };
1810   testLevenshteinAtMostTwoDiff0DelLeft = {
1811     expr = strings.levenshteinAtMost 2 "abcdef" "bcdefz";
1812     expected = true;
1813   };
1815   testLevenshteinAtMostTwoDiff0DelRight = {
1816     expr = strings.levenshteinAtMost 2 "abcdef" "zabcde";
1817     expected = true;
1818   };
1820   testLevenshteinAtMostTwoDiff1False = {
1821     expr = strings.levenshteinAtMost 2 "abcdef" "bddez";
1822     expected = false;
1823   };
1825   testLevenshteinAtMostTwoDiff1DelLeft = {
1826     expr = strings.levenshteinAtMost 2 "abcdef" "bcdez";
1827     expected = true;
1828   };
1830   testLevenshteinAtMostTwoDiff1DelRight = {
1831     expr = strings.levenshteinAtMost 2 "abcdef" "zbcde";
1832     expected = true;
1833   };
1835   testLevenshteinAtMostTwoDiff2False = {
1836     expr = strings.levenshteinAtMost 2 "hello" "hxo";
1837     expected = false;
1838   };
1840   testLevenshteinAtMostTwoDiff2True = {
1841     expr = strings.levenshteinAtMost 2 "hello" "heo";
1842     expected = true;
1843   };
1845   testLevenshteinAtMostTwoDiff3 = {
1846     expr = strings.levenshteinAtMost 2 "hello" "ho";
1847     expected = false;
1848   };
1850   testLevenshteinAtMostThreeFalse = {
1851     expr = strings.levenshteinAtMost 3 "hello" "Holla!";
1852     expected = false;
1853   };
1855   testLevenshteinAtMostThreeTrue = {
1856     expr = strings.levenshteinAtMost 3 "hello" "Holla";
1857     expected = true;
1858   };
1860   # lazyDerivation
1862   testLazyDerivationIsLazyInDerivationForAttrNames = {
1863     expr = attrNames (lazyDerivation {
1864       derivation = throw "not lazy enough";
1865     });
1866     # It's ok to add attribute names here when lazyDerivation is improved
1867     # in accordance with its inline comments.
1868     expected = [ "drvPath" "meta" "name" "out" "outPath" "outputName" "outputs" "system" "type" ];
1869   };
1871   testLazyDerivationIsLazyInDerivationForPassthruAttr = {
1872     expr = (lazyDerivation {
1873       derivation = throw "not lazy enough";
1874       passthru.tests = "whatever is in tests";
1875     }).tests;
1876     expected = "whatever is in tests";
1877   };
1879   testLazyDerivationIsLazyInDerivationForPassthruAttr2 = {
1880     # passthru.tests is not a special case. It works for any attr.
1881     expr = (lazyDerivation {
1882       derivation = throw "not lazy enough";
1883       passthru.foo = "whatever is in foo";
1884     }).foo;
1885     expected = "whatever is in foo";
1886   };
1888   testLazyDerivationIsLazyInDerivationForMeta = {
1889     expr = (lazyDerivation {
1890       derivation = throw "not lazy enough";
1891       meta = "whatever is in meta";
1892     }).meta;
1893     expected = "whatever is in meta";
1894   };
1896   testLazyDerivationReturnsDerivationAttrs = let
1897     derivation = {
1898       type = "derivation";
1899       outputs = ["out"];
1900       out = "test out";
1901       outPath = "test outPath";
1902       outputName = "out";
1903       drvPath = "test drvPath";
1904       name = "test name";
1905       system = "test system";
1906       meta = "test meta";
1907     };
1908   in {
1909     expr = lazyDerivation { inherit derivation; };
1910     expected = derivation;
1911   };
1913   testTypeDescriptionInt = {
1914     expr = (with types; int).description;
1915     expected = "signed integer";
1916   };
1917   testTypeDescriptionListOfInt = {
1918     expr = (with types; listOf int).description;
1919     expected = "list of signed integer";
1920   };
1921   testTypeDescriptionListOfListOfInt = {
1922     expr = (with types; listOf (listOf int)).description;
1923     expected = "list of list of signed integer";
1924   };
1925   testTypeDescriptionListOfEitherStrOrBool = {
1926     expr = (with types; listOf (either str bool)).description;
1927     expected = "list of (string or boolean)";
1928   };
1929   testTypeDescriptionEitherListOfStrOrBool = {
1930     expr = (with types; either (listOf bool) str).description;
1931     expected = "(list of boolean) or string";
1932   };
1933   testTypeDescriptionEitherStrOrListOfBool = {
1934     expr = (with types; either str (listOf bool)).description;
1935     expected = "string or list of boolean";
1936   };
1937   testTypeDescriptionOneOfListOfStrOrBool = {
1938     expr = (with types; oneOf [ (listOf bool) str ]).description;
1939     expected = "(list of boolean) or string";
1940   };
1941   testTypeDescriptionOneOfListOfStrOrBoolOrNumber = {
1942     expr = (with types; oneOf [ (listOf bool) str number ]).description;
1943     expected = "(list of boolean) or string or signed integer or floating point number";
1944   };
1945   testTypeDescriptionEitherListOfBoolOrEitherStringOrNumber = {
1946     expr = (with types; either (listOf bool) (either str number)).description;
1947     expected = "(list of boolean) or string or signed integer or floating point number";
1948   };
1949   testTypeDescriptionEitherEitherListOfBoolOrStringOrNumber = {
1950     expr = (with types; either (either (listOf bool) str) number).description;
1951     expected = "(list of boolean) or string or signed integer or floating point number";
1952   };
1953   testTypeDescriptionEitherNullOrBoolOrString = {
1954     expr = (with types; either (nullOr bool) str).description;
1955     expected = "null or boolean or string";
1956   };
1957   testTypeDescriptionEitherListOfEitherBoolOrStrOrInt = {
1958     expr = (with types; either (listOf (either bool str)) int).description;
1959     expected = "(list of (boolean or string)) or signed integer";
1960   };
1961   testTypeDescriptionEitherIntOrListOrEitherBoolOrStr = {
1962     expr = (with types; either int (listOf (either bool str))).description;
1963     expected = "signed integer or list of (boolean or string)";
1964   };
1966 # Meta
1967   testGetExe'Output = {
1968     expr = getExe' {
1969       type = "derivation";
1970       out = "somelonghash";
1971       bin = "somelonghash";
1972     } "executable";
1973     expected = "somelonghash/bin/executable";
1974   };
1976   testGetExeOutput = {
1977     expr = getExe {
1978       type = "derivation";
1979       out = "somelonghash";
1980       bin = "somelonghash";
1981       meta.mainProgram = "mainProgram";
1982     };
1983     expected = "somelonghash/bin/mainProgram";
1984   };
1986   testGetExe'FailureFirstArg = testingThrow (
1987     getExe' "not a derivation" "executable"
1988   );
1990   testGetExe'FailureSecondArg = testingThrow (
1991     getExe' { type = "derivation"; } "dir/executable"
1992   );
1994   testPlatformMatch = {
1995     expr = meta.platformMatch { system = "x86_64-linux"; } "x86_64-linux";
1996     expected = true;
1997   };
1999   testPlatformMatchAttrs = {
2000     expr = meta.platformMatch (systems.elaborate "x86_64-linux") (systems.elaborate "x86_64-linux").parsed;
2001     expected = true;
2002   };
2004   testPlatformMatchNoMatch = {
2005     expr = meta.platformMatch { system = "x86_64-darwin"; } "x86_64-linux";
2006     expected = false;
2007   };
2009   testPlatformMatchMissingSystem = {
2010     expr = meta.platformMatch { } "x86_64-linux";
2011     expected = false;
2012   };