forgejo-lts: 7.0.10 -> 7.0.11
[NixPkgs.git] / pkgs / pkgs-lib / tests / formats.nix
blob1c6f741353faa7cbfa452d5dfca2fd42e90b7927
1 { pkgs }:
2 let
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:
8     format.type.merge [] [
9       {
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;
12         # inject the name
13         file = "format-test-${name}";
14       }
15     ];
17   # run a diff between expected and real output
18   runDiff = name: drv: expected: pkgs.runCommand name {
19     passAsFile = ["expected"];
20     inherit expected drv;
21   } ''
22     if diff -u "$expectedPath" "$drv"; then
23       touch "$out"
24     else
25       echo
26       echo "Got different values than expected; diff above."
27       exit 1
28     fi
29   '';
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;
36   };
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:
43     let
44       # trigger a deep type check using the module system
45       typeCheck = lib.modules.mergeDefinitions
46         [ "tests" name ]
47         format.type
48         [
49           {
50             file = "format-test-${name}";
51             value = input;
52           }
53         ];
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" ];
61           testName = name;
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;
65         }
66         ''
67           echo "Type check $testName passed when it shouldn't."
68           echo "The following data was used as input:"
69           echo
70           cat "$inputTextPath"
71           exit 1
72         '';
73     in {
74       name = "fail-${name}";
75       path = if eval.success then typeSucceeds else typeFails;
76     };
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")
84   ];
86 in runBuildTests {
88   jsonAtoms = shouldPass {
89     format = formats.json {};
90     input = {
91       null = null;
92       false = false;
93       true = true;
94       int = 10;
95       float = 3.141;
96       str = "foo";
97       attrs.foo = null;
98       list = [ null null ];
99       path = ./formats.nix;
100     };
101     expected = ''
102       {
103         "attrs": {
104           "foo": null
105         },
106         "false": false,
107         "float": 3.141,
108         "int": 10,
109         "list": [
110           null,
111           null
112         ],
113         "null": null,
114         "path": "${./formats.nix}",
115         "str": "foo",
116         "true": true
117       }
118     '';
119   };
121   yaml_1_1Atoms = shouldPass {
122     format = formats.yaml {};
123     input = {
124       null = null;
125       false = false;
126       true = true;
127       float = 3.141;
128       str = "foo";
129       attrs.foo = null;
130       list = [ null null ];
131       path = ./formats.nix;
132       no = "no";
133       time = "22:30:00";
134     };
135     expected = ''
136       attrs:
137         foo: null
138       'false': false
139       float: 3.141
140       list:
141       - null
142       - null
143       'no': 'no'
144       'null': null
145       path: ${./formats.nix}
146       str: foo
147       time: '22:30:00'
148       'true': true
149     '';
150   };
152   iniAtoms = shouldPass {
153     format = formats.ini {};
154     input = {
155       foo = {
156         bool = true;
157         int = 10;
158         float = 3.141;
159         str = "string";
160       };
161     };
162     expected = ''
163       [foo]
164       bool=true
165       float=3.141000
166       int=10
167       str=string
168     '';
169   };
171   iniInvalidAtom = shouldFail {
172     format = formats.ini {};
173     input = {
174       foo = {
175         function = _: 1;
176       };
177     };
178   };
180   iniDuplicateKeysWithoutList = shouldFail {
181     format = formats.ini {};
182     input = {
183       foo = {
184         bar = [ null true "test" 1.2 10 ];
185         baz = false;
186         qux = "qux";
187       };
188     };
189   };
191   iniDuplicateKeys = shouldPass {
192     format = formats.ini { listsAsDuplicateKeys = true; };
193     input = {
194       foo = {
195         bar = [ null true "test" 1.2 10 ];
196         baz = false;
197         qux = "qux";
198       };
199     };
200     expected = ''
201       [foo]
202       bar=null
203       bar=true
204       bar=test
205       bar=1.200000
206       bar=10
207       baz=false
208       qux=qux
209     '';
210   };
212   iniListToValue = shouldPass {
213     format = formats.ini { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
214     input = {
215       foo = {
216         bar = [ null true "test" 1.2 10 ];
217         baz = false;
218         qux = "qux";
219       };
220     };
221     expected = ''
222       [foo]
223       bar=null, true, test, 1.200000, 10
224       baz=false
225       qux=qux
226     '';
227   };
229   iniCoercedDuplicateKeys = shouldPass rec {
230     format = formats.ini {
231       listsAsDuplicateKeys = true;
232       atomsCoercedToLists = true;
233     };
234     input = format.type.merge [ ] [
235       {
236         file = "format-test-inner-iniCoercedDuplicateKeys";
237         value = { foo = { bar = 1; }; };
238       }
239       {
240         file = "format-test-inner-iniCoercedDuplicateKeys";
241         value = { foo = { bar = 2; }; };
242       }
243     ];
244     expected = ''
245       [foo]
246       bar=1
247       bar=2
248     '';
249   };
251   iniCoercedListToValue = shouldPass rec {
252     format = formats.ini {
253       listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { });
254       atomsCoercedToLists = true;
255     };
256     input = format.type.merge [ ] [
257       {
258         file = "format-test-inner-iniCoercedListToValue";
259         value = { foo = { bar = 1; }; };
260       }
261       {
262         file = "format-test-inner-iniCoercedListToValue";
263         value = { foo = { bar = 2; }; };
264       }
265     ];
266     expected = ''
267       [foo]
268       bar=1, 2
269     '';
270   };
272   iniCoercedNoLists = shouldFail {
273     format = formats.ini { atomsCoercedToLists = true; };
274     input = {
275       foo = {
276         bar = 1;
277       };
278     };
279   };
281   iniNoCoercedNoLists = shouldFail {
282     format = formats.ini { atomsCoercedToLists = false; };
283     input = {
284       foo = {
285         bar = 1;
286       };
287     };
288   };
290   iniWithGlobalNoSections = shouldPass {
291     format = formats.iniWithGlobalSection {};
292     input = {};
293     expected = "";
294   };
296   iniWithGlobalOnlySections = shouldPass {
297     format = formats.iniWithGlobalSection {};
298     input = {
299       sections = {
300         foo = {
301           bar = "baz";
302         };
303       };
304     };
305     expected = ''
306       [foo]
307       bar=baz
308     '';
309   };
311   iniWithGlobalOnlyGlobal = shouldPass {
312     format = formats.iniWithGlobalSection {};
313     input = {
314       globalSection = {
315         bar = "baz";
316       };
317     };
318     expected = ''
319       bar=baz
321     '';
322   };
324   iniWithGlobalWrongSections = shouldFail {
325     format = formats.iniWithGlobalSection {};
326     input = {
327       foo = {};
328     };
329   };
331   iniWithGlobalEverything = shouldPass {
332     format = formats.iniWithGlobalSection {};
333     input = {
334       globalSection = {
335         bar = true;
336       };
337       sections = {
338         foo = {
339           bool = true;
340           int = 10;
341           float = 3.141;
342           str = "string";
343         };
344       };
345     };
346     expected = ''
347       bar=true
349       [foo]
350       bool=true
351       float=3.141000
352       int=10
353       str=string
354     '';
355   };
357   iniWithGlobalListToValue = shouldPass {
358     format = formats.iniWithGlobalSection { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
359     input = {
360       globalSection = {
361         bar = [ null true "test" 1.2 10 ];
362         baz = false;
363         qux = "qux";
364       };
365       sections = {
366         foo = {
367           bar = [ null true "test" 1.2 10 ];
368           baz = false;
369           qux = "qux";
370         };
371       };
372     };
373     expected = ''
374       bar=null, true, test, 1.200000, 10
375       baz=false
376       qux=qux
378       [foo]
379       bar=null, true, test, 1.200000, 10
380       baz=false
381       qux=qux
382     '';
383   };
385   iniWithGlobalCoercedDuplicateKeys = shouldPass rec {
386     format = formats.iniWithGlobalSection {
387       listsAsDuplicateKeys = true;
388       atomsCoercedToLists = true;
389     };
390     input = format.type.merge [ ] [
391       {
392         file = "format-test-inner-iniWithGlobalCoercedDuplicateKeys";
393         value = {
394           globalSection = { baz = 4; };
395           sections = { foo = { bar = 1; }; };
396         };
397       }
398       {
399         file = "format-test-inner-iniWithGlobalCoercedDuplicateKeys";
400         value = {
401           globalSection = { baz = 3; };
402           sections = { foo = { bar = 2; }; };
403         };
404       }
405     ];
406     expected = ''
407       baz=3
408       baz=4
410       [foo]
411       bar=2
412       bar=1
413     '';
414   };
416   iniWithGlobalCoercedListToValue = shouldPass rec {
417     format = formats.iniWithGlobalSection {
418       listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault { });
419       atomsCoercedToLists = true;
420     };
421     input = format.type.merge [ ] [
422       {
423         file = "format-test-inner-iniWithGlobalCoercedListToValue";
424         value = {
425           globalSection = { baz = 4; };
426           sections = { foo = { bar = 1; }; };
427         };
428       }
429       {
430         file = "format-test-inner-iniWithGlobalCoercedListToValue";
431         value = {
432           globalSection = { baz = 3; };
433           sections = { foo = { bar = 2; }; };
434         };
435       }
436     ];
437     expected = ''
438       baz=3, 4
440       [foo]
441       bar=2, 1
442     '';
443   };
445   iniWithGlobalCoercedNoLists = shouldFail {
446     format = formats.iniWithGlobalSection { atomsCoercedToLists = true; };
447     input = {
448       globalSection = { baz = 4; };
449       foo = { bar = 1; };
450     };
451   };
453   iniWithGlobalNoCoercedNoLists = shouldFail {
454     format = formats.iniWithGlobalSection { atomsCoercedToLists = false; };
455     input = {
456       globalSection = { baz = 4; };
457       foo = { bar = 1; };
458     };
459   };
461   keyValueAtoms = shouldPass {
462     format = formats.keyValue {};
463     input = {
464       bool = true;
465       int = 10;
466       float = 3.141;
467       str = "string";
468     };
469     expected = ''
470       bool=true
471       float=3.141000
472       int=10
473       str=string
474     '';
475   };
477   keyValueDuplicateKeys = shouldPass {
478     format = formats.keyValue { listsAsDuplicateKeys = true; };
479     input = {
480       bar = [ null true "test" 1.2 10 ];
481       baz = false;
482       qux = "qux";
483     };
484     expected = ''
485       bar=null
486       bar=true
487       bar=test
488       bar=1.200000
489       bar=10
490       baz=false
491       qux=qux
492     '';
493   };
495   keyValueListToValue = shouldPass {
496     format = formats.keyValue { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
497     input = {
498       bar = [ null true "test" 1.2 10 ];
499       baz = false;
500       qux = "qux";
501     };
502     expected = ''
503       bar=null, true, test, 1.200000, 10
504       baz=false
505       qux=qux
506     '';
507   };
509   tomlAtoms = shouldPass {
510     format = formats.toml {};
511     input = {
512       false = false;
513       true = true;
514       int = 10;
515       float = 3.141;
516       str = "foo";
517       attrs.foo = "foo";
518       list = [ 1 2 ];
519       level1.level2.level3.level4 = "deep";
520     };
521     expected = ''
522       false = false
523       float = 3.141
524       int = 10
525       list = [1, 2]
526       str = "foo"
527       true = true
528       [attrs]
529       foo = "foo"
531       [level1.level2.level3]
532       level4 = "deep"
533     '';
534   };
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 {};
542     input = {
543       floaty = 3.1415;
544       tautologies = true;
545       contradictions = false;
546       foo = "bar";
547       # # Disallowed at eval time, because it's ambiguous:
548       # # add to store or convert to string?
549       # root = /root;
550       "1" = 2;
551       package = pkgs.hello;
552       "ütf 8" = "dûh";
553       # NB: Some editors (vscode) show this _whole_ line in right-to-left order
554       "الجبر" = "أكثر من مجرد أرقام";
555     };
556     expected = ''
557       # Generated with Nix
559       1 = 2
560       contradictions = false
561       floaty = 3.141500
562       foo = bar
563       package = ${pkgs.hello}
564       tautologies = true
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
567     '';
568   };
570   phpAtoms = shouldPass rec {
571     format = formats.php { finalVariable = "config"; };
572     input = {
573       null = null;
574       false = false;
575       true = true;
576       int = 10;
577       float = 3.141;
578       str = "foo";
579       str_special = "foo\ntesthello'''";
580       attrs.foo = null;
581       list = [ null null ];
582       mixed = format.lib.mkMixedArray [ 10 3.141 ] {
583         str = "foo";
584         attrs.foo = null;
585       };
586       raw = format.lib.mkRaw "random_function()";
587     };
588     expected = ''
589       <?php
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];
593     '';
594   };
596   phpReturn = shouldPass {
597     format = formats.php { };
598     input = {
599       int = 10;
600       float = 3.141;
601       str = "foo";
602       str_special = "foo\ntesthello'''";
603       attrs.foo = null;
604     };
605     expected = ''
606       <?php
607       declare(strict_types=1);
608       return ['attrs' => ['foo' => null], 'float' => 3.141000, 'int' => 10, 'str' => 'foo', 'str_special' => 'foo
609       testhello\'\'\'${"'"}];
610     '';
611   };