1# to run these tests:
2# nix-instantiate --eval --strict nixpkgs/lib/tests/misc.nix
3# if the resulting list is empty, all tests passed
4with import ../default.nix;
5
6let
7
8 testSanitizeDerivationName = { name, expected }:
9 let
10 drv = derivation {
11 name = strings.sanitizeDerivationName name;
12 builder = "x";
13 system = "x";
14 };
15 in {
16 # Evaluate the derivation so an invalid name would be caught
17 expr = builtins.seq drv.drvPath drv.name;
18 inherit expected;
19 };
20
21in
22
23runTests {
24
25
26# TRIVIAL
27
28 testId = {
29 expr = id 1;
30 expected = 1;
31 };
32
33 testConst = {
34 expr = const 2 3;
35 expected = 2;
36 };
37
38 testPipe = {
39 expr = pipe 2 [
40 (x: x + 2) # 2 + 2 = 4
41 (x: x * 2) # 4 * 2 = 8
42 ];
43 expected = 8;
44 };
45
46 testPipeEmpty = {
47 expr = pipe 2 [];
48 expected = 2;
49 };
50
51 testPipeStrings = {
52 expr = pipe [ 3 4 ] [
53 (map toString)
54 (map (s: s + "\n"))
55 concatStrings
56 ];
57 expected = ''
58 3
59 4
60 '';
61 };
62
63 /*
64 testOr = {
65 expr = or true false;
66 expected = true;
67 };
68 */
69
70 testAnd = {
71 expr = and true false;
72 expected = false;
73 };
74
75 testFix = {
76 expr = fix (x: {a = if x ? a then "a" else "b";});
77 expected = {a = "a";};
78 };
79
80 testComposeExtensions = {
81 expr = let obj = makeExtensible (self: { foo = self.bar; });
82 f = self: super: { bar = false; baz = true; };
83 g = self: super: { bar = super.baz or false; };
84 f_o_g = composeExtensions f g;
85 composed = obj.extend f_o_g;
86 in composed.foo;
87 expected = true;
88 };
89
90 testComposeManyExtensions0 = {
91 expr = let obj = makeExtensible (self: { foo = true; });
92 emptyComposition = composeManyExtensions [];
93 composed = obj.extend emptyComposition;
94 in composed.foo;
95 expected = true;
96 };
97
98 testComposeManyExtensions =
99 let f = self: super: { bar = false; baz = true; };
100 g = self: super: { bar = super.baz or false; };
101 h = self: super: { qux = super.bar or false; };
102 obj = makeExtensible (self: { foo = self.qux; });
103 in {
104 expr = let composition = composeManyExtensions [f g h];
105 composed = obj.extend composition;
106 in composed.foo;
107 expected = (obj.extend (composeExtensions f (composeExtensions g h))).foo;
108 };
109
110 testBitAnd = {
111 expr = (bitAnd 3 10);
112 expected = 2;
113 };
114
115 testBitOr = {
116 expr = (bitOr 3 10);
117 expected = 11;
118 };
119
120 testBitXor = {
121 expr = (bitXor 3 10);
122 expected = 9;
123 };
124
125 testToHexString = {
126 expr = toHexString 250;
127 expected = "FA";
128 };
129
130 testToBaseDigits = {
131 expr = toBaseDigits 2 6;
132 expected = [ 1 1 0 ];
133 };
134
135 testFunctionArgsFunctor = {
136 expr = functionArgs { __functor = self: { a, b }: null; };
137 expected = { a = false; b = false; };
138 };
139
140 testFunctionArgsSetFunctionArgs = {
141 expr = functionArgs (setFunctionArgs (args: args.x) { x = false; });
142 expected = { x = false; };
143 };
144
145# STRINGS
146
147 testConcatMapStrings = {
148 expr = concatMapStrings (x: x + ";") ["a" "b" "c"];
149 expected = "a;b;c;";
150 };
151
152 testConcatStringsSep = {
153 expr = concatStringsSep "," ["a" "b" "c"];
154 expected = "a,b,c";
155 };
156
157 testSplitStringsSimple = {
158 expr = strings.splitString "." "a.b.c.d";
159 expected = [ "a" "b" "c" "d" ];
160 };
161
162 testSplitStringsEmpty = {
163 expr = strings.splitString "." "a..b";
164 expected = [ "a" "" "b" ];
165 };
166
167 testSplitStringsOne = {
168 expr = strings.splitString ":" "a.b";
169 expected = [ "a.b" ];
170 };
171
172 testSplitStringsNone = {
173 expr = strings.splitString "." "";
174 expected = [ "" ];
175 };
176
177 testSplitStringsFirstEmpty = {
178 expr = strings.splitString "/" "/a/b/c";
179 expected = [ "" "a" "b" "c" ];
180 };
181
182 testSplitStringsLastEmpty = {
183 expr = strings.splitString ":" "2001:db8:0:0042::8a2e:370:";
184 expected = [ "2001" "db8" "0" "0042" "" "8a2e" "370" "" ];
185 };
186
187 testSplitStringsRegex = {
188 expr = strings.splitString "\\[{}]()^$?*+|." "A\\[{}]()^$?*+|.B";
189 expected = [ "A" "B" ];
190 };
191
192 testSplitStringsDerivation = {
193 expr = take 3 (strings.splitString "/" (derivation {
194 name = "name";
195 builder = "builder";
196 system = "system";
197 }));
198 expected = ["" "nix" "store"];
199 };
200
201 testSplitVersionSingle = {
202 expr = versions.splitVersion "1";
203 expected = [ "1" ];
204 };
205
206 testSplitVersionDouble = {
207 expr = versions.splitVersion "1.2";
208 expected = [ "1" "2" ];
209 };
210
211 testSplitVersionTriple = {
212 expr = versions.splitVersion "1.2.3";
213 expected = [ "1" "2" "3" ];
214 };
215
216 testIsStorePath = {
217 expr =
218 let goodPath =
219 "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11";
220 in {
221 storePath = isStorePath goodPath;
222 storePathDerivation = isStorePath (import ../.. { system = "x86_64-linux"; }).hello;
223 storePathAppendix = isStorePath
224 "${goodPath}/bin/python";
225 nonAbsolute = isStorePath (concatStrings (tail (stringToCharacters goodPath)));
226 asPath = isStorePath (/. + goodPath);
227 otherPath = isStorePath "/something/else";
228 otherVals = {
229 attrset = isStorePath {};
230 list = isStorePath [];
231 int = isStorePath 42;
232 };
233 };
234 expected = {
235 storePath = true;
236 storePathDerivation = true;
237 storePathAppendix = false;
238 nonAbsolute = false;
239 asPath = true;
240 otherPath = false;
241 otherVals = {
242 attrset = false;
243 list = false;
244 int = false;
245 };
246 };
247 };
248
249 testEscapeXML = {
250 expr = escapeXML ''"test" 'test' < & >'';
251 expected = ""test" 'test' < & >";
252 };
253
254# LISTS
255
256 testFilter = {
257 expr = filter (x: x != "a") ["a" "b" "c" "a"];
258 expected = ["b" "c"];
259 };
260
261 testFold =
262 let
263 f = op: fold: fold op 0 (range 0 100);
264 # fold with associative operator
265 assoc = f builtins.add;
266 # fold with non-associative operator
267 nonAssoc = f builtins.sub;
268 in {
269 expr = {
270 assocRight = assoc foldr;
271 # right fold with assoc operator is same as left fold
272 assocRightIsLeft = assoc foldr == assoc foldl;
273 nonAssocRight = nonAssoc foldr;
274 nonAssocLeft = nonAssoc foldl;
275 # with non-assoc operator the fold results are not the same
276 nonAssocRightIsNotLeft = nonAssoc foldl != nonAssoc foldr;
277 # fold is an alias for foldr
278 foldIsRight = nonAssoc fold == nonAssoc foldr;
279 };
280 expected = {
281 assocRight = 5050;
282 assocRightIsLeft = true;
283 nonAssocRight = 50;
284 nonAssocLeft = (-5050);
285 nonAssocRightIsNotLeft = true;
286 foldIsRight = true;
287 };
288 };
289
290 testTake = testAllTrue [
291 ([] == (take 0 [ 1 2 3 ]))
292 ([1] == (take 1 [ 1 2 3 ]))
293 ([ 1 2 ] == (take 2 [ 1 2 3 ]))
294 ([ 1 2 3 ] == (take 3 [ 1 2 3 ]))
295 ([ 1 2 3 ] == (take 4 [ 1 2 3 ]))
296 ];
297
298 testFoldAttrs = {
299 expr = foldAttrs (n: a: [n] ++ a) [] [
300 { a = 2; b = 7; }
301 { a = 3; c = 8; }
302 ];
303 expected = { a = [ 2 3 ]; b = [7]; c = [8];};
304 };
305
306 testSort = {
307 expr = sort builtins.lessThan [ 40 2 30 42 ];
308 expected = [2 30 40 42];
309 };
310
311 testToIntShouldConvertStringToInt = {
312 expr = toInt "27";
313 expected = 27;
314 };
315
316 testToIntShouldThrowErrorIfItCouldNotConvertToInt = {
317 expr = builtins.tryEval (toInt "\"foo\"");
318 expected = { success = false; value = false; };
319 };
320
321 testHasAttrByPathTrue = {
322 expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; };
323 expected = true;
324 };
325
326 testHasAttrByPathFalse = {
327 expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; };
328 expected = false;
329 };
330
331
332# ATTRSETS
333
334 # code from the example
335 testRecursiveUpdateUntil = {
336 expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {
337 # first attribute set
338 foo.bar = 1;
339 foo.baz = 2;
340 bar = 3;
341 } {
342 #second attribute set
343 foo.bar = 1;
344 foo.quz = 2;
345 baz = 4;
346 };
347 expected = {
348 foo.bar = 1; # 'foo.*' from the second set
349 foo.quz = 2; #
350 bar = 3; # 'bar' from the first set
351 baz = 4; # 'baz' from the second set
352 };
353 };
354
355 testOverrideExistingEmpty = {
356 expr = overrideExisting {} { a = 1; };
357 expected = {};
358 };
359
360 testOverrideExistingDisjoint = {
361 expr = overrideExisting { b = 2; } { a = 1; };
362 expected = { b = 2; };
363 };
364
365 testOverrideExistingOverride = {
366 expr = overrideExisting { a = 3; b = 2; } { a = 1; };
367 expected = { a = 1; b = 2; };
368 };
369
370# GENERATORS
371# these tests assume attributes are converted to lists
372# in alphabetical order
373
374 testMkKeyValueDefault = {
375 expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar";
376 expected = ''f\:oo:bar'';
377 };
378
379 testMkValueString = {
380 expr = let
381 vals = {
382 int = 42;
383 string = ''fo"o'';
384 bool = true;
385 bool2 = false;
386 null = null;
387 # float = 42.23; # floats are strange
388 };
389 in mapAttrs
390 (const (generators.mkValueStringDefault {}))
391 vals;
392 expected = {
393 int = "42";
394 string = ''fo"o'';
395 bool = "true";
396 bool2 = "false";
397 null = "null";
398 # float = "42.23" true false [ "bar" ] ]'';
399 };
400 };
401
402 testToKeyValue = {
403 expr = generators.toKeyValue {} {
404 key = "value";
405 "other=key" = "baz";
406 };
407 expected = ''
408 key=value
409 other\=key=baz
410 '';
411 };
412
413 testToINIEmpty = {
414 expr = generators.toINI {} {};
415 expected = "";
416 };
417
418 testToINIEmptySection = {
419 expr = generators.toINI {} { foo = {}; bar = {}; };
420 expected = ''
421 [bar]
422
423 [foo]
424 '';
425 };
426
427 testToINIDuplicateKeys = {
428 expr = generators.toINI { listsAsDuplicateKeys = true; } { foo.bar = true; baz.qux = [ 1 false ]; };
429 expected = ''
430 [baz]
431 qux=1
432 qux=false
433
434 [foo]
435 bar=true
436 '';
437 };
438
439 testToINIDefaultEscapes = {
440 expr = generators.toINI {} {
441 "no [ and ] allowed unescaped" = {
442 "and also no = in keys" = 42;
443 };
444 };
445 expected = ''
446 [no \[ and \] allowed unescaped]
447 and also no \= in keys=42
448 '';
449 };
450
451 testToINIDefaultFull = {
452 expr = generators.toINI {} {
453 "section 1" = {
454 attribute1 = 5;
455 x = "Me-se JarJar Binx";
456 # booleans are converted verbatim by default
457 boolean = false;
458 };
459 "foo[]" = {
460 "he\\h=he" = "this is okay";
461 };
462 };
463 expected = ''
464 [foo\[\]]
465 he\h\=he=this is okay
466
467 [section 1]
468 attribute1=5
469 boolean=false
470 x=Me-se JarJar Binx
471 '';
472 };
473
474 /* right now only invocation check */
475 testToJSONSimple =
476 let val = {
477 foobar = [ "baz" 1 2 3 ];
478 };
479 in {
480 expr = generators.toJSON {} val;
481 # trivial implementation
482 expected = builtins.toJSON val;
483 };
484
485 /* right now only invocation check */
486 testToYAMLSimple =
487 let val = {
488 list = [ { one = 1; } { two = 2; } ];
489 all = 42;
490 };
491 in {
492 expr = generators.toYAML {} val;
493 # trivial implementation
494 expected = builtins.toJSON val;
495 };
496
497 testToPretty =
498 let
499 deriv = derivation { name = "test"; builder = "/bin/sh"; system = builtins.currentSystem; };
500 in {
501 expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
502 int = 42;
503 float = 0.1337;
504 bool = true;
505 emptystring = "";
506 string = ''fno"rd'';
507 newlinestring = "\n";
508 path = /. + "/foo";
509 null_ = null;
510 function = x: x;
511 functionArgs = { arg ? 4, foo }: arg;
512 list = [ 3 4 function [ false ] ];
513 emptylist = [];
514 attrs = { foo = null; "foo bar" = "baz"; };
515 emptyattrs = {};
516 drv = deriv;
517 };
518 expected = rec {
519 int = "42";
520 float = "~0.133700";
521 bool = "true";
522 emptystring = ''""'';
523 string = ''"fno\"rd"'';
524 newlinestring = "\"\\n\"";
525 path = "/foo";
526 null_ = "null";
527 function = "<function>";
528 functionArgs = "<function, args: {arg?, foo}>";
529 list = "[ 3 4 ${function} [ false ] ]";
530 emptylist = "[ ]";
531 attrs = "{ foo = null; \"foo bar\" = \"baz\"; }";
532 emptyattrs = "{ }";
533 drv = "<derivation ${deriv.drvPath}>";
534 };
535 };
536
537 testToPrettyLimit =
538 let
539 a.b = 1;
540 a.c = a;
541 in {
542 expr = generators.toPretty { } (generators.withRecursion { throwOnDepthLimit = false; depthLimit = 2; } a);
543 expected = "{\n b = 1;\n c = {\n b = \"<unevaluated>\";\n c = {\n b = \"<unevaluated>\";\n c = \"<unevaluated>\";\n };\n };\n}";
544 };
545
546 testToPrettyLimitThrow =
547 let
548 a.b = 1;
549 a.c = a;
550 in {
551 expr = (builtins.tryEval
552 (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))).success;
553 expected = false;
554 };
555
556 testToPrettyMultiline = {
557 expr = mapAttrs (const (generators.toPretty { })) rec {
558 list = [ 3 4 [ false ] ];
559 attrs = { foo = null; bar.foo = "baz"; };
560 newlinestring = "\n";
561 multilinestring = ''
562 hello
563 there
564 test
565 '';
566 multilinestring' = ''
567 hello
568 there
569 test'';
570 };
571 expected = rec {
572 list = ''
573 [
574 3
575 4
576 [
577 false
578 ]
579 ]'';
580 attrs = ''
581 {
582 bar = {
583 foo = "baz";
584 };
585 foo = null;
586 }'';
587 newlinestring = "''\n \n''";
588 multilinestring = ''
589 '''
590 hello
591 there
592 test
593 ''''';
594 multilinestring' = ''
595 '''
596 hello
597 there
598 test''''';
599
600 };
601 };
602
603 testToPrettyAllowPrettyValues = {
604 expr = generators.toPretty { allowPrettyValues = true; }
605 { __pretty = v: "«" + v + "»"; val = "foo"; };
606 expected = "«foo»";
607 };
608
609
610# CLI
611
612 testToGNUCommandLine = {
613 expr = cli.toGNUCommandLine {} {
614 data = builtins.toJSON { id = 0; };
615 X = "PUT";
616 retry = 3;
617 retry-delay = null;
618 url = [ "https://example.com/foo" "https://example.com/bar" ];
619 silent = false;
620 verbose = true;
621 };
622
623 expected = [
624 "-X" "PUT"
625 "--data" "{\"id\":0}"
626 "--retry" "3"
627 "--url" "https://example.com/foo"
628 "--url" "https://example.com/bar"
629 "--verbose"
630 ];
631 };
632
633 testToGNUCommandLineShell = {
634 expr = cli.toGNUCommandLineShell {} {
635 data = builtins.toJSON { id = 0; };
636 X = "PUT";
637 retry = 3;
638 retry-delay = null;
639 url = [ "https://example.com/foo" "https://example.com/bar" ];
640 silent = false;
641 verbose = true;
642 };
643
644 expected = "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
645 };
646
647 testSanitizeDerivationNameLeadingDots = testSanitizeDerivationName {
648 name = "..foo";
649 expected = "foo";
650 };
651
652 testSanitizeDerivationNameAscii = testSanitizeDerivationName {
653 name = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
654 expected = "-+--.-0123456789-=-?-ABCDEFGHIJKLMNOPQRSTUVWXYZ-_-abcdefghijklmnopqrstuvwxyz-";
655 };
656
657 testSanitizeDerivationNameTooLong = testSanitizeDerivationName {
658 name = "This string is loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
659 expected = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
660 };
661
662 testSanitizeDerivationNameTooLongWithInvalid = testSanitizeDerivationName {
663 name = "Hello there aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&&&&&&&";
664 expected = "there-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-";
665 };
666
667 testSanitizeDerivationNameEmpty = testSanitizeDerivationName {
668 name = "";
669 expected = "unknown";
670 };
671
672 testFreeformOptions = {
673 expr =
674 let
675 submodule = { lib, ... }: {
676 freeformType = lib.types.attrsOf (lib.types.submodule {
677 options.bar = lib.mkOption {};
678 });
679 options.bar = lib.mkOption {};
680 };
681
682 module = { lib, ... }: {
683 options.foo = lib.mkOption {
684 type = lib.types.submodule submodule;
685 };
686 };
687
688 options = (evalModules {
689 modules = [ module ];
690 }).options;
691
692 locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
693 in map (o: o.loc) locs;
694 expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
695 };
696
697 testCartesianProductOfEmptySet = {
698 expr = cartesianProductOfSets {};
699 expected = [ {} ];
700 };
701
702 testCartesianProductOfOneSet = {
703 expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
704 expected = [ { a = 1; } { a = 2; } { a = 3; } ];
705 };
706
707 testCartesianProductOfTwoSets = {
708 expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
709 expected = [
710 { a = 1; b = 10; }
711 { a = 1; b = 20; }
712 ];
713 };
714
715 testCartesianProductOfTwoSetsWithOneEmpty = {
716 expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
717 expected = [ ];
718 };
719
720 testCartesianProductOfThreeSets = {
721 expr = cartesianProductOfSets {
722 a = [ 1 2 3 ];
723 b = [ 10 20 30 ];
724 c = [ 100 200 300 ];
725 };
726 expected = [
727 { a = 1; b = 10; c = 100; }
728 { a = 1; b = 10; c = 200; }
729 { a = 1; b = 10; c = 300; }
730
731 { a = 1; b = 20; c = 100; }
732 { a = 1; b = 20; c = 200; }
733 { a = 1; b = 20; c = 300; }
734
735 { a = 1; b = 30; c = 100; }
736 { a = 1; b = 30; c = 200; }
737 { a = 1; b = 30; c = 300; }
738
739 { a = 2; b = 10; c = 100; }
740 { a = 2; b = 10; c = 200; }
741 { a = 2; b = 10; c = 300; }
742
743 { a = 2; b = 20; c = 100; }
744 { a = 2; b = 20; c = 200; }
745 { a = 2; b = 20; c = 300; }
746
747 { a = 2; b = 30; c = 100; }
748 { a = 2; b = 30; c = 200; }
749 { a = 2; b = 30; c = 300; }
750
751 { a = 3; b = 10; c = 100; }
752 { a = 3; b = 10; c = 200; }
753 { a = 3; b = 10; c = 300; }
754
755 { a = 3; b = 20; c = 100; }
756 { a = 3; b = 20; c = 200; }
757 { a = 3; b = 20; c = 300; }
758
759 { a = 3; b = 30; c = 100; }
760 { a = 3; b = 30; c = 200; }
761 { a = 3; b = 30; c = 300; }
762 ];
763 };
764}