3 inherit (pkgs) lib formats;
5 # merging allows us to add metadata to the input
6 # this makes error messages more readable during development
7 mergeInput = name: format: input:
10 # explicitly throw here to trigger the code path that prints the error message for users
11 value = lib.throwIfNot (format.type.check input) (builtins.trace input "definition does not pass the type's check function") input;
13 file = "format-test-${name}";
17 # run a diff between expected and real output
18 runDiff = name: drv: expected: pkgs.runCommand name {
19 passAsFile = ["expected"];
22 if diff -u "$expectedPath" "$drv"; then
26 echo "Got different values than expected; diff above."
31 # use this to check for proper serialization
32 # in practice you do not have to supply the name parameter as this one will be added by runBuildTests
33 shouldPass = { format, input, expected }: name: {
34 name = "pass-${name}";
35 path = runDiff "test-format-${name}" (format.generate "test-format-${name}" (mergeInput name format input)) expected;
38 # use this function to assert that a type check must fail
39 # in practice you do not have to supply the name parameter as this one will be added by runBuildTests
40 # note that as per 352e7d330a26 and 352e7d330a26 the type checking of attrsets and lists are not strict
41 # this means that the code below needs to properly merge the module type definition and also evaluate the (lazy) return value
42 shouldFail = { format, input }: name:
44 # trigger a deep type check using the module system
45 typeCheck = lib.modules.mergeDefinitions
50 file = "format-test-${name}";
54 # actually use the return value to trigger the evaluation
55 eval = builtins.tryEval (typeCheck.mergedValue == input);
56 # the check failing is what we want, so don't do anything here
57 typeFails = pkgs.runCommand "test-format-${name}" {} "touch $out";
58 # bail with some verbose information in case the type check passes
59 typeSucceeds = pkgs.runCommand "test-format-${name}" {
60 passAsFile = [ "inputText" ];
62 # this will fail if the input contains functions as values
63 # however that should get caught by the type check already
64 inputText = builtins.toJSON input;
67 echo "Type check $testName passed when it shouldn't."
68 echo "The following data was used as input:"
74 name = "fail-${name}";
75 path = if eval.success then typeSucceeds else typeFails;
78 # this function creates a linkFarm for all the tests below such that the results are easily visible in the filesystem after a build
79 # the parameters are an attrset of name: test pairs where the name is automatically passed to the test
80 # the test therefore is an invocation of ShouldPass or shouldFail with the attrset parameters but *not* the name (which this adds for convenience)
81 runBuildTests = (lib.flip lib.pipe) [
82 (lib.mapAttrsToList (name: value: value name))
83 (pkgs.linkFarm "nixpkgs-pkgs-lib-format-tests")
88 jsonAtoms = shouldPass {
89 format = formats.json {};
114 "path": "${./formats.nix}",
121 yaml_1_1Atoms = shouldPass {
122 format = formats.yaml {};
130 list = [ null null ];
131 path = ./formats.nix;
145 path: ${./formats.nix}
152 iniAtoms = shouldPass {
153 format = formats.ini {};
171 iniInvalidAtom = shouldFail {
172 format = formats.ini {};
180 iniDuplicateKeysWithoutList = shouldFail {
181 format = formats.ini {};
184 bar = [ null true "test" 1.2 10 ];
191 iniDuplicateKeys = shouldPass {
192 format = formats.ini { listsAsDuplicateKeys = true; };
195 bar = [ null true "test" 1.2 10 ];
212 iniListToValue = shouldPass {
213 format = formats.ini { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
216 bar = [ null true "test" 1.2 10 ];
223 bar=null, true, test, 1.200000, 10
229 iniCoercedDuplicateKeys = shouldPass rec {
230 format = formats.ini {
231 listsAsDuplicateKeys = true;
232 atomsCoercedToLists = true;
234 input = format.type.merge [ ] [
236 file = "format-test-inner-iniCoercedDuplicateKeys";
237 value = { foo = { bar = 1; }; };
240 file = "format-test-inner-iniCoercedDuplicateKeys";
241 value = { foo = { bar = 2; }; };
251 iniCoercedListToValue = shouldPass rec {
252 format = formats.ini {
253 listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { });
254 atomsCoercedToLists = true;
256 input = format.type.merge [ ] [
258 file = "format-test-inner-iniCoercedListToValue";
259 value = { foo = { bar = 1; }; };
262 file = "format-test-inner-iniCoercedListToValue";
263 value = { foo = { bar = 2; }; };
272 iniCoercedNoLists = shouldFail {
273 format = formats.ini { atomsCoercedToLists = true; };
281 iniNoCoercedNoLists = shouldFail {
282 format = formats.ini { atomsCoercedToLists = false; };
290 iniWithGlobalNoSections = shouldPass {
291 format = formats.iniWithGlobalSection {};
296 iniWithGlobalOnlySections = shouldPass {
297 format = formats.iniWithGlobalSection {};
311 iniWithGlobalOnlyGlobal = shouldPass {
312 format = formats.iniWithGlobalSection {};
324 iniWithGlobalWrongSections = shouldFail {
325 format = formats.iniWithGlobalSection {};
331 iniWithGlobalEverything = shouldPass {
332 format = formats.iniWithGlobalSection {};
357 iniWithGlobalListToValue = shouldPass {
358 format = formats.iniWithGlobalSection { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
361 bar = [ null true "test" 1.2 10 ];
367 bar = [ null true "test" 1.2 10 ];
374 bar=null, true, test, 1.200000, 10
379 bar=null, true, test, 1.200000, 10
385 iniWithGlobalCoercedDuplicateKeys = shouldPass rec {
386 format = formats.iniWithGlobalSection {
387 listsAsDuplicateKeys = true;
388 atomsCoercedToLists = true;
390 input = format.type.merge [ ] [
392 file = "format-test-inner-iniWithGlobalCoercedDuplicateKeys";
394 globalSection = { baz = 4; };
395 sections = { foo = { bar = 1; }; };
399 file = "format-test-inner-iniWithGlobalCoercedDuplicateKeys";
401 globalSection = { baz = 3; };
402 sections = { foo = { bar = 2; }; };
416 iniWithGlobalCoercedListToValue = shouldPass rec {
417 format = formats.iniWithGlobalSection {
418 listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { });
419 atomsCoercedToLists = true;
421 input = format.type.merge [ ] [
423 file = "format-test-inner-iniWithGlobalCoercedListToValue";
425 globalSection = { baz = 4; };
426 sections = { foo = { bar = 1; }; };
430 file = "format-test-inner-iniWithGlobalCoercedListToValue";
432 globalSection = { baz = 3; };
433 sections = { foo = { bar = 2; }; };
445 iniWithGlobalCoercedNoLists = shouldFail {
446 format = formats.iniWithGlobalSection { atomsCoercedToLists = true; };
448 globalSection = { baz = 4; };
453 iniWithGlobalNoCoercedNoLists = shouldFail {
454 format = formats.iniWithGlobalSection { atomsCoercedToLists = false; };
456 globalSection = { baz = 4; };
461 keyValueAtoms = shouldPass {
462 format = formats.keyValue {};
477 keyValueDuplicateKeys = shouldPass {
478 format = formats.keyValue { listsAsDuplicateKeys = true; };
480 bar = [ null true "test" 1.2 10 ];
495 keyValueListToValue = shouldPass {
496 format = formats.keyValue { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
498 bar = [ null true "test" 1.2 10 ];
503 bar=null, true, test, 1.200000, 10
509 tomlAtoms = shouldPass {
510 format = formats.toml {};
519 level1.level2.level3.level4 = "deep";
531 [level1.level2.level3]
536 # This test is responsible for
537 # 1. testing type coercions
538 # 2. providing a more readable example test
539 # Whereas java-properties/default.nix tests the low level escaping, etc.
540 javaProperties = shouldPass {
541 format = formats.javaProperties {};
545 contradictions = false;
547 # # Disallowed at eval time, because it's ambiguous:
548 # # add to store or convert to string?
551 package = pkgs.hello;
553 # NB: Some editors (vscode) show this _whole_ line in right-to-left order
554 "الجبر" = "أكثر من مجرد أرقام";
560 contradictions = false
563 package = ${pkgs.hello}
565 \u00fctf\ 8 = d\u00fbh
566 \u0627\u0644\u062c\u0628\u0631 = \u0623\u0643\u062b\u0631 \u0645\u0646 \u0645\u062c\u0631\u062f \u0623\u0631\u0642\u0627\u0645
570 phpAtoms = shouldPass rec {
571 format = formats.php { finalVariable = "config"; };
579 str_special = "foo\ntesthello'''";
581 list = [ null null ];
582 mixed = format.lib.mkMixedArray [ 10 3.141 ] {
586 raw = format.lib.mkRaw "random_function()";
590 declare(strict_types=1);
591 $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
592 testhello\'\'\'${"'"}, 'true' => true];
596 phpReturn = shouldPass {
597 format = formats.php { };
602 str_special = "foo\ntesthello'''";
607 declare(strict_types=1);
608 return ['attrs' => ['foo' => null], 'float' => 3.141000, 'int' => 10, 'str' => 'foo', 'str_special' => 'foo
609 testhello\'\'\'${"'"}];