nixpkgs/pkgs/pkgs-lib/tests/formats.nix
benaryorg 8b2d86b982
pkgs.formats: toINIWithGlobalSection wrapper
The new format is based on the existing wrapper and generates an INI file with an unnamed global section at the top as is used by *stunnel* for instance.
Technically the INI format is a subset of this however testing, type checking, and API guarantees profit from two separate generators.

Co-authored-by: tim-tx <tim-tx@users.noreply.github.com>
Signed-off-by: benaryorg <binary@benary.org>
2024-02-12 17:58:48 +00:00

428 lines
10 KiB
Nix
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{ pkgs }:
let
inherit (pkgs) lib formats;
# merging allows us to add metadata to the input
# this makes error messages more readable during development
mergeInput = name: format: input:
format.type.merge [] [
{
# explicitly throw here to trigger the code path that prints the error message for users
value = lib.throwIfNot (format.type.check input) (builtins.trace input "definition does not pass the type's check function") input;
# inject the name
file = "format-test-${name}";
}
];
# run a diff between expected and real output
runDiff = name: drv: expected: pkgs.runCommand name {
passAsFile = ["expected"];
inherit expected drv;
} ''
if diff -u "$expectedPath" "$drv"; then
touch "$out"
else
echo
echo "Got different values than expected; diff above."
exit 1
fi
'';
# use this to check for proper serialization
# in practice you do not have to supply the name parameter as this one will be added by runBuildTests
shouldPass = { format, input, expected }: name: {
name = "pass-${name}";
path = runDiff "test-format-${name}" (format.generate "test-format-${name}" (mergeInput name format input)) expected;
};
# use this function to assert that a type check must fail
# in practice you do not have to supply the name parameter as this one will be added by runBuildTests
# note that as per 352e7d330a26 and 352e7d330a26 the type checking of attrsets and lists are not strict
# this means that the code below needs to properly merge the module type definition and also evaluate the (lazy) return value
shouldFail = { format, input }: name:
let
# trigger a deep type check using the module system
typeCheck = lib.modules.mergeDefinitions
[ "tests" name ]
format.type
[
{
file = "format-test-${name}";
value = input;
}
];
# actually use the return value to trigger the evaluation
eval = builtins.tryEval (typeCheck.mergedValue == input);
# the check failing is what we want, so don't do anything here
typeFails = pkgs.runCommand "test-format-${name}" {} "touch $out";
# bail with some verbose information in case the type check passes
typeSucceeds = pkgs.runCommand "test-format-${name}" {
passAsFile = [ "inputText" ];
testName = name;
# this will fail if the input contains functions as values
# however that should get caught by the type check already
inputText = builtins.toJSON input;
}
''
echo "Type check $testName passed when it shouldn't."
echo "The following data was used as input:"
echo
cat "$inputTextPath"
exit 1
'';
in {
name = "fail-${name}";
path = if eval.success then typeSucceeds else typeFails;
};
# this function creates a linkFarm for all the tests below such that the results are easily visible in the filesystem after a build
# the parameters are an attrset of name: test pairs where the name is automatically passed to the test
# the test therefore is an invocation of ShouldPass or shouldFail with the attrset parameters but *not* the name (which this adds for convenience)
runBuildTests = (lib.flip lib.pipe) [
(lib.mapAttrsToList (name: value: value name))
(pkgs.linkFarm "nixpkgs-pkgs-lib-format-tests")
];
in runBuildTests {
jsonAtoms = shouldPass {
format = formats.json {};
input = {
null = null;
false = false;
true = true;
int = 10;
float = 3.141;
str = "foo";
attrs.foo = null;
list = [ null null ];
path = ./formats.nix;
};
expected = ''
{
"attrs": {
"foo": null
},
"false": false,
"float": 3.141,
"int": 10,
"list": [
null,
null
],
"null": null,
"path": "${./formats.nix}",
"str": "foo",
"true": true
}
'';
};
yamlAtoms = shouldPass {
format = formats.yaml {};
input = {
null = null;
false = false;
true = true;
float = 3.141;
str = "foo";
attrs.foo = null;
list = [ null null ];
path = ./formats.nix;
};
expected = ''
attrs:
foo: null
'false': false
float: 3.141
list:
- null
- null
'null': null
path: ${./formats.nix}
str: foo
'true': true
'';
};
iniAtoms = shouldPass {
format = formats.ini {};
input = {
foo = {
bool = true;
int = 10;
float = 3.141;
str = "string";
};
};
expected = ''
[foo]
bool=true
float=3.141000
int=10
str=string
'';
};
iniInvalidAtom = shouldFail {
format = formats.ini {};
input = {
foo = {
function = _: 1;
};
};
};
iniDuplicateKeysWithoutList = shouldFail {
format = formats.ini {};
input = {
foo = {
bar = [ null true "test" 1.2 10 ];
baz = false;
qux = "qux";
};
};
};
iniDuplicateKeys = shouldPass {
format = formats.ini { listsAsDuplicateKeys = true; };
input = {
foo = {
bar = [ null true "test" 1.2 10 ];
baz = false;
qux = "qux";
};
};
expected = ''
[foo]
bar=null
bar=true
bar=test
bar=1.200000
bar=10
baz=false
qux=qux
'';
};
iniListToValue = shouldPass {
format = formats.ini { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
input = {
foo = {
bar = [ null true "test" 1.2 10 ];
baz = false;
qux = "qux";
};
};
expected = ''
[foo]
bar=null, true, test, 1.200000, 10
baz=false
qux=qux
'';
};
iniWithGlobalNoSections = shouldPass {
format = formats.iniWithGlobalSection {};
input = {};
expected = "";
};
iniWithGlobalOnlySections = shouldPass {
format = formats.iniWithGlobalSection {};
input = {
sections = {
foo = {
bar = "baz";
};
};
};
expected = ''
[foo]
bar=baz
'';
};
iniWithGlobalOnlyGlobal = shouldPass {
format = formats.iniWithGlobalSection {};
input = {
globalSection = {
bar = "baz";
};
};
expected = ''
bar=baz
'';
};
iniWithGlobalWrongSections = shouldFail {
format = formats.iniWithGlobalSection {};
input = {
foo = {};
};
};
iniWithGlobalEverything = shouldPass {
format = formats.iniWithGlobalSection {};
input = {
globalSection = {
bar = true;
};
sections = {
foo = {
bool = true;
int = 10;
float = 3.141;
str = "string";
};
};
};
expected = ''
bar=true
[foo]
bool=true
float=3.141000
int=10
str=string
'';
};
iniWithGlobalListToValue = shouldPass {
format = formats.iniWithGlobalSection { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
input = {
globalSection = {
bar = [ null true "test" 1.2 10 ];
baz = false;
qux = "qux";
};
sections = {
foo = {
bar = [ null true "test" 1.2 10 ];
baz = false;
qux = "qux";
};
};
};
expected = ''
bar=null, true, test, 1.200000, 10
baz=false
qux=qux
[foo]
bar=null, true, test, 1.200000, 10
baz=false
qux=qux
'';
};
keyValueAtoms = shouldPass {
format = formats.keyValue {};
input = {
bool = true;
int = 10;
float = 3.141;
str = "string";
};
expected = ''
bool=true
float=3.141000
int=10
str=string
'';
};
keyValueDuplicateKeys = shouldPass {
format = formats.keyValue { listsAsDuplicateKeys = true; };
input = {
bar = [ null true "test" 1.2 10 ];
baz = false;
qux = "qux";
};
expected = ''
bar=null
bar=true
bar=test
bar=1.200000
bar=10
baz=false
qux=qux
'';
};
keyValueListToValue = shouldPass {
format = formats.keyValue { listToValue = lib.concatMapStringsSep ", " (lib.generators.mkValueStringDefault {}); };
input = {
bar = [ null true "test" 1.2 10 ];
baz = false;
qux = "qux";
};
expected = ''
bar=null, true, test, 1.200000, 10
baz=false
qux=qux
'';
};
tomlAtoms = shouldPass {
format = formats.toml {};
input = {
false = false;
true = true;
int = 10;
float = 3.141;
str = "foo";
attrs.foo = "foo";
list = [ 1 2 ];
level1.level2.level3.level4 = "deep";
};
expected = ''
false = false
float = 3.141
int = 10
list = [1, 2]
str = "foo"
true = true
[attrs]
foo = "foo"
[level1.level2.level3]
level4 = "deep"
'';
};
# This test is responsible for
# 1. testing type coercions
# 2. providing a more readable example test
# Whereas java-properties/default.nix tests the low level escaping, etc.
javaProperties = shouldPass {
format = formats.javaProperties {};
input = {
floaty = 3.1415;
tautologies = true;
contradictions = false;
foo = "bar";
# # Disallowed at eval time, because it's ambiguous:
# # add to store or convert to string?
# root = /root;
"1" = 2;
package = pkgs.hello;
"ütf 8" = "dûh";
# NB: Some editors (vscode) show this _whole_ line in right-to-left order
"الجبر" = "أكثر من مجرد أرقام";
};
expected = ''
# Generated with Nix
1 = 2
contradictions = false
floaty = 3.141500
foo = bar
package = ${pkgs.hello}
tautologies = true
\u00fctf\ 8 = d\u00fbh
\u0627\u0644\u062c\u0628\u0631 = \u0623\u0643\u062b\u0631 \u0645\u0646 \u0645\u062c\u0631\u062f \u0623\u0631\u0642\u0627\u0645
'';
};
}