vuls: init at 0.27.0
[NixPkgs.git] / lib / tests / misc.nix
blob116d86cdfb3fb5d763e657316abe10ce4e4e655d
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     extends
49     filter
50     fix
51     fold
52     foldAttrs
53     foldl
54     foldl'
55     foldlAttrs
56     foldr
57     functionArgs
58     generators
59     genList
60     getExe
61     getExe'
62     getLicenseFromSpdxIdOr
63     groupBy
64     groupBy'
65     hasAttrByPath
66     hasInfix
67     id
68     ifilter0
69     isStorePath
70     lazyDerivation
71     length
72     lists
73     listToAttrs
74     makeExtensible
75     makeIncludePath
76     makeOverridable
77     mapAttrs
78     mapCartesianProduct
79     matchAttrs
80     mergeAttrs
81     meta
82     mod
83     nameValuePair
84     optionalDrvAttr
85     optionAttrSetToDocList
86     overrideExisting
87     packagesFromDirectoryRecursive
88     pipe
89     range
90     recursiveUpdateUntil
91     removePrefix
92     replicate
93     runTests
94     setFunctionArgs
95     showAttrPath
96     sort
97     sortOn
98     stringLength
99     strings
100     stringToCharacters
101     systems
102     tail
103     take
104     testAllTrue
105     toBaseDigits
106     toExtension
107     toHexString
108     fromHexString
109     toInt
110     toIntBase10
111     toShellVars
112     types
113     updateManyAttrsByPath
114     versions
115     xor
116     ;
118   testingThrow = expr: {
119     expr = (builtins.tryEval (builtins.seq expr "didn't throw"));
120     expected = { success = false; value = false; };
121   };
122   testingEval = expr: {
123     expr = (builtins.tryEval expr).success;
124     expected = true;
125   };
127   testSanitizeDerivationName = { name, expected }:
128   let
129     drv = derivation {
130       name = strings.sanitizeDerivationName name;
131       builder = "x";
132       system = "x";
133     };
134   in {
135     # Evaluate the derivation so an invalid name would be caught
136     expr = builtins.seq drv.drvPath drv.name;
137     inherit expected;
138   };
142 runTests {
144 # CUSTOMIZATION
146   testFunctionArgsMakeOverridable = {
147     expr = functionArgs (makeOverridable ({ a, b, c ? null}: {}));
148     expected = { a = false; b = false; c = true; };
149   };
151   testFunctionArgsMakeOverridableOverride = {
152     expr = functionArgs (makeOverridable ({ a, b, c ? null }: {}) { a = 1; b = 2; }).override;
153     expected = { a = false; b = false; c = true; };
154   };
156   testCallPackageWithOverridePreservesArguments =
157     let
158       f = { a ? 0, b }: {};
159       f' = callPackageWith { a = 1; b = 2; } f {};
160     in {
161       expr = functionArgs f'.override;
162       expected = functionArgs f;
163     };
165   testCallPackagesWithOverridePreservesArguments =
166     let
167       f = { a ? 0, b }: { nested = {}; };
168       f' = callPackagesWith { a = 1; b = 2; } f {};
169     in {
170       expr = functionArgs f'.nested.override;
171       expected = functionArgs f;
172     };
174 # TRIVIAL
176   testId = {
177     expr = id 1;
178     expected = 1;
179   };
181   testConst = {
182     expr = const 2 3;
183     expected = 2;
184   };
186   testPipe = {
187     expr = pipe 2 [
188       (x: x + 2) # 2 + 2 = 4
189       (x: x * 2) # 4 * 2 = 8
190     ];
191     expected = 8;
192   };
194   testPipeEmpty = {
195     expr = pipe 2 [];
196     expected = 2;
197   };
199   testPipeStrings = {
200     expr = pipe [ 3 4 ] [
201       (map toString)
202       (map (s: s + "\n"))
203       concatStrings
204     ];
205     expected = ''
206       3
207       4
208     '';
209   };
211   /*
212     testOr = {
213       expr = or true false;
214       expected = true;
215     };
216   */
218   testAnd = {
219     expr = and true false;
220     expected = false;
221   };
223   testXor = {
224     expr = [
225       (xor true false)
226       (xor true true)
227       (xor false false)
228       (xor false true)
229     ];
230     expected = [
231       true
232       false
233       false
234       true
235     ];
236   };
238   testComposeExtensions = {
239     expr = let obj = makeExtensible (self: { foo = self.bar; });
240                f = self: super: { bar = false; baz = true; };
241                g = self: super: { bar = super.baz or false; };
242                f_o_g = composeExtensions f g;
243                composed = obj.extend f_o_g;
244            in composed.foo;
245     expected = true;
246   };
248   testComposeManyExtensions0 = {
249     expr = let obj = makeExtensible (self: { foo = true; });
250                emptyComposition = composeManyExtensions [];
251                composed = obj.extend emptyComposition;
252            in composed.foo;
253     expected = true;
254   };
256   testComposeManyExtensions =
257     let f = self: super: { bar = false; baz = true; };
258         g = self: super: { bar = super.baz or false; };
259         h = self: super: { qux = super.bar or false; };
260         obj = makeExtensible (self: { foo = self.qux; });
261     in {
262     expr = let composition = composeManyExtensions [f g h];
263                composed = obj.extend composition;
264            in composed.foo;
265     expected = (obj.extend (composeExtensions f (composeExtensions g h))).foo;
266   };
268   testBitAnd = {
269     expr = (bitAnd 3 10);
270     expected = 2;
271   };
273   testBitOr = {
274     expr = (bitOr 3 10);
275     expected = 11;
276   };
278   testBitXor = {
279     expr = (bitXor 3 10);
280     expected = 9;
281   };
283   testToHexString = {
284     expr = toHexString 250;
285     expected = "FA";
286   };
288   testFromHexStringFirstExample = {
289     expr = fromHexString "FF";
290     expected = 255;
291   };
293   testFromHexStringSecondExample = {
294     expr = fromHexString (builtins.hashString "sha256" "test");
295     expected = 9223372036854775807;
296   };
298   testFromHexStringWithPrefix = {
299     expr = fromHexString "0Xf";
300     expected = 15;
301   };
303   testToBaseDigits = {
304     expr = toBaseDigits 2 6;
305     expected = [ 1 1 0 ];
306   };
308   testFunctionArgsFunctor = {
309     expr = functionArgs { __functor = self: { a, b }: null; };
310     expected = { a = false; b = false; };
311   };
313   testFunctionArgsSetFunctionArgs = {
314     expr = functionArgs (setFunctionArgs (args: args.x) { x = false; });
315     expected = { x = false; };
316   };
318 # STRINGS
320   testConcatMapStrings = {
321     expr = concatMapStrings (x: x + ";") ["a" "b" "c"];
322     expected = "a;b;c;";
323   };
325   testConcatStringsSep = {
326     expr = concatStringsSep "," ["a" "b" "c"];
327     expected = "a,b,c";
328   };
330   testConcatLines = {
331     expr = concatLines ["a" "b" "c"];
332     expected = "a\nb\nc\n";
333   };
335   testMakeIncludePathWithPkgs = {
336     expr = (makeIncludePath [
337       # makeIncludePath preferably selects the "dev" output
338       { dev.outPath = "/dev"; out.outPath = "/out"; outPath = "/default"; }
339       # "out" is used if "dev" is not found
340       { out.outPath = "/out"; outPath = "/default"; }
341       # And it returns the derivation directly if there's no "out" either
342       { outPath = "/default"; }
343       # Same if the output is specified explicitly, even if there's a "dev"
344       { dev.outPath = "/dev"; outPath = "/default"; outputSpecified = true; }
345     ]);
346     expected = "/dev/include:/out/include:/default/include:/default/include";
347   };
349   testMakeIncludePathWithEmptyList = {
350     expr = (makeIncludePath [ ]);
351     expected = "";
352   };
354   testMakeIncludePathWithOneString = {
355     expr = (makeIncludePath [ "/usr" ]);
356     expected = "/usr/include";
357   };
359   testMakeIncludePathWithManyString = {
360     expr = (makeIncludePath [ "/usr" "/usr/local" ]);
361     expected = "/usr/include:/usr/local/include";
362   };
364   testReplicateString = {
365     expr = strings.replicate 5 "hello";
366     expected = "hellohellohellohellohello";
367   };
369   # Test various strings are trimmed correctly
370   testTrimString = {
371     expr =
372     let
373       testValues = f: mapAttrs (_: f) {
374         empty = "";
375         cr = "\r";
376         lf = "\n";
377         tab = "\t";
378         spaces = "   ";
379         leading = "  Hello, world";
380         trailing = "Hello, world   ";
381         mixed = " Hello, world ";
382         mixed-tabs = " \t\tHello, world \t \t ";
383         multiline = "  Hello,\n  world!  ";
384         multiline-crlf = "  Hello,\r\n  world!  ";
385       };
386     in
387       {
388         leading = testValues (strings.trimWith { start = true; });
389         trailing = testValues (strings.trimWith { end = true; });
390         both = testValues strings.trim;
391       };
392     expected = {
393       leading = {
394         empty = "";
395         cr = "";
396         lf = "";
397         tab = "";
398         spaces = "";
399         leading = "Hello, world";
400         trailing = "Hello, world   ";
401         mixed = "Hello, world ";
402         mixed-tabs = "Hello, world \t \t ";
403         multiline = "Hello,\n  world!  ";
404         multiline-crlf = "Hello,\r\n  world!  ";
405       };
406       trailing = {
407         empty = "";
408         cr = "";
409         lf = "";
410         tab = "";
411         spaces = "";
412         leading = "  Hello, world";
413         trailing = "Hello, world";
414         mixed = " Hello, world";
415         mixed-tabs = " \t\tHello, world";
416         multiline = "  Hello,\n  world!";
417         multiline-crlf = "  Hello,\r\n  world!";
418       };
419       both = {
420         empty = "";
421         cr = "";
422         lf = "";
423         tab = "";
424         spaces = "";
425         leading = "Hello, world";
426         trailing = "Hello, world";
427         mixed = "Hello, world";
428         mixed-tabs = "Hello, world";
429         multiline = "Hello,\n  world!";
430         multiline-crlf = "Hello,\r\n  world!";
431       };
432     };
433   };
435   testSplitStringsSimple = {
436     expr = strings.splitString "." "a.b.c.d";
437     expected = [ "a" "b" "c" "d" ];
438   };
440   testSplitStringsEmpty = {
441     expr = strings.splitString "." "a..b";
442     expected = [ "a" "" "b" ];
443   };
445   testSplitStringsOne = {
446     expr = strings.splitString ":" "a.b";
447     expected = [ "a.b" ];
448   };
450   testSplitStringsNone = {
451     expr = strings.splitString "." "";
452     expected = [ "" ];
453   };
455   testSplitStringsFirstEmpty = {
456     expr = strings.splitString "/" "/a/b/c";
457     expected = [ "" "a" "b" "c" ];
458   };
460   testSplitStringsLastEmpty = {
461     expr = strings.splitString ":" "2001:db8:0:0042::8a2e:370:";
462     expected = [ "2001" "db8" "0" "0042" "" "8a2e" "370" "" ];
463   };
465   testSplitStringsRegex = {
466     expr = strings.splitString "\\[{}]()^$?*+|." "A\\[{}]()^$?*+|.B";
467     expected = [ "A" "B" ];
468   };
470   testEscapeShellArg = {
471     expr = strings.escapeShellArg "esc'ape\nme";
472     expected = "'esc'\\''ape\nme'";
473   };
475   testEscapeShellArgEmpty = {
476     expr = strings.escapeShellArg "";
477     expected = "''";
478   };
480   testEscapeShellArgs = {
481     expr = strings.escapeShellArgs ["one" "two three" "four'five"];
482     expected = "one 'two three' 'four'\\''five'";
483   };
485   testEscapeShellArgsUnicode = {
486     expr = strings.escapeShellArg "á";
487     expected = "'á'";
488   };
490   testSplitStringsDerivation = {
491     expr = take 3  (strings.splitString "/" (derivation {
492       name = "name";
493       builder = "builder";
494       system = "system";
495     }));
496     expected = ["" "nix" "store"];
497   };
499   testSplitVersionSingle = {
500     expr = versions.splitVersion "1";
501     expected = [ "1" ];
502   };
504   testSplitVersionDouble = {
505     expr = versions.splitVersion "1.2";
506     expected = [ "1" "2" ];
507   };
509   testSplitVersionTriple = {
510     expr = versions.splitVersion "1.2.3";
511     expected = [ "1" "2" "3" ];
512   };
514   testPadVersionLess = {
515     expr = versions.pad 3 "1.2";
516     expected = "1.2.0";
517   };
519   testPadVersionLessExtra = {
520     expr = versions.pad 3 "1.3-rc1";
521     expected = "1.3.0-rc1";
522   };
524   testPadVersionMore = {
525     expr = versions.pad 3 "1.2.3.4";
526     expected = "1.2.3";
527   };
529   testIsStorePath =  {
530     expr =
531       let goodPath =
532             "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11";
533       in {
534         storePath = isStorePath goodPath;
535         storePathDerivation = isStorePath (import ../.. { system = "x86_64-linux"; }).hello;
536         storePathAppendix = isStorePath
537           "${goodPath}/bin/python";
538         nonAbsolute = isStorePath (concatStrings (tail (stringToCharacters goodPath)));
539         asPath = isStorePath (/. + goodPath);
540         otherPath = isStorePath "/something/else";
541         otherVals = {
542           attrset = isStorePath {};
543           list = isStorePath [];
544           int = isStorePath 42;
545         };
546       };
547     expected = {
548       storePath = true;
549       storePathDerivation = true;
550       storePathAppendix = false;
551       nonAbsolute = false;
552       asPath = true;
553       otherPath = false;
554       otherVals = {
555         attrset = false;
556         list = false;
557         int = false;
558       };
559     };
560   };
562   testEscapeXML = {
563     expr = escapeXML ''"test" 'test' < & >'';
564     expected = "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;";
565   };
567   testToShellVars = {
568     expr = ''
569       ${toShellVars {
570         STRing01 = "just a 'string'";
571         _array_ = [ "with" "more strings" ];
572         assoc."with some" = ''
573           strings
574           possibly newlines
575         '';
576         drv = {
577           outPath = "/drv";
578           foo = "ignored attribute";
579         };
580         path = /path;
581         stringable = {
582           __toString = _: "hello toString";
583           bar = "ignored attribute";
584         };
585       }}
586     '';
587     expected = ''
588       STRing01='just a '\'''string'\''''
589       declare -a _array_=(with 'more strings')
590       declare -A assoc=(['with some']='strings
591       possibly newlines
592       ')
593       drv=/drv
594       path=/path
595       stringable='hello toString'
596     '';
597   };
599   testHasInfixFalse = {
600     expr = hasInfix "c" "abde";
601     expected = false;
602   };
604   testHasInfixTrue = {
605     expr = hasInfix "c" "abcde";
606     expected = true;
607   };
609   testHasInfixDerivation = {
610     expr = hasInfix "hello" (import ../.. { system = "x86_64-linux"; }).hello;
611     expected = true;
612   };
614   testHasInfixPath = {
615     expr = hasInfix "tests" ./.;
616     expected = true;
617   };
619   testHasInfixPathStoreDir = {
620     expr = hasInfix builtins.storeDir ./.;
621     expected = true;
622   };
624   testHasInfixToString = {
625     expr = hasInfix "a" { __toString = _: "a"; };
626     expected = true;
627   };
629   testRemovePrefixExample1 = {
630     expr = removePrefix "foo." "foo.bar.baz";
631     expected = "bar.baz";
632   };
633   testRemovePrefixExample2 = {
634     expr = removePrefix "xxx" "foo.bar.baz";
635     expected = "foo.bar.baz";
636   };
637   testRemovePrefixEmptyPrefix = {
638     expr = removePrefix "" "foo";
639     expected = "foo";
640   };
641   testRemovePrefixEmptyString = {
642     expr = removePrefix "foo" "";
643     expected = "";
644   };
645   testRemovePrefixEmptyBoth = {
646     expr = removePrefix "" "";
647     expected = "";
648   };
650   testNormalizePath = {
651     expr = strings.normalizePath "//a/b//c////d/";
652     expected = "/a/b/c/d/";
653   };
655   testCharToInt = {
656     expr = strings.charToInt "A";
657     expected = 65;
658   };
660   testEscapeC = {
661     expr = strings.escapeC [ " " ] "Hello World";
662     expected = "Hello\\x20World";
663   };
665   testEscapeURL = testAllTrue [
666     ("" == strings.escapeURL "")
667     ("Hello" == strings.escapeURL "Hello")
668     ("Hello%20World" == strings.escapeURL "Hello World")
669     ("Hello%2FWorld" == strings.escapeURL "Hello/World")
670     ("42%25" == strings.escapeURL "42%")
671     ("%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:/@$'()*,;")
672   ];
674   testToInt = testAllTrue [
675     # Naive
676     (123 == toInt "123")
677     (0 == toInt "0")
678     # Whitespace Padding
679     (123 == toInt " 123")
680     (123 == toInt "123 ")
681     (123 == toInt " 123 ")
682     (123 == toInt "   123   ")
683     (0 == toInt " 0")
684     (0 == toInt "0 ")
685     (0 == toInt " 0 ")
686     (-1 == toInt "-1")
687     (-1 == toInt " -1 ")
688   ];
690   testToIntFails = testAllTrue [
691     ( builtins.tryEval (toInt "") == { success = false; value = false; } )
692     ( builtins.tryEval (toInt "123 123") == { success = false; value = false; } )
693     ( builtins.tryEval (toInt "0 123") == { success = false; value = false; } )
694     ( builtins.tryEval (toInt " 0d ") == { success = false; value = false; } )
695     ( builtins.tryEval (toInt " 1d ") == { success = false; value = false; } )
696     ( builtins.tryEval (toInt " d0 ") == { success = false; value = false; } )
697     ( builtins.tryEval (toInt "00") == { success = false; value = false; } )
698     ( builtins.tryEval (toInt "01") == { success = false; value = false; } )
699     ( builtins.tryEval (toInt "002") == { success = false; value = false; } )
700     ( builtins.tryEval (toInt " 002 ") == { success = false; value = false; } )
701     ( builtins.tryEval (toInt " foo ") == { success = false; value = false; } )
702     ( builtins.tryEval (toInt " foo 123 ") == { success = false; value = false; } )
703     ( builtins.tryEval (toInt " foo123 ") == { success = false; value = false; } )
704   ];
706   testToIntBase10 = testAllTrue [
707     # Naive
708     (123 == toIntBase10 "123")
709     (0 == toIntBase10 "0")
710     # Whitespace Padding
711     (123 == toIntBase10 " 123")
712     (123 == toIntBase10 "123 ")
713     (123 == toIntBase10 " 123 ")
714     (123 == toIntBase10 "   123   ")
715     (0 == toIntBase10 " 0")
716     (0 == toIntBase10 "0 ")
717     (0 == toIntBase10 " 0 ")
718     # Zero Padding
719     (123 == toIntBase10 "0123")
720     (123 == toIntBase10 "0000123")
721     (0 == toIntBase10 "000000")
722     # Whitespace and Zero Padding
723     (123 == toIntBase10 " 0123")
724     (123 == toIntBase10 "0123 ")
725     (123 == toIntBase10 " 0123 ")
726     (123 == toIntBase10 " 0000123")
727     (123 == toIntBase10 "0000123 ")
728     (123 == toIntBase10 " 0000123 ")
729     (0 == toIntBase10 " 000000")
730     (0 == toIntBase10 "000000 ")
731     (0 == toIntBase10 " 000000 ")
732     (-1 == toIntBase10 "-1")
733     (-1 == toIntBase10 " -1 ")
734   ];
736   testToIntBase10Fails = testAllTrue [
737     ( builtins.tryEval (toIntBase10 "") == { success = false; value = false; } )
738     ( builtins.tryEval (toIntBase10 "123 123") == { success = false; value = false; } )
739     ( builtins.tryEval (toIntBase10 "0 123") == { success = false; value = false; } )
740     ( builtins.tryEval (toIntBase10 " 0d ") == { success = false; value = false; } )
741     ( builtins.tryEval (toIntBase10 " 1d ") == { success = false; value = false; } )
742     ( builtins.tryEval (toIntBase10 " d0 ") == { success = false; value = false; } )
743     ( builtins.tryEval (toIntBase10 " foo ") == { success = false; value = false; } )
744     ( builtins.tryEval (toIntBase10 " foo 123 ") == { success = false; value = false; } )
745     ( builtins.tryEval (toIntBase10 " foo 00123 ") == { success = false; value = false; } )
746     ( builtins.tryEval (toIntBase10 " foo00123 ") == { success = false; value = false; } )
747   ];
749 # LISTS
751   testFilter = {
752     expr = filter (x: x != "a") ["a" "b" "c" "a"];
753     expected = ["b" "c"];
754   };
756   testIfilter0Example = {
757     expr = ifilter0 (i: v: i == 0 || v > 2) [ 1 2 3 ];
758     expected = [ 1 3 ];
759   };
760   testIfilter0Empty = {
761     expr = ifilter0 (i: v: abort "shouldn't be evaluated!") [ ];
762     expected = [ ];
763   };
764   testIfilter0IndexOnly = {
765     expr = length (ifilter0 (i: v: mod i 2 == 0) [ (throw "0") (throw "1") (throw "2") (throw "3")]);
766     expected = 2;
767   };
768   testIfilter0All = {
769     expr = ifilter0 (i: v: true) [ 10 11 12 13 14 15 ];
770     expected = [ 10 11 12 13 14 15 ];
771   };
772   testIfilter0First = {
773     expr = ifilter0 (i: v: i == 0) [ 10 11 12 13 14 15 ];
774     expected = [ 10 ];
775   };
776   testIfilter0Last = {
777     expr = ifilter0 (i: v: i == 5) [ 10 11 12 13 14 15 ];
778     expected = [ 15 ];
779   };
781   testFold =
782     let
783       f = op: fold: fold op 0 (range 0 100);
784       # fold with associative operator
785       assoc = f builtins.add;
786       # fold with non-associative operator
787       nonAssoc = f builtins.sub;
788     in {
789       expr = {
790         assocRight = assoc foldr;
791         # right fold with assoc operator is same as left fold
792         assocRightIsLeft = assoc foldr == assoc foldl;
793         nonAssocRight = nonAssoc foldr;
794         nonAssocLeft = nonAssoc foldl;
795         # with non-assoc operator the fold results are not the same
796         nonAssocRightIsNotLeft = nonAssoc foldl != nonAssoc foldr;
797         # fold is an alias for foldr
798         foldIsRight = nonAssoc fold == nonAssoc foldr;
799       };
800       expected = {
801         assocRight = 5050;
802         assocRightIsLeft = true;
803         nonAssocRight = 50;
804         nonAssocLeft = (-5050);
805         nonAssocRightIsNotLeft = true;
806         foldIsRight = true;
807       };
808     };
810   testFoldl'Empty = {
811     expr = foldl' (acc: el: abort "operation not called") 0 [ ];
812     expected = 0;
813   };
815   testFoldl'IntegerAdding = {
816     expr = foldl' (acc: el: acc + el) 0 [ 1 2 3 ];
817     expected = 6;
818   };
820   # The accumulator isn't forced deeply
821   testFoldl'NonDeep = {
822     expr = take 3 (foldl'
823       (acc: el: [ el ] ++ acc)
824       [ (abort "unevaluated list entry") ]
825       [ 1 2 3 ]);
826     expected = [ 3 2 1 ];
827   };
829   # Compared to builtins.foldl', lib.foldl' evaluates the first accumulator strictly too
830   testFoldl'StrictInitial = {
831     expr = (builtins.tryEval (foldl' (acc: el: el) (throw "hello") [])).success;
832     expected = false;
833   };
835   # Make sure we don't get a stack overflow for large lists
836   # This number of elements would notably cause a stack overflow if it was implemented without the `foldl'` builtin
837   testFoldl'Large = {
838     expr = foldl' (acc: el: acc + el) 0 (range 0 100000);
839     expected = 5000050000;
840   };
842   testTake = testAllTrue [
843     ([] == (take 0 [  1 2 3 ]))
844     ([1] == (take 1 [  1 2 3 ]))
845     ([ 1 2 ] == (take 2 [  1 2 3 ]))
846     ([ 1 2 3 ] == (take 3 [  1 2 3 ]))
847     ([ 1 2 3 ] == (take 4 [  1 2 3 ]))
848   ];
850   testListHasPrefixExample1 = {
851     expr = lists.hasPrefix [ 1 2 ] [ 1 2 3 4 ];
852     expected = true;
853   };
854   testListHasPrefixExample2 = {
855     expr = lists.hasPrefix [ 0 1 ] [ 1 2 3 4 ];
856     expected = false;
857   };
858   testListHasPrefixLazy = {
859     expr = lists.hasPrefix [ 1 ] [ 1 (abort "lib.lists.hasPrefix is not lazy") ];
860     expected = true;
861   };
862   testListHasPrefixEmptyPrefix = {
863     expr = lists.hasPrefix [ ] [ 1 2 ];
864     expected = true;
865   };
866   testListHasPrefixEmptyList = {
867     expr = lists.hasPrefix [ 1 2 ] [ ];
868     expected = false;
869   };
871   testListRemovePrefixExample1 = {
872     expr = lists.removePrefix [ 1 2 ] [ 1 2 3 4 ];
873     expected = [ 3 4 ];
874   };
875   testListRemovePrefixExample2 = {
876     expr = (builtins.tryEval (lists.removePrefix [ 0 1 ] [ 1 2 3 4 ])).success;
877     expected = false;
878   };
879   testListRemovePrefixEmptyPrefix = {
880     expr = lists.removePrefix [ ] [ 1 2 ];
881     expected = [ 1 2 ];
882   };
883   testListRemovePrefixEmptyList = {
884     expr = (builtins.tryEval (lists.removePrefix [ 1 2 ] [ ])).success;
885     expected = false;
886   };
888   testFoldAttrs = {
889     expr = foldAttrs (n: a: [n] ++ a) [] [
890     { a = 2; b = 7; }
891     { a = 3;        c = 8; }
892     ];
893     expected = { a = [ 2 3 ]; b = [7]; c = [8];};
894   };
896   testListCommonPrefixExample1 = {
897     expr = lists.commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ];
898     expected = [ 1 2 ];
899   };
900   testListCommonPrefixExample2 = {
901     expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ];
902     expected = [ 1 2 3 ];
903   };
904   testListCommonPrefixExample3 = {
905     expr = lists.commonPrefix [ 1 2 3 ] [ 4 5 6 ];
906     expected = [ ];
907   };
908   testListCommonPrefixEmpty = {
909     expr = lists.commonPrefix [ ] [ 1 2 3 ];
910     expected = [ ];
911   };
912   testListCommonPrefixSame = {
913     expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 ];
914     expected = [ 1 2 3 ];
915   };
916   testListCommonPrefixLazy = {
917     expr = lists.commonPrefix [ 1 ] [ 1 (abort "lib.lists.commonPrefix shouldn't evaluate this")];
918     expected = [ 1 ];
919   };
920   # This would stack overflow if `commonPrefix` were implemented using recursion
921   testListCommonPrefixLong =
922     let
923       longList = genList (n: n) 100000;
924     in {
925       expr = lists.commonPrefix longList longList;
926       expected = longList;
927     };
929   testSort = {
930     expr = sort builtins.lessThan [ 40 2 30 42 ];
931     expected = [2 30 40 42];
932   };
934   testSortOn = {
935     expr = sortOn stringLength [ "aa" "b" "cccc" ];
936     expected = [ "b" "aa" "cccc" ];
937   };
939   testSortOnEmpty = {
940     expr = sortOn (throw "nope") [ ];
941     expected = [ ];
942   };
944   testSortOnIncomparable = {
945     expr =
946       map
947         (x: x.f x.ok)
948         (sortOn (x: x.ok) [
949           { ok = 1; f = x: x; }
950           { ok = 3; f = x: x + 3; }
951           { ok = 2; f = x: x; }
952         ]);
953     expected = [ 1 2 6 ];
954   };
956   testReplicate = {
957     expr = replicate 3 "a";
958     expected = ["a" "a" "a"];
959   };
961   testToIntShouldConvertStringToInt = {
962     expr = toInt "27";
963     expected = 27;
964   };
966   testToIntShouldThrowErrorIfItCouldNotConvertToInt = {
967     expr = builtins.tryEval (toInt "\"foo\"");
968     expected = { success = false; value = false; };
969   };
971   testHasAttrByPathTrue = {
972     expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; };
973     expected = true;
974   };
976   testHasAttrByPathFalse = {
977     expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; };
978     expected = false;
979   };
981   testHasAttrByPathNonStrict = {
982     expr = hasAttrByPath [] (throw "do not use");
983     expected = true;
984   };
986   testLongestValidPathPrefix_empty_empty = {
987     expr = attrsets.longestValidPathPrefix [ ] { };
988     expected = [ ];
989   };
991   testLongestValidPathPrefix_empty_nonStrict = {
992     expr = attrsets.longestValidPathPrefix [ ] (throw "do not use");
993     expected = [ ];
994   };
996   testLongestValidPathPrefix_zero = {
997     expr = attrsets.longestValidPathPrefix [ "a" (throw "do not use") ] { d = null; };
998     expected = [ ];
999   };
1001   testLongestValidPathPrefix_zero_b = {
1002     expr = attrsets.longestValidPathPrefix [ "z" "z" ] "remarkably harmonious";
1003     expected = [ ];
1004   };
1006   testLongestValidPathPrefix_one = {
1007     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a = null; };
1008     expected = [ "a" ];
1009   };
1011   testLongestValidPathPrefix_two = {
1012     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b = null; };
1013     expected = [ "a" "b" ];
1014   };
1016   testLongestValidPathPrefix_three = {
1017     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c = null; };
1018     expected = [ "a" "b" "c" ];
1019   };
1021   testLongestValidPathPrefix_three_extra = {
1022     expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c.d = throw "nope"; };
1023     expected = [ "a" "b" "c" ];
1024   };
1026   testFindFirstIndexExample1 = {
1027     expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ];
1028     expected = 1;
1029   };
1031   testFindFirstIndexExample2 = {
1032     expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ 1 6 4 ];
1033     expected = "a very specific default";
1034   };
1036   testFindFirstIndexEmpty = {
1037     expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null [];
1038     expected = null;
1039   };
1041   testFindFirstIndexSingleMatch = {
1042     expr = lists.findFirstIndex (x: x == 5) null [ 5 ];
1043     expected = 0;
1044   };
1046   testFindFirstIndexSingleDefault = {
1047     expr = lists.findFirstIndex (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
1048     expected = null;
1049   };
1051   testFindFirstIndexNone = {
1052     expr = builtins.tryEval (lists.findFirstIndex (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
1053     expected = { success = false; value = false; };
1054   };
1056   # Makes sure that the implementation doesn't cause a stack overflow
1057   testFindFirstIndexBig = {
1058     expr = lists.findFirstIndex (x: x == 1000000) null (range 0 1000000);
1059     expected = 1000000;
1060   };
1062   testFindFirstIndexLazy = {
1063     expr = lists.findFirstIndex (x: x == 1) null [ 1 (abort "list elements after the match must not be evaluated") ];
1064     expected = 0;
1065   };
1067   testFindFirstExample1 = {
1068     expr = lists.findFirst (x: x > 3) 7 [ 1 6 4 ];
1069     expected = 6;
1070   };
1072   testFindFirstExample2 = {
1073     expr = lists.findFirst (x: x > 9) 7 [ 1 6 4 ];
1074     expected = 7;
1075   };
1077   testAllUnique_true = {
1078     expr = allUnique [ 3 2 4 1 ];
1079     expected = true;
1080   };
1081   testAllUnique_false = {
1082     expr = allUnique [ 3 2 3 4 ];
1083     expected = false;
1084   };
1086 # ATTRSETS
1088   testConcatMapAttrs = {
1089     expr = concatMapAttrs
1090       (name: value: {
1091         ${name} = value;
1092         ${name + value} = value;
1093       })
1094       {
1095         foo = "bar";
1096         foobar = "baz";
1097       };
1098     expected = {
1099       foo = "bar";
1100       foobar = "baz";
1101       foobarbaz = "baz";
1102     };
1103   };
1105   # code from example
1106   testFoldlAttrs = {
1107     expr = {
1108       example = foldlAttrs
1109         (acc: name: value: {
1110           sum = acc.sum + value;
1111           names = acc.names ++ [ name ];
1112         })
1113         { sum = 0; names = [ ]; }
1114         {
1115           foo = 1;
1116           bar = 10;
1117         };
1118       # should just return the initial value
1119       emptySet = foldlAttrs (throw "function not needed") 123 { };
1120       # should just evaluate to the last value
1121       valuesNotNeeded = foldlAttrs (acc: _name: _v: acc) 3 { z = throw "value z not needed"; a = throw "value a not needed"; };
1122       # the accumulator doesnt have to be an attrset it can be as trivial as being just a number or string
1123       trivialAcc = foldlAttrs (acc: _name: v: acc * 10 + v) 1 { z = 1; a = 2; };
1124     };
1125     expected = {
1126       example = {
1127         sum = 11;
1128         names = [ "bar" "foo" ];
1129       };
1130       emptySet = 123;
1131       valuesNotNeeded = 3;
1132       trivialAcc = 121;
1133     };
1134   };
1137   testMergeAttrsListExample1 = {
1138     expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ];
1139     expected = { a = 0; b = 1; c = 2; d = 3; };
1140   };
1141   testMergeAttrsListExample2 = {
1142     expr = attrsets.mergeAttrsList [ { a = 0; } { a = 1; } ];
1143     expected = { a = 1; };
1144   };
1145   testMergeAttrsListExampleMany =
1146     let
1147       list = genList (n:
1148         listToAttrs (genList (m:
1149           let
1150             # Integer divide n by two to create duplicate attributes
1151             str = "halfn${toString (n / 2)}m${toString m}";
1152           in
1153           nameValuePair str str
1154         ) 100)
1155       ) 100;
1156     in {
1157       expr = attrsets.mergeAttrsList list;
1158       expected = foldl' mergeAttrs { } list;
1159     };
1161   # code from the example
1162   testRecursiveUpdateUntil = {
1163     expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {
1164       # first attribute set
1165       foo.bar = 1;
1166       foo.baz = 2;
1167       bar = 3;
1168     } {
1169       #second attribute set
1170       foo.bar = 1;
1171       foo.quz = 2;
1172       baz = 4;
1173     };
1174     expected = {
1175       foo.bar = 1; # 'foo.*' from the second set
1176       foo.quz = 2; #
1177       bar = 3;     # 'bar' from the first set
1178       baz = 4;     # 'baz' from the second set
1179     };
1180   };
1182   testMatchAttrsMatchingExact = {
1183     expr = matchAttrs { cpu = { bits = 64; }; } { cpu = { bits = 64; }; };
1184     expected = true;
1185   };
1187   testMatchAttrsMismatch = {
1188     expr = matchAttrs { cpu = { bits = 128; }; } { cpu = { bits = 64; }; };
1189     expected = false;
1190   };
1192   testMatchAttrsMatchingImplicit = {
1193     expr = matchAttrs { cpu = { }; } { cpu = { bits = 64; }; };
1194     expected = true;
1195   };
1197   testMatchAttrsMissingAttrs = {
1198     expr = matchAttrs { cpu = {}; } { };
1199     expected = false;
1200   };
1202   testOverrideExistingEmpty = {
1203     expr = overrideExisting {} { a = 1; };
1204     expected = {};
1205   };
1207   testOverrideExistingDisjoint = {
1208     expr = overrideExisting { b = 2; } { a = 1; };
1209     expected = { b = 2; };
1210   };
1212   testOverrideExistingOverride = {
1213     expr = overrideExisting { a = 3; b = 2; } { a = 1; };
1214     expected = { a = 1; b = 2; };
1215   };
1217   testListAttrsReverse = let
1218     exampleAttrs = {foo=1; bar="asdf"; baz = [1 3 3 7]; fnord=null;};
1219     exampleSingletonList = [{name="foo"; value=1;}];
1220   in {
1221     expr = {
1222       isReverseToListToAttrs = builtins.listToAttrs (attrsToList exampleAttrs) == exampleAttrs;
1223       isReverseToAttrsToList = attrsToList (builtins.listToAttrs exampleSingletonList) == exampleSingletonList;
1224       testDuplicatePruningBehaviour = attrsToList (builtins.listToAttrs [{name="a"; value=2;} {name="a"; value=1;}]);
1225     };
1226     expected = {
1227       isReverseToAttrsToList = true;
1228       isReverseToListToAttrs = true;
1229       testDuplicatePruningBehaviour = [{name="a"; value=2;}];
1230     };
1231   };
1233   testAttrsToListsCanDealWithFunctions = testingEval (
1234     attrsToList { someFunc= a: a + 1;}
1235   );
1237 # FIXED-POINTS
1239   testFix = {
1240     expr = fix (x: {a = if x ? a then "a" else "b";});
1241     expected = {a = "a";};
1242   };
1244   testToExtension = {
1245     expr = [
1246       (fix (final: { a = 0; c = final.a; }))
1247       (fix (extends (toExtension { a = 1; b = 2; }) (final: { a = 0; c = final.a; })))
1248       (fix (extends (toExtension (prev: { a = 1; b = prev.a; })) (final: { a = 0; c = final.a; })))
1249       (fix (extends (toExtension (final: prev: { a = 1; b = prev.a; c = final.a + 1; })) (final: { a = 0; c = final.a; })))
1250     ];
1251     expected = [
1252       { a = 0; c = 0; }
1253       { a = 1; b = 2; c = 1; }
1254       { a = 1; b = 0; c = 1; }
1255       { a = 1; b = 0; c = 2; }
1256     ];
1257   };
1259 # GENERATORS
1260 # these tests assume attributes are converted to lists
1261 # in alphabetical order
1263   testMkKeyValueDefault = {
1264     expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar";
1265     expected = ''f\:oo:bar'';
1266   };
1268   testMkValueString = {
1269     expr = let
1270       vals = {
1271         int = 42;
1272         string = ''fo"o'';
1273         bool = true;
1274         bool2 = false;
1275         null = null;
1276         # float = 42.23; # floats are strange
1277       };
1278       in mapAttrs
1279         (const (generators.mkValueStringDefault {}))
1280         vals;
1281     expected = {
1282       int = "42";
1283       string = ''fo"o'';
1284       bool = "true";
1285       bool2 = "false";
1286       null = "null";
1287       # float = "42.23" true false [ "bar" ] ]'';
1288     };
1289   };
1291   testToKeyValue = {
1292     expr = generators.toKeyValue {} {
1293       key = "value";
1294       "other=key" = "baz";
1295     };
1296     expected = ''
1297       key=value
1298       other\=key=baz
1299     '';
1300   };
1302   testToINIEmpty = {
1303     expr = generators.toINI {} {};
1304     expected = "";
1305   };
1307   testToINIEmptySection = {
1308     expr = generators.toINI {} { foo = {}; bar = {}; };
1309     expected = ''
1310       [bar]
1312       [foo]
1313     '';
1314   };
1316   testToINIDuplicateKeys = {
1317     expr = generators.toINI { listsAsDuplicateKeys = true; } { foo.bar = true; baz.qux = [ 1 false ]; };
1318     expected = ''
1319       [baz]
1320       qux=1
1321       qux=false
1323       [foo]
1324       bar=true
1325     '';
1326   };
1328   testToINIDefaultEscapes = {
1329     expr = generators.toINI {} {
1330       "no [ and ] allowed unescaped" = {
1331         "and also no = in keys" = 42;
1332       };
1333     };
1334     expected = ''
1335       [no \[ and \] allowed unescaped]
1336       and also no \= in keys=42
1337     '';
1338   };
1340   testToINIDefaultFull = {
1341     expr = generators.toINI {} {
1342       "section 1" = {
1343         attribute1 = 5;
1344         x = "Me-se JarJar Binx";
1345         # booleans are converted verbatim by default
1346         boolean = false;
1347       };
1348       "foo[]" = {
1349         "he\\h=he" = "this is okay";
1350       };
1351     };
1352     expected = ''
1353       [foo\[\]]
1354       he\h\=he=this is okay
1356       [section 1]
1357       attribute1=5
1358       boolean=false
1359       x=Me-se JarJar Binx
1360     '';
1361   };
1363   testToINIWithGlobalSectionEmpty = {
1364     expr = generators.toINIWithGlobalSection {} {
1365       globalSection = {
1366       };
1367       sections = {
1368       };
1369     };
1370     expected = ''
1371     '';
1372   };
1374   testToINIWithGlobalSectionGlobalEmptyIsTheSameAsToINI =
1375     let
1376       sections = {
1377         "section 1" = {
1378           attribute1 = 5;
1379           x = "Me-se JarJar Binx";
1380         };
1381         "foo" = {
1382           "he\\h=he" = "this is okay";
1383         };
1384       };
1385     in {
1386       expr =
1387         generators.toINIWithGlobalSection {} {
1388             globalSection = {};
1389             sections = sections;
1390         };
1391       expected = generators.toINI {} sections;
1392   };
1394   testToINIWithGlobalSectionFull = {
1395     expr = generators.toINIWithGlobalSection {} {
1396       globalSection = {
1397         foo = "bar";
1398         test = false;
1399       };
1400       sections = {
1401         "section 1" = {
1402           attribute1 = 5;
1403           x = "Me-se JarJar Binx";
1404         };
1405         "foo" = {
1406           "he\\h=he" = "this is okay";
1407         };
1408       };
1409     };
1410     expected = ''
1411       foo=bar
1412       test=false
1414       [foo]
1415       he\h\=he=this is okay
1417       [section 1]
1418       attribute1=5
1419       x=Me-se JarJar Binx
1420     '';
1421   };
1423   testToGitINI = {
1424     expr = generators.toGitINI {
1425       user = {
1426         email = "user@example.org";
1427         name = "John Doe";
1428         signingKey = "00112233445566778899AABBCCDDEEFF";
1429       };
1430       gpg.program = "path-to-gpg";
1431       tag.gpgSign = true;
1432       include.path = "~/path/to/config.inc";
1433       includeIf."gitdif:~/src/dir".path = "~/path/to/conditional.inc";
1434       extra = {
1435         boolean = true;
1436         integer = 38;
1437         name = "value";
1438         subsection.value = "test";
1439       };};
1440     expected = ''
1441       [extra]
1442       ${"\t"}boolean = true
1443       ${"\t"}integer = 38
1444       ${"\t"}name = "value"
1446       [extra "subsection"]
1447       ${"\t"}value = "test"
1449       [gpg]
1450       ${"\t"}program = "path-to-gpg"
1452       [include]
1453       ${"\t"}path = "~/path/to/config.inc"
1455       [includeIf "gitdif:~/src/dir"]
1456       ${"\t"}path = "~/path/to/conditional.inc"
1458       [tag]
1459       ${"\t"}gpgSign = true
1461       [user]
1462       ${"\t"}email = "user@example.org"
1463       ${"\t"}name = "John Doe"
1464       ${"\t"}signingKey = "00112233445566778899AABBCCDDEEFF"
1465     '';
1466   };
1468   # right now only invocation check
1469   testToJSONSimple =
1470     let val = {
1471       foobar = [ "baz" 1 2 3 ];
1472     };
1473     in {
1474       expr = generators.toJSON {} val;
1475       # trivial implementation
1476       expected = builtins.toJSON val;
1477   };
1479   # right now only invocation check
1480   testToYAMLSimple =
1481     let val = {
1482       list = [ { one = 1; } { two = 2; } ];
1483       all = 42;
1484     };
1485     in {
1486       expr = generators.toYAML {} val;
1487       # trivial implementation
1488       expected = builtins.toJSON val;
1489   };
1491   testToPretty =
1492     let
1493       deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
1494     in {
1495     expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
1496       int = 42;
1497       float = 0.1337;
1498       bool = true;
1499       emptystring = "";
1500       string = "fn\${o}\"r\\d";
1501       newlinestring = "\n";
1502       path = /. + "/foo";
1503       null_ = null;
1504       function = x: x;
1505       functionArgs = { arg ? 4, foo }: arg;
1506       list = [ 3 4 function [ false ] ];
1507       emptylist = [];
1508       attrs = { foo = null; "foo b/ar" = "baz"; };
1509       emptyattrs = {};
1510       drv = deriv;
1511     };
1512     expected = rec {
1513       int = "42";
1514       float = "0.1337";
1515       bool = "true";
1516       emptystring = ''""'';
1517       string = ''"fn\''${o}\"r\\d"'';
1518       newlinestring = "\"\\n\"";
1519       path = "/foo";
1520       null_ = "null";
1521       function = "<function>";
1522       functionArgs = "<function, args: {arg?, foo}>";
1523       list = "[ 3 4 ${function} [ false ] ]";
1524       emptylist = "[ ]";
1525       attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }";
1526       emptyattrs = "{ }";
1527       drv = "<derivation ${deriv.name}>";
1528     };
1529   };
1531   testToPrettyLimit =
1532     let
1533       a.b = 1;
1534       a.c = a;
1535     in {
1536       expr = generators.toPretty { } (generators.withRecursion { throwOnDepthLimit = false; depthLimit = 2; } a);
1537       expected = "{\n  b = 1;\n  c = {\n    b = \"<unevaluated>\";\n    c = {\n      b = \"<unevaluated>\";\n      c = \"<unevaluated>\";\n    };\n  };\n}";
1538     };
1540   testToPrettyLimitThrow =
1541     let
1542       a.b = 1;
1543       a.c = a;
1544     in {
1545       expr = (builtins.tryEval
1546         (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))).success;
1547       expected = false;
1548     };
1550   testWithRecursionDealsWithFunctors =
1551     let
1552       functor = {
1553         __functor = self: { a, b, }: null;
1554       };
1555       a = {
1556         value = "1234";
1557         b = functor;
1558         c.d = functor;
1559       };
1560     in {
1561       expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a);
1562       expected = "{\n  b = <function, args: {a, b}>;\n  c = {\n    d = \"<unevaluated>\";\n  };\n  value = \"<unevaluated>\";\n}";
1563     };
1565   testToPrettyMultiline = {
1566     expr = mapAttrs (const (generators.toPretty { })) {
1567       list = [ 3 4 [ false ] ];
1568       attrs = { foo = null; bar.foo = "baz"; };
1569       newlinestring = "\n";
1570       multilinestring = ''
1571         hello
1572         ''${there}
1573         te'''st
1574       '';
1575       multilinestring' = ''
1576         hello
1577         there
1578         test'';
1579     };
1580     expected = {
1581       list = ''
1582         [
1583           3
1584           4
1585           [
1586             false
1587           ]
1588         ]'';
1589       attrs = ''
1590         {
1591           bar = {
1592             foo = "baz";
1593           };
1594           foo = null;
1595         }'';
1596       newlinestring = "''\n  \n''";
1597       multilinestring = ''
1598         '''
1599           hello
1600           '''''${there}
1601           te''''st
1602         ''''';
1603       multilinestring' = ''
1604         '''
1605           hello
1606           there
1607           test''''';
1609     };
1610   };
1612   testToPrettyAllowPrettyValues = {
1613     expr = generators.toPretty { allowPrettyValues = true; }
1614              { __pretty = v: "«" + v + "»"; val = "foo"; };
1615     expected  = "«foo»";
1616   };
1618   testToPlist = {
1619     expr = mapAttrs (const (generators.toPlist { })) {
1620       value = {
1621         nested.values = {
1622           int = 42;
1623           float = 0.1337;
1624           bool = true;
1625           emptystring = "";
1626           string = "fn\${o}\"r\\d";
1627           newlinestring = "\n";
1628           path = /. + "/foo";
1629           null_ = null;
1630           list = [ 3 4 "test" ];
1631           emptylist = [];
1632           attrs = { foo = null; "foo b/ar" = "baz"; };
1633           emptyattrs = {};
1634         };
1635       };
1636     };
1637     expected = { value = builtins.readFile ./test-to-plist-expected.plist; };
1638   };
1640   testToLuaEmptyAttrSet = {
1641     expr = generators.toLua {} {};
1642     expected = ''{}'';
1643   };
1645   testToLuaEmptyList = {
1646     expr = generators.toLua {} [];
1647     expected = ''{}'';
1648   };
1650   testToLuaListOfVariousTypes = {
1651     expr = generators.toLua {} [ null 43 3.14159 true ];
1652     expected = ''
1653       {
1654         nil,
1655         43,
1656         3.14159,
1657         true
1658       }'';
1659   };
1661   testToLuaString = {
1662     expr = generators.toLua {} ''double-quote (") and single quotes (')'';
1663     expected = ''"double-quote (\") and single quotes (')"'';
1664   };
1666   testToLuaAttrsetWithLuaInline = {
1667     expr = generators.toLua {} { x = generators.mkLuaInline ''"abc" .. "def"''; };
1668     expected = ''
1669       {
1670         ["x"] = ("abc" .. "def")
1671       }'';
1672   };
1674   testToLuaAttrsetWithSpaceInKey = {
1675     expr = generators.toLua {} { "some space and double-quote (\")" = 42; };
1676     expected = ''
1677       {
1678         ["some space and double-quote (\")"] = 42
1679       }'';
1680   };
1682   testToLuaWithoutMultiline = {
1683     expr = generators.toLua { multiline = false; } [ 41 43 ];
1684     expected = ''{ 41, 43 }'';
1685   };
1687   testToLuaEmptyBindings = {
1688     expr = generators.toLua { asBindings = true; } {};
1689     expected = "";
1690   };
1692   testToLuaBindings = {
1693     expr = generators.toLua { asBindings = true; } { x1 = 41; _y = { a = 43; }; };
1694     expected = ''
1695       _y = {
1696         ["a"] = 43
1697       }
1698       x1 = 41
1699     '';
1700   };
1702   testToLuaPartialTableBindings = {
1703     expr = generators.toLua { asBindings = true; } { "x.y" = 42; };
1704     expected = ''
1705       x.y = 42
1706     '';
1707   };
1709   testToLuaIndentedBindings = {
1710     expr = generators.toLua { asBindings = true; indent = "  "; } { x = { y = 42; }; };
1711     expected = "  x = {\n    [\"y\"] = 42\n  }\n";
1712   };
1714   testToLuaBindingsWithSpace = testingThrow (
1715     generators.toLua { asBindings = true; } { "with space" = 42; }
1716   );
1718   testToLuaBindingsWithLeadingDigit = testingThrow (
1719     generators.toLua { asBindings = true; } { "11eleven" = 42; }
1720   );
1722   testToLuaBasicExample = {
1723     expr = generators.toLua {} {
1724       cmd = [ "typescript-language-server" "--stdio" ];
1725       settings.workspace.library = generators.mkLuaInline ''vim.api.nvim_get_runtime_file("", true)'';
1726     };
1727     expected = ''
1728       {
1729         ["cmd"] = {
1730           "typescript-language-server",
1731           "--stdio"
1732         },
1733         ["settings"] = {
1734           ["workspace"] = {
1735             ["library"] = (vim.api.nvim_get_runtime_file("", true))
1736           }
1737         }
1738       }'';
1739   };
1741 # CLI
1743   testToGNUCommandLine = {
1744     expr = cli.toGNUCommandLine {} {
1745       data = builtins.toJSON { id = 0; };
1746       X = "PUT";
1747       retry = 3;
1748       retry-delay = null;
1749       url = [ "https://example.com/foo" "https://example.com/bar" ];
1750       silent = false;
1751       verbose = true;
1752     };
1754     expected = [
1755       "-X" "PUT"
1756       "--data" "{\"id\":0}"
1757       "--retry" "3"
1758       "--url" "https://example.com/foo"
1759       "--url" "https://example.com/bar"
1760       "--verbose"
1761     ];
1762   };
1764   testToGNUCommandLineSeparator = {
1765     expr = cli.toGNUCommandLine { optionValueSeparator = "="; } {
1766       data = builtins.toJSON { id = 0; };
1767       X = "PUT";
1768       retry = 3;
1769       retry-delay = null;
1770       url = [ "https://example.com/foo" "https://example.com/bar" ];
1771       silent = false;
1772       verbose = true;
1773     };
1775     expected = [
1776       "-X=PUT"
1777       "--data={\"id\":0}"
1778       "--retry=3"
1779       "--url=https://example.com/foo"
1780       "--url=https://example.com/bar"
1781       "--verbose"
1782     ];
1783   };
1785   testToGNUCommandLineShell = {
1786     expr = cli.toGNUCommandLineShell {} {
1787       data = builtins.toJSON { id = 0; };
1788       X = "PUT";
1789       retry = 3;
1790       retry-delay = null;
1791       url = [ "https://example.com/foo" "https://example.com/bar" ];
1792       silent = false;
1793       verbose = true;
1794     };
1796     expected = "-X PUT --data '{\"id\":0}' --retry 3 --url https://example.com/foo --url https://example.com/bar --verbose";
1797   };
1799   testSanitizeDerivationNameLeadingDots = testSanitizeDerivationName {
1800     name = "..foo";
1801     expected = "foo";
1802   };
1804   testSanitizeDerivationNameUnicode = testSanitizeDerivationName {
1805     name = "fö";
1806     expected = "f-";
1807   };
1809   testSanitizeDerivationNameAscii = testSanitizeDerivationName {
1810     name = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
1811     expected = "-+--.-0123456789-=-?-ABCDEFGHIJKLMNOPQRSTUVWXYZ-_-abcdefghijklmnopqrstuvwxyz-";
1812   };
1814   testSanitizeDerivationNameTooLong = testSanitizeDerivationName {
1815     name = "This string is loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1816     expected = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
1817   };
1819   testSanitizeDerivationNameTooLongWithInvalid = testSanitizeDerivationName {
1820     name = "Hello there aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&&&&&&&";
1821     expected = "there-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-";
1822   };
1824   testSanitizeDerivationNameEmpty = testSanitizeDerivationName {
1825     name = "";
1826     expected = "unknown";
1827   };
1829   testFreeformOptions = {
1830     expr =
1831       let
1832         submodule = { lib, ... }: {
1833           freeformType = lib.types.attrsOf (lib.types.submodule {
1834             options.bar = lib.mkOption {};
1835           });
1836           options.bar = lib.mkOption {};
1837         };
1839         module = { lib, ... }: {
1840           options.foo = lib.mkOption {
1841             type = lib.types.submodule submodule;
1842           };
1843         };
1845         options = (evalModules {
1846           modules = [ module ];
1847         }).options;
1849         locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
1850       in map (o: o.loc) locs;
1851     expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
1852   };
1854   testCartesianProductOfEmptySet = {
1855     expr = cartesianProduct {};
1856     expected = [ {} ];
1857   };
1859   testCartesianProductOfOneSet = {
1860     expr = cartesianProduct { a = [ 1 2 3 ]; };
1861     expected = [ { a = 1; } { a = 2; } { a = 3; } ];
1862   };
1864   testCartesianProductOfTwoSets = {
1865     expr = cartesianProduct { a = [ 1 ]; b = [ 10 20 ]; };
1866     expected = [
1867       { a = 1; b = 10; }
1868       { a = 1; b = 20; }
1869     ];
1870   };
1872   testCartesianProductOfTwoSetsWithOneEmpty = {
1873     expr = cartesianProduct { a = [ ]; b = [ 10 20 ]; };
1874     expected = [ ];
1875   };
1877   testCartesianProductOfThreeSets = {
1878     expr = cartesianProduct {
1879       a = [   1   2   3 ];
1880       b = [  10  20  30 ];
1881       c = [ 100 200 300 ];
1882     };
1883     expected = [
1884       { a = 1; b = 10; c = 100; }
1885       { a = 1; b = 10; c = 200; }
1886       { a = 1; b = 10; c = 300; }
1888       { a = 1; b = 20; c = 100; }
1889       { a = 1; b = 20; c = 200; }
1890       { a = 1; b = 20; c = 300; }
1892       { a = 1; b = 30; c = 100; }
1893       { a = 1; b = 30; c = 200; }
1894       { a = 1; b = 30; c = 300; }
1896       { a = 2; b = 10; c = 100; }
1897       { a = 2; b = 10; c = 200; }
1898       { a = 2; b = 10; c = 300; }
1900       { a = 2; b = 20; c = 100; }
1901       { a = 2; b = 20; c = 200; }
1902       { a = 2; b = 20; c = 300; }
1904       { a = 2; b = 30; c = 100; }
1905       { a = 2; b = 30; c = 200; }
1906       { a = 2; b = 30; c = 300; }
1908       { a = 3; b = 10; c = 100; }
1909       { a = 3; b = 10; c = 200; }
1910       { a = 3; b = 10; c = 300; }
1912       { a = 3; b = 20; c = 100; }
1913       { a = 3; b = 20; c = 200; }
1914       { a = 3; b = 20; c = 300; }
1916       { a = 3; b = 30; c = 100; }
1917       { a = 3; b = 30; c = 200; }
1918       { a = 3; b = 30; c = 300; }
1919     ];
1920   };
1922   testMapCartesianProductOfOneSet = {
1923     expr = mapCartesianProduct ({a}: a * 2) { a = [ 1 2 3 ]; };
1924     expected = [ 2 4 6 ];
1925   };
1927   testMapCartesianProductOfTwoSets = {
1928     expr = mapCartesianProduct ({a,b}: a + b) { a = [ 1 ]; b = [ 10 20 ]; };
1929     expected = [ 11 21 ];
1930   };
1932   testMapCartesianProcutOfTwoSetsWithOneEmpty = {
1933     expr = mapCartesianProduct (x: x.a + x.b) { a = [ ]; b = [ 10 20 ]; };
1934     expected = [ ];
1935   };
1937   testMapCartesianProductOfThreeSets = {
1938     expr = mapCartesianProduct ({a,b,c}: a + b + c) {
1939       a = [ 1 2 3 ];
1940       b = [ 10 20 30 ];
1941       c = [ 100 200 300 ];
1942     };
1943     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 ];
1944   };
1946   # The example from the showAttrPath documentation
1947   testShowAttrPathExample = {
1948     expr = showAttrPath [ "foo" "10" "bar" ];
1949     expected = "foo.\"10\".bar";
1950   };
1952   testShowAttrPathEmpty = {
1953     expr = showAttrPath [];
1954     expected = "<root attribute path>";
1955   };
1957   testShowAttrPathVarious = {
1958     expr = showAttrPath [
1959       "."
1960       "foo"
1961       "2"
1962       "a2-b"
1963       "_bc'de"
1964     ];
1965     expected = ''".".foo."2".a2-b._bc'de'';
1966   };
1968   testGroupBy = {
1969     expr = groupBy (n: toString (mod n 5)) (range 0 16);
1970     expected = {
1971       "0" = [ 0 5 10 15 ];
1972       "1" = [ 1 6 11 16 ];
1973       "2" = [ 2 7 12 ];
1974       "3" = [ 3 8 13 ];
1975       "4" = [ 4 9 14 ];
1976     };
1977   };
1979   testGroupBy' = {
1980     expr = groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ];
1981     expected = { false = 3; true = 12; };
1982   };
1984   # The example from the updateManyAttrsByPath documentation
1985   testUpdateManyAttrsByPathExample = {
1986     expr = updateManyAttrsByPath [
1987       {
1988         path = [ "a" "b" ];
1989         update = old: { d = old.c; };
1990       }
1991       {
1992         path = [ "a" "b" "c" ];
1993         update = old: old + 1;
1994       }
1995       {
1996         path = [ "x" "y" ];
1997         update = old: "xy";
1998       }
1999     ] { a.b.c = 0; };
2000     expected = { a = { b = { d = 1; }; }; x = { y = "xy"; }; };
2001   };
2003   # If there are no updates, the value is passed through
2004   testUpdateManyAttrsByPathNone = {
2005     expr = updateManyAttrsByPath [] "something";
2006     expected = "something";
2007   };
2009   # A single update to the root path is just like applying the function directly
2010   testUpdateManyAttrsByPathSingleIncrement = {
2011     expr = updateManyAttrsByPath [
2012       {
2013         path = [ ];
2014         update = old: old + 1;
2015       }
2016     ] 0;
2017     expected = 1;
2018   };
2020   # Multiple updates can be applied are done in order
2021   testUpdateManyAttrsByPathMultipleIncrements = {
2022     expr = updateManyAttrsByPath [
2023       {
2024         path = [ ];
2025         update = old: old + "a";
2026       }
2027       {
2028         path = [ ];
2029         update = old: old + "b";
2030       }
2031       {
2032         path = [ ];
2033         update = old: old + "c";
2034       }
2035     ] "";
2036     expected = "abc";
2037   };
2039   # If an update doesn't use the value, all previous updates are not evaluated
2040   testUpdateManyAttrsByPathLazy = {
2041     expr = updateManyAttrsByPath [
2042       {
2043         path = [ ];
2044         update = old: old + throw "nope";
2045       }
2046       {
2047         path = [ ];
2048         update = old: "untainted";
2049       }
2050     ] (throw "start");
2051     expected = "untainted";
2052   };
2054   # Deeply nested attributes can be updated without affecting others
2055   testUpdateManyAttrsByPathDeep = {
2056     expr = updateManyAttrsByPath [
2057       {
2058         path = [ "a" "b" "c" ];
2059         update = old: old + 1;
2060       }
2061     ] {
2062       a.b.c = 0;
2064       a.b.z = 0;
2065       a.y.z = 0;
2066       x.y.z = 0;
2067     };
2068     expected = {
2069       a.b.c = 1;
2071       a.b.z = 0;
2072       a.y.z = 0;
2073       x.y.z = 0;
2074     };
2075   };
2077   # Nested attributes are updated first
2078   testUpdateManyAttrsByPathNestedBeforehand = {
2079     expr = updateManyAttrsByPath [
2080       {
2081         path = [ "a" ];
2082         update = old: old // { x = old.b; };
2083       }
2084       {
2085         path = [ "a" "b" ];
2086         update = old: old + 1;
2087       }
2088     ] {
2089       a.b = 0;
2090     };
2091     expected = {
2092       a.b = 1;
2093       a.x = 1;
2094     };
2095   };
2097   ## Levenshtein distance functions and co.
2098   testCommonPrefixLengthEmpty = {
2099     expr = strings.commonPrefixLength "" "hello";
2100     expected = 0;
2101   };
2103   testCommonPrefixLengthSame = {
2104     expr = strings.commonPrefixLength "hello" "hello";
2105     expected = 5;
2106   };
2108   testCommonPrefixLengthDiffering = {
2109     expr = strings.commonPrefixLength "hello" "hey";
2110     expected = 2;
2111   };
2113   testCommonSuffixLengthEmpty = {
2114     expr = strings.commonSuffixLength "" "hello";
2115     expected = 0;
2116   };
2118   testCommonSuffixLengthSame = {
2119     expr = strings.commonSuffixLength "hello" "hello";
2120     expected = 5;
2121   };
2123   testCommonSuffixLengthDiffering = {
2124     expr = strings.commonSuffixLength "test" "rest";
2125     expected = 3;
2126   };
2128   testLevenshteinEmpty = {
2129     expr = strings.levenshtein "" "";
2130     expected = 0;
2131   };
2133   testLevenshteinOnlyAdd = {
2134     expr = strings.levenshtein "" "hello there";
2135     expected = 11;
2136   };
2138   testLevenshteinOnlyRemove = {
2139     expr = strings.levenshtein "hello there" "";
2140     expected = 11;
2141   };
2143   testLevenshteinOnlyTransform = {
2144     expr = strings.levenshtein "abcdef" "ghijkl";
2145     expected = 6;
2146   };
2148   testLevenshteinMixed = {
2149     expr = strings.levenshtein "kitchen" "sitting";
2150     expected = 5;
2151   };
2153   testLevenshteinAtMostZeroFalse = {
2154     expr = strings.levenshteinAtMost 0 "foo" "boo";
2155     expected = false;
2156   };
2158   testLevenshteinAtMostZeroTrue = {
2159     expr = strings.levenshteinAtMost 0 "foo" "foo";
2160     expected = true;
2161   };
2163   testLevenshteinAtMostOneFalse = {
2164     expr = strings.levenshteinAtMost 1 "car" "ct";
2165     expected = false;
2166   };
2168   testLevenshteinAtMostOneTrue = {
2169     expr = strings.levenshteinAtMost 1 "car" "cr";
2170     expected = true;
2171   };
2173   # We test levenshteinAtMost 2 particularly well because it uses a complicated
2174   # implementation
2175   testLevenshteinAtMostTwoIsEmpty = {
2176     expr = strings.levenshteinAtMost 2 "" "";
2177     expected = true;
2178   };
2180   testLevenshteinAtMostTwoIsZero = {
2181     expr = strings.levenshteinAtMost 2 "abcdef" "abcdef";
2182     expected = true;
2183   };
2185   testLevenshteinAtMostTwoIsOne = {
2186     expr = strings.levenshteinAtMost 2 "abcdef" "abddef";
2187     expected = true;
2188   };
2190   testLevenshteinAtMostTwoDiff0False = {
2191     expr = strings.levenshteinAtMost 2 "abcdef" "aczyef";
2192     expected = false;
2193   };
2195   testLevenshteinAtMostTwoDiff0Outer = {
2196     expr = strings.levenshteinAtMost 2 "abcdef" "zbcdez";
2197     expected = true;
2198   };
2200   testLevenshteinAtMostTwoDiff0DelLeft = {
2201     expr = strings.levenshteinAtMost 2 "abcdef" "bcdefz";
2202     expected = true;
2203   };
2205   testLevenshteinAtMostTwoDiff0DelRight = {
2206     expr = strings.levenshteinAtMost 2 "abcdef" "zabcde";
2207     expected = true;
2208   };
2210   testLevenshteinAtMostTwoDiff1False = {
2211     expr = strings.levenshteinAtMost 2 "abcdef" "bddez";
2212     expected = false;
2213   };
2215   testLevenshteinAtMostTwoDiff1DelLeft = {
2216     expr = strings.levenshteinAtMost 2 "abcdef" "bcdez";
2217     expected = true;
2218   };
2220   testLevenshteinAtMostTwoDiff1DelRight = {
2221     expr = strings.levenshteinAtMost 2 "abcdef" "zbcde";
2222     expected = true;
2223   };
2225   testLevenshteinAtMostTwoDiff2False = {
2226     expr = strings.levenshteinAtMost 2 "hello" "hxo";
2227     expected = false;
2228   };
2230   testLevenshteinAtMostTwoDiff2True = {
2231     expr = strings.levenshteinAtMost 2 "hello" "heo";
2232     expected = true;
2233   };
2235   testLevenshteinAtMostTwoDiff3 = {
2236     expr = strings.levenshteinAtMost 2 "hello" "ho";
2237     expected = false;
2238   };
2240   testLevenshteinAtMostThreeFalse = {
2241     expr = strings.levenshteinAtMost 3 "hello" "Holla!";
2242     expected = false;
2243   };
2245   testLevenshteinAtMostThreeTrue = {
2246     expr = strings.levenshteinAtMost 3 "hello" "Holla";
2247     expected = true;
2248   };
2250   # DERIVATIONS
2252   testLazyDerivationIsLazyInDerivationForAttrNames = {
2253     expr = attrNames (lazyDerivation {
2254       derivation = throw "not lazy enough";
2255     });
2256     # It's ok to add attribute names here when lazyDerivation is improved
2257     # in accordance with its inline comments.
2258     expected = [ "drvPath" "meta" "name" "out" "outPath" "outputName" "outputs" "system" "type" ];
2259   };
2261   testLazyDerivationIsLazyInDerivationForPassthruAttr = {
2262     expr = (lazyDerivation {
2263       derivation = throw "not lazy enough";
2264       passthru.tests = "whatever is in tests";
2265     }).tests;
2266     expected = "whatever is in tests";
2267   };
2269   testLazyDerivationIsLazyInDerivationForPassthruAttr2 = {
2270     # passthru.tests is not a special case. It works for any attr.
2271     expr = (lazyDerivation {
2272       derivation = throw "not lazy enough";
2273       passthru.foo = "whatever is in foo";
2274     }).foo;
2275     expected = "whatever is in foo";
2276   };
2278   testLazyDerivationIsLazyInDerivationForMeta = {
2279     expr = (lazyDerivation {
2280       derivation = throw "not lazy enough";
2281       meta = "whatever is in meta";
2282     }).meta;
2283     expected = "whatever is in meta";
2284   };
2286   testLazyDerivationReturnsDerivationAttrs = let
2287     derivation = {
2288       type = "derivation";
2289       outputs = ["out"];
2290       out = "test out";
2291       outPath = "test outPath";
2292       outputName = "out";
2293       drvPath = "test drvPath";
2294       name = "test name";
2295       system = "test system";
2296       meta = "test meta";
2297     };
2298   in {
2299     expr = lazyDerivation { inherit derivation; };
2300     expected = derivation;
2301   };
2303   testOptionalDrvAttr = let
2304     mkDerivation = args: derivation (args // {
2305       builder = "builder";
2306       system = "system";
2307       __ignoreNulls = true;
2308     });
2309   in {
2310     expr = (mkDerivation {
2311       name = "foo";
2312       x = optionalDrvAttr true 1;
2313       y = optionalDrvAttr false 1;
2314     }).drvPath;
2315     expected = (mkDerivation {
2316       name = "foo";
2317       x = 1;
2318     }).drvPath;
2319   };
2321   testLazyDerivationMultiOutputReturnsDerivationAttrs = let
2322     derivation = {
2323       type = "derivation";
2324       outputs = ["out" "dev"];
2325       dev = "test dev";
2326       out = "test out";
2327       outPath = "test outPath";
2328       outputName = "out";
2329       drvPath = "test drvPath";
2330       name = "test name";
2331       system = "test system";
2332       meta.position = "/hi:23";
2333     };
2334   in {
2335     expr = lazyDerivation { inherit derivation; outputs = ["out" "dev"]; passthru.meta.position = "/hi:23"; };
2336     expected = derivation;
2337   };
2339   testTypeDescriptionInt = {
2340     expr = (with types; int).description;
2341     expected = "signed integer";
2342   };
2343   testTypeDescriptionIntsPositive = {
2344     expr = (with types; ints.positive).description;
2345     expected = "positive integer, meaning >0";
2346   };
2347   testTypeDescriptionIntsPositiveOrEnumAuto = {
2348     expr = (with types; either ints.positive (enum ["auto"])).description;
2349     expected = ''positive integer, meaning >0, or value "auto" (singular enum)'';
2350   };
2351   testTypeDescriptionListOfPositive = {
2352     expr = (with types; listOf ints.positive).description;
2353     expected = "list of (positive integer, meaning >0)";
2354   };
2355   testTypeDescriptionListOfInt = {
2356     expr = (with types; listOf int).description;
2357     expected = "list of signed integer";
2358   };
2359   testTypeDescriptionListOfListOfInt = {
2360     expr = (with types; listOf (listOf int)).description;
2361     expected = "list of list of signed integer";
2362   };
2363   testTypeDescriptionListOfEitherStrOrBool = {
2364     expr = (with types; listOf (either str bool)).description;
2365     expected = "list of (string or boolean)";
2366   };
2367   testTypeDescriptionEitherListOfStrOrBool = {
2368     expr = (with types; either (listOf bool) str).description;
2369     expected = "(list of boolean) or string";
2370   };
2371   testTypeDescriptionEitherStrOrListOfBool = {
2372     expr = (with types; either str (listOf bool)).description;
2373     expected = "string or list of boolean";
2374   };
2375   testTypeDescriptionOneOfListOfStrOrBool = {
2376     expr = (with types; oneOf [ (listOf bool) str ]).description;
2377     expected = "(list of boolean) or string";
2378   };
2379   testTypeDescriptionOneOfListOfStrOrBoolOrNumber = {
2380     expr = (with types; oneOf [ (listOf bool) str number ]).description;
2381     expected = "(list of boolean) or string or signed integer or floating point number";
2382   };
2383   testTypeDescriptionEitherListOfBoolOrEitherStringOrNumber = {
2384     expr = (with types; either (listOf bool) (either str number)).description;
2385     expected = "(list of boolean) or string or signed integer or floating point number";
2386   };
2387   testTypeDescriptionEitherEitherListOfBoolOrStringOrNumber = {
2388     expr = (with types; either (either (listOf bool) str) number).description;
2389     expected = "(list of boolean) or string or signed integer or floating point number";
2390   };
2391   testTypeDescriptionEitherNullOrBoolOrString = {
2392     expr = (with types; either (nullOr bool) str).description;
2393     expected = "null or boolean or string";
2394   };
2395   testTypeDescriptionEitherListOfEitherBoolOrStrOrInt = {
2396     expr = (with types; either (listOf (either bool str)) int).description;
2397     expected = "(list of (boolean or string)) or signed integer";
2398   };
2399   testTypeDescriptionEitherIntOrListOrEitherBoolOrStr = {
2400     expr = (with types; either int (listOf (either bool str))).description;
2401     expected = "signed integer or list of (boolean or string)";
2402   };
2404 # Meta
2405   testGetExe'Output = {
2406     expr = getExe' {
2407       type = "derivation";
2408       out = "somelonghash";
2409       bin = "somelonghash";
2410     } "executable";
2411     expected = "somelonghash/bin/executable";
2412   };
2414   testGetExeOutput = {
2415     expr = getExe {
2416       type = "derivation";
2417       out = "somelonghash";
2418       bin = "somelonghash";
2419       meta.mainProgram = "mainProgram";
2420     };
2421     expected = "somelonghash/bin/mainProgram";
2422   };
2424   testGetExe'FailureFirstArg = testingThrow (
2425     getExe' "not a derivation" "executable"
2426   );
2428   testGetExe'FailureSecondArg = testingThrow (
2429     getExe' { type = "derivation"; } "dir/executable"
2430   );
2432   testGetLicenseFromSpdxIdOrExamples = {
2433     expr = [
2434       (getLicenseFromSpdxIdOr "MIT" null)
2435       (getLicenseFromSpdxIdOr "mIt" null)
2436       (getLicenseFromSpdxIdOr "MY LICENSE" lib.licenses.free)
2437       (getLicenseFromSpdxIdOr "MY LICENSE" null)
2438     ];
2439     expected = [
2440       lib.licenses.mit
2441       lib.licenses.mit
2442       lib.licenses.free
2443       null
2444     ];
2445   };
2447   testGetLicenseFromSpdxIdOrThrow = testingThrow (
2448     getLicenseFromSpdxIdOr "MY LICENSE" (throw "No SPDX ID matches MY LICENSE")
2449   );
2451   testPlatformMatch = {
2452     expr = meta.platformMatch { system = "x86_64-linux"; } "x86_64-linux";
2453     expected = true;
2454   };
2456   testPlatformMatchAttrs = {
2457     expr = meta.platformMatch (systems.elaborate "x86_64-linux") (systems.elaborate "x86_64-linux").parsed;
2458     expected = true;
2459   };
2461   testPlatformMatchNoMatch = {
2462     expr = meta.platformMatch { system = "x86_64-darwin"; } "x86_64-linux";
2463     expected = false;
2464   };
2466   testPlatformMatchMissingSystem = {
2467     expr = meta.platformMatch { } "x86_64-linux";
2468     expected = false;
2469   };
2471   testPackagesFromDirectoryRecursive = {
2472     expr = packagesFromDirectoryRecursive {
2473       callPackage = path: overrides: import path overrides;
2474       directory = ./packages-from-directory;
2475     };
2476     expected = {
2477       a = "a";
2478       b = "b";
2479       # Note: Other files/directories in `./test-data/c/` are ignored and can be
2480       # used by `package.nix`.
2481       c = "c";
2482       my-namespace = {
2483         d = "d";
2484         e = "e";
2485         f = "f";
2486         my-sub-namespace = {
2487           g = "g";
2488           h = "h";
2489         };
2490       };
2491     };
2492   };
2494   # Check that `packagesFromDirectoryRecursive` can process a directory with a
2495   # top-level `package.nix` file into a single package.
2496   testPackagesFromDirectoryRecursiveTopLevelPackageNix = {
2497     expr = packagesFromDirectoryRecursive {
2498       callPackage = path: overrides: import path overrides;
2499       directory = ./packages-from-directory/c;
2500     };
2501     expected = "c";
2502   };