diff --git a/lib/tests/checkAndMergeCompat.nix b/lib/tests/checkAndMergeCompat.nix new file mode 100644 index 000000000000..dc6f26fbf917 --- /dev/null +++ b/lib/tests/checkAndMergeCompat.nix @@ -0,0 +1,379 @@ +{ + pkgs ? import ../.. { }, + currLibPath ? ../., + prevLibPath ? "${ + pkgs.fetchFromGitHub { + owner = "nixos"; + repo = "nixpkgs"; + # Parent commit of [#391544](https://github.com/NixOS/nixpkgs/pull/391544) + # Which was before the type.merge.v2 introduction + rev = "bcf94dd3f07189b7475d823c8d67d08b58289905"; + hash = "sha256-MuMiIY3MX5pFSOCvutmmRhV6RD0R3CG0Hmazkg8cMFI="; + } + }/lib", +}: +let + lib = import currLibPath; + + lib_with_merge_v2 = lib; + lib_with_merge_v1 = import prevLibPath; + + getMatrix = + { + getType ? null, + # If getType is set this is only used as test prefix + # And the type from getType is used + outerTypeName, + innerTypeName, + value, + testAttrs, + }: + let + evalModules.call_v1 = lib_with_merge_v1.evalModules; + evalModules.call_v2 = lib_with_merge_v2.evalModules; + outerTypes.outer_v1 = lib_with_merge_v1.types; + outerTypes.outer_v2 = lib_with_merge_v2.types; + innerTypes.inner_v1 = lib_with_merge_v1.types; + innerTypes.inner_v2 = lib_with_merge_v2.types; + in + lib.mapAttrs ( + _: evalModules: + lib.mapAttrs ( + _: outerTypes: + lib.mapAttrs (_: innerTypes: { + "test_${outerTypeName}_${innerTypeName}" = testAttrs // { + expr = + (evalModules { + modules = [ + (m: { + options.foo = m.lib.mkOption { + type = + if getType != null then + getType outerTypes innerTypes + else + outerTypes.${outerTypeName} innerTypes.${innerTypeName}; + default = value; + }; + }) + ]; + }).config.foo; + }; + }) innerTypes + ) outerTypes + ) evalModules; +in +{ + # AttrsOf string + attrsOf_str_ok = getMatrix { + outerTypeName = "attrsOf"; + innerTypeName = "str"; + value = { + bar = "test"; + }; + testAttrs = { + expected = { + bar = "test"; + }; + }; + }; + attrsOf_str_err_inner = getMatrix { + outerTypeName = "attrsOf"; + innerTypeName = "str"; + value = { + bar = 1; # not a string + }; + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = "A definition for option `foo.bar' is not of type `string'.*"; + }; + }; + }; + attrsOf_str_err_outer = getMatrix { + outerTypeName = "attrsOf"; + innerTypeName = "str"; + value = [ "foo" ]; # not an attrset + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = "A definition for option `foo' is not of type `attribute set of string'.*"; + }; + }; + }; + + # listOf string + listOf_str_ok = getMatrix { + outerTypeName = "listOf"; + innerTypeName = "str"; + value = [ + "foo" + "bar" + ]; + testAttrs = { + expected = [ + "foo" + "bar" + ]; + }; + }; + listOf_str_err_inner = getMatrix { + outerTypeName = "listOf"; + innerTypeName = "str"; + value = [ + "foo" + 1 + ]; # not a string + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = ''A definition for option `foo."\[definition 1-entry 2\]"' is not of type `string'.''; + }; + }; + }; + listOf_str_err_outer = getMatrix { + outerTypeName = "listOf"; + innerTypeName = "str"; + value = { + foo = 42; + }; # not a list + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = "A definition for option `foo' is not of type `list of string'.*"; + }; + }; + }; + + attrsOf_submodule_ok = getMatrix { + getType = + a: b: + a.attrsOf ( + b.submodule (m: { + options.nested = m.lib.mkOption { + type = m.lib.types.str; + }; + }) + ); + outerTypeName = "attrsOf"; + innerTypeName = "submodule"; + value = { + foo = { + nested = "test1"; + }; + bar = { + nested = "test2"; + }; + }; + testAttrs = { + expected = { + foo = { + nested = "test1"; + }; + bar = { + nested = "test2"; + }; + }; + }; + }; + attrsOf_submodule_err_inner = getMatrix { + outerTypeName = "attrsOf"; + innerTypeName = "submodule"; + getType = + a: b: + a.attrsOf ( + b.submodule (m: { + options.nested = m.lib.mkOption { + type = m.lib.types.str; + }; + }) + ); + value = { + foo = [ 1 ]; # not a submodule + bar = { + nested = "test2"; + }; + }; + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = "A definition for option `foo.foo' is not of type `submodule'.*"; + }; + }; + }; + attrsOf_submodule_err_outer = getMatrix { + outerTypeName = "attrsOf"; + innerTypeName = "submodule"; + getType = + a: b: + a.attrsOf ( + b.submodule (m: { + options.nested = m.lib.mkOption { + type = m.lib.types.str; + }; + }) + ); + value = [ 123 ]; # not an attrsOf + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = ''A definition for option `foo' is not of type `attribute set of \(submodule\).*''; + }; + }; + }; + + # either + either_str_attrsOf_ok = getMatrix { + outerTypeName = "either"; + innerTypeName = "str_or_attrsOf_str"; + + getType = a: b: a.either b.str (b.attrsOf a.str); + value = "string value"; + testAttrs = { + expected = "string value"; + }; + }; + either_str_attrsOf_err_1 = getMatrix { + outerTypeName = "either"; + innerTypeName = "str_or_attrsOf_str"; + + getType = a: b: a.either b.str (b.attrsOf a.str); + value = 1; + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = "A definition for option `foo' is not of type `string or attribute set of string'.*"; + }; + }; + }; + either_str_attrsOf_err_2 = getMatrix { + outerTypeName = "either"; + innerTypeName = "str_or_attrsOf_str"; + + getType = a: b: a.either b.str (b.attrsOf a.str); + value = { + bar = 1; # not a string + }; + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = "A definition for option `foo.bar' is not of type `string'.*"; + }; + }; + }; + + # Coereced to + coerce_attrsOf_str_to_listOf_str_run = getMatrix { + outerTypeName = "coercedTo"; + innerTypeName = "attrsOf_str->listOf_str"; + getType = a: b: a.coercedTo (b.attrsOf b.str) builtins.attrValues (b.listOf b.str); + value = { + bar = "test1"; # coerced to listOf string + foo = "test2"; # coerced to listOf string + }; + testAttrs = { + expected = [ + "test1" + "test2" + ]; + }; + }; + coerce_attrsOf_str_to_listOf_str_final = getMatrix { + outerTypeName = "coercedTo"; + innerTypeName = "attrsOf_str->listOf_str"; + getType = a: b: a.coercedTo (b.attrsOf b.str) (abort "This shouldnt run") (b.listOf b.str); + value = [ + "test1" + "test2" + ]; # already a listOf string + testAttrs = { + expected = [ + "test1" + "test2" + ]; # Order should be kept + }; + }; + coerce_attrsOf_str_to_listOf_err_coercer_input = getMatrix { + outerTypeName = "coercedTo"; + innerTypeName = "attrsOf_str->listOf_str"; + getType = a: b: a.coercedTo (b.attrsOf b.str) builtins.attrValues (b.listOf b.str); + value = [ + { } + { } + ]; # not coercible to listOf string, with the given coercer + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = ''A definition for option `foo."\[definition 1-entry 1\]"' is not of type `string'.*''; + }; + }; + }; + coerce_attrsOf_str_to_listOf_err_coercer_ouput = getMatrix { + outerTypeName = "coercedTo"; + innerTypeName = "attrsOf_str->listOf_str"; + getType = a: b: a.coercedTo (b.attrsOf b.str) builtins.attrValues (b.listOf b.str); + value = { + foo = { + bar = 1; + }; # coercer produces wrong type -> [ { bar = 1; } ] + }; + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = ''A definition for option `foo."\[definition 1-entry 1\]"' is not of type `string'.*''; + }; + }; + }; + coerce_str_to_int_coercer_ouput = getMatrix { + outerTypeName = "coercedTo"; + innerTypeName = "int->str"; + getType = a: b: a.coercedTo b.int builtins.toString a.str; + value = [ ]; + testAttrs = { + expectedError = { + type = "ThrownError"; + msg = ''A definition for option `foo' is not of type `string or signed integer convertible to it.*''; + }; + }; + }; + + # Submodule + submodule_with_ok = getMatrix { + outerTypeName = "submoduleWith"; + innerTypeName = "mixed_types"; + getType = + a: b: + a.submodule (m: { + options.attrs = m.lib.mkOption { + type = b.attrsOf b.str; + }; + options.list = m.lib.mkOption { + type = b.listOf b.str; + }; + options.either = m.lib.mkOption { + type = b.either a.str a.int; + }; + }); + value = { + attrs = { + foo = "bar"; + }; + list = [ + "foo" + "bar" + ]; + either = 123; # int + }; + testAttrs = { + expected = { + attrs = { + foo = "bar"; + }; + list = [ + "foo" + "bar" + ]; + either = 123; + }; + }; + }; +} diff --git a/lib/tests/nix-unit.nix b/lib/tests/nix-unit.nix new file mode 100644 index 000000000000..ff62aff4d96b --- /dev/null +++ b/lib/tests/nix-unit.nix @@ -0,0 +1,25 @@ +{ + pkgs ? import ../.. { }, +}: +let + prevNixpkgs = pkgs.fetchFromGitHub { + owner = "nixos"; + repo = "nixpkgs"; + # Parent commit of [#391544](https://github.com/NixOS/nixpkgs/pull/391544) + # Which was before the type.merge.v2 introduction + rev = "bcf94dd3f07189b7475d823c8d67d08b58289905"; + hash = "sha256-MuMiIY3MX5pFSOCvutmmRhV6RD0R3CG0Hmazkg8cMFI="; + }; +in +(pkgs.runCommand "lib-cross-eval-merge-v2" + { + nativeBuildInputs = [ pkgs.nix-unit ]; + } + '' + export HOME=$TMPDIR + nix-unit --eval-store "$HOME" ${./checkAndMergeCompat.nix} \ + --arg currLibPath "${../.}" \ + --arg prevLibPath "${prevNixpkgs}/lib" + mkdir $out + '' +) diff --git a/lib/tests/release.nix b/lib/tests/release.nix index 3eb62912ffc4..5a515a55dcdc 100644 --- a/lib/tests/release.nix +++ b/lib/tests/release.nix @@ -29,6 +29,9 @@ in pkgsBB.symlinkJoin { name = "nixpkgs-lib-tests"; paths = map testWithNix nixVersions ++ [ + (import ./nix-unit.nix { + inherit pkgs; + }) (import ./maintainers.nix { inherit pkgs; lib = import ../.;