Merge pull request #309460 from r-ryantm/auto-update/home-manager
[NixPkgs.git] / lib / tests / misc.nix
blob6774939023d20ac2a15cd2dd9c05adfc4ef7aeea
1 /**
2   Nix evaluation tests for various lib functions.
4   Since these tests are implemented with Nix evaluation,
5   error checking is limited to what `builtins.tryEval` can detect,
6   which is `throw`'s and `abort`'s, without error messages.
8   If you need to test error messages or more complex evaluations, see
9   `lib/tests/modules.sh`, `lib/tests/sources.sh` or `lib/tests/filesystem.sh` as examples.
11   To run these tests:
13     [nixpkgs]$ nix-instantiate --eval --strict lib/tests/misc.nix
15   If the resulting list is empty, all tests passed.
16   Alternatively, to run all `lib` tests:
18     [nixpkgs]$ nix-build lib/tests/release.nix
21 let
22   lib = import ../default.nix;
24   inherit (lib)
25     allUnique
26     and
27     attrNames
28     attrsets
29     attrsToList
30     bitAnd
31     bitOr
32     bitXor
33     boolToString
34     callPackagesWith
35     callPackageWith
36     cartesianProduct
37     cli
38     composeExtensions
39     composeManyExtensions
40     concatLines
41     concatMapAttrs
42     concatMapStrings
43     concatStrings
44     concatStringsSep
45     const
46     escapeXML
47     evalModules
48     filter
49     fix
50     fold
51     foldAttrs
52     foldl
53     foldl'
54     foldlAttrs
55     foldr
56     functionArgs
57     generators
58     genList
59     getExe
60     getExe'
61     groupBy
62     groupBy'
63     hasAttrByPath
64     hasInfix
65     id
66     ifilter0
67     isStorePath
68     lazyDerivation
69     length
70     lists
71     listToAttrs
72     makeExtensible
73     makeIncludePath
74     makeOverridable
75     mapAttrs
76     mapCartesianProduct
77     matchAttrs
78     mergeAttrs
79     meta
80     mod
81     nameValuePair
82     optionalDrvAttr
83     optionAttrSetToDocList
84     overrideExisting
85     packagesFromDirectoryRecursive
86     pipe
87     range
88     recursiveUpdateUntil
89     removePrefix
90     replicate
91     runTests
92     setFunctionArgs
93     showAttrPath
94     sort
95     sortOn
96     stringLength
97     strings
98     stringToCharacters
99     systems
100     tail
101     take
102     testAllTrue
103     toBaseDigits
104     toHexString
105     toInt
106     toIntBase10
107     toShellVars
108     types
109     updateManyAttrsByPath
110     versions
111     xor
112     ;
114   testingThrow = expr: {
115     expr = (builtins.tryEval (builtins.seq expr "didn't throw"));
116     expected = { success = false; value = false; };
117   };
118   testingEval = expr: {
119     expr = (builtins.tryEval expr).success;
120     expected = true;
121   };
123   testSanitizeDerivationName = { name, expected }:
124   let
125     drv = derivation {
126       name = strings.sanitizeDerivationName name;
127       builder = "x";
128       system = "x";
129     };
130   in {
131     # Evaluate the derivation so an invalid name would be caught
132     expr = builtins.seq drv.drvPath drv.name;
133     inherit expected;
134   };
138 runTests {
140 # CUSTOMIZATION
142   testFunctionArgsMakeOverridable = {
143     expr = functionArgs (makeOverridable ({ a, b, c ? null}: {}));
144     expected = { a = false; b = false; c = true; };
145   };
147   testFunctionArgsMakeOverridableOverride = {
148     expr = functionArgs (makeOverridable ({ a, b, c ? null }: {}) { a = 1; b = 2; }).override;
149     expected = { a = false; b = false; c = true; };
150   };
152   testCallPackageWithOverridePreservesArguments =
153     let
154       f = { a ? 0, b }: {};
155       f' = callPackageWith { a = 1; b = 2; } f {};
156     in {
157       expr = functionArgs f'.override;
158       expected = functionArgs f;
159     };
161   testCallPackagesWithOverridePreservesArguments =
162     let
163       f = { a ? 0, b }: { nested = {}; };
164       f' = callPackagesWith { a = 1; b = 2; } f {};
165     in {
166       expr = functionArgs f'.nested.override;
167       expected = functionArgs f;
168     };
170 # TRIVIAL
172   testId = {
173     expr = id 1;
174     expected = 1;
175   };
177   testConst = {
178     expr = const 2 3;
179     expected = 2;
180   };
182   testPipe = {
183     expr = pipe 2 [
184       (x: x + 2) # 2 + 2 = 4
185       (x: x * 2) # 4 * 2 = 8
186     ];
187     expected = 8;
188   };
190   testPipeEmpty = {
191     expr = pipe 2 [];
192     expected = 2;
193   };
195   testPipeStrings = {
196     expr = pipe [ 3 4 ] [
197       (map toString)
198       (map (s: s + "\n"))
199       concatStrings
200     ];
201     expected = ''
202       3
203       4
204     '';
205   };
207   /*
208     testOr = {
209       expr = or true false;
210       expected = true;
211     };
212   */
214   testAnd = {
215     expr = and true false;
216     expected = false;
217   };
219   testXor = {
220     expr = [
221       (xor true false)
222       (xor true true)
223       (xor false false)
224       (xor false true)
225     ];
226     expected = [
227       true
228       false
229       false
230       true
231     ];
232   };
234   testFix = {
235     expr = fix (x: {a = if x ? a then "a" else "b";});
236     expected = {a = "a";};
237   };
239   testComposeExtensions = {
240     expr = let obj = makeExtensible (self: { foo = self.bar; });
241                f = self: super: { bar = false; baz = true; };
242                g = self: super: { bar = super.baz or false; };
243                f_o_g = composeExtensions f g;
244                composed = obj.extend f_o_g;
245            in composed.foo;
246     expected = true;
247   };
249   testComposeManyExtensions0 = {
250     expr = let obj = makeExtensible (self: { foo = true; });
251                emptyComposition = composeManyExtensions [];
252                composed = obj.extend emptyComposition;
253            in composed.foo;
254     expected = true;
255   };
257   testComposeManyExtensions =
258     let f = self: super: { bar = false; baz = true; };
259         g = self: super: { bar = super.baz or false; };
260         h = self: super: { qux = super.bar or false; };
261         obj = makeExtensible (self: { foo = self.qux; });
262     in {
263     expr = let composition = composeManyExtensions [f g h];
264                composed = obj.extend composition;
265            in composed.foo;
266     expected = (obj.extend (composeExtensions f (composeExtensions g h))).foo;
267   };
269   testBitAnd = {
270     expr = (bitAnd 3 10);
271     expected = 2;
272   };
274   testBitOr = {
275     expr = (bitOr 3 10);
276     expected = 11;
277   };
279   testBitXor = {
280     expr = (bitXor 3 10);
281     expected = 9;
282   };
284   testToHexString = {
285     expr = toHexString 250;
286     expected = "FA";
287   };
289   testToBaseDigits = {
290     expr = toBaseDigits 2 6;
291     expected = [ 1 1 0 ];
292   };
294   testFunctionArgsFunctor = {
295     expr = functionArgs { __functor = self: { a, b }: null; };
296     expected = { a = false; b = false; };
297   };
299   testFunctionArgsSetFunctionArgs = {
300     expr = functionArgs (setFunctionArgs (args: args.x) { x = false; });
301     expected = { x = false; };
302   };
304 # STRINGS
306   testConcatMapStrings = {
307     expr = concatMapStrings (x: x + ";") ["a" "b" "c"];
308     expected = "a;b;c;";
309   };
311   testConcatStringsSep = {
312     expr = concatStringsSep "," ["a" "b" "c"];
313     expected = "a,b,c";
314   };
316   testConcatLines = {
317     expr = concatLines ["a" "b" "c"];
318     expected = "a\nb\nc\n";
319   };
321   testMakeIncludePathWithPkgs = {
322     expr = (makeIncludePath [
323       # makeIncludePath preferably selects the "dev" output
324       { dev.outPath = "/dev"; out.outPath = "/out"; outPath = "/default"; }
325       # "out" is used if "dev" is not found
326       { out.outPath = "/out"; outPath = "/default"; }
327       # And it returns the derivation directly if there's no "out" either
328       { outPath = "/default"; }
329       # Same if the output is specified explicitly, even if there's a "dev"
330       { dev.outPath = "/dev"; outPath = "/default"; outputSpecified = true; }
331     ]);
332     expected = "/dev/include:/out/include:/default/include:/default/include";
333   };
335   testMakeIncludePathWithEmptyList = {
336     expr = (makeIncludePath [ ]);
337     expected = "";
338   };
340   testMakeIncludePathWithOneString = {
341     expr = (makeIncludePath [ "/usr" ]);
342     expected = "/usr/include";
343   };
345   testMakeIncludePathWithManyString = {
346     expr = (makeIncludePath [ "/usr" "/usr/local" ]);
347     expected = "/usr/include:/usr/local/include";
348   };
350   testReplicateString = {
351     expr = strings.replicate 5 "hello";
352     expected = "hellohellohellohellohello";
353   };
355   testSplitStringsSimple = {
356     expr = strings.splitString "." "a.b.c.d";
357     expected = [ "a" "b" "c" "d" ];
358   };
360   testSplitStringsEmpty = {
361     expr = strings.splitString "." "a..b";
362     expected = [ "a" "" "b" ];
363   };
365   testSplitStringsOne = {
366     expr = strings.splitString ":" "a.b";
367     expected = [ "a.b" ];
368   };
370   testSplitStringsNone = {
371     expr = strings.splitString "." "";
372     expected = [ "" ];
373   };
375   testSplitStringsFirstEmpty = {
376     expr = strings.splitString "/" "/a/b/c";
377     expected = [ "" "a" "b" "c" ];
378   };
380   testSplitStringsLastEmpty = {
381     expr = strings.splitString ":" "2001:db8:0:0042::8a2e:370:";
382     expected = [ "2001" "db8" "0" "0042" "" "8a2e" "370" "" ];
383   };
385   testSplitStringsRegex = {
386     expr = strings.splitString "\\[{}]()^$?*+|." "A\\[{}]()^$?*+|.B";
387     expected = [ "A" "B" ];
388   };
390   testSplitStringsDerivation = {
391     expr = take 3  (strings.splitString "/" (derivation {
392       name = "name";
393       builder = "builder";
394       system = "system";
395     }));
396     expected = ["" "nix" "store"];
397   };
399   testSplitVersionSingle = {
400     expr = versions.splitVersion "1";
401     expected = [ "1" ];
402   };
404   testSplitVersionDouble = {
405     expr = versions.splitVersion "1.2";
406     expected = [ "1" "2" ];
407   };
409   testSplitVersionTriple = {
410     expr = versions.splitVersion "1.2.3";
411     expected = [ "1" "2" "3" ];
412   };
414   testPadVersionLess = {
415     expr = versions.pad 3 "1.2";
416     expected = "1.2.0";
417   };
419   testPadVersionLessExtra = {
420     expr = versions.pad 3 "1.3-rc1";
421     expected = "1.3.0-rc1";
422   };
424   testPadVersionMore = {
425     expr = versions.pad 3 "1.2.3.4";
426     expected = "1.2.3";
427   };
429   testIsStorePath =  {
430     expr =
431       let goodPath =
432             "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11";
433       in {
434         storePath = isStorePath goodPath;
435         storePathDerivation = isStorePath (import ../.. { system = "x86_64-linux"; }).hello;
436         storePathAppendix = isStorePath
437           "${goodPath}/bin/python";
438         nonAbsolute = isStorePath (concatStrings (tail (stringToCharacters goodPath)));
439         asPath = isStorePath (/. + goodPath);
440         otherPath = isStorePath "/something/else";
441         otherVals = {
442           attrset = isStorePath {};
443           list = isStorePath [];
444           int = isStorePath 42;
445         };
446       };
447     expected = {
448       storePath = true;
449       storePathDerivation = true;
450       storePathAppendix = false;
451       nonAbsolute = false;
452       asPath = true;
453       otherPath = false;
454       otherVals = {
455         attrset = false;
456         list = false;
457         int = false;
458       };
459     };
460   };
462   testEscapeXML = {
463     expr = escapeXML ''"test" 'test' < & >'';
464     expected = "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;";
465   };
467   testToShellVars = {
468     expr = ''
469       ${toShellVars {
470         STRing01 = "just a 'string'";
471         _array_ = [ "with" "more strings" ];
472         assoc."with some" = ''
473           strings
474           possibly newlines
475         '';
476         drv = {
477           outPath = "/drv";
478           foo = "ignored attribute";
479         };
480         path = /path;
481         stringable = {
482           __toString = _: "hello toString";
483           bar = "ignored attribute";
484         };
485       }}
486     '';
487     expected = ''
488       STRing01='just a '\'''string'\''''
489       declare -a _array_=('with' 'more strings')
490       declare -A assoc=(['with some']='strings
491       possibly newlines
492       ')
493       drv='/drv'
494       path='/path'
495       stringable='hello toString'
496     '';
497   };
499   testHasInfixFalse = {
500     expr = hasInfix "c" "abde";
501     expected = false;
502   };
504   testHasInfixTrue = {
505     expr = hasInfix "c" "abcde";
506     expected = true;
507   };
509   testHasInfixDerivation = {
510     expr = hasInfix "hello" (import ../.. { system = "x86_64-linux"; }).hello;
511     expected = true;
512   };
514   testHasInfixPath = {
515     expr = hasInfix "tests" ./.;
516     expected = true;
517   };
519   testHasInfixPathStoreDir = {
520     expr = hasInfix builtins.storeDir ./.;
521     expected = true;
522   };
524   testHasInfixToString = {
525     expr = hasInfix "a" { __toString = _: "a"; };
526     expected = true;
527   };
529   testRemovePrefixExample1 = {
530     expr = removePrefix "foo." "foo.bar.baz";
531     expected = "bar.baz";
532   };
533   testRemovePrefixExample2 = {
534     expr = removePrefix "xxx" "foo.bar.baz";
535     expected = "foo.bar.baz";
536   };
537   testRemovePrefixEmptyPrefix = {
538     expr = removePrefix "" "foo";
539     expected = "foo";
540   };
541   testRemovePrefixEmptyString = {
542     expr = removePrefix "foo" "";
543     expected = "";
544   };
545   testRemovePrefixEmptyBoth = {
546     expr = removePrefix "" "";
547     expected = "";
548   };
550   testNormalizePath = {
551     expr = strings.normalizePath "//a/b//c////d/";
552     expected = "/a/b/c/d/";
553   };
555   testCharToInt = {
556     expr = strings.charToInt "A";
557     expected = 65;
558   };
560   testEscapeC = {
561     expr = strings.escapeC [ " " ] "Hello World";
562     expected = "Hello\\x20World";
563   };
565   testEscapeURL = testAllTrue [
566     ("" == strings.escapeURL "")
567     ("Hello" == strings.escapeURL "Hello")
568     ("Hello%20World" == strings.escapeURL "Hello World")
569     ("Hello%2FWorld" == strings.escapeURL "Hello/World")
570     ("42%25" == strings.escapeURL "42%")
571     ("%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:/@$'()*,;")
572   ];
574   testToInt = testAllTrue [
575     # Naive
576     (123 == toInt "123")
577     (0 == toInt "0")
578     # Whitespace Padding
579     (123 == toInt " 123")
580     (123 == toInt "123 ")
581     (123 == toInt " 123 ")
582     (123 == toInt "   123   ")
583     (0 == toInt " 0")
584     (0 == toInt "0 ")
585     (0 == toInt " 0 ")
586     (-1 == toInt "-1")
587     (-1 == toInt " -1 ")
588   ];
590   testToIntFails = testAllTrue [
591     ( builtins.tryEval (toInt "") == { success = false; value = false; } )
592     ( builtins.tryEval (toInt "123 123") == { success = false; value = false; } )
593     ( builtins.tryEval (toInt "0 123") == { success = false; value = false; } )
594     ( builtins.tryEval (toInt " 0d ") == { success = false; value = false; } )
595     ( builtins.tryEval (toInt " 1d ") == { success = false; value = false; } )
596     ( builtins.tryEval (toInt " d0 ") == { success = false; value = false; } )
597     ( builtins.tryEval (toInt "00") == { success = false; value = false; } )
598     ( builtins.tryEval (toInt "01") == { success = false; value = false; } )
599     ( builtins.tryEval (toInt "002") == { success = false; value = false; } )
600     ( builtins.tryEval (toInt " 002 ") == { success = false; value = false; } )
601     ( builtins.tryEval (toInt " foo ") == { success = false; value = false; } )
602     ( builtins.tryEval (toInt " foo 123 ") == { success = false; value = false; } )
603     ( builtins.tryEval (toInt " foo123 ") == { success = false; value = false; } )
604   ];
606   testToIntBase10 = testAllTrue [
607     # Naive
608     (123 == toIntBase10 "123")
609     (0 == toIntBase10 "0")
610     # Whitespace Padding
611     (123 == toIntBase10 " 123")
612     (123 == toIntBase10 "123 ")
613     (123 == toIntBase10 " 123 ")
614     (123 == toIntBase10 "   123   ")
615     (0 == toIntBase10 " 0")
616     (0 == toIntBase10 "0 ")
617     (0 == toIntBase10 " 0 ")
618     # Zero Padding
619     (123 == toIntBase10 "0123")
620     (123 == toIntBase10 "0000123")
621     (0 == toIntBase10 "000000")
622     # Whitespace and Zero Padding
623     (123 == toIntBase10 " 0123")
624     (123 == toIntBase10 "0123 ")
625     (123 == toIntBase10 " 0123 ")
626     (123 == toIntBase10 " 0000123")
627     (123 == toIntBase10 "0000123 ")
628     (123 == toIntBase10 " 0000123 ")
629     (0 == toIntBase10 " 000000")
630     (0 == toIntBase10 "000000 ")
631     (0 == toIntBase10 " 000000 ")
632     (-1 == toIntBase10 "-1")
633     (-1 == toIntBase10 " -1 ")
634   ];
636   testToIntBase10Fails = testAllTrue [
637     ( builtins.tryEval (toIntBase10 "") == { success = false; value = false; } )
638     ( builtins.tryEval (toIntBase10 "123 123") == { success = false; value = false; } )
639     ( builtins.tryEval (toIntBase10 "0 123") == { success = false; value = false; } )
640     ( builtins.tryEval (toIntBase10 " 0d ") == { success = false; value = false; } )
641     ( builtins.tryEval (toIntBase10 " 1d ") == { success = false; value = false; } )
642     ( builtins.tryEval (toIntBase10 " d0 ") == { success = false; value = false; } )
643     ( builtins.tryEval (toIntBase10 " foo ") == { success = false; value = false; } )
644     ( builtins.tryEval (toIntBase10 " foo 123 ") == { success = false; value = false; } )
645     ( builtins.tryEval (toIntBase10 " foo 00123 ") == { success = false; value = false; } )
646     ( builtins.tryEval (toIntBase10 " foo00123 ") == { success = false; value = false; } )
647   ];
649 # LISTS
651   testFilter = {
652     expr = filter (x: x != "a") ["a" "b" "c" "a"];
653     expected = ["b" "c"];
654   };
656   testIfilter0Example = {
657     expr = ifilter0 (i: v: i == 0 || v > 2) [ 1 2 3 ];
658     expected = [ 1 3 ];
659   };
660   testIfilter0Empty = {
661     expr = ifilter0 (i: v: abort "shouldn't be evaluated!") [ ];
662     expected = [ ];
663   };
664   testIfilter0IndexOnly = {
665     expr = length (ifilter0 (i: v: mod i 2 == 0) [ (throw "0") (throw "1") (throw "2") (throw "3")]);
666     expected = 2;
667   };
668   testIfilter0All = {
669     expr = ifilter0 (i: v: true) [ 10 11 12 13 14 15 ];
670     expected = [ 10 11 12 13 14 15 ];
671   };
672   testIfilter0First = {
673     expr = ifilter0 (i: v: i == 0) [ 10 11 12 13 14 15 ];
674     expected = [ 10 ];
675   };
676   testIfilter0Last = {
677     expr = ifilter0 (i: v: i == 5) [ 10 11 12 13 14 15 ];
678     expected = [ 15 ];
679   };
681   testFold =
682     let
683       f = op: fold: fold op 0 (range 0 100);
684       # fold with associative operator
685       assoc = f builtins.add;
686       # fold with non-associative operator
687       nonAssoc = f builtins.sub;
688     in {
689       expr = {
690         assocRight = assoc foldr;
691         # right fold with assoc operator is same as left fold
692         assocRightIsLeft = assoc foldr == assoc foldl;
693         nonAssocRight = nonAssoc foldr;
694         nonAssocLeft = nonAssoc foldl;
695         # with non-assoc operator the fold results are not the same
696         nonAssocRightIsNotLeft = nonAssoc foldl != nonAssoc foldr;
697         # fold is an alias for foldr
698         foldIsRight = nonAssoc fold == nonAssoc foldr;
699       };
700       expected = {
701         assocRight = 5050;
702         assocRightIsLeft = true;
703         nonAssocRight = 50;
704         nonAssocLeft = (-5050);
705         nonAssocRightIsNotLeft = true;
706         foldIsRight = true;
707       };
708     };
710   testFoldl'Empty = {
711     expr = foldl' (acc: el: abort "operation not called") 0 [ ];
712     expected = 0;
713   };
715   testFoldl'IntegerAdding = {
716     expr = foldl' (acc: el: acc + el) 0 [ 1 2 3 ];
717     expected = 6;
718   };
720   # The accumulator isn't forced deeply
721   testFoldl'NonDeep = {
722     expr = take 3 (foldl'
723       (acc: el: [ el ] ++ acc)
724       [ (abort "unevaluated list entry") ]
725       [ 1 2 3 ]);
726     expected = [ 3 2 1 ];
727   };
729   # Compared to builtins.foldl', lib.foldl' evaluates the first accumulator strictly too
730   testFoldl'StrictInitial = {
731     expr = (builtins.tryEval (foldl' (acc: el: el) (throw "hello") [])).success;
732     expected = false;
733   };
735   # Make sure we don't get a stack overflow for large lists
736   # This number of elements would notably cause a stack overflow if it was implemented without the `foldl'` builtin
737   testFoldl'Large = {
738     expr = foldl' (acc: el: acc + el) 0 (range 0 100000);
739     expected = 5000050000;
740   };
742   testTake = testAllTrue [
743     ([] == (take 0 [  1 2 3 ]))
744     ([1] == (take 1 [  1 2 3 ]))
745     ([ 1 2 ] == (take 2 [  1 2 3 ]))
746     ([ 1 2 3 ] == (take 3 [  1 2 3 ]))
747     ([ 1 2 3 ] == (take 4 [  1 2 3 ]))
748   ];
750   testListHasPrefixExample1 = {
751     expr = lists.hasPrefix [ 1 2 ] [ 1 2 3 4 ];
752     expected = true;
753   };
754   testListHasPrefixExample2 = {
755     expr = lists.hasPrefix [ 0 1 ] [ 1 2 3 4 ];
756     expected = false;
757   };
758   testListHasPrefixLazy = {
759     expr = lists.hasPrefix [ 1 ] [ 1 (abort "lib.lists.hasPrefix is not lazy") ];
760     expected = true;
761   };
762   testListHasPrefixEmptyPrefix = {
763     expr = lists.hasPrefix [ ] [ 1 2 ];
764     expected = true;
765   };
766   testListHasPrefixEmptyList = {
767     expr = lists.hasPrefix [ 1 2 ] [ ];
768     expected = false;
769   };
771   testListRemovePrefixExample1 = {
772     expr = lists.removePrefix [ 1 2 ] [ 1 2 3 4 ];
773     expected = [ 3 4 ];
774   };
775   testListRemovePrefixExample2 = {
776     expr = (builtins.tryEval (lists.removePrefix [ 0 1 ] [ 1 2 3 4 ])).success;
777     expected = false;
778   };
779   testListRemovePrefixEmptyPrefix = {
780     expr = lists.removePrefix [ ] [ 1 2 ];
781     expected = [ 1 2 ];
782   };
783   testListRemovePrefixEmptyList = {
784     expr = (builtins.tryEval (lists.removePrefix [ 1 2 ] [ ])).success;
785     expected = false;
786   };
788   testFoldAttrs = {
789     expr = foldAttrs (n: a: [n] ++ a) [] [
790     { a = 2; b = 7; }
791     { a = 3;        c = 8; }
792     ];
793     expected = { a = [ 2 3 ]; b = [7]; c = [8];};
794   };
796   testListCommonPrefixExample1 = {
797     expr = lists.commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ];
798     expected = [ 1 2 ];
799   };
800   testListCommonPrefixExample2 = {
801     expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ];
802     expected = [ 1 2 3 ];
803   };
804   testListCommonPrefixExample3 = {
805     expr = lists.commonPrefix [ 1 2 3 ] [ 4 5 6 ];
806     expected = [ ];
807   };
808   testListCommonPrefixEmpty = {
809     expr = lists.commonPrefix [ ] [ 1 2 3 ];
810     expected = [ ];
811   };
812   testListCommonPrefixSame = {
813     expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 ];
814     expected = [ 1 2 3 ];
815   };
816   testListCommonPrefixLazy = {
817     expr = lists.commonPrefix [ 1 ] [ 1 (abort "lib.lists.commonPrefix shouldn't evaluate this")];
818     expected = [ 1 ];
819   };
820   # This would stack overflow if `commonPrefix` were implemented using recursion
821   testListCommonPrefixLong =
822     let
823       longList = genList (n: n) 100000;
824     in {
825       expr = lists.commonPrefix longList longList;
826       expected = longList;
827     };
829   testSort = {
830     expr = sort builtins.lessThan [ 40 2 30 42 ];
831     expected = [2 30 40 42];
832   };
834   testSortOn = {
835     expr = sortOn stringLength [ "aa" "b" "cccc" ];
836     expected = [ "b" "aa" "cccc" ];
837   };
839   testSortOnEmpty = {
840     expr = sortOn (throw "nope") [ ];
841     expected = [ ];
842   };
844   testSortOnIncomparable = {
845     expr =
846       map
847         (x: x.f x.ok)
848         (sortOn (x: x.ok) [
849           { ok = 1; f = x: x; }
850           { ok = 3; f = x: x + 3; }
851           { ok = 2; f = x: x; }
852         ]);
853     expected = [ 1 2 6 ];
854   };
856   testReplicate = {
857     expr = replicate 3 "a";
858     expected = ["a" "a" "a"];
859   };
861   testToIntShouldConvertStringToInt = {
862     expr = toInt "27";
863     expected = 27;
864   };
866   testToIntShouldThrowErrorIfItCouldNotConvertToInt = {
867     expr = builtins.tryEval (toInt "\"foo\"");
868     expected = { success = false; value = false; };
869   };
871   testHasAttrByPathTrue = {
872     expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; };
873     expected = true;
874   };
876   testHasAttrByPathFalse = {
877     expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; };
878     expected = false;
879   };
881   testHasAttrByPathNonStrict = {
882     expr = hasAttrByPath [] (throw "do not use");
883     expected = true;
884   };
886   testLongestValidPathPrefix_empty_empty = {
887     expr = attrsets.longestValidPathPrefix [ ] { };
888     expected = [ ];
889   };
891   testLongestValidPathPrefix_empty_nonStrict = {
892     expr = attrsets.longestValidPathPrefix [ ] (throw "do not use");
893     expected = [ ];
894   };
896   testLongestValidPathPrefix_zero = {
897     expr = attrsets.longestValidPathPrefix [ "a" (throw "do not use") ] { d = null; };
898     expected = [ ];
899   };
901   testLongestValidPathPrefix_zero_b = {
902     expr = attrsets.longestValidPathPrefix [ "z" "z" ] "remarkably harmonious";
903     expected = [ ];
904   };
906   testLongestValidPathPrefix_one = {
907     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a = null; };
908     expected = [ "a" ];
909   };
911   testLongestValidPathPrefix_two = {
912     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b = null; };
913     expected = [ "a" "b" ];
914   };
916   testLongestValidPathPrefix_three = {
917     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c = null; };
918     expected = [ "a" "b" "c" ];
919   };
921   testLongestValidPathPrefix_three_extra = {
922     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c.d = throw "nope"; };
923     expected = [ "a" "b" "c" ];
924   };
926   testFindFirstIndexExample1 = {
927     expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ];
928     expected = 1;
929   };
931   testFindFirstIndexExample2 = {
932     expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ 1 6 4 ];
933     expected = "a very specific default";
934   };
936   testFindFirstIndexEmpty = {
937     expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null [];
938     expected = null;
939   };
941   testFindFirstIndexSingleMatch = {
942     expr = lists.findFirstIndex (x: x == 5) null [ 5 ];
943     expected = 0;
944   };
946   testFindFirstIndexSingleDefault = {
947     expr = lists.findFirstIndex (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
948     expected = null;
949   };
951   testFindFirstIndexNone = {
952     expr = builtins.tryEval (lists.findFirstIndex (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
953     expected = { success = false; value = false; };
954   };
956   # Makes sure that the implementation doesn't cause a stack overflow
957   testFindFirstIndexBig = {
958     expr = lists.findFirstIndex (x: x == 1000000) null (range 0 1000000);
959     expected = 1000000;
960   };
962   testFindFirstIndexLazy = {
963     expr = lists.findFirstIndex (x: x == 1) null [ 1 (abort "list elements after the match must not be evaluated") ];
964     expected = 0;
965   };
967   testFindFirstExample1 = {
968     expr = lists.findFirst (x: x > 3) 7 [ 1 6 4 ];
969     expected = 6;
970   };
972   testFindFirstExample2 = {
973     expr = lists.findFirst (x: x > 9) 7 [ 1 6 4 ];
974     expected = 7;
975   };
977   testAllUnique_true = {
978     expr = allUnique [ 3 2 4 1 ];
979     expected = true;
980   };
981   testAllUnique_false = {
982     expr = allUnique [ 3 2 3 4 ];
983     expected = false;
984   };
986 # ATTRSETS
988   testConcatMapAttrs = {
989     expr = concatMapAttrs
990       (name: value: {
991         ${name} = value;
992         ${name + value} = value;
993       })
994       {
995         foo = "bar";
996         foobar = "baz";
997       };
998     expected = {
999       foo = "bar";
1000       foobar = "baz";
1001       foobarbaz = "baz";
1002     };
1003   };
1005   # code from example
1006   testFoldlAttrs = {
1007     expr = {
1008       example = foldlAttrs
1009         (acc: name: value: {
1010           sum = acc.sum + value;
1011           names = acc.names ++ [ name ];
1012         })
1013         { sum = 0; names = [ ]; }
1014         {
1015           foo = 1;
1016           bar = 10;
1017         };
1018       # should just return the initial value
1019       emptySet = foldlAttrs (throw "function not needed") 123 { };
1020       # should just evaluate to the last value
1021       valuesNotNeeded = foldlAttrs (acc: _name: _v: acc) 3 { z = throw "value z not needed"; a = throw "value a not needed"; };
1022       # the accumulator doesnt have to be an attrset it can be as trivial as being just a number or string
1023       trivialAcc = foldlAttrs (acc: _name: v: acc * 10 + v) 1 { z = 1; a = 2; };
1024     };
1025     expected = {
1026       example = {
1027         sum = 11;
1028         names = [ "bar" "foo" ];
1029       };
1030       emptySet = 123;
1031       valuesNotNeeded = 3;
1032       trivialAcc = 121;
1033     };
1034   };
1037   testMergeAttrsListExample1 = {
1038     expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ];
1039     expected = { a = 0; b = 1; c = 2; d = 3; };
1040   };
1041   testMergeAttrsListExample2 = {
1042     expr = attrsets.mergeAttrsList [ { a = 0; } { a = 1; } ];
1043     expected = { a = 1; };
1044   };
1045   testMergeAttrsListExampleMany =
1046     let
1047       list = genList (n:
1048         listToAttrs (genList (m:
1049           let
1050             # Integer divide n by two to create duplicate attributes
1051             str = "halfn${toString (n / 2)}m${toString m}";
1052           in
1053           nameValuePair str str
1054         ) 100)
1055       ) 100;
1056     in {
1057       expr = attrsets.mergeAttrsList list;
1058       expected = foldl' mergeAttrs { } list;
1059     };
1061   # code from the example
1062   testRecursiveUpdateUntil = {
1063     expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {
1064       # first attribute set
1065       foo.bar = 1;
1066       foo.baz = 2;
1067       bar = 3;
1068     } {
1069       #second attribute set
1070       foo.bar = 1;
1071       foo.quz = 2;
1072       baz = 4;
1073     };
1074     expected = {
1075       foo.bar = 1; # 'foo.*' from the second set
1076       foo.quz = 2; #
1077       bar = 3;     # 'bar' from the first set
1078       baz = 4;     # 'baz' from the second set
1079     };
1080   };
1082   testMatchAttrsMatchingExact = {
1083     expr = matchAttrs { cpu = { bits = 64; }; } { cpu = { bits = 64; }; };
1084     expected = true;
1085   };
1087   testMatchAttrsMismatch = {
1088     expr = matchAttrs { cpu = { bits = 128; }; } { cpu = { bits = 64; }; };
1089     expected = false;
1090   };
1092   testMatchAttrsMatchingImplicit = {
1093     expr = matchAttrs { cpu = { }; } { cpu = { bits = 64; }; };
1094     expected = true;
1095   };
1097   testMatchAttrsMissingAttrs = {
1098     expr = matchAttrs { cpu = {}; } { };
1099     expected = false;
1100   };
1102   testOverrideExistingEmpty = {
1103     expr = overrideExisting {} { a = 1; };
1104     expected = {};
1105   };
1107   testOverrideExistingDisjoint = {
1108     expr = overrideExisting { b = 2; } { a = 1; };
1109     expected = { b = 2; };
1110   };
1112   testOverrideExistingOverride = {
1113     expr = overrideExisting { a = 3; b = 2; } { a = 1; };
1114     expected = { a = 1; b = 2; };
1115   };
1117   testListAttrsReverse = let
1118     exampleAttrs = {foo=1; bar="asdf"; baz = [1 3 3 7]; fnord=null;};
1119     exampleSingletonList = [{name="foo"; value=1;}];
1120   in {
1121     expr = {
1122       isReverseToListToAttrs = builtins.listToAttrs (attrsToList exampleAttrs) == exampleAttrs;
1123       isReverseToAttrsToList = attrsToList (builtins.listToAttrs exampleSingletonList) == exampleSingletonList;
1124       testDuplicatePruningBehaviour = attrsToList (builtins.listToAttrs [{name="a"; value=2;} {name="a"; value=1;}]);
1125     };
1126     expected = {
1127       isReverseToAttrsToList = true;
1128       isReverseToListToAttrs = true;
1129       testDuplicatePruningBehaviour = [{name="a"; value=2;}];
1130     };
1131   };
1133   testAttrsToListsCanDealWithFunctions = testingEval (
1134     attrsToList { someFunc= a: a + 1;}
1135   );
1137 # GENERATORS
1138 # these tests assume attributes are converted to lists
1139 # in alphabetical order
1141   testMkKeyValueDefault = {
1142     expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar";
1143     expected = ''f\:oo:bar'';
1144   };
1146   testMkValueString = {
1147     expr = let
1148       vals = {
1149         int = 42;
1150         string = ''fo"o'';
1151         bool = true;
1152         bool2 = false;
1153         null = null;
1154         # float = 42.23; # floats are strange
1155       };
1156       in mapAttrs
1157         (const (generators.mkValueStringDefault {}))
1158         vals;
1159     expected = {
1160       int = "42";
1161       string = ''fo"o'';
1162       bool = "true";
1163       bool2 = "false";
1164       null = "null";
1165       # float = "42.23" true false [ "bar" ] ]'';
1166     };
1167   };
1169   testToKeyValue = {
1170     expr = generators.toKeyValue {} {
1171       key = "value";
1172       "other=key" = "baz";
1173     };
1174     expected = ''
1175       key=value
1176       other\=key=baz
1177     '';
1178   };
1180   testToINIEmpty = {
1181     expr = generators.toINI {} {};
1182     expected = "";
1183   };
1185   testToINIEmptySection = {
1186     expr = generators.toINI {} { foo = {}; bar = {}; };
1187     expected = ''
1188       [bar]
1190       [foo]
1191     '';
1192   };
1194   testToINIDuplicateKeys = {
1195     expr = generators.toINI { listsAsDuplicateKeys = true; } { foo.bar = true; baz.qux = [ 1 false ]; };
1196     expected = ''
1197       [baz]
1198       qux=1
1199       qux=false
1201       [foo]
1202       bar=true
1203     '';
1204   };
1206   testToINIDefaultEscapes = {
1207     expr = generators.toINI {} {
1208       "no [ and ] allowed unescaped" = {
1209         "and also no = in keys" = 42;
1210       };
1211     };
1212     expected = ''
1213       [no \[ and \] allowed unescaped]
1214       and also no \= in keys=42
1215     '';
1216   };
1218   testToINIDefaultFull = {
1219     expr = generators.toINI {} {
1220       "section 1" = {
1221         attribute1 = 5;
1222         x = "Me-se JarJar Binx";
1223         # booleans are converted verbatim by default
1224         boolean = false;
1225       };
1226       "foo[]" = {
1227         "he\\h=he" = "this is okay";
1228       };
1229     };
1230     expected = ''
1231       [foo\[\]]
1232       he\h\=he=this is okay
1234       [section 1]
1235       attribute1=5
1236       boolean=false
1237       x=Me-se JarJar Binx
1238     '';
1239   };
1241   testToINIWithGlobalSectionEmpty = {
1242     expr = generators.toINIWithGlobalSection {} {
1243       globalSection = {
1244       };
1245       sections = {
1246       };
1247     };
1248     expected = ''
1249     '';
1250   };
1252   testToINIWithGlobalSectionGlobalEmptyIsTheSameAsToINI =
1253     let
1254       sections = {
1255         "section 1" = {
1256           attribute1 = 5;
1257           x = "Me-se JarJar Binx";
1258         };
1259         "foo" = {
1260           "he\\h=he" = "this is okay";
1261         };
1262       };
1263     in {
1264       expr =
1265         generators.toINIWithGlobalSection {} {
1266             globalSection = {};
1267             sections = sections;
1268         };
1269       expected = generators.toINI {} sections;
1270   };
1272   testToINIWithGlobalSectionFull = {
1273     expr = generators.toINIWithGlobalSection {} {
1274       globalSection = {
1275         foo = "bar";
1276         test = false;
1277       };
1278       sections = {
1279         "section 1" = {
1280           attribute1 = 5;
1281           x = "Me-se JarJar Binx";
1282         };
1283         "foo" = {
1284           "he\\h=he" = "this is okay";
1285         };
1286       };
1287     };
1288     expected = ''
1289       foo=bar
1290       test=false
1292       [foo]
1293       he\h\=he=this is okay
1295       [section 1]
1296       attribute1=5
1297       x=Me-se JarJar Binx
1298     '';
1299   };
1301   testToGitINI = {
1302     expr = generators.toGitINI {
1303       user = {
1304         email = "user@example.org";
1305         name = "John Doe";
1306         signingKey = "00112233445566778899AABBCCDDEEFF";
1307       };
1308       gpg.program = "path-to-gpg";
1309       tag.gpgSign = true;
1310       include.path = "~/path/to/config.inc";
1311       includeIf."gitdif:~/src/dir".path = "~/path/to/conditional.inc";
1312       extra = {
1313         boolean = true;
1314         integer = 38;
1315         name = "value";
1316         subsection.value = "test";
1317       };};
1318     expected = ''
1319       [extra]
1320       ${"\t"}boolean = true
1321       ${"\t"}integer = 38
1322       ${"\t"}name = "value"
1324       [extra "subsection"]
1325       ${"\t"}value = "test"
1327       [gpg]
1328       ${"\t"}program = "path-to-gpg"
1330       [include]
1331       ${"\t"}path = "~/path/to/config.inc"
1333       [includeIf "gitdif:~/src/dir"]
1334       ${"\t"}path = "~/path/to/conditional.inc"
1336       [tag]
1337       ${"\t"}gpgSign = true
1339       [user]
1340       ${"\t"}email = "user@example.org"
1341       ${"\t"}name = "John Doe"
1342       ${"\t"}signingKey = "00112233445566778899AABBCCDDEEFF"
1343     '';
1344   };
1346   # right now only invocation check
1347   testToJSONSimple =
1348     let val = {
1349       foobar = [ "baz" 1 2 3 ];
1350     };
1351     in {
1352       expr = generators.toJSON {} val;
1353       # trivial implementation
1354       expected = builtins.toJSON val;
1355   };
1357   # right now only invocation check
1358   testToYAMLSimple =
1359     let val = {
1360       list = [ { one = 1; } { two = 2; } ];
1361       all = 42;
1362     };
1363     in {
1364       expr = generators.toYAML {} val;
1365       # trivial implementation
1366       expected = builtins.toJSON val;
1367   };
1369   testToPretty =
1370     let
1371       deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
1372     in {
1373     expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
1374       int = 42;
1375       float = 0.1337;
1376       bool = true;
1377       emptystring = "";
1378       string = "fn\${o}\"r\\d";
1379       newlinestring = "\n";
1380       path = /. + "/foo";
1381       null_ = null;
1382       function = x: x;
1383       functionArgs = { arg ? 4, foo }: arg;
1384       list = [ 3 4 function [ false ] ];
1385       emptylist = [];
1386       attrs = { foo = null; "foo b/ar" = "baz"; };
1387       emptyattrs = {};
1388       drv = deriv;
1389     };
1390     expected = rec {
1391       int = "42";
1392       float = "0.1337";
1393       bool = "true";
1394       emptystring = ''""'';
1395       string = ''"fn\''${o}\"r\\d"'';
1396       newlinestring = "\"\\n\"";
1397       path = "/foo";
1398       null_ = "null";
1399       function = "<function>";
1400       functionArgs = "<function, args: {arg?, foo}>";
1401       list = "[ 3 4 ${function} [ false ] ]";
1402       emptylist = "[ ]";
1403       attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }";
1404       emptyattrs = "{ }";
1405       drv = "<derivation ${deriv.name}>";
1406     };
1407   };
1409   testToPrettyLimit =
1410     let
1411       a.b = 1;
1412       a.c = a;
1413     in {
1414       expr = generators.toPretty { } (generators.withRecursion { throwOnDepthLimit = false; depthLimit = 2; } a);
1415       expected = "{\n  b = 1;\n  c = {\n    b = \"<unevaluated>\";\n    c = {\n      b = \"<unevaluated>\";\n      c = \"<unevaluated>\";\n    };\n  };\n}";
1416     };
1418   testToPrettyLimitThrow =
1419     let
1420       a.b = 1;
1421       a.c = a;
1422     in {
1423       expr = (builtins.tryEval
1424         (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))).success;
1425       expected = false;
1426     };
1428   testWithRecursionDealsWithFunctors =
1429     let
1430       functor = {
1431         __functor = self: { a, b, }: null;
1432       };
1433       a = {
1434         value = "1234";
1435         b = functor;
1436         c.d = functor;
1437       };
1438     in {
1439       expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a);
1440       expected = "{\n  b = <function, args: {a, b}>;\n  c = {\n    d = \"<unevaluated>\";\n  };\n  value = \"<unevaluated>\";\n}";
1441     };
1443   testToPrettyMultiline = {
1444     expr = mapAttrs (const (generators.toPretty { })) {
1445       list = [ 3 4 [ false ] ];
1446       attrs = { foo = null; bar.foo = "baz"; };
1447       newlinestring = "\n";
1448       multilinestring = ''
1449         hello
1450         ''${there}
1451         te'''st
1452       '';
1453       multilinestring' = ''
1454         hello
1455         there
1456         test'';
1457     };
1458     expected = {
1459       list = ''
1460         [
1461           3
1462           4
1463           [
1464             false
1465           ]
1466         ]'';
1467       attrs = ''
1468         {
1469           bar = {
1470             foo = "baz";
1471           };
1472           foo = null;
1473         }'';
1474       newlinestring = "''\n  \n''";
1475       multilinestring = ''
1476         '''
1477           hello
1478           '''''${there}
1479           te''''st
1480         ''''';
1481       multilinestring' = ''
1482         '''
1483           hello
1484           there
1485           test''''';
1487     };
1488   };
1490   testToPrettyAllowPrettyValues = {
1491     expr = generators.toPretty { allowPrettyValues = true; }
1492              { __pretty = v: "«" + v + "»"; val = "foo"; };
1493     expected  = "«foo»";
1494   };
1496   testToPlist = {
1497     expr = mapAttrs (const (generators.toPlist { })) {
1498       value = {
1499         nested.values = {
1500           int = 42;
1501           float = 0.1337;
1502           bool = true;
1503           emptystring = "";
1504           string = "fn\${o}\"r\\d";
1505           newlinestring = "\n";
1506           path = /. + "/foo";
1507           null_ = null;
1508           list = [ 3 4 "test" ];
1509           emptylist = [];
1510           attrs = { foo = null; "foo b/ar" = "baz"; };
1511           emptyattrs = {};
1512         };
1513       };
1514     };
1515     expected = { value = builtins.readFile ./test-to-plist-expected.plist; };
1516   };
1518   testToLuaEmptyAttrSet = {
1519     expr = generators.toLua {} {};
1520     expected = ''{}'';
1521   };
1523   testToLuaEmptyList = {
1524     expr = generators.toLua {} [];
1525     expected = ''{}'';
1526   };
1528   testToLuaListOfVariousTypes = {
1529     expr = generators.toLua {} [ null 43 3.14159 true ];
1530     expected = ''
1531       {
1532         nil,
1533         43,
1534         3.14159,
1535         true
1536       }'';
1537   };
1539   testToLuaString = {
1540     expr = generators.toLua {} ''double-quote (") and single quotes (')'';
1541     expected = ''"double-quote (\") and single quotes (')"'';
1542   };
1544   testToLuaAttrsetWithLuaInline = {
1545     expr = generators.toLua {} { x = generators.mkLuaInline ''"abc" .. "def"''; };
1546     expected = ''
1547       {
1548         ["x"] = ("abc" .. "def")
1549       }'';
1550   };
1552   testToLuaAttrsetWithSpaceInKey = {
1553     expr = generators.toLua {} { "some space and double-quote (\")" = 42; };
1554     expected = ''
1555       {
1556         ["some space and double-quote (\")"] = 42
1557       }'';
1558   };
1560   testToLuaWithoutMultiline = {
1561     expr = generators.toLua { multiline = false; } [ 41 43 ];
1562     expected = ''{ 41, 43 }'';
1563   };
1565   testToLuaEmptyBindings = {
1566     expr = generators.toLua { asBindings = true; } {};
1567     expected = "";
1568   };
1570   testToLuaBindings = {
1571     expr = generators.toLua { asBindings = true; } { x1 = 41; _y = { a = 43; }; };
1572     expected = ''
1573       _y = {
1574         ["a"] = 43
1575       }
1576       x1 = 41
1577     '';
1578   };
1580   testToLuaPartialTableBindings = {
1581     expr = generators.toLua { asBindings = true; } { "x.y" = 42; };
1582     expected = ''
1583       x.y = 42
1584     '';
1585   };
1587   testToLuaIndentedBindings = {
1588     expr = generators.toLua { asBindings = true; indent = "  "; } { x = { y = 42; }; };
1589     expected = "  x = {\n    [\"y\"] = 42\n  }\n";
1590   };
1592   testToLuaBindingsWithSpace = testingThrow (
1593     generators.toLua { asBindings = true; } { "with space" = 42; }
1594   );
1596   testToLuaBindingsWithLeadingDigit = testingThrow (
1597     generators.toLua { asBindings = true; } { "11eleven" = 42; }
1598   );
1600   testToLuaBasicExample = {
1601     expr = generators.toLua {} {
1602       cmd = [ "typescript-language-server" "--stdio" ];
1603       settings.workspace.library = generators.mkLuaInline ''vim.api.nvim_get_runtime_file("", true)'';
1604     };
1605     expected = ''
1606       {
1607         ["cmd"] = {
1608           "typescript-language-server",
1609           "--stdio"
1610         },
1611         ["settings"] = {
1612           ["workspace"] = {
1613             ["library"] = (vim.api.nvim_get_runtime_file("", true))
1614           }
1615         }
1616       }'';
1617   };
1619 # CLI
1621   testToGNUCommandLine = {
1622     expr = cli.toGNUCommandLine {} {
1623       data = builtins.toJSON { id = 0; };
1624       X = "PUT";
1625       retry = 3;
1626       retry-delay = null;
1627       url = [ "https://example.com/foo" "https://example.com/bar" ];
1628       silent = false;
1629       verbose = true;
1630     };
1632     expected = [
1633       "-X" "PUT"
1634       "--data" "{\"id\":0}"
1635       "--retry" "3"
1636       "--url" "https://example.com/foo"
1637       "--url" "https://example.com/bar"
1638       "--verbose"
1639     ];
1640   };
1642   testToGNUCommandLineShell = {
1643     expr = cli.toGNUCommandLineShell {} {
1644       data = builtins.toJSON { id = 0; };
1645       X = "PUT";
1646       retry = 3;
1647       retry-delay = null;
1648       url = [ "https://example.com/foo" "https://example.com/bar" ];
1649       silent = false;
1650       verbose = true;
1651     };
1653     expected = "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
1654   };
1656   testSanitizeDerivationNameLeadingDots = testSanitizeDerivationName {
1657     name = "..foo";
1658     expected = "foo";
1659   };
1661   testSanitizeDerivationNameUnicode = testSanitizeDerivationName {
1662     name = "fö";
1663     expected = "f-";
1664   };
1666   testSanitizeDerivationNameAscii = testSanitizeDerivationName {
1667     name = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
1668     expected = "-+--.-0123456789-=-?-ABCDEFGHIJKLMNOPQRSTUVWXYZ-_-abcdefghijklmnopqrstuvwxyz-";
1669   };
1671   testSanitizeDerivationNameTooLong = testSanitizeDerivationName {
1672     name = "This string is loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1673     expected = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1674   };
1676   testSanitizeDerivationNameTooLongWithInvalid = testSanitizeDerivationName {
1677     name = "Hello there aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&&&&&&&";
1678     expected = "there-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-";
1679   };
1681   testSanitizeDerivationNameEmpty = testSanitizeDerivationName {
1682     name = "";
1683     expected = "unknown";
1684   };
1686   testFreeformOptions = {
1687     expr =
1688       let
1689         submodule = { lib, ... }: {
1690           freeformType = lib.types.attrsOf (lib.types.submodule {
1691             options.bar = lib.mkOption {};
1692           });
1693           options.bar = lib.mkOption {};
1694         };
1696         module = { lib, ... }: {
1697           options.foo = lib.mkOption {
1698             type = lib.types.submodule submodule;
1699           };
1700         };
1702         options = (evalModules {
1703           modules = [ module ];
1704         }).options;
1706         locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
1707       in map (o: o.loc) locs;
1708     expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
1709   };
1711   testCartesianProductOfEmptySet = {
1712     expr = cartesianProduct {};
1713     expected = [ {} ];
1714   };
1716   testCartesianProductOfOneSet = {
1717     expr = cartesianProduct { a = [ 1 2 3 ]; };
1718     expected = [ { a = 1; } { a = 2; } { a = 3; } ];
1719   };
1721   testCartesianProductOfTwoSets = {
1722     expr = cartesianProduct { a = [ 1 ]; b = [ 10 20 ]; };
1723     expected = [
1724       { a = 1; b = 10; }
1725       { a = 1; b = 20; }
1726     ];
1727   };
1729   testCartesianProductOfTwoSetsWithOneEmpty = {
1730     expr = cartesianProduct { a = [ ]; b = [ 10 20 ]; };
1731     expected = [ ];
1732   };
1734   testCartesianProductOfThreeSets = {
1735     expr = cartesianProduct {
1736       a = [   1   2   3 ];
1737       b = [  10  20  30 ];
1738       c = [ 100 200 300 ];
1739     };
1740     expected = [
1741       { a = 1; b = 10; c = 100; }
1742       { a = 1; b = 10; c = 200; }
1743       { a = 1; b = 10; c = 300; }
1745       { a = 1; b = 20; c = 100; }
1746       { a = 1; b = 20; c = 200; }
1747       { a = 1; b = 20; c = 300; }
1749       { a = 1; b = 30; c = 100; }
1750       { a = 1; b = 30; c = 200; }
1751       { a = 1; b = 30; c = 300; }
1753       { a = 2; b = 10; c = 100; }
1754       { a = 2; b = 10; c = 200; }
1755       { a = 2; b = 10; c = 300; }
1757       { a = 2; b = 20; c = 100; }
1758       { a = 2; b = 20; c = 200; }
1759       { a = 2; b = 20; c = 300; }
1761       { a = 2; b = 30; c = 100; }
1762       { a = 2; b = 30; c = 200; }
1763       { a = 2; b = 30; c = 300; }
1765       { a = 3; b = 10; c = 100; }
1766       { a = 3; b = 10; c = 200; }
1767       { a = 3; b = 10; c = 300; }
1769       { a = 3; b = 20; c = 100; }
1770       { a = 3; b = 20; c = 200; }
1771       { a = 3; b = 20; c = 300; }
1773       { a = 3; b = 30; c = 100; }
1774       { a = 3; b = 30; c = 200; }
1775       { a = 3; b = 30; c = 300; }
1776     ];
1777   };
1779   testMapCartesianProductOfOneSet = {
1780     expr = mapCartesianProduct ({a}: a * 2) { a = [ 1 2 3 ]; };
1781     expected = [ 2 4 6 ];
1782   };
1784   testMapCartesianProductOfTwoSets = {
1785     expr = mapCartesianProduct ({a,b}: a + b) { a = [ 1 ]; b = [ 10 20 ]; };
1786     expected = [ 11 21 ];
1787   };
1789   testMapCartesianProcutOfTwoSetsWithOneEmpty = {
1790     expr = mapCartesianProduct (x: x.a + x.b) { a = [ ]; b = [ 10 20 ]; };
1791     expected = [ ];
1792   };
1794   testMapCartesianProductOfThreeSets = {
1795     expr = mapCartesianProduct ({a,b,c}: a + b + c) {
1796       a = [ 1 2 3 ];
1797       b = [ 10 20 30 ];
1798       c = [ 100 200 300 ];
1799     };
1800     expected = [ 111 211 311 121 221 321 131 231 331 112 212 312 122 222 322 132 232 332 113 213 313 123 223 323 133 233 333 ];
1801   };
1803   # The example from the showAttrPath documentation
1804   testShowAttrPathExample = {
1805     expr = showAttrPath [ "foo" "10" "bar" ];
1806     expected = "foo.\"10\".bar";
1807   };
1809   testShowAttrPathEmpty = {
1810     expr = showAttrPath [];
1811     expected = "<root attribute path>";
1812   };
1814   testShowAttrPathVarious = {
1815     expr = showAttrPath [
1816       "."
1817       "foo"
1818       "2"
1819       "a2-b"
1820       "_bc'de"
1821     ];
1822     expected = ''".".foo."2".a2-b._bc'de'';
1823   };
1825   testGroupBy = {
1826     expr = groupBy (n: toString (mod n 5)) (range 0 16);
1827     expected = {
1828       "0" = [ 0 5 10 15 ];
1829       "1" = [ 1 6 11 16 ];
1830       "2" = [ 2 7 12 ];
1831       "3" = [ 3 8 13 ];
1832       "4" = [ 4 9 14 ];
1833     };
1834   };
1836   testGroupBy' = {
1837     expr = groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ];
1838     expected = { false = 3; true = 12; };
1839   };
1841   # The example from the updateManyAttrsByPath documentation
1842   testUpdateManyAttrsByPathExample = {
1843     expr = updateManyAttrsByPath [
1844       {
1845         path = [ "a" "b" ];
1846         update = old: { d = old.c; };
1847       }
1848       {
1849         path = [ "a" "b" "c" ];
1850         update = old: old + 1;
1851       }
1852       {
1853         path = [ "x" "y" ];
1854         update = old: "xy";
1855       }
1856     ] { a.b.c = 0; };
1857     expected = { a = { b = { d = 1; }; }; x = { y = "xy"; }; };
1858   };
1860   # If there are no updates, the value is passed through
1861   testUpdateManyAttrsByPathNone = {
1862     expr = updateManyAttrsByPath [] "something";
1863     expected = "something";
1864   };
1866   # A single update to the root path is just like applying the function directly
1867   testUpdateManyAttrsByPathSingleIncrement = {
1868     expr = updateManyAttrsByPath [
1869       {
1870         path = [ ];
1871         update = old: old + 1;
1872       }
1873     ] 0;
1874     expected = 1;
1875   };
1877   # Multiple updates can be applied are done in order
1878   testUpdateManyAttrsByPathMultipleIncrements = {
1879     expr = updateManyAttrsByPath [
1880       {
1881         path = [ ];
1882         update = old: old + "a";
1883       }
1884       {
1885         path = [ ];
1886         update = old: old + "b";
1887       }
1888       {
1889         path = [ ];
1890         update = old: old + "c";
1891       }
1892     ] "";
1893     expected = "abc";
1894   };
1896   # If an update doesn't use the value, all previous updates are not evaluated
1897   testUpdateManyAttrsByPathLazy = {
1898     expr = updateManyAttrsByPath [
1899       {
1900         path = [ ];
1901         update = old: old + throw "nope";
1902       }
1903       {
1904         path = [ ];
1905         update = old: "untainted";
1906       }
1907     ] (throw "start");
1908     expected = "untainted";
1909   };
1911   # Deeply nested attributes can be updated without affecting others
1912   testUpdateManyAttrsByPathDeep = {
1913     expr = updateManyAttrsByPath [
1914       {
1915         path = [ "a" "b" "c" ];
1916         update = old: old + 1;
1917       }
1918     ] {
1919       a.b.c = 0;
1921       a.b.z = 0;
1922       a.y.z = 0;
1923       x.y.z = 0;
1924     };
1925     expected = {
1926       a.b.c = 1;
1928       a.b.z = 0;
1929       a.y.z = 0;
1930       x.y.z = 0;
1931     };
1932   };
1934   # Nested attributes are updated first
1935   testUpdateManyAttrsByPathNestedBeforehand = {
1936     expr = updateManyAttrsByPath [
1937       {
1938         path = [ "a" ];
1939         update = old: old // { x = old.b; };
1940       }
1941       {
1942         path = [ "a" "b" ];
1943         update = old: old + 1;
1944       }
1945     ] {
1946       a.b = 0;
1947     };
1948     expected = {
1949       a.b = 1;
1950       a.x = 1;
1951     };
1952   };
1954   ## Levenshtein distance functions and co.
1955   testCommonPrefixLengthEmpty = {
1956     expr = strings.commonPrefixLength "" "hello";
1957     expected = 0;
1958   };
1960   testCommonPrefixLengthSame = {
1961     expr = strings.commonPrefixLength "hello" "hello";
1962     expected = 5;
1963   };
1965   testCommonPrefixLengthDiffering = {
1966     expr = strings.commonPrefixLength "hello" "hey";
1967     expected = 2;
1968   };
1970   testCommonSuffixLengthEmpty = {
1971     expr = strings.commonSuffixLength "" "hello";
1972     expected = 0;
1973   };
1975   testCommonSuffixLengthSame = {
1976     expr = strings.commonSuffixLength "hello" "hello";
1977     expected = 5;
1978   };
1980   testCommonSuffixLengthDiffering = {
1981     expr = strings.commonSuffixLength "test" "rest";
1982     expected = 3;
1983   };
1985   testLevenshteinEmpty = {
1986     expr = strings.levenshtein "" "";
1987     expected = 0;
1988   };
1990   testLevenshteinOnlyAdd = {
1991     expr = strings.levenshtein "" "hello there";
1992     expected = 11;
1993   };
1995   testLevenshteinOnlyRemove = {
1996     expr = strings.levenshtein "hello there" "";
1997     expected = 11;
1998   };
2000   testLevenshteinOnlyTransform = {
2001     expr = strings.levenshtein "abcdef" "ghijkl";
2002     expected = 6;
2003   };
2005   testLevenshteinMixed = {
2006     expr = strings.levenshtein "kitchen" "sitting";
2007     expected = 5;
2008   };
2010   testLevenshteinAtMostZeroFalse = {
2011     expr = strings.levenshteinAtMost 0 "foo" "boo";
2012     expected = false;
2013   };
2015   testLevenshteinAtMostZeroTrue = {
2016     expr = strings.levenshteinAtMost 0 "foo" "foo";
2017     expected = true;
2018   };
2020   testLevenshteinAtMostOneFalse = {
2021     expr = strings.levenshteinAtMost 1 "car" "ct";
2022     expected = false;
2023   };
2025   testLevenshteinAtMostOneTrue = {
2026     expr = strings.levenshteinAtMost 1 "car" "cr";
2027     expected = true;
2028   };
2030   # We test levenshteinAtMost 2 particularly well because it uses a complicated
2031   # implementation
2032   testLevenshteinAtMostTwoIsEmpty = {
2033     expr = strings.levenshteinAtMost 2 "" "";
2034     expected = true;
2035   };
2037   testLevenshteinAtMostTwoIsZero = {
2038     expr = strings.levenshteinAtMost 2 "abcdef" "abcdef";
2039     expected = true;
2040   };
2042   testLevenshteinAtMostTwoIsOne = {
2043     expr = strings.levenshteinAtMost 2 "abcdef" "abddef";
2044     expected = true;
2045   };
2047   testLevenshteinAtMostTwoDiff0False = {
2048     expr = strings.levenshteinAtMost 2 "abcdef" "aczyef";
2049     expected = false;
2050   };
2052   testLevenshteinAtMostTwoDiff0Outer = {
2053     expr = strings.levenshteinAtMost 2 "abcdef" "zbcdez";
2054     expected = true;
2055   };
2057   testLevenshteinAtMostTwoDiff0DelLeft = {
2058     expr = strings.levenshteinAtMost 2 "abcdef" "bcdefz";
2059     expected = true;
2060   };
2062   testLevenshteinAtMostTwoDiff0DelRight = {
2063     expr = strings.levenshteinAtMost 2 "abcdef" "zabcde";
2064     expected = true;
2065   };
2067   testLevenshteinAtMostTwoDiff1False = {
2068     expr = strings.levenshteinAtMost 2 "abcdef" "bddez";
2069     expected = false;
2070   };
2072   testLevenshteinAtMostTwoDiff1DelLeft = {
2073     expr = strings.levenshteinAtMost 2 "abcdef" "bcdez";
2074     expected = true;
2075   };
2077   testLevenshteinAtMostTwoDiff1DelRight = {
2078     expr = strings.levenshteinAtMost 2 "abcdef" "zbcde";
2079     expected = true;
2080   };
2082   testLevenshteinAtMostTwoDiff2False = {
2083     expr = strings.levenshteinAtMost 2 "hello" "hxo";
2084     expected = false;
2085   };
2087   testLevenshteinAtMostTwoDiff2True = {
2088     expr = strings.levenshteinAtMost 2 "hello" "heo";
2089     expected = true;
2090   };
2092   testLevenshteinAtMostTwoDiff3 = {
2093     expr = strings.levenshteinAtMost 2 "hello" "ho";
2094     expected = false;
2095   };
2097   testLevenshteinAtMostThreeFalse = {
2098     expr = strings.levenshteinAtMost 3 "hello" "Holla!";
2099     expected = false;
2100   };
2102   testLevenshteinAtMostThreeTrue = {
2103     expr = strings.levenshteinAtMost 3 "hello" "Holla";
2104     expected = true;
2105   };
2107   # DERIVATIONS
2109   testLazyDerivationIsLazyInDerivationForAttrNames = {
2110     expr = attrNames (lazyDerivation {
2111       derivation = throw "not lazy enough";
2112     });
2113     # It's ok to add attribute names here when lazyDerivation is improved
2114     # in accordance with its inline comments.
2115     expected = [ "drvPath" "meta" "name" "out" "outPath" "outputName" "outputs" "system" "type" ];
2116   };
2118   testLazyDerivationIsLazyInDerivationForPassthruAttr = {
2119     expr = (lazyDerivation {
2120       derivation = throw "not lazy enough";
2121       passthru.tests = "whatever is in tests";
2122     }).tests;
2123     expected = "whatever is in tests";
2124   };
2126   testLazyDerivationIsLazyInDerivationForPassthruAttr2 = {
2127     # passthru.tests is not a special case. It works for any attr.
2128     expr = (lazyDerivation {
2129       derivation = throw "not lazy enough";
2130       passthru.foo = "whatever is in foo";
2131     }).foo;
2132     expected = "whatever is in foo";
2133   };
2135   testLazyDerivationIsLazyInDerivationForMeta = {
2136     expr = (lazyDerivation {
2137       derivation = throw "not lazy enough";
2138       meta = "whatever is in meta";
2139     }).meta;
2140     expected = "whatever is in meta";
2141   };
2143   testLazyDerivationReturnsDerivationAttrs = let
2144     derivation = {
2145       type = "derivation";
2146       outputs = ["out"];
2147       out = "test out";
2148       outPath = "test outPath";
2149       outputName = "out";
2150       drvPath = "test drvPath";
2151       name = "test name";
2152       system = "test system";
2153       meta = "test meta";
2154     };
2155   in {
2156     expr = lazyDerivation { inherit derivation; };
2157     expected = derivation;
2158   };
2160   testOptionalDrvAttr = let
2161     mkDerivation = args: derivation (args // {
2162       builder = "builder";
2163       system = "system";
2164       __ignoreNulls = true;
2165     });
2166   in {
2167     expr = (mkDerivation {
2168       name = "foo";
2169       x = optionalDrvAttr true 1;
2170       y = optionalDrvAttr false 1;
2171     }).drvPath;
2172     expected = (mkDerivation {
2173       name = "foo";
2174       x = 1;
2175     }).drvPath;
2176   };
2178   testLazyDerivationMultiOutputReturnsDerivationAttrs = let
2179     derivation = {
2180       type = "derivation";
2181       outputs = ["out" "dev"];
2182       dev = "test dev";
2183       out = "test out";
2184       outPath = "test outPath";
2185       outputName = "out";
2186       drvPath = "test drvPath";
2187       name = "test name";
2188       system = "test system";
2189       meta.position = "/hi:23";
2190     };
2191   in {
2192     expr = lazyDerivation { inherit derivation; outputs = ["out" "dev"]; passthru.meta.position = "/hi:23"; };
2193     expected = derivation;
2194   };
2196   testTypeDescriptionInt = {
2197     expr = (with types; int).description;
2198     expected = "signed integer";
2199   };
2200   testTypeDescriptionIntsPositive = {
2201     expr = (with types; ints.positive).description;
2202     expected = "positive integer, meaning >0";
2203   };
2204   testTypeDescriptionIntsPositiveOrEnumAuto = {
2205     expr = (with types; either ints.positive (enum ["auto"])).description;
2206     expected = ''positive integer, meaning >0, or value "auto" (singular enum)'';
2207   };
2208   testTypeDescriptionListOfPositive = {
2209     expr = (with types; listOf ints.positive).description;
2210     expected = "list of (positive integer, meaning >0)";
2211   };
2212   testTypeDescriptionListOfInt = {
2213     expr = (with types; listOf int).description;
2214     expected = "list of signed integer";
2215   };
2216   testTypeDescriptionListOfListOfInt = {
2217     expr = (with types; listOf (listOf int)).description;
2218     expected = "list of list of signed integer";
2219   };
2220   testTypeDescriptionListOfEitherStrOrBool = {
2221     expr = (with types; listOf (either str bool)).description;
2222     expected = "list of (string or boolean)";
2223   };
2224   testTypeDescriptionEitherListOfStrOrBool = {
2225     expr = (with types; either (listOf bool) str).description;
2226     expected = "(list of boolean) or string";
2227   };
2228   testTypeDescriptionEitherStrOrListOfBool = {
2229     expr = (with types; either str (listOf bool)).description;
2230     expected = "string or list of boolean";
2231   };
2232   testTypeDescriptionOneOfListOfStrOrBool = {
2233     expr = (with types; oneOf [ (listOf bool) str ]).description;
2234     expected = "(list of boolean) or string";
2235   };
2236   testTypeDescriptionOneOfListOfStrOrBoolOrNumber = {
2237     expr = (with types; oneOf [ (listOf bool) str number ]).description;
2238     expected = "(list of boolean) or string or signed integer or floating point number";
2239   };
2240   testTypeDescriptionEitherListOfBoolOrEitherStringOrNumber = {
2241     expr = (with types; either (listOf bool) (either str number)).description;
2242     expected = "(list of boolean) or string or signed integer or floating point number";
2243   };
2244   testTypeDescriptionEitherEitherListOfBoolOrStringOrNumber = {
2245     expr = (with types; either (either (listOf bool) str) number).description;
2246     expected = "(list of boolean) or string or signed integer or floating point number";
2247   };
2248   testTypeDescriptionEitherNullOrBoolOrString = {
2249     expr = (with types; either (nullOr bool) str).description;
2250     expected = "null or boolean or string";
2251   };
2252   testTypeDescriptionEitherListOfEitherBoolOrStrOrInt = {
2253     expr = (with types; either (listOf (either bool str)) int).description;
2254     expected = "(list of (boolean or string)) or signed integer";
2255   };
2256   testTypeDescriptionEitherIntOrListOrEitherBoolOrStr = {
2257     expr = (with types; either int (listOf (either bool str))).description;
2258     expected = "signed integer or list of (boolean or string)";
2259   };
2261 # Meta
2262   testGetExe'Output = {
2263     expr = getExe' {
2264       type = "derivation";
2265       out = "somelonghash";
2266       bin = "somelonghash";
2267     } "executable";
2268     expected = "somelonghash/bin/executable";
2269   };
2271   testGetExeOutput = {
2272     expr = getExe {
2273       type = "derivation";
2274       out = "somelonghash";
2275       bin = "somelonghash";
2276       meta.mainProgram = "mainProgram";
2277     };
2278     expected = "somelonghash/bin/mainProgram";
2279   };
2281   testGetExe'FailureFirstArg = testingThrow (
2282     getExe' "not a derivation" "executable"
2283   );
2285   testGetExe'FailureSecondArg = testingThrow (
2286     getExe' { type = "derivation"; } "dir/executable"
2287   );
2289   testPlatformMatch = {
2290     expr = meta.platformMatch { system = "x86_64-linux"; } "x86_64-linux";
2291     expected = true;
2292   };
2294   testPlatformMatchAttrs = {
2295     expr = meta.platformMatch (systems.elaborate "x86_64-linux") (systems.elaborate "x86_64-linux").parsed;
2296     expected = true;
2297   };
2299   testPlatformMatchNoMatch = {
2300     expr = meta.platformMatch { system = "x86_64-darwin"; } "x86_64-linux";
2301     expected = false;
2302   };
2304   testPlatformMatchMissingSystem = {
2305     expr = meta.platformMatch { } "x86_64-linux";
2306     expected = false;
2307   };
2309   testPackagesFromDirectoryRecursive = {
2310     expr = packagesFromDirectoryRecursive {
2311       callPackage = path: overrides: import path overrides;
2312       directory = ./packages-from-directory;
2313     };
2314     expected = {
2315       a = "a";
2316       b = "b";
2317       # Note: Other files/directories in `./test-data/c/` are ignored and can be
2318       # used by `package.nix`.
2319       c = "c";
2320       my-namespace = {
2321         d = "d";
2322         e = "e";
2323         f = "f";
2324         my-sub-namespace = {
2325           g = "g";
2326           h = "h";
2327         };
2328       };
2329     };
2330   };
2332   # Check that `packagesFromDirectoryRecursive` can process a directory with a
2333   # top-level `package.nix` file into a single package.
2334   testPackagesFromDirectoryRecursiveTopLevelPackageNix = {
2335     expr = packagesFromDirectoryRecursive {
2336       callPackage = path: overrides: import path overrides;
2337       directory = ./packages-from-directory/c;
2338     };
2339     expected = "c";
2340   };