Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at python-updates 926 lines 20 kB view raw
1{ pkgs }: 2let 3 inherit (pkgs) lib formats; 4 5 # merging allows us to add metadata to the input 6 # this makes error messages more readable during development 7 mergeInput = 8 name: format: input: 9 format.type.merge 10 [ ] 11 [ 12 { 13 # explicitly throw here to trigger the code path that prints the error message for users 14 value = 15 lib.throwIfNot (format.type.check input) 16 (builtins.trace input "definition does not pass the type's check function") 17 input; 18 # inject the name 19 file = "format-test-${name}"; 20 } 21 ]; 22 23 # run a diff between expected and real output 24 runDiff = 25 name: drv: expected: 26 pkgs.runCommand name 27 { 28 passAsFile = [ "expected" ]; 29 inherit expected drv; 30 } 31 '' 32 if diff -u "$expectedPath" "$drv"; then 33 touch "$out" 34 else 35 echo 36 echo "Got different values than expected; diff above." 37 exit 1 38 fi 39 ''; 40 41 # use this to check for proper serialization 42 # in practice you do not have to supply the name parameter as this one will be added by runBuildTests 43 shouldPass = 44 { 45 format, 46 input, 47 expected, 48 }: 49 name: { 50 name = "pass-${name}"; 51 path = runDiff "test-format-${name}" (format.generate "test-format-${name}" ( 52 mergeInput name format input 53 )) expected; 54 }; 55 56 # use this function to assert that a type check must fail 57 # in practice you do not have to supply the name parameter as this one will be added by runBuildTests 58 # note that as per 352e7d330a26 and 352e7d330a26 the type checking of attrsets and lists are not strict 59 # this means that the code below needs to properly merge the module type definition and also evaluate the (lazy) return value 60 shouldFail = 61 { format, input }: 62 name: 63 let 64 # trigger a deep type check using the module system 65 typeCheck = lib.modules.mergeDefinitions [ "tests" name ] format.type [ 66 { 67 file = "format-test-${name}"; 68 value = input; 69 } 70 ]; 71 # actually use the return value to trigger the evaluation 72 eval = builtins.tryEval (typeCheck.mergedValue == input); 73 # the check failing is what we want, so don't do anything here 74 typeFails = pkgs.runCommand "test-format-${name}" { } "touch $out"; 75 # bail with some verbose information in case the type check passes 76 typeSucceeds = 77 pkgs.runCommand "test-format-${name}" 78 { 79 passAsFile = [ "inputText" ]; 80 testName = name; 81 # this will fail if the input contains functions as values 82 # however that should get caught by the type check already 83 inputText = builtins.toJSON input; 84 } 85 '' 86 echo "Type check $testName passed when it shouldn't." 87 echo "The following data was used as input:" 88 echo 89 cat "$inputTextPath" 90 exit 1 91 ''; 92 in 93 { 94 name = "fail-${name}"; 95 path = if eval.success then typeSucceeds else typeFails; 96 }; 97 98 # this function creates a linkFarm for all the tests below such that the results are easily visible in the filesystem after a build 99 # the parameters are an attrset of name: test pairs where the name is automatically passed to the test 100 # the test therefore is an invocation of ShouldPass or shouldFail with the attrset parameters but *not* the name (which this adds for convenience) 101 runBuildTests = (lib.flip lib.pipe) [ 102 (lib.mapAttrsToList (name: value: value name)) 103 (pkgs.linkFarm "nixpkgs-pkgs-lib-format-tests") 104 ]; 105 106in 107runBuildTests { 108 109 jsonAtoms = shouldPass { 110 format = formats.json { }; 111 input = { 112 null = null; 113 false = false; 114 true = true; 115 int = 10; 116 float = 3.141; 117 str = "foo"; 118 attrs.foo = null; 119 list = [ 120 null 121 null 122 ]; 123 path = ./testfile; 124 }; 125 expected = '' 126 { 127 "attrs": { 128 "foo": null 129 }, 130 "false": false, 131 "float": 3.141, 132 "int": 10, 133 "list": [ 134 null, 135 null 136 ], 137 "null": null, 138 "path": "${./testfile}", 139 "str": "foo", 140 "true": true 141 } 142 ''; 143 }; 144 145 yaml_1_1Atoms = shouldPass { 146 format = formats.yaml_1_1 { }; 147 input = { 148 null = null; 149 false = false; 150 true = true; 151 float = 3.141; 152 str = "foo"; 153 attrs.foo = null; 154 list = [ 155 null 156 null 157 ]; 158 path = ./testfile; 159 no = "no"; 160 time = "22:30:00"; 161 }; 162 expected = '' 163 attrs: 164 foo: null 165 'false': false 166 float: 3.141 167 list: 168 - null 169 - null 170 'no': 'no' 171 'null': null 172 path: ${./testfile} 173 str: foo 174 time: '22:30:00' 175 'true': true 176 ''; 177 }; 178 179 yaml_1_2Atoms = shouldPass { 180 format = formats.yaml_1_2 { }; 181 input = { 182 null = null; 183 false = false; 184 true = true; 185 float = 3.141; 186 str = "foo"; 187 attrs.foo = null; 188 list = [ 189 null 190 null 191 ]; 192 path = ./testfile; 193 no = "no"; 194 time = "22:30:00"; 195 }; 196 expected = '' 197 attrs: 198 foo: null 199 'false': false 200 float: 3.141 201 list: 202 - null 203 - null 204 no: no 205 'null': null 206 path: ${./testfile} 207 str: foo 208 time: 22:30:00 209 'true': true 210 ''; 211 }; 212 213 iniAtoms = shouldPass { 214 format = formats.ini { }; 215 input = { 216 foo = { 217 bool = true; 218 int = 10; 219 float = 3.141; 220 str = "string"; 221 }; 222 }; 223 expected = '' 224 [foo] 225 bool=true 226 float=3.141000 227 int=10 228 str=string 229 ''; 230 }; 231 232 iniInvalidAtom = shouldFail { 233 format = formats.ini { }; 234 input = { 235 foo = { 236 function = _: 1; 237 }; 238 }; 239 }; 240 241 iniDuplicateKeysWithoutList = shouldFail { 242 format = formats.ini { }; 243 input = { 244 foo = { 245 bar = [ 246 null 247 true 248 "test" 249 1.2 250 10 251 ]; 252 baz = false; 253 qux = "qux"; 254 }; 255 }; 256 }; 257 258 iniDuplicateKeys = shouldPass { 259 format = formats.ini { listsAsDuplicateKeys = true; }; 260 input = { 261 foo = { 262 bar = [ 263 null 264 true 265 "test" 266 1.2 267 10 268 ]; 269 baz = false; 270 qux = "qux"; 271 }; 272 }; 273 expected = '' 274 [foo] 275 bar=null 276 bar=true 277 bar=test 278 bar=1.200000 279 bar=10 280 baz=false 281 qux=qux 282 ''; 283 }; 284 285 iniListToValue = shouldPass { 286 format = formats.ini { 287 listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { }); 288 }; 289 input = { 290 foo = { 291 bar = [ 292 null 293 true 294 "test" 295 1.2 296 10 297 ]; 298 baz = false; 299 qux = "qux"; 300 }; 301 }; 302 expected = '' 303 [foo] 304 bar=null, true, test, 1.200000, 10 305 baz=false 306 qux=qux 307 ''; 308 }; 309 310 iniCoercedDuplicateKeys = shouldPass rec { 311 format = formats.ini { 312 listsAsDuplicateKeys = true; 313 atomsCoercedToLists = true; 314 }; 315 input = 316 format.type.merge 317 [ ] 318 [ 319 { 320 file = "format-test-inner-iniCoercedDuplicateKeys"; 321 value = { 322 foo = { 323 bar = 1; 324 }; 325 }; 326 } 327 { 328 file = "format-test-inner-iniCoercedDuplicateKeys"; 329 value = { 330 foo = { 331 bar = 2; 332 }; 333 }; 334 } 335 ]; 336 expected = '' 337 [foo] 338 bar=1 339 bar=2 340 ''; 341 }; 342 343 iniCoercedListToValue = shouldPass rec { 344 format = formats.ini { 345 listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { }); 346 atomsCoercedToLists = true; 347 }; 348 input = 349 format.type.merge 350 [ ] 351 [ 352 { 353 file = "format-test-inner-iniCoercedListToValue"; 354 value = { 355 foo = { 356 bar = 1; 357 }; 358 }; 359 } 360 { 361 file = "format-test-inner-iniCoercedListToValue"; 362 value = { 363 foo = { 364 bar = 2; 365 }; 366 }; 367 } 368 ]; 369 expected = '' 370 [foo] 371 bar=1, 2 372 ''; 373 }; 374 375 iniCoercedNoLists = shouldFail { 376 format = formats.ini { atomsCoercedToLists = true; }; 377 input = { 378 foo = { 379 bar = 1; 380 }; 381 }; 382 }; 383 384 iniNoCoercedNoLists = shouldFail { 385 format = formats.ini { atomsCoercedToLists = false; }; 386 input = { 387 foo = { 388 bar = 1; 389 }; 390 }; 391 }; 392 393 iniWithGlobalNoSections = shouldPass { 394 format = formats.iniWithGlobalSection { }; 395 input = { }; 396 expected = ""; 397 }; 398 399 iniWithGlobalOnlySections = shouldPass { 400 format = formats.iniWithGlobalSection { }; 401 input = { 402 sections = { 403 foo = { 404 bar = "baz"; 405 }; 406 }; 407 }; 408 expected = '' 409 [foo] 410 bar=baz 411 ''; 412 }; 413 414 iniWithGlobalOnlyGlobal = shouldPass { 415 format = formats.iniWithGlobalSection { }; 416 input = { 417 globalSection = { 418 bar = "baz"; 419 }; 420 }; 421 expected = '' 422 bar=baz 423 424 ''; 425 }; 426 427 iniWithGlobalWrongSections = shouldFail { 428 format = formats.iniWithGlobalSection { }; 429 input = { 430 foo = { }; 431 }; 432 }; 433 434 iniWithGlobalEverything = shouldPass { 435 format = formats.iniWithGlobalSection { }; 436 input = { 437 globalSection = { 438 bar = true; 439 }; 440 sections = { 441 foo = { 442 bool = true; 443 int = 10; 444 float = 3.141; 445 str = "string"; 446 }; 447 }; 448 }; 449 expected = '' 450 bar=true 451 452 [foo] 453 bool=true 454 float=3.141000 455 int=10 456 str=string 457 ''; 458 }; 459 460 iniWithGlobalListToValue = shouldPass { 461 format = formats.iniWithGlobalSection { 462 listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { }); 463 }; 464 input = { 465 globalSection = { 466 bar = [ 467 null 468 true 469 "test" 470 1.2 471 10 472 ]; 473 baz = false; 474 qux = "qux"; 475 }; 476 sections = { 477 foo = { 478 bar = [ 479 null 480 true 481 "test" 482 1.2 483 10 484 ]; 485 baz = false; 486 qux = "qux"; 487 }; 488 }; 489 }; 490 expected = '' 491 bar=null, true, test, 1.200000, 10 492 baz=false 493 qux=qux 494 495 [foo] 496 bar=null, true, test, 1.200000, 10 497 baz=false 498 qux=qux 499 ''; 500 }; 501 502 iniWithGlobalCoercedDuplicateKeys = shouldPass rec { 503 format = formats.iniWithGlobalSection { 504 listsAsDuplicateKeys = true; 505 atomsCoercedToLists = true; 506 }; 507 input = 508 format.type.merge 509 [ ] 510 [ 511 { 512 file = "format-test-inner-iniWithGlobalCoercedDuplicateKeys"; 513 value = { 514 globalSection = { 515 baz = 4; 516 }; 517 sections = { 518 foo = { 519 bar = 1; 520 }; 521 }; 522 }; 523 } 524 { 525 file = "format-test-inner-iniWithGlobalCoercedDuplicateKeys"; 526 value = { 527 globalSection = { 528 baz = 3; 529 }; 530 sections = { 531 foo = { 532 bar = 2; 533 }; 534 }; 535 }; 536 } 537 ]; 538 expected = '' 539 baz=3 540 baz=4 541 542 [foo] 543 bar=2 544 bar=1 545 ''; 546 }; 547 548 iniWithGlobalCoercedListToValue = shouldPass rec { 549 format = formats.iniWithGlobalSection { 550 listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { }); 551 atomsCoercedToLists = true; 552 }; 553 input = 554 format.type.merge 555 [ ] 556 [ 557 { 558 file = "format-test-inner-iniWithGlobalCoercedListToValue"; 559 value = { 560 globalSection = { 561 baz = 4; 562 }; 563 sections = { 564 foo = { 565 bar = 1; 566 }; 567 }; 568 }; 569 } 570 { 571 file = "format-test-inner-iniWithGlobalCoercedListToValue"; 572 value = { 573 globalSection = { 574 baz = 3; 575 }; 576 sections = { 577 foo = { 578 bar = 2; 579 }; 580 }; 581 }; 582 } 583 ]; 584 expected = '' 585 baz=3, 4 586 587 [foo] 588 bar=2, 1 589 ''; 590 }; 591 592 iniWithGlobalCoercedNoLists = shouldFail { 593 format = formats.iniWithGlobalSection { atomsCoercedToLists = true; }; 594 input = { 595 globalSection = { 596 baz = 4; 597 }; 598 foo = { 599 bar = 1; 600 }; 601 }; 602 }; 603 604 iniWithGlobalNoCoercedNoLists = shouldFail { 605 format = formats.iniWithGlobalSection { atomsCoercedToLists = false; }; 606 input = { 607 globalSection = { 608 baz = 4; 609 }; 610 foo = { 611 bar = 1; 612 }; 613 }; 614 }; 615 616 keyValueAtoms = shouldPass { 617 format = formats.keyValue { }; 618 input = { 619 bool = true; 620 int = 10; 621 float = 3.141; 622 str = "string"; 623 }; 624 expected = '' 625 bool=true 626 float=3.141000 627 int=10 628 str=string 629 ''; 630 }; 631 632 keyValueDuplicateKeys = shouldPass { 633 format = formats.keyValue { listsAsDuplicateKeys = true; }; 634 input = { 635 bar = [ 636 null 637 true 638 "test" 639 1.2 640 10 641 ]; 642 baz = false; 643 qux = "qux"; 644 }; 645 expected = '' 646 bar=null 647 bar=true 648 bar=test 649 bar=1.200000 650 bar=10 651 baz=false 652 qux=qux 653 ''; 654 }; 655 656 keyValueListToValue = shouldPass { 657 format = formats.keyValue { 658 listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { }); 659 }; 660 input = { 661 bar = [ 662 null 663 true 664 "test" 665 1.2 666 10 667 ]; 668 baz = false; 669 qux = "qux"; 670 }; 671 expected = '' 672 bar=null, true, test, 1.200000, 10 673 baz=false 674 qux=qux 675 ''; 676 }; 677 678 tomlAtoms = shouldPass { 679 format = formats.toml { }; 680 input = { 681 false = false; 682 true = true; 683 int = 10; 684 float = 3.141; 685 str = "foo"; 686 attrs.foo = "foo"; 687 list = [ 688 1 689 2 690 ]; 691 level1.level2.level3.level4 = "deep"; 692 }; 693 expected = '' 694 false = false 695 float = 3.141 696 int = 10 697 list = [1, 2] 698 str = "foo" 699 true = true 700 701 [attrs] 702 foo = "foo" 703 704 [level1.level2.level3] 705 level4 = "deep" 706 ''; 707 }; 708 709 cdnAtoms = shouldPass { 710 format = formats.cdn { }; 711 input = { 712 null = null; 713 false = false; 714 true = true; 715 int = 10; 716 float = 3.141; 717 str = "foo"; 718 attrs.foo = null; 719 list = [ 720 1 721 null 722 ]; 723 path = ./testfile; 724 }; 725 expected = '' 726 attrs { 727 "foo": null 728 } 729 "false": false 730 "float": 3.141 731 "int": 10 732 list [ 733 1, 734 null 735 ] 736 "null": null 737 "path": "${./testfile}" 738 "str": "foo" 739 "true": true 740 ''; 741 }; 742 743 # This test is responsible for 744 # 1. testing type coercions 745 # 2. providing a more readable example test 746 # Whereas java-properties/default.nix tests the low level escaping, etc. 747 javaProperties = shouldPass { 748 format = formats.javaProperties { }; 749 input = { 750 floaty = 3.1415; 751 tautologies = true; 752 contradictions = false; 753 foo = "bar"; 754 # # Disallowed at eval time, because it's ambiguous: 755 # # add to store or convert to string? 756 # root = /root; 757 "1" = 2; 758 package = pkgs.hello; 759 "ütf 8" = "dûh"; 760 # NB: Some editors (vscode) show this _whole_ line in right-to-left order 761 "الجبر" = "أكثر من مجرد أرقام"; 762 }; 763 expected = '' 764 # Generated with Nix 765 766 1 = 2 767 contradictions = false 768 floaty = 3.141500 769 foo = bar 770 package = ${pkgs.hello} 771 tautologies = true 772 \u00fctf\ 8 = d\u00fbh 773 \u0627\u0644\u062c\u0628\u0631 = \u0623\u0643\u062b\u0631 \u0645\u0646 \u0645\u062c\u0631\u062f \u0623\u0631\u0642\u0627\u0645 774 ''; 775 }; 776 777 luaTable = shouldPass { 778 format = formats.lua { }; 779 input = { 780 null = null; 781 false = false; 782 true = true; 783 int = 10; 784 float = 3.141; 785 str = "foo"; 786 attrs.foo = null; 787 list = [ 788 null 789 null 790 ]; 791 path = ./testfile; 792 inline = lib.mkLuaInline "hello('world')"; 793 }; 794 expected = '' 795 return { 796 ["attrs"] = { 797 ["foo"] = nil, 798 }, 799 ["false"] = false, 800 ["float"] = 3.141, 801 ["inline"] = (hello("world")), 802 ["int"] = 10, 803 ["list"] = { 804 nil, 805 nil, 806 }, 807 ["null"] = nil, 808 ["path"] = "${./testfile}", 809 ["str"] = "foo", 810 ["true"] = true, 811 } 812 ''; 813 }; 814 815 luaBindings = shouldPass { 816 format = formats.lua { 817 asBindings = true; 818 }; 819 input = { 820 null = null; 821 _false = false; 822 _true = true; 823 int = 10; 824 float = 3.141; 825 str = "foo"; 826 attrs.foo = null; 827 list = [ 828 null 829 null 830 ]; 831 path = ./testfile; 832 inline = lib.mkLuaInline "hello('world')"; 833 }; 834 expected = '' 835 _false = false 836 _true = true 837 attrs = { 838 ["foo"] = nil, 839 } 840 float = 3.141 841 inline = (hello("world")) 842 int = 10 843 list = { 844 nil, 845 nil, 846 } 847 null = nil 848 path = "${./testfile}" 849 str = "foo" 850 ''; 851 }; 852 853 phpAtoms = shouldPass rec { 854 format = formats.php { finalVariable = "config"; }; 855 input = { 856 null = null; 857 false = false; 858 true = true; 859 int = 10; 860 float = 3.141; 861 str = "foo"; 862 str_special = "foo\ntesthello'''"; 863 attrs.foo = null; 864 list = [ 865 null 866 null 867 ]; 868 mixed = format.lib.mkMixedArray [ 10 3.141 ] { 869 str = "foo"; 870 attrs.foo = null; 871 }; 872 raw = format.lib.mkRaw "random_function()"; 873 }; 874 expected = '' 875 <?php 876 declare(strict_types=1); 877 $config = ['attrs' => ['foo' => null], 'false' => false, 'float' => 3.141000, 'int' => 10, 'list' => [null, null], 'mixed' => [10, 3.141000, 'attrs' => ['foo' => null], 'str' => 'foo'], 'null' => null, 'raw' => random_function(), 'str' => 'foo', 'str_special' => 'foo 878 testhello\'\'\'${"'"}, 'true' => true]; 879 ''; 880 }; 881 882 phpReturn = shouldPass { 883 format = formats.php { }; 884 input = { 885 int = 10; 886 float = 3.141; 887 str = "foo"; 888 str_special = "foo\ntesthello'''"; 889 attrs.foo = null; 890 }; 891 expected = '' 892 <?php 893 declare(strict_types=1); 894 return ['attrs' => ['foo' => null], 'float' => 3.141000, 'int' => 10, 'str' => 'foo', 'str_special' => 'foo 895 testhello\'\'\'${"'"}]; 896 ''; 897 }; 898 899 badgerfishToXmlGenerate = shouldPass { 900 format = formats.xml { }; 901 input = { 902 root = { 903 "@id" = "123"; 904 "@class" = "example"; 905 child1 = { 906 "@name" = "child1Name"; 907 "#text" = "text node"; 908 }; 909 child2 = { 910 grandchild = "This is a grandchild text node."; 911 }; 912 nulltest = null; 913 }; 914 }; 915 expected = '' 916 <?xml version="1.0" encoding="utf-8"?> 917 <root class="example" id="123"> 918 <child1 name="child1Name">text node</child1> 919 <child2> 920 <grandchild>This is a grandchild text node.</grandchild> 921 </child2> 922 <nulltest></nulltest> 923 </root> 924 ''; 925 }; 926}