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