aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/check-eval.nix7
-rwxr-xr-xtests/filesystem.sh84
-rw-r--r--tests/maintainer-module.nix32
-rw-r--r--tests/maintainers.nix53
-rw-r--r--tests/misc.nix1657
-rwxr-xr-xtests/modules.sh408
-rw-r--r--tests/modules/adhoc-freeformType-survives-type-merge.nix14
-rw-r--r--tests/modules/alias-with-priority-can-override.nix55
-rw-r--r--tests/modules/alias-with-priority.nix55
-rw-r--r--tests/modules/attrsOf-conditional-check.nix7
-rw-r--r--tests/modules/attrsOf-lazy-check.nix7
-rw-r--r--tests/modules/class-check.nix76
-rw-r--r--tests/modules/declare-attrsOf.nix13
-rw-r--r--tests/modules/declare-attrsOfSub-any-enable.nix29
-rw-r--r--tests/modules/declare-bare-submodule-deep-option-duplicate.nix10
-rw-r--r--tests/modules/declare-bare-submodule-deep-option.nix10
-rw-r--r--tests/modules/declare-bare-submodule-nested-option.nix19
-rw-r--r--tests/modules/declare-bare-submodule.nix18
-rw-r--r--tests/modules/declare-coerced-value-unsound.nix10
-rw-r--r--tests/modules/declare-coerced-value.nix10
-rw-r--r--tests/modules/declare-either.nix5
-rw-r--r--tests/modules/declare-enable-nested.nix14
-rw-r--r--tests/modules/declare-enable.nix14
-rw-r--r--tests/modules/declare-int-between-value.nix9
-rw-r--r--tests/modules/declare-int-positive-value-nested.nix9
-rw-r--r--tests/modules/declare-int-positive-value.nix9
-rw-r--r--tests/modules/declare-int-unsigned-value.nix9
-rw-r--r--tests/modules/declare-lazyAttrsOf.nix6
-rw-r--r--tests/modules/declare-mkPackageOption.nix19
-rw-r--r--tests/modules/declare-oneOf.nix9
-rw-r--r--tests/modules/declare-set.nix12
-rw-r--r--tests/modules/declare-submodule-via-evalModules.nix28
-rw-r--r--tests/modules/declare-submoduleWith-modules.nix28
-rw-r--r--tests/modules/declare-submoduleWith-noshorthand.nix13
-rw-r--r--tests/modules/declare-submoduleWith-path.nix12
-rw-r--r--tests/modules/declare-submoduleWith-shorthand.nix14
-rw-r--r--tests/modules/declare-submoduleWith-special.nix17
-rw-r--r--tests/modules/declare-variants.nix9
-rw-r--r--tests/modules/default.nix8
-rw-r--r--tests/modules/deferred-module-error.nix20
-rw-r--r--tests/modules/deferred-module.nix58
-rw-r--r--tests/modules/define-_module-args-custom.nix7
-rw-r--r--tests/modules/define-attrsOfSub-bar-enable.nix3
-rw-r--r--tests/modules/define-attrsOfSub-bar.nix3
-rw-r--r--tests/modules/define-attrsOfSub-foo-enable-force.nix5
-rw-r--r--tests/modules/define-attrsOfSub-foo-enable-if.nix5
-rw-r--r--tests/modules/define-attrsOfSub-foo-enable.nix3
-rw-r--r--tests/modules/define-attrsOfSub-foo-force-enable.nix7
-rw-r--r--tests/modules/define-attrsOfSub-foo-if-enable.nix7
-rw-r--r--tests/modules/define-attrsOfSub-foo.nix3
-rw-r--r--tests/modules/define-attrsOfSub-force-foo-enable.nix7
-rw-r--r--tests/modules/define-attrsOfSub-if-foo-enable.nix7
-rw-r--r--tests/modules/define-bare-submodule-values.nix4
-rw-r--r--tests/modules/define-enable-abort.nix3
-rw-r--r--tests/modules/define-enable-force.nix5
-rw-r--r--tests/modules/define-enable-throw.nix3
-rw-r--r--tests/modules/define-enable-with-custom-arg.nix7
-rw-r--r--tests/modules/define-enable-with-top-level-mkIf.nix5
-rw-r--r--tests/modules/define-enable.nix3
-rw-r--r--tests/modules/define-force-attrsOfSub-foo-enable.nix5
-rw-r--r--tests/modules/define-force-enable.nix5
-rw-r--r--tests/modules/define-freeform-keywords-shorthand.nix15
-rw-r--r--tests/modules/define-if-attrsOfSub-foo-enable.nix5
-rw-r--r--tests/modules/define-module-check.nix3
-rw-r--r--tests/modules/define-option-dependently-nested.nix16
-rw-r--r--tests/modules/define-option-dependently.nix16
-rw-r--r--tests/modules/define-settingsDict-a-is-b.nix3
-rw-r--r--tests/modules/define-shorthandOnlyDefinesConfig-true.nix1
-rw-r--r--tests/modules/define-submoduleWith-noshorthand.nix3
-rw-r--r--tests/modules/define-submoduleWith-shorthand.nix3
-rw-r--r--tests/modules/define-value-int-negative.nix3
-rw-r--r--tests/modules/define-value-int-positive.nix3
-rw-r--r--tests/modules/define-value-int-zero.nix3
-rw-r--r--tests/modules/define-value-list.nix3
-rw-r--r--tests/modules/define-value-string-arbitrary.nix3
-rw-r--r--tests/modules/define-value-string-bigint.nix3
-rw-r--r--tests/modules/define-value-string-properties.nix12
-rw-r--r--tests/modules/define-value-string.nix3
-rw-r--r--tests/modules/define-variant.nix22
-rw-r--r--tests/modules/disable-declare-enable.nix5
-rw-r--r--tests/modules/disable-define-enable-string-path.nix5
-rw-r--r--tests/modules/disable-define-enable.nix5
-rw-r--r--tests/modules/disable-enable-modules.nix5
-rw-r--r--tests/modules/disable-module-bad-key.nix16
-rw-r--r--tests/modules/disable-module-with-key.nix34
-rw-r--r--tests/modules/disable-module-with-toString-key.nix34
-rw-r--r--tests/modules/disable-recursive/bar.nix5
-rw-r--r--tests/modules/disable-recursive/disable-bar.nix7
-rw-r--r--tests/modules/disable-recursive/disable-foo.nix7
-rw-r--r--tests/modules/disable-recursive/foo.nix5
-rw-r--r--tests/modules/disable-recursive/main.nix8
-rw-r--r--tests/modules/doRename-basic.nix11
-rw-r--r--tests/modules/doRename-warnings.nix14
-rw-r--r--tests/modules/emptyValues.nix36
-rw-r--r--tests/modules/extendModules-168767-imports.nix41
-rw-r--r--tests/modules/freeform-attrsOf.nix3
-rw-r--r--tests/modules/freeform-lazyAttrsOf.nix3
-rw-r--r--tests/modules/freeform-nested.nix14
-rw-r--r--tests/modules/freeform-str-dep-unstr.nix8
-rw-r--r--tests/modules/freeform-submodules.nix22
-rw-r--r--tests/modules/freeform-unstr-dep-str.nix8
-rw-r--r--tests/modules/functionTo/list-order.nix25
-rw-r--r--tests/modules/functionTo/merging-attrs.nix27
-rw-r--r--tests/modules/functionTo/merging-list.nix24
-rw-r--r--tests/modules/functionTo/submodule-options.nix61
-rw-r--r--tests/modules/functionTo/trivial.nix17
-rw-r--r--tests/modules/functionTo/wrong-type.nix18
-rw-r--r--tests/modules/import-configuration.nix12
-rw-r--r--tests/modules/import-custom-arg.nix6
-rw-r--r--tests/modules/import-from-store.nix11
-rw-r--r--tests/modules/merge-module-with-key.nix49
-rw-r--r--tests/modules/module-class-is-darwin.nix4
-rw-r--r--tests/modules/module-class-is-nixos.nix4
-rw-r--r--tests/modules/module-imports-_type-check.nix3
-rw-r--r--tests/modules/optionTypeFile.nix28
-rw-r--r--tests/modules/optionTypeMerging.nix27
-rw-r--r--tests/modules/raw.nix30
-rw-r--r--tests/modules/shorthand-meta.nix19
-rw-r--r--tests/modules/submoduleFiles.nix21
-rw-r--r--tests/modules/types-anything/attrs-coercible.nix12
-rw-r--r--tests/modules/types-anything/equal-atoms.nix26
-rw-r--r--tests/modules/types-anything/functions.nix23
-rw-r--r--tests/modules/types-anything/lists.nix16
-rw-r--r--tests/modules/types-anything/mk-mods.nix44
-rw-r--r--tests/modules/types-anything/nested-attrs.nix22
-rw-r--r--tests/release.nix64
-rwxr-xr-xtests/sources.sh66
-rw-r--r--tests/systems.nix42
-rw-r--r--tests/teams.nix50
-rw-r--r--tests/test-to-plist-expected.plist46
130 files changed, 4206 insertions, 0 deletions
diff --git a/tests/check-eval.nix b/tests/check-eval.nix
new file mode 100644
index 000000000..8bd7b605a
--- /dev/null
+++ b/tests/check-eval.nix
@@ -0,0 +1,7 @@
+# Throws an error if any of our lib tests fail.
+
+let tests = [ "misc" "systems" ];
+ all = builtins.concatLists (map (f: import (./. + "/${f}.nix")) tests);
+in if all == []
+ then null
+ else throw (builtins.toJSON all)
diff --git a/tests/filesystem.sh b/tests/filesystem.sh
new file mode 100755
index 000000000..cfd333d00
--- /dev/null
+++ b/tests/filesystem.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+
+# Tests lib/filesystem.nix
+# Run:
+# [nixpkgs]$ lib/tests/filesystem.sh
+# or:
+# [nixpkgs]$ nix-build lib/tests/release.nix
+
+set -euo pipefail
+shopt -s inherit_errexit
+
+# Use
+# || die
+die() {
+ echo >&2 "test case failed: " "$@"
+ exit 1
+}
+
+if test -n "${TEST_LIB:-}"; then
+ NIX_PATH=nixpkgs="$(dirname "$TEST_LIB")"
+else
+ NIX_PATH=nixpkgs="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.."; pwd)"
+fi
+export NIX_PATH
+
+work="$(mktemp -d)"
+clean_up() {
+ rm -rf "$work"
+}
+trap clean_up EXIT
+cd "$work"
+
+mkdir directory
+touch regular
+ln -s target symlink
+mkfifo fifo
+
+expectSuccess() {
+ local expr=$1
+ local expectedResultRegex=$2
+ if ! result=$(nix-instantiate --eval --strict --json \
+ --expr "with (import <nixpkgs/lib>).filesystem; $expr"); then
+ die "$expr failed to evaluate, but it was expected to succeed"
+ fi
+ if [[ ! "$result" =~ $expectedResultRegex ]]; then
+ die "$expr == $result, but $expectedResultRegex was expected"
+ fi
+}
+
+expectFailure() {
+ local expr=$1
+ local expectedErrorRegex=$2
+ if result=$(nix-instantiate --eval --strict --json 2>"$work/stderr" \
+ --expr "with (import <nixpkgs/lib>).filesystem; $expr"); then
+ die "$expr evaluated successfully to $result, but it was expected to fail"
+ fi
+ if [[ ! "$(<"$work/stderr")" =~ $expectedErrorRegex ]]; then
+ die "Error was $(<"$work/stderr"), but $expectedErrorRegex was expected"
+ fi
+}
+
+expectSuccess "pathType /." '"directory"'
+expectSuccess "pathType $PWD/directory" '"directory"'
+expectSuccess "pathType $PWD/regular" '"regular"'
+expectSuccess "pathType $PWD/symlink" '"symlink"'
+expectSuccess "pathType $PWD/fifo" '"unknown"'
+# Different errors depending on whether the builtins.readFilePath primop is available or not
+expectFailure "pathType $PWD/non-existent" "error: (evaluation aborted with the following error message: 'lib.filesystem.pathType: Path $PWD/non-existent does not exist.'|getting status of '$PWD/non-existent': No such file or directory)"
+
+expectSuccess "pathIsDirectory /." "true"
+expectSuccess "pathIsDirectory $PWD/directory" "true"
+expectSuccess "pathIsDirectory $PWD/regular" "false"
+expectSuccess "pathIsDirectory $PWD/symlink" "false"
+expectSuccess "pathIsDirectory $PWD/fifo" "false"
+expectSuccess "pathIsDirectory $PWD/non-existent" "false"
+
+expectSuccess "pathIsRegularFile /." "false"
+expectSuccess "pathIsRegularFile $PWD/directory" "false"
+expectSuccess "pathIsRegularFile $PWD/regular" "true"
+expectSuccess "pathIsRegularFile $PWD/symlink" "false"
+expectSuccess "pathIsRegularFile $PWD/fifo" "false"
+expectSuccess "pathIsRegularFile $PWD/non-existent" "false"
+
+echo >&2 tests ok
diff --git a/tests/maintainer-module.nix b/tests/maintainer-module.nix
new file mode 100644
index 000000000..afa12587a
--- /dev/null
+++ b/tests/maintainer-module.nix
@@ -0,0 +1,32 @@
+{ lib, ... }:
+let
+ inherit (lib) types;
+in {
+ options = {
+ name = lib.mkOption {
+ type = types.str;
+ };
+ email = lib.mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ };
+ matrix = lib.mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ };
+ github = lib.mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ };
+ githubId = lib.mkOption {
+ type = types.nullOr types.ints.unsigned;
+ default = null;
+ };
+ keys = lib.mkOption {
+ type = types.listOf (types.submodule {
+ options.fingerprint = lib.mkOption { type = types.str; };
+ });
+ default = [];
+ };
+ };
+}
diff --git a/tests/maintainers.nix b/tests/maintainers.nix
new file mode 100644
index 000000000..be1c8aaa8
--- /dev/null
+++ b/tests/maintainers.nix
@@ -0,0 +1,53 @@
+# to run these tests (and the others)
+# nix-build nixpkgs/lib/tests/release.nix
+# These tests should stay in sync with the comment in maintainers/maintainers-list.nix
+{ # The pkgs used for dependencies for the testing itself
+ pkgs ? import ../.. {}
+, lib ? pkgs.lib
+}:
+
+let
+ checkMaintainer = handle: uncheckedAttrs:
+ let
+ prefix = [ "lib" "maintainers" handle ];
+ checkedAttrs = (lib.modules.evalModules {
+ inherit prefix;
+ modules = [
+ ./maintainer-module.nix
+ {
+ _file = toString ../../maintainers/maintainer-list.nix;
+ config = uncheckedAttrs;
+ }
+ ];
+ }).config;
+
+ checks = lib.optional (checkedAttrs.github != null && checkedAttrs.githubId == null) ''
+ echo ${lib.escapeShellArg (lib.showOption prefix)}': If `github` is specified, `githubId` must be too.'
+ # Calling this too often would hit non-authenticated API limits, but this
+ # shouldn't happen since such errors will get fixed rather quickly
+ info=$(curl -sS https://api.github.com/users/${checkedAttrs.github})
+ id=$(jq -r '.id' <<< "$info")
+ echo "The GitHub ID for GitHub user ${checkedAttrs.github} is $id:"
+ echo -e " githubId = $id;\n"
+ '' ++ lib.optional (checkedAttrs.email == null && checkedAttrs.github == null && checkedAttrs.matrix == null) ''
+ echo ${lib.escapeShellArg (lib.showOption prefix)}': At least one of `email`, `github` or `matrix` must be specified, so that users know how to reach you.'
+ '' ++ lib.optional (checkedAttrs.email != null && lib.hasSuffix "noreply.github.com" checkedAttrs.email) ''
+ echo ${lib.escapeShellArg (lib.showOption prefix)}': If an email address is given, it should allow people to reach you. If you do not want that, you can just provide `github` or `matrix` instead.'
+ '';
+ in lib.deepSeq checkedAttrs checks;
+
+ missingGithubIds = lib.concatLists (lib.mapAttrsToList checkMaintainer lib.maintainers);
+
+ success = pkgs.runCommand "checked-maintainers-success" {} ">$out";
+
+ failure = pkgs.runCommand "checked-maintainers-failure" {
+ nativeBuildInputs = [ pkgs.curl pkgs.jq ];
+ outputHash = "sha256:${lib.fakeSha256}";
+ outputHAlgo = "sha256";
+ outputHashMode = "flat";
+ SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+ } ''
+ ${lib.concatStringsSep "\n" missingGithubIds}
+ exit 1
+ '';
+in if missingGithubIds == [] then success else failure
diff --git a/tests/misc.nix b/tests/misc.nix
new file mode 100644
index 000000000..ce980436c
--- /dev/null
+++ b/tests/misc.nix
@@ -0,0 +1,1657 @@
+# to run these tests:
+# nix-instantiate --eval --strict nixpkgs/lib/tests/misc.nix
+# if the resulting list is empty, all tests passed
+with import ../default.nix;
+
+let
+ testingThrow = expr: {
+ expr = (builtins.tryEval (builtins.seq expr "didn't throw"));
+ expected = { success = false; value = false; };
+ };
+ testingDeepThrow = expr: testingThrow (builtins.deepSeq expr expr);
+
+ testSanitizeDerivationName = { name, expected }:
+ let
+ drv = derivation {
+ name = strings.sanitizeDerivationName name;
+ builder = "x";
+ system = "x";
+ };
+ in {
+ # Evaluate the derivation so an invalid name would be caught
+ expr = builtins.seq drv.drvPath drv.name;
+ inherit expected;
+ };
+
+in
+
+runTests {
+
+# TRIVIAL
+
+ testId = {
+ expr = id 1;
+ expected = 1;
+ };
+
+ testConst = {
+ expr = const 2 3;
+ expected = 2;
+ };
+
+ testPipe = {
+ expr = pipe 2 [
+ (x: x + 2) # 2 + 2 = 4
+ (x: x * 2) # 4 * 2 = 8
+ ];
+ expected = 8;
+ };
+
+ testPipeEmpty = {
+ expr = pipe 2 [];
+ expected = 2;
+ };
+
+ testPipeStrings = {
+ expr = pipe [ 3 4 ] [
+ (map toString)
+ (map (s: s + "\n"))
+ concatStrings
+ ];
+ expected = ''
+ 3
+ 4
+ '';
+ };
+
+ /*
+ testOr = {
+ expr = or true false;
+ expected = true;
+ };
+ */
+
+ testAnd = {
+ expr = and true false;
+ expected = false;
+ };
+
+ testFix = {
+ expr = fix (x: {a = if x ? a then "a" else "b";});
+ expected = {a = "a";};
+ };
+
+ testComposeExtensions = {
+ expr = let obj = makeExtensible (self: { foo = self.bar; });
+ f = self: super: { bar = false; baz = true; };
+ g = self: super: { bar = super.baz or false; };
+ f_o_g = composeExtensions f g;
+ composed = obj.extend f_o_g;
+ in composed.foo;
+ expected = true;
+ };
+
+ testComposeManyExtensions0 = {
+ expr = let obj = makeExtensible (self: { foo = true; });
+ emptyComposition = composeManyExtensions [];
+ composed = obj.extend emptyComposition;
+ in composed.foo;
+ expected = true;
+ };
+
+ testComposeManyExtensions =
+ let f = self: super: { bar = false; baz = true; };
+ g = self: super: { bar = super.baz or false; };
+ h = self: super: { qux = super.bar or false; };
+ obj = makeExtensible (self: { foo = self.qux; });
+ in {
+ expr = let composition = composeManyExtensions [f g h];
+ composed = obj.extend composition;
+ in composed.foo;
+ expected = (obj.extend (composeExtensions f (composeExtensions g h))).foo;
+ };
+
+ testBitAnd = {
+ expr = (bitAnd 3 10);
+ expected = 2;
+ };
+
+ testBitOr = {
+ expr = (bitOr 3 10);
+ expected = 11;
+ };
+
+ testBitXor = {
+ expr = (bitXor 3 10);
+ expected = 9;
+ };
+
+ testToHexString = {
+ expr = toHexString 250;
+ expected = "FA";
+ };
+
+ testToBaseDigits = {
+ expr = toBaseDigits 2 6;
+ expected = [ 1 1 0 ];
+ };
+
+ testFunctionArgsFunctor = {
+ expr = functionArgs { __functor = self: { a, b }: null; };
+ expected = { a = false; b = false; };
+ };
+
+ testFunctionArgsSetFunctionArgs = {
+ expr = functionArgs (setFunctionArgs (args: args.x) { x = false; });
+ expected = { x = false; };
+ };
+
+# STRINGS
+
+ testConcatMapStrings = {
+ expr = concatMapStrings (x: x + ";") ["a" "b" "c"];
+ expected = "a;b;c;";
+ };
+
+ testConcatStringsSep = {
+ expr = concatStringsSep "," ["a" "b" "c"];
+ expected = "a,b,c";
+ };
+
+ testConcatLines = {
+ expr = concatLines ["a" "b" "c"];
+ expected = "a\nb\nc\n";
+ };
+
+ testSplitStringsSimple = {
+ expr = strings.splitString "." "a.b.c.d";
+ expected = [ "a" "b" "c" "d" ];
+ };
+
+ testSplitStringsEmpty = {
+ expr = strings.splitString "." "a..b";
+ expected = [ "a" "" "b" ];
+ };
+
+ testSplitStringsOne = {
+ expr = strings.splitString ":" "a.b";
+ expected = [ "a.b" ];
+ };
+
+ testSplitStringsNone = {
+ expr = strings.splitString "." "";
+ expected = [ "" ];
+ };
+
+ testSplitStringsFirstEmpty = {
+ expr = strings.splitString "/" "/a/b/c";
+ expected = [ "" "a" "b" "c" ];
+ };
+
+ testSplitStringsLastEmpty = {
+ expr = strings.splitString ":" "2001:db8:0:0042::8a2e:370:";
+ expected = [ "2001" "db8" "0" "0042" "" "8a2e" "370" "" ];
+ };
+
+ testSplitStringsRegex = {
+ expr = strings.splitString "\\[{}]()^$?*+|." "A\\[{}]()^$?*+|.B";
+ expected = [ "A" "B" ];
+ };
+
+ testSplitStringsDerivation = {
+ expr = take 3 (strings.splitString "/" (derivation {
+ name = "name";
+ builder = "builder";
+ system = "system";
+ }));
+ expected = ["" "nix" "store"];
+ };
+
+ testSplitVersionSingle = {
+ expr = versions.splitVersion "1";
+ expected = [ "1" ];
+ };
+
+ testSplitVersionDouble = {
+ expr = versions.splitVersion "1.2";
+ expected = [ "1" "2" ];
+ };
+
+ testSplitVersionTriple = {
+ expr = versions.splitVersion "1.2.3";
+ expected = [ "1" "2" "3" ];
+ };
+
+ testPadVersionLess = {
+ expr = versions.pad 3 "1.2";
+ expected = "1.2.0";
+ };
+
+ testPadVersionLessExtra = {
+ expr = versions.pad 3 "1.3-rc1";
+ expected = "1.3.0-rc1";
+ };
+
+ testPadVersionMore = {
+ expr = versions.pad 3 "1.2.3.4";
+ expected = "1.2.3";
+ };
+
+ testIsStorePath = {
+ expr =
+ let goodPath =
+ "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11";
+ in {
+ storePath = isStorePath goodPath;
+ storePathDerivation = isStorePath (import ../.. { system = "x86_64-linux"; }).hello;
+ storePathAppendix = isStorePath
+ "${goodPath}/bin/python";
+ nonAbsolute = isStorePath (concatStrings (tail (stringToCharacters goodPath)));
+ asPath = isStorePath (/. + goodPath);
+ otherPath = isStorePath "/something/else";
+ otherVals = {
+ attrset = isStorePath {};
+ list = isStorePath [];
+ int = isStorePath 42;
+ };
+ };
+ expected = {
+ storePath = true;
+ storePathDerivation = true;
+ storePathAppendix = false;
+ nonAbsolute = false;
+ asPath = true;
+ otherPath = false;
+ otherVals = {
+ attrset = false;
+ list = false;
+ int = false;
+ };
+ };
+ };
+
+ testEscapeXML = {
+ expr = escapeXML ''"test" 'test' < & >'';
+ expected = "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;";
+ };
+
+ testToShellVars = {
+ expr = ''
+ ${toShellVars {
+ STRing01 = "just a 'string'";
+ _array_ = [ "with" "more strings" ];
+ assoc."with some" = ''
+ strings
+ possibly newlines
+ '';
+ drv = {
+ outPath = "/drv";
+ foo = "ignored attribute";
+ };
+ path = /path;
+ stringable = {
+ __toString = _: "hello toString";
+ bar = "ignored attribute";
+ };
+ }}
+ '';
+ expected = ''
+ STRing01='just a '\'''string'\''''
+ declare -a _array_=('with' 'more strings')
+ declare -A assoc=(['with some']='strings
+ possibly newlines
+ ')
+ drv='/drv'
+ path='/path'
+ stringable='hello toString'
+ '';
+ };
+
+ testHasInfixFalse = {
+ expr = hasInfix "c" "abde";
+ expected = false;
+ };
+
+ testHasInfixTrue = {
+ expr = hasInfix "c" "abcde";
+ expected = true;
+ };
+
+ testHasInfixDerivation = {
+ expr = hasInfix "hello" (import ../.. { system = "x86_64-linux"; }).hello;
+ expected = true;
+ };
+
+ testHasInfixPath = {
+ expr = hasInfix "tests" ./.;
+ expected = true;
+ };
+
+ testHasInfixPathStoreDir = {
+ expr = hasInfix builtins.storeDir ./.;
+ expected = true;
+ };
+
+ testHasInfixToString = {
+ expr = hasInfix "a" { __toString = _: "a"; };
+ expected = true;
+ };
+
+ testNormalizePath = {
+ expr = strings.normalizePath "//a/b//c////d/";
+ expected = "/a/b/c/d/";
+ };
+
+ testCharToInt = {
+ expr = strings.charToInt "A";
+ expected = 65;
+ };
+
+ testEscapeC = {
+ expr = strings.escapeC [ " " ] "Hello World";
+ expected = "Hello\\x20World";
+ };
+
+ testEscapeURL = testAllTrue [
+ ("" == strings.escapeURL "")
+ ("Hello" == strings.escapeURL "Hello")
+ ("Hello%20World" == strings.escapeURL "Hello World")
+ ("Hello%2FWorld" == strings.escapeURL "Hello/World")
+ ("42%25" == strings.escapeURL "42%")
+ ("%20%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%09%3A%2F%40%24%27%28%29%2A%2C%3B" == strings.escapeURL " ?&=#+%!<>#\"{}|\\^[]`\t:/@$'()*,;")
+ ];
+
+ testToInt = testAllTrue [
+ # Naive
+ (123 == toInt "123")
+ (0 == toInt "0")
+ # Whitespace Padding
+ (123 == toInt " 123")
+ (123 == toInt "123 ")
+ (123 == toInt " 123 ")
+ (123 == toInt " 123 ")
+ (0 == toInt " 0")
+ (0 == toInt "0 ")
+ (0 == toInt " 0 ")
+ (-1 == toInt "-1")
+ (-1 == toInt " -1 ")
+ ];
+
+ testToIntFails = testAllTrue [
+ ( builtins.tryEval (toInt "") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt "123 123") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt "0 123") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt " 0d ") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt " 1d ") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt " d0 ") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt "00") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt "01") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt "002") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt " 002 ") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt " foo ") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt " foo 123 ") == { success = false; value = false; } )
+ ( builtins.tryEval (toInt " foo123 ") == { success = false; value = false; } )
+ ];
+
+ testToIntBase10 = testAllTrue [
+ # Naive
+ (123 == toIntBase10 "123")
+ (0 == toIntBase10 "0")
+ # Whitespace Padding
+ (123 == toIntBase10 " 123")
+ (123 == toIntBase10 "123 ")
+ (123 == toIntBase10 " 123 ")
+ (123 == toIntBase10 " 123 ")
+ (0 == toIntBase10 " 0")
+ (0 == toIntBase10 "0 ")
+ (0 == toIntBase10 " 0 ")
+ # Zero Padding
+ (123 == toIntBase10 "0123")
+ (123 == toIntBase10 "0000123")
+ (0 == toIntBase10 "000000")
+ # Whitespace and Zero Padding
+ (123 == toIntBase10 " 0123")
+ (123 == toIntBase10 "0123 ")
+ (123 == toIntBase10 " 0123 ")
+ (123 == toIntBase10 " 0000123")
+ (123 == toIntBase10 "0000123 ")
+ (123 == toIntBase10 " 0000123 ")
+ (0 == toIntBase10 " 000000")
+ (0 == toIntBase10 "000000 ")
+ (0 == toIntBase10 " 000000 ")
+ (-1 == toIntBase10 "-1")
+ (-1 == toIntBase10 " -1 ")
+ ];
+
+ testToIntBase10Fails = testAllTrue [
+ ( builtins.tryEval (toIntBase10 "") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 "123 123") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 "0 123") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 " 0d ") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 " 1d ") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 " d0 ") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 " foo ") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 " foo 123 ") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 " foo 00123 ") == { success = false; value = false; } )
+ ( builtins.tryEval (toIntBase10 " foo00123 ") == { success = false; value = false; } )
+ ];
+
+# LISTS
+
+ testFilter = {
+ expr = filter (x: x != "a") ["a" "b" "c" "a"];
+ expected = ["b" "c"];
+ };
+
+ testFold =
+ let
+ f = op: fold: fold op 0 (range 0 100);
+ # fold with associative operator
+ assoc = f builtins.add;
+ # fold with non-associative operator
+ nonAssoc = f builtins.sub;
+ in {
+ expr = {
+ assocRight = assoc foldr;
+ # right fold with assoc operator is same as left fold
+ assocRightIsLeft = assoc foldr == assoc foldl;
+ nonAssocRight = nonAssoc foldr;
+ nonAssocLeft = nonAssoc foldl;
+ # with non-assoc operator the fold results are not the same
+ nonAssocRightIsNotLeft = nonAssoc foldl != nonAssoc foldr;
+ # fold is an alias for foldr
+ foldIsRight = nonAssoc fold == nonAssoc foldr;
+ };
+ expected = {
+ assocRight = 5050;
+ assocRightIsLeft = true;
+ nonAssocRight = 50;
+ nonAssocLeft = (-5050);
+ nonAssocRightIsNotLeft = true;
+ foldIsRight = true;
+ };
+ };
+
+ testTake = testAllTrue [
+ ([] == (take 0 [ 1 2 3 ]))
+ ([1] == (take 1 [ 1 2 3 ]))
+ ([ 1 2 ] == (take 2 [ 1 2 3 ]))
+ ([ 1 2 3 ] == (take 3 [ 1 2 3 ]))
+ ([ 1 2 3 ] == (take 4 [ 1 2 3 ]))
+ ];
+
+ testFoldAttrs = {
+ expr = foldAttrs (n: a: [n] ++ a) [] [
+ { a = 2; b = 7; }
+ { a = 3; c = 8; }
+ ];
+ expected = { a = [ 2 3 ]; b = [7]; c = [8];};
+ };
+
+ testSort = {
+ expr = sort builtins.lessThan [ 40 2 30 42 ];
+ expected = [2 30 40 42];
+ };
+
+ testReplicate = {
+ expr = replicate 3 "a";
+ expected = ["a" "a" "a"];
+ };
+
+ testToIntShouldConvertStringToInt = {
+ expr = toInt "27";
+ expected = 27;
+ };
+
+ testToIntShouldThrowErrorIfItCouldNotConvertToInt = {
+ expr = builtins.tryEval (toInt "\"foo\"");
+ expected = { success = false; value = false; };
+ };
+
+ testHasAttrByPathTrue = {
+ expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; };
+ expected = true;
+ };
+
+ testHasAttrByPathFalse = {
+ expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; };
+ expected = false;
+ };
+
+ testFindFirstExample1 = {
+ expr = findFirst (x: x > 3) 7 [ 1 6 4 ];
+ expected = 6;
+ };
+
+ testFindFirstExample2 = {
+ expr = findFirst (x: x > 9) 7 [ 1 6 4 ];
+ expected = 7;
+ };
+
+ testFindFirstEmpty = {
+ expr = findFirst (abort "when the list is empty, the predicate is not needed") null [];
+ expected = null;
+ };
+
+ testFindFirstSingleMatch = {
+ expr = findFirst (x: x == 5) null [ 5 ];
+ expected = 5;
+ };
+
+ testFindFirstSingleDefault = {
+ expr = findFirst (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
+ expected = null;
+ };
+
+ testFindFirstNone = {
+ expr = builtins.tryEval (findFirst (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
+ expected = { success = false; value = false; };
+ };
+
+ # Makes sure that the implementation doesn't cause a stack overflow
+ testFindFirstBig = {
+ expr = findFirst (x: x == 1000000) null (range 0 1000000);
+ expected = 1000000;
+ };
+
+ testFindFirstLazy = {
+ expr = findFirst (x: x == 1) 7 [ 1 (abort "list elements after the match must not be evaluated") ];
+ expected = 1;
+ };
+
+# ATTRSETS
+
+ testConcatMapAttrs = {
+ expr = concatMapAttrs
+ (name: value: {
+ ${name} = value;
+ ${name + value} = value;
+ })
+ {
+ foo = "bar";
+ foobar = "baz";
+ };
+ expected = {
+ foo = "bar";
+ foobar = "baz";
+ foobarbaz = "baz";
+ };
+ };
+
+ # code from example
+ testFoldlAttrs = {
+ expr = {
+ example = foldlAttrs
+ (acc: name: value: {
+ sum = acc.sum + value;
+ names = acc.names ++ [ name ];
+ })
+ { sum = 0; names = [ ]; }
+ {
+ foo = 1;
+ bar = 10;
+ };
+ # should just return the initial value
+ emptySet = foldlAttrs (throw "function not needed") 123 { };
+ # should just evaluate to the last value
+ accNotNeeded = foldlAttrs (_acc: _name: v: v) (throw "accumulator not needed") { z = 3; a = 2; };
+ # the accumulator doesnt have to be an attrset it can be as trivial as being just a number or string
+ trivialAcc = foldlAttrs (acc: _name: v: acc * 10 + v) 1 { z = 1; a = 2; };
+ };
+ expected = {
+ example = {
+ sum = 11;
+ names = [ "bar" "foo" ];
+ };
+ emptySet = 123;
+ accNotNeeded = 3;
+ trivialAcc = 121;
+ };
+ };
+
+ # code from the example
+ testRecursiveUpdateUntil = {
+ expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {
+ # first attribute set
+ foo.bar = 1;
+ foo.baz = 2;
+ bar = 3;
+ } {
+ #second attribute set
+ foo.bar = 1;
+ foo.quz = 2;
+ baz = 4;
+ };
+ expected = {
+ foo.bar = 1; # 'foo.*' from the second set
+ foo.quz = 2; #
+ bar = 3; # 'bar' from the first set
+ baz = 4; # 'baz' from the second set
+ };
+ };
+
+ testOverrideExistingEmpty = {
+ expr = overrideExisting {} { a = 1; };
+ expected = {};
+ };
+
+ testOverrideExistingDisjoint = {
+ expr = overrideExisting { b = 2; } { a = 1; };
+ expected = { b = 2; };
+ };
+
+ testOverrideExistingOverride = {
+ expr = overrideExisting { a = 3; b = 2; } { a = 1; };
+ expected = { a = 1; b = 2; };
+ };
+
+# GENERATORS
+# these tests assume attributes are converted to lists
+# in alphabetical order
+
+ testMkKeyValueDefault = {
+ expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar";
+ expected = ''f\:oo:bar'';
+ };
+
+ testMkValueString = {
+ expr = let
+ vals = {
+ int = 42;
+ string = ''fo"o'';
+ bool = true;
+ bool2 = false;
+ null = null;
+ # float = 42.23; # floats are strange
+ };
+ in mapAttrs
+ (const (generators.mkValueStringDefault {}))
+ vals;
+ expected = {
+ int = "42";
+ string = ''fo"o'';
+ bool = "true";
+ bool2 = "false";
+ null = "null";
+ # float = "42.23" true false [ "bar" ] ]'';
+ };
+ };
+
+ testToKeyValue = {
+ expr = generators.toKeyValue {} {
+ key = "value";
+ "other=key" = "baz";
+ };
+ expected = ''
+ key=value
+ other\=key=baz
+ '';
+ };
+
+ testToINIEmpty = {
+ expr = generators.toINI {} {};
+ expected = "";
+ };
+
+ testToINIEmptySection = {
+ expr = generators.toINI {} { foo = {}; bar = {}; };
+ expected = ''
+ [bar]
+
+ [foo]
+ '';
+ };
+
+ testToINIDuplicateKeys = {
+ expr = generators.toINI { listsAsDuplicateKeys = true; } { foo.bar = true; baz.qux = [ 1 false ]; };
+ expected = ''
+ [baz]
+ qux=1
+ qux=false
+
+ [foo]
+ bar=true
+ '';
+ };
+
+ testToINIDefaultEscapes = {
+ expr = generators.toINI {} {
+ "no [ and ] allowed unescaped" = {
+ "and also no = in keys" = 42;
+ };
+ };
+ expected = ''
+ [no \[ and \] allowed unescaped]
+ and also no \= in keys=42
+ '';
+ };
+
+ testToINIDefaultFull = {
+ expr = generators.toINI {} {
+ "section 1" = {
+ attribute1 = 5;
+ x = "Me-se JarJar Binx";
+ # booleans are converted verbatim by default
+ boolean = false;
+ };
+ "foo[]" = {
+ "he\\h=he" = "this is okay";
+ };
+ };
+ expected = ''
+ [foo\[\]]
+ he\h\=he=this is okay
+
+ [section 1]
+ attribute1=5
+ boolean=false
+ x=Me-se JarJar Binx
+ '';
+ };
+
+ testToINIWithGlobalSectionEmpty = {
+ expr = generators.toINIWithGlobalSection {} {
+ globalSection = {
+ };
+ sections = {
+ };
+ };
+ expected = ''
+ '';
+ };
+
+ testToINIWithGlobalSectionGlobalEmptyIsTheSameAsToINI =
+ let
+ sections = {
+ "section 1" = {
+ attribute1 = 5;
+ x = "Me-se JarJar Binx";
+ };
+ "foo" = {
+ "he\\h=he" = "this is okay";
+ };
+ };
+ in {
+ expr =
+ generators.toINIWithGlobalSection {} {
+ globalSection = {};
+ sections = sections;
+ };
+ expected = generators.toINI {} sections;
+ };
+
+ testToINIWithGlobalSectionFull = {
+ expr = generators.toINIWithGlobalSection {} {
+ globalSection = {
+ foo = "bar";
+ test = false;
+ };
+ sections = {
+ "section 1" = {
+ attribute1 = 5;
+ x = "Me-se JarJar Binx";
+ };
+ "foo" = {
+ "he\\h=he" = "this is okay";
+ };
+ };
+ };
+ expected = ''
+ foo=bar
+ test=false
+
+ [foo]
+ he\h\=he=this is okay
+
+ [section 1]
+ attribute1=5
+ x=Me-se JarJar Binx
+ '';
+ };
+
+ /* right now only invocation check */
+ testToJSONSimple =
+ let val = {
+ foobar = [ "baz" 1 2 3 ];
+ };
+ in {
+ expr = generators.toJSON {} val;
+ # trivial implementation
+ expected = builtins.toJSON val;
+ };
+
+ /* right now only invocation check */
+ testToYAMLSimple =
+ let val = {
+ list = [ { one = 1; } { two = 2; } ];
+ all = 42;
+ };
+ in {
+ expr = generators.toYAML {} val;
+ # trivial implementation
+ expected = builtins.toJSON val;
+ };
+
+ testToPretty =
+ let
+ deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
+ in {
+ expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
+ int = 42;
+ float = 0.1337;
+ bool = true;
+ emptystring = "";
+ string = "fn\${o}\"r\\d";
+ newlinestring = "\n";
+ path = /. + "/foo";
+ null_ = null;
+ function = x: x;
+ functionArgs = { arg ? 4, foo }: arg;
+ list = [ 3 4 function [ false ] ];
+ emptylist = [];
+ attrs = { foo = null; "foo b/ar" = "baz"; };
+ emptyattrs = {};
+ drv = deriv;
+ };
+ expected = rec {
+ int = "42";
+ float = "0.1337";
+ bool = "true";
+ emptystring = ''""'';
+ string = ''"fn\''${o}\"r\\d"'';
+ newlinestring = "\"\\n\"";
+ path = "/foo";
+ null_ = "null";
+ function = "<function>";
+ functionArgs = "<function, args: {arg?, foo}>";
+ list = "[ 3 4 ${function} [ false ] ]";
+ emptylist = "[ ]";
+ attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }";
+ emptyattrs = "{ }";
+ drv = "<derivation ${deriv.name}>";
+ };
+ };
+
+ testToPrettyLimit =
+ let
+ a.b = 1;
+ a.c = a;
+ in {
+ expr = generators.toPretty { } (generators.withRecursion { throwOnDepthLimit = false; depthLimit = 2; } a);
+ expected = "{\n b = 1;\n c = {\n b = \"<unevaluated>\";\n c = {\n b = \"<unevaluated>\";\n c = \"<unevaluated>\";\n };\n };\n}";
+ };
+
+ testToPrettyLimitThrow =
+ let
+ a.b = 1;
+ a.c = a;
+ in {
+ expr = (builtins.tryEval
+ (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))).success;
+ expected = false;
+ };
+
+ testWithRecursionDealsWithFunctors =
+ let
+ functor = {
+ __functor = self: { a, b, }: null;
+ };
+ a = {
+ value = "1234";
+ b = functor;
+ c.d = functor;
+ };
+ in {
+ expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a);
+ expected = "{\n b = <function, args: {a, b}>;\n c = {\n d = \"<unevaluated>\";\n };\n value = \"<unevaluated>\";\n}";
+ };
+
+ testToPrettyMultiline = {
+ expr = mapAttrs (const (generators.toPretty { })) rec {
+ list = [ 3 4 [ false ] ];
+ attrs = { foo = null; bar.foo = "baz"; };
+ newlinestring = "\n";
+ multilinestring = ''
+ hello
+ ''${there}
+ te'''st
+ '';
+ multilinestring' = ''
+ hello
+ there
+ test'';
+ };
+ expected = rec {
+ list = ''
+ [
+ 3
+ 4
+ [
+ false
+ ]
+ ]'';
+ attrs = ''
+ {
+ bar = {
+ foo = "baz";
+ };
+ foo = null;
+ }'';
+ newlinestring = "''\n \n''";
+ multilinestring = ''
+ '''
+ hello
+ '''''${there}
+ te''''st
+ ''''';
+ multilinestring' = ''
+ '''
+ hello
+ there
+ test''''';
+
+ };
+ };
+
+ testToPrettyAllowPrettyValues = {
+ expr = generators.toPretty { allowPrettyValues = true; }
+ { __pretty = v: "«" + v + "»"; val = "foo"; };
+ expected = "«foo»";
+ };
+
+ testToPlist =
+ let
+ deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
+ in {
+ expr = mapAttrs (const (generators.toPlist { })) {
+ value = {
+ nested.values = rec {
+ int = 42;
+ float = 0.1337;
+ bool = true;
+ emptystring = "";
+ string = "fn\${o}\"r\\d";
+ newlinestring = "\n";
+ path = /. + "/foo";
+ null_ = null;
+ list = [ 3 4 "test" ];
+ emptylist = [];
+ attrs = { foo = null; "foo b/ar" = "baz"; };
+ emptyattrs = {};
+ };
+ };
+ };
+ expected = { value = builtins.readFile ./test-to-plist-expected.plist; };
+ };
+
+ testToLuaEmptyAttrSet = {
+ expr = generators.toLua {} {};
+ expected = ''{}'';
+ };
+
+ testToLuaEmptyList = {
+ expr = generators.toLua {} [];
+ expected = ''{}'';
+ };
+
+ testToLuaListOfVariousTypes = {
+ expr = generators.toLua {} [ null 43 3.14159 true ];
+ expected = ''
+ {
+ nil,
+ 43,
+ 3.14159,
+ true
+ }'';
+ };
+
+ testToLuaString = {
+ expr = generators.toLua {} ''double-quote (") and single quotes (')'';
+ expected = ''"double-quote (\") and single quotes (')"'';
+ };
+
+ testToLuaAttrsetWithLuaInline = {
+ expr = generators.toLua {} { x = generators.mkLuaInline ''"abc" .. "def"''; };
+ expected = ''
+ {
+ ["x"] = ("abc" .. "def")
+ }'';
+ };
+
+ testToLuaAttrsetWithSpaceInKey = {
+ expr = generators.toLua {} { "some space and double-quote (\")" = 42; };
+ expected = ''
+ {
+ ["some space and double-quote (\")"] = 42
+ }'';
+ };
+
+ testToLuaWithoutMultiline = {
+ expr = generators.toLua { multiline = false; } [ 41 43 ];
+ expected = ''{ 41, 43 }'';
+ };
+
+ testToLuaEmptyBindings = {
+ expr = generators.toLua { asBindings = true; } {};
+ expected = "";
+ };
+
+ testToLuaBindings = {
+ expr = generators.toLua { asBindings = true; } { x1 = 41; _y = { a = 43; }; };
+ expected = ''
+ _y = {
+ ["a"] = 43
+ }
+ x1 = 41
+ '';
+ };
+
+ testToLuaPartialTableBindings = {
+ expr = generators.toLua { asBindings = true; } { "x.y" = 42; };
+ expected = ''
+ x.y = 42
+ '';
+ };
+
+ testToLuaIndentedBindings = {
+ expr = generators.toLua { asBindings = true; indent = " "; } { x = { y = 42; }; };
+ expected = " x = {\n [\"y\"] = 42\n }\n";
+ };
+
+ testToLuaBindingsWithSpace = testingThrow (
+ generators.toLua { asBindings = true; } { "with space" = 42; }
+ );
+
+ testToLuaBindingsWithLeadingDigit = testingThrow (
+ generators.toLua { asBindings = true; } { "11eleven" = 42; }
+ );
+
+ testToLuaBasicExample = {
+ expr = generators.toLua {} {
+ cmd = [ "typescript-language-server" "--stdio" ];
+ settings.workspace.library = generators.mkLuaInline ''vim.api.nvim_get_runtime_file("", true)'';
+ };
+ expected = ''
+ {
+ ["cmd"] = {
+ "typescript-language-server",
+ "--stdio"
+ },
+ ["settings"] = {
+ ["workspace"] = {
+ ["library"] = (vim.api.nvim_get_runtime_file("", true))
+ }
+ }
+ }'';
+ };
+
+# CLI
+
+ testToGNUCommandLine = {
+ expr = cli.toGNUCommandLine {} {
+ data = builtins.toJSON { id = 0; };
+ X = "PUT";
+ retry = 3;
+ retry-delay = null;
+ url = [ "https://example.com/foo" "https://example.com/bar" ];
+ silent = false;
+ verbose = true;
+ };
+
+ expected = [
+ "-X" "PUT"
+ "--data" "{\"id\":0}"
+ "--retry" "3"
+ "--url" "https://example.com/foo"
+ "--url" "https://example.com/bar"
+ "--verbose"
+ ];
+ };
+
+ testToGNUCommandLineShell = {
+ expr = cli.toGNUCommandLineShell {} {
+ data = builtins.toJSON { id = 0; };
+ X = "PUT";
+ retry = 3;
+ retry-delay = null;
+ url = [ "https://example.com/foo" "https://example.com/bar" ];
+ silent = false;
+ verbose = true;
+ };
+
+ expected = "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
+ };
+
+ testSanitizeDerivationNameLeadingDots = testSanitizeDerivationName {
+ name = "..foo";
+ expected = "foo";
+ };
+
+ testSanitizeDerivationNameUnicode = testSanitizeDerivationName {
+ name = "fö";
+ expected = "f-";
+ };
+
+ testSanitizeDerivationNameAscii = testSanitizeDerivationName {
+ name = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
+ expected = "-+--.-0123456789-=-?-ABCDEFGHIJKLMNOPQRSTUVWXYZ-_-abcdefghijklmnopqrstuvwxyz-";
+ };
+
+ testSanitizeDerivationNameTooLong = testSanitizeDerivationName {
+ name = "This string is loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
+ expected = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong";
+ };
+
+ testSanitizeDerivationNameTooLongWithInvalid = testSanitizeDerivationName {
+ name = "Hello there aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&&&&&&&";
+ expected = "there-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-";
+ };
+
+ testSanitizeDerivationNameEmpty = testSanitizeDerivationName {
+ name = "";
+ expected = "unknown";
+ };
+
+ testFreeformOptions = {
+ expr =
+ let
+ submodule = { lib, ... }: {
+ freeformType = lib.types.attrsOf (lib.types.submodule {
+ options.bar = lib.mkOption {};
+ });
+ options.bar = lib.mkOption {};
+ };
+
+ module = { lib, ... }: {
+ options.foo = lib.mkOption {
+ type = lib.types.submodule submodule;
+ };
+ };
+
+ options = (evalModules {
+ modules = [ module ];
+ }).options;
+
+ locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
+ in map (o: o.loc) locs;
+ expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
+ };
+
+ testCartesianProductOfEmptySet = {
+ expr = cartesianProductOfSets {};
+ expected = [ {} ];
+ };
+
+ testCartesianProductOfOneSet = {
+ expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
+ expected = [ { a = 1; } { a = 2; } { a = 3; } ];
+ };
+
+ testCartesianProductOfTwoSets = {
+ expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
+ expected = [
+ { a = 1; b = 10; }
+ { a = 1; b = 20; }
+ ];
+ };
+
+ testCartesianProductOfTwoSetsWithOneEmpty = {
+ expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
+ expected = [ ];
+ };
+
+ testCartesianProductOfThreeSets = {
+ expr = cartesianProductOfSets {
+ a = [ 1 2 3 ];
+ b = [ 10 20 30 ];
+ c = [ 100 200 300 ];
+ };
+ expected = [
+ { a = 1; b = 10; c = 100; }
+ { a = 1; b = 10; c = 200; }
+ { a = 1; b = 10; c = 300; }
+
+ { a = 1; b = 20; c = 100; }
+ { a = 1; b = 20; c = 200; }
+ { a = 1; b = 20; c = 300; }
+
+ { a = 1; b = 30; c = 100; }
+ { a = 1; b = 30; c = 200; }
+ { a = 1; b = 30; c = 300; }
+
+ { a = 2; b = 10; c = 100; }
+ { a = 2; b = 10; c = 200; }
+ { a = 2; b = 10; c = 300; }
+
+ { a = 2; b = 20; c = 100; }
+ { a = 2; b = 20; c = 200; }
+ { a = 2; b = 20; c = 300; }
+
+ { a = 2; b = 30; c = 100; }
+ { a = 2; b = 30; c = 200; }
+ { a = 2; b = 30; c = 300; }
+
+ { a = 3; b = 10; c = 100; }
+ { a = 3; b = 10; c = 200; }
+ { a = 3; b = 10; c = 300; }
+
+ { a = 3; b = 20; c = 100; }
+ { a = 3; b = 20; c = 200; }
+ { a = 3; b = 20; c = 300; }
+
+ { a = 3; b = 30; c = 100; }
+ { a = 3; b = 30; c = 200; }
+ { a = 3; b = 30; c = 300; }
+ ];
+ };
+
+ # The example from the showAttrPath documentation
+ testShowAttrPathExample = {
+ expr = showAttrPath [ "foo" "10" "bar" ];
+ expected = "foo.\"10\".bar";
+ };
+
+ testShowAttrPathEmpty = {
+ expr = showAttrPath [];
+ expected = "<root attribute path>";
+ };
+
+ testShowAttrPathVarious = {
+ expr = showAttrPath [
+ "."
+ "foo"
+ "2"
+ "a2-b"
+ "_bc'de"
+ ];
+ expected = ''".".foo."2".a2-b._bc'de'';
+ };
+
+ testGroupBy = {
+ expr = groupBy (n: toString (mod n 5)) (range 0 16);
+ expected = {
+ "0" = [ 0 5 10 15 ];
+ "1" = [ 1 6 11 16 ];
+ "2" = [ 2 7 12 ];
+ "3" = [ 3 8 13 ];
+ "4" = [ 4 9 14 ];
+ };
+ };
+
+ testGroupBy' = {
+ expr = groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ];
+ expected = { false = 3; true = 12; };
+ };
+
+ # The example from the updateManyAttrsByPath documentation
+ testUpdateManyAttrsByPathExample = {
+ expr = updateManyAttrsByPath [
+ {
+ path = [ "a" "b" ];
+ update = old: { d = old.c; };
+ }
+ {
+ path = [ "a" "b" "c" ];
+ update = old: old + 1;
+ }
+ {
+ path = [ "x" "y" ];
+ update = old: "xy";
+ }
+ ] { a.b.c = 0; };
+ expected = { a = { b = { d = 1; }; }; x = { y = "xy"; }; };
+ };
+
+ # If there are no updates, the value is passed through
+ testUpdateManyAttrsByPathNone = {
+ expr = updateManyAttrsByPath [] "something";
+ expected = "something";
+ };
+
+ # A single update to the root path is just like applying the function directly
+ testUpdateManyAttrsByPathSingleIncrement = {
+ expr = updateManyAttrsByPath [
+ {
+ path = [ ];
+ update = old: old + 1;
+ }
+ ] 0;
+ expected = 1;
+ };
+
+ # Multiple updates can be applied are done in order
+ testUpdateManyAttrsByPathMultipleIncrements = {
+ expr = updateManyAttrsByPath [
+ {
+ path = [ ];
+ update = old: old + "a";
+ }
+ {
+ path = [ ];
+ update = old: old + "b";
+ }
+ {
+ path = [ ];
+ update = old: old + "c";
+ }
+ ] "";
+ expected = "abc";
+ };
+
+ # If an update doesn't use the value, all previous updates are not evaluated
+ testUpdateManyAttrsByPathLazy = {
+ expr = updateManyAttrsByPath [
+ {
+ path = [ ];
+ update = old: old + throw "nope";
+ }
+ {
+ path = [ ];
+ update = old: "untainted";
+ }
+ ] (throw "start");
+ expected = "untainted";
+ };
+
+ # Deeply nested attributes can be updated without affecting others
+ testUpdateManyAttrsByPathDeep = {
+ expr = updateManyAttrsByPath [
+ {
+ path = [ "a" "b" "c" ];
+ update = old: old + 1;
+ }
+ ] {
+ a.b.c = 0;
+
+ a.b.z = 0;
+ a.y.z = 0;
+ x.y.z = 0;
+ };
+ expected = {
+ a.b.c = 1;
+
+ a.b.z = 0;
+ a.y.z = 0;
+ x.y.z = 0;
+ };
+ };
+
+ # Nested attributes are updated first
+ testUpdateManyAttrsByPathNestedBeforehand = {
+ expr = updateManyAttrsByPath [
+ {
+ path = [ "a" ];
+ update = old: old // { x = old.b; };
+ }
+ {
+ path = [ "a" "b" ];
+ update = old: old + 1;
+ }
+ ] {
+ a.b = 0;
+ };
+ expected = {
+ a.b = 1;
+ a.x = 1;
+ };
+ };
+
+ ## Levenshtein distance functions and co.
+ testCommonPrefixLengthEmpty = {
+ expr = strings.commonPrefixLength "" "hello";
+ expected = 0;
+ };
+
+ testCommonPrefixLengthSame = {
+ expr = strings.commonPrefixLength "hello" "hello";
+ expected = 5;
+ };
+
+ testCommonPrefixLengthDiffering = {
+ expr = strings.commonPrefixLength "hello" "hey";
+ expected = 2;
+ };
+
+ testCommonSuffixLengthEmpty = {
+ expr = strings.commonSuffixLength "" "hello";
+ expected = 0;
+ };
+
+ testCommonSuffixLengthSame = {
+ expr = strings.commonSuffixLength "hello" "hello";
+ expected = 5;
+ };
+
+ testCommonSuffixLengthDiffering = {
+ expr = strings.commonSuffixLength "test" "rest";
+ expected = 3;
+ };
+
+ testLevenshteinEmpty = {
+ expr = strings.levenshtein "" "";
+ expected = 0;
+ };
+
+ testLevenshteinOnlyAdd = {
+ expr = strings.levenshtein "" "hello there";
+ expected = 11;
+ };
+
+ testLevenshteinOnlyRemove = {
+ expr = strings.levenshtein "hello there" "";
+ expected = 11;
+ };
+
+ testLevenshteinOnlyTransform = {
+ expr = strings.levenshtein "abcdef" "ghijkl";
+ expected = 6;
+ };
+
+ testLevenshteinMixed = {
+ expr = strings.levenshtein "kitchen" "sitting";
+ expected = 5;
+ };
+
+ testLevenshteinAtMostZeroFalse = {
+ expr = strings.levenshteinAtMost 0 "foo" "boo";
+ expected = false;
+ };
+
+ testLevenshteinAtMostZeroTrue = {
+ expr = strings.levenshteinAtMost 0 "foo" "foo";
+ expected = true;
+ };
+
+ testLevenshteinAtMostOneFalse = {
+ expr = strings.levenshteinAtMost 1 "car" "ct";
+ expected = false;
+ };
+
+ testLevenshteinAtMostOneTrue = {
+ expr = strings.levenshteinAtMost 1 "car" "cr";
+ expected = true;
+ };
+
+ # We test levenshteinAtMost 2 particularly well because it uses a complicated
+ # implementation
+ testLevenshteinAtMostTwoIsEmpty = {
+ expr = strings.levenshteinAtMost 2 "" "";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoIsZero = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "abcdef";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoIsOne = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "abddef";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoDiff0False = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "aczyef";
+ expected = false;
+ };
+
+ testLevenshteinAtMostTwoDiff0Outer = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "zbcdez";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoDiff0DelLeft = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "bcdefz";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoDiff0DelRight = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "zabcde";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoDiff1False = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "bddez";
+ expected = false;
+ };
+
+ testLevenshteinAtMostTwoDiff1DelLeft = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "bcdez";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoDiff1DelRight = {
+ expr = strings.levenshteinAtMost 2 "abcdef" "zbcde";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoDiff2False = {
+ expr = strings.levenshteinAtMost 2 "hello" "hxo";
+ expected = false;
+ };
+
+ testLevenshteinAtMostTwoDiff2True = {
+ expr = strings.levenshteinAtMost 2 "hello" "heo";
+ expected = true;
+ };
+
+ testLevenshteinAtMostTwoDiff3 = {
+ expr = strings.levenshteinAtMost 2 "hello" "ho";
+ expected = false;
+ };
+
+ testLevenshteinAtMostThreeFalse = {
+ expr = strings.levenshteinAtMost 3 "hello" "Holla!";
+ expected = false;
+ };
+
+ testLevenshteinAtMostThreeTrue = {
+ expr = strings.levenshteinAtMost 3 "hello" "Holla";
+ expected = true;
+ };
+
+ # lazyDerivation
+
+ testLazyDerivationIsLazyInDerivationForAttrNames = {
+ expr = attrNames (lazyDerivation {
+ derivation = throw "not lazy enough";
+ });
+ # It's ok to add attribute names here when lazyDerivation is improved
+ # in accordance with its inline comments.
+ expected = [ "drvPath" "meta" "name" "out" "outPath" "outputName" "outputs" "system" "type" ];
+ };
+
+ testLazyDerivationIsLazyInDerivationForPassthruAttr = {
+ expr = (lazyDerivation {
+ derivation = throw "not lazy enough";
+ passthru.tests = "whatever is in tests";
+ }).tests;
+ expected = "whatever is in tests";
+ };
+
+ testLazyDerivationIsLazyInDerivationForPassthruAttr2 = {
+ # passthru.tests is not a special case. It works for any attr.
+ expr = (lazyDerivation {
+ derivation = throw "not lazy enough";
+ passthru.foo = "whatever is in foo";
+ }).foo;
+ expected = "whatever is in foo";
+ };
+
+ testLazyDerivationIsLazyInDerivationForMeta = {
+ expr = (lazyDerivation {
+ derivation = throw "not lazy enough";
+ meta = "whatever is in meta";
+ }).meta;
+ expected = "whatever is in meta";
+ };
+
+ testLazyDerivationReturnsDerivationAttrs = let
+ derivation = {
+ type = "derivation";
+ outputs = ["out"];
+ out = "test out";
+ outPath = "test outPath";
+ outputName = "out";
+ drvPath = "test drvPath";
+ name = "test name";
+ system = "test system";
+ meta = "test meta";
+ };
+ in {
+ expr = lazyDerivation { inherit derivation; };
+ expected = derivation;
+ };
+
+ testTypeDescriptionInt = {
+ expr = (with types; int).description;
+ expected = "signed integer";
+ };
+ testTypeDescriptionListOfInt = {
+ expr = (with types; listOf int).description;
+ expected = "list of signed integer";
+ };
+ testTypeDescriptionListOfListOfInt = {
+ expr = (with types; listOf (listOf int)).description;
+ expected = "list of list of signed integer";
+ };
+ testTypeDescriptionListOfEitherStrOrBool = {
+ expr = (with types; listOf (either str bool)).description;
+ expected = "list of (string or boolean)";
+ };
+ testTypeDescriptionEitherListOfStrOrBool = {
+ expr = (with types; either (listOf bool) str).description;
+ expected = "(list of boolean) or string";
+ };
+ testTypeDescriptionEitherStrOrListOfBool = {
+ expr = (with types; either str (listOf bool)).description;
+ expected = "string or list of boolean";
+ };
+ testTypeDescriptionOneOfListOfStrOrBool = {
+ expr = (with types; oneOf [ (listOf bool) str ]).description;
+ expected = "(list of boolean) or string";
+ };
+ testTypeDescriptionOneOfListOfStrOrBoolOrNumber = {
+ expr = (with types; oneOf [ (listOf bool) str number ]).description;
+ expected = "(list of boolean) or string or signed integer or floating point number";
+ };
+ testTypeDescriptionEitherListOfBoolOrEitherStringOrNumber = {
+ expr = (with types; either (listOf bool) (either str number)).description;
+ expected = "(list of boolean) or string or signed integer or floating point number";
+ };
+ testTypeDescriptionEitherEitherListOfBoolOrStringOrNumber = {
+ expr = (with types; either (either (listOf bool) str) number).description;
+ expected = "(list of boolean) or string or signed integer or floating point number";
+ };
+ testTypeDescriptionEitherNullOrBoolOrString = {
+ expr = (with types; either (nullOr bool) str).description;
+ expected = "null or boolean or string";
+ };
+ testTypeDescriptionEitherListOfEitherBoolOrStrOrInt = {
+ expr = (with types; either (listOf (either bool str)) int).description;
+ expected = "(list of (boolean or string)) or signed integer";
+ };
+ testTypeDescriptionEitherIntOrListOrEitherBoolOrStr = {
+ expr = (with types; either int (listOf (either bool str))).description;
+ expected = "signed integer or list of (boolean or string)";
+ };
+}
diff --git a/tests/modules.sh b/tests/modules.sh
new file mode 100755
index 000000000..7aebba6b5
--- /dev/null
+++ b/tests/modules.sh
@@ -0,0 +1,408 @@
+#!/usr/bin/env bash
+#
+# This script is used to test that the module system is working as expected.
+# By default it test the version of nixpkgs which is defined in the NIX_PATH.
+
+set -o errexit -o noclobber -o nounset -o pipefail
+shopt -s failglob inherit_errexit
+
+# https://stackoverflow.com/a/246128/6605742
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+
+cd "$DIR"/modules
+
+pass=0
+fail=0
+
+evalConfig() {
+ local attr=$1
+ shift
+ local script="import ./default.nix { modules = [ $* ];}"
+ nix-instantiate --timeout 1 -E "$script" -A "$attr" --eval-only --show-trace --read-write-mode
+}
+
+reportFailure() {
+ local attr=$1
+ shift
+ local script="import ./default.nix { modules = [ $* ];}"
+ echo 2>&1 "$ nix-instantiate -E '$script' -A '$attr' --eval-only"
+ evalConfig "$attr" "$@" || true
+ ((++fail))
+}
+
+checkConfigOutput() {
+ local outputContains=$1
+ shift
+ if evalConfig "$@" 2>/dev/null | grep --silent "$outputContains" ; then
+ ((++pass))
+ else
+ echo 2>&1 "error: Expected result matching '$outputContains', while evaluating"
+ reportFailure "$@"
+ fi
+}
+
+checkConfigError() {
+ local errorContains=$1
+ local err=""
+ shift
+ if err="$(evalConfig "$@" 2>&1 >/dev/null)"; then
+ echo 2>&1 "error: Expected error code, got exit code 0, while evaluating"
+ reportFailure "$@"
+ else
+ if echo "$err" | grep -zP --silent "$errorContains" ; then
+ ((++pass))
+ else
+ echo 2>&1 "error: Expected error matching '$errorContains', while evaluating"
+ reportFailure "$@"
+ fi
+ fi
+}
+
+# Shorthand meta attribute does not duplicate the config
+checkConfigOutput '^"one two"$' config.result ./shorthand-meta.nix
+
+# Check boolean option.
+checkConfigOutput '^false$' config.enable ./declare-enable.nix
+checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
+checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*' config.enable ./define-enable-throw.nix
+checkConfigError 'while evaluating a definition from `.*/define-enable-abort.nix' config.enable ./define-enable-abort.nix
+checkConfigError 'while evaluating the error message for definitions for .enable., which is an option that does not exist' config.enable ./define-enable-abort.nix
+
+checkConfigOutput '^1$' config.bare-submodule.nested ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix
+checkConfigOutput '^2$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-deep-option.nix
+checkConfigOutput '^42$' config.bare-submodule.nested ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix ./declare-bare-submodule-deep-option.nix ./define-bare-submodule-values.nix
+checkConfigOutput '^420$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-nested-option.nix ./declare-bare-submodule-deep-option.nix ./define-bare-submodule-values.nix
+checkConfigOutput '^2$' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-deep-option.nix ./define-shorthandOnlyDefinesConfig-true.nix
+checkConfigError 'The option .bare-submodule.deep. in .*/declare-bare-submodule-deep-option.nix. is already declared in .*/declare-bare-submodule-deep-option-duplicate.nix' config.bare-submodule.deep ./declare-bare-submodule.nix ./declare-bare-submodule-deep-option.nix ./declare-bare-submodule-deep-option-duplicate.nix
+
+# Check integer types.
+# unsigned
+checkConfigOutput '^42$' config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
+checkConfigError 'A definition for option .* is not of type.*unsigned integer.*. Definition values:\n\s*- In .*: -23' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix
+# positive
+checkConfigError 'A definition for option .* is not of type.*positive integer.*. Definition values:\n\s*- In .*: 0' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix
+# between
+checkConfigOutput '^42$' config.value ./declare-int-between-value.nix ./define-value-int-positive.nix
+checkConfigError 'A definition for option .* is not of type.*between.*-21 and 43.*inclusive.*. Definition values:\n\s*- In .*: -23' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix
+
+# Check either types
+# types.either
+checkConfigOutput '^42$' config.value ./declare-either.nix ./define-value-int-positive.nix
+checkConfigOutput '^"24"$' config.value ./declare-either.nix ./define-value-string.nix
+# types.oneOf
+checkConfigOutput '^42$' config.value ./declare-oneOf.nix ./define-value-int-positive.nix
+checkConfigOutput '^\[ \]$' config.value ./declare-oneOf.nix ./define-value-list.nix
+checkConfigOutput '^"24"$' config.value ./declare-oneOf.nix ./define-value-string.nix
+
+# Check mkForce without submodules.
+set -- config.enable ./declare-enable.nix ./define-enable.nix
+checkConfigOutput '^true$' "$@"
+checkConfigOutput '^false$' "$@" ./define-force-enable.nix
+checkConfigOutput '^false$' "$@" ./define-enable-force.nix
+
+# Check mkForce with option and submodules.
+checkConfigError 'attribute .*foo.* .* not found' config.attrsOfSub.foo.enable ./declare-attrsOfSub-any-enable.nix
+checkConfigOutput '^false$' config.attrsOfSub.foo.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix
+set -- config.attrsOfSub.foo.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo-enable.nix
+checkConfigOutput '^true$' "$@"
+checkConfigOutput '^false$' "$@" ./define-force-attrsOfSub-foo-enable.nix
+checkConfigOutput '^false$' "$@" ./define-attrsOfSub-force-foo-enable.nix
+checkConfigOutput '^false$' "$@" ./define-attrsOfSub-foo-force-enable.nix
+checkConfigOutput '^false$' "$@" ./define-attrsOfSub-foo-enable-force.nix
+
+# Check overriding effect of mkForce on submodule definitions.
+checkConfigError 'attribute .*bar.* .* not found' config.attrsOfSub.bar.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix
+checkConfigOutput '^false$' config.attrsOfSub.bar.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix ./define-attrsOfSub-bar.nix
+set -- config.attrsOfSub.bar.enable ./declare-attrsOfSub-any-enable.nix ./define-attrsOfSub-foo.nix ./define-attrsOfSub-bar-enable.nix
+checkConfigOutput '^true$' "$@"
+checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-force-attrsOfSub-foo-enable.nix
+checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-attrsOfSub-force-foo-enable.nix
+checkConfigOutput '^true$' "$@" ./define-attrsOfSub-foo-force-enable.nix
+checkConfigOutput '^true$' "$@" ./define-attrsOfSub-foo-enable-force.nix
+
+# Check mkIf with submodules.
+checkConfigError 'attribute .*foo.* .* not found' config.attrsOfSub.foo.enable ./declare-enable.nix ./declare-attrsOfSub-any-enable.nix
+set -- config.attrsOfSub.foo.enable ./declare-enable.nix ./declare-attrsOfSub-any-enable.nix
+checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-if-attrsOfSub-foo-enable.nix
+checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-attrsOfSub-if-foo-enable.nix
+checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-attrsOfSub-foo-if-enable.nix
+checkConfigOutput '^false$' "$@" ./define-attrsOfSub-foo-enable-if.nix
+checkConfigOutput '^true$' "$@" ./define-enable.nix ./define-if-attrsOfSub-foo-enable.nix
+checkConfigOutput '^true$' "$@" ./define-enable.nix ./define-attrsOfSub-if-foo-enable.nix
+checkConfigOutput '^true$' "$@" ./define-enable.nix ./define-attrsOfSub-foo-if-enable.nix
+checkConfigOutput '^true$' "$@" ./define-enable.nix ./define-attrsOfSub-foo-enable-if.nix
+
+# Check disabledModules with config definitions and option declarations.
+set -- config.enable ./define-enable.nix ./declare-enable.nix
+checkConfigOutput '^true$' "$@"
+checkConfigOutput '^false$' "$@" ./disable-define-enable.nix
+checkConfigOutput '^false$' "$@" ./disable-define-enable-string-path.nix
+checkConfigError "The option .*enable.* does not exist. Definition values:\n\s*- In .*: true" "$@" ./disable-declare-enable.nix
+checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix
+checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix
+
+checkConfigOutput '^true$' 'config.positive.enable' ./disable-module-with-key.nix
+checkConfigOutput '^false$' 'config.negative.enable' ./disable-module-with-key.nix
+checkConfigError 'Module ..*disable-module-bad-key.nix. contains a disabledModules item that is an attribute set, presumably a module, that does not have a .key. attribute. .*' 'config.enable' ./disable-module-bad-key.nix
+
+# Not sure if we want to keep supporting module keys that aren't strings, paths or v?key, but we shouldn't remove support accidentally.
+checkConfigOutput '^true$' 'config.positive.enable' ./disable-module-with-toString-key.nix
+checkConfigOutput '^false$' 'config.negative.enable' ./disable-module-with-toString-key.nix
+
+# Check _module.args.
+set -- config.enable ./declare-enable.nix ./define-enable-with-custom-arg.nix
+checkConfigError 'while evaluating the module argument .*custom.* in .*define-enable-with-custom-arg.nix.*:' "$@"
+checkConfigOutput '^true$' "$@" ./define-_module-args-custom.nix
+
+# Check that using _module.args on imports cause infinite recursions, with
+# the proper error context.
+set -- "$@" ./define-_module-args-custom.nix ./import-custom-arg.nix
+checkConfigError 'while evaluating the module argument .*custom.* in .*import-custom-arg.nix.*:' "$@"
+checkConfigError 'infinite recursion encountered' "$@"
+
+# Check _module.check.
+set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-attrsOfSub-foo.nix
+checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*' "$@"
+checkConfigOutput '^true$' "$@" ./define-module-check.nix
+
+# Check coerced value.
+set --
+checkConfigOutput '^"42"$' config.value ./declare-coerced-value.nix
+checkConfigOutput '^"24"$' config.value ./declare-coerced-value.nix ./define-value-string.nix
+checkConfigError 'A definition for option .* is not.*string or signed integer convertible to it.*. Definition values:\n\s*- In .*: \[ \]' config.value ./declare-coerced-value.nix ./define-value-list.nix
+
+# Check coerced value with unsound coercion
+checkConfigOutput '^12$' config.value ./declare-coerced-value-unsound.nix
+checkConfigError 'A definition for option .* is not of type .*. Definition values:\n\s*- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
+checkConfigError 'toInt: Could not convert .* to int' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
+
+# Check mkAliasOptionModule.
+checkConfigOutput '^true$' config.enable ./alias-with-priority.nix
+checkConfigOutput '^true$' config.enableAlias ./alias-with-priority.nix
+checkConfigOutput '^false$' config.enable ./alias-with-priority-can-override.nix
+checkConfigOutput '^false$' config.enableAlias ./alias-with-priority-can-override.nix
+
+# Check mkPackageOption
+checkConfigOutput '^"hello"$' config.package.pname ./declare-mkPackageOption.nix
+checkConfigError 'The option .undefinedPackage. is used but not defined' config.undefinedPackage ./declare-mkPackageOption.nix
+checkConfigOutput '^null$' config.nullablePackage ./declare-mkPackageOption.nix
+
+# submoduleWith
+
+## specialArgs should work
+checkConfigOutput '^"foo"$' config.submodule.foo ./declare-submoduleWith-special.nix
+
+## shorthandOnlyDefines config behaves as expected
+checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix
+checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix
+checkConfigError "You're trying to define a value of type \`bool'\n\s*rather than an attribute set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
+checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-noshorthand.nix
+
+## submoduleWith should merge all modules in one swoop
+checkConfigOutput '^true$' config.submodule.inner ./declare-submoduleWith-modules.nix
+checkConfigOutput '^true$' config.submodule.outer ./declare-submoduleWith-modules.nix
+# Should also be able to evaluate the type name (which evaluates freeformType,
+# which evaluates all the modules defined by the type)
+checkConfigOutput '^"submodule"$' options.submodule.type.description ./declare-submoduleWith-modules.nix
+
+## submodules can be declared using (evalModules {...}).type
+checkConfigOutput '^true$' config.submodule.inner ./declare-submodule-via-evalModules.nix
+checkConfigOutput '^true$' config.submodule.outer ./declare-submodule-via-evalModules.nix
+# Should also be able to evaluate the type name (which evaluates freeformType,
+# which evaluates all the modules defined by the type)
+checkConfigOutput '^"submodule"$' options.submodule.type.description ./declare-submodule-via-evalModules.nix
+
+## Paths should be allowed as values and work as expected
+checkConfigOutput '^true$' config.submodule.enable ./declare-submoduleWith-path.nix
+
+## deferredModule
+# default module is merged into nodes.foo
+checkConfigOutput '"beta"' config.nodes.foo.settingsDict.c ./deferred-module.nix
+# errors from the default module are reported with accurate location
+checkConfigError 'In `the-file-that-contains-the-bad-config.nix, via option default'\'': "bogus"' config.nodes.foo.bottom ./deferred-module.nix
+checkConfigError '.*lib/tests/modules/deferred-module-error.nix, via option deferred [(]:anon-1:anon-1:anon-1[)] does not look like a module.' config.result ./deferred-module-error.nix
+
+# Check the file location information is propagated into submodules
+checkConfigOutput the-file.nix config.submodule.internalFiles.0 ./submoduleFiles.nix
+
+
+# Check that disabledModules works recursively and correctly
+checkConfigOutput '^true$' config.enable ./disable-recursive/main.nix
+checkConfigOutput '^true$' config.enable ./disable-recursive/{main.nix,disable-foo.nix}
+checkConfigOutput '^true$' config.enable ./disable-recursive/{main.nix,disable-bar.nix}
+checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
+
+# Check that imports can depend on derivations
+checkConfigOutput '^true$' config.enable ./import-from-store.nix
+
+# Check that configs can be conditional on option existence
+checkConfigOutput '^true$' config.enable ./define-option-dependently.nix ./declare-enable.nix ./declare-int-positive-value.nix
+checkConfigOutput '^360$' config.value ./define-option-dependently.nix ./declare-enable.nix ./declare-int-positive-value.nix
+checkConfigOutput '^7$' config.value ./define-option-dependently.nix ./declare-int-positive-value.nix
+checkConfigOutput '^true$' config.set.enable ./define-option-dependently-nested.nix ./declare-enable-nested.nix ./declare-int-positive-value-nested.nix
+checkConfigOutput '^360$' config.set.value ./define-option-dependently-nested.nix ./declare-enable-nested.nix ./declare-int-positive-value-nested.nix
+checkConfigOutput '^7$' config.set.value ./define-option-dependently-nested.nix ./declare-int-positive-value-nested.nix
+
+# Check attrsOf and lazyAttrsOf. Only lazyAttrsOf should be lazy, and only
+# attrsOf should work with conditional definitions
+# In addition, lazyAttrsOf should honor an options emptyValue
+checkConfigError "is not lazy" config.isLazy ./declare-attrsOf.nix ./attrsOf-lazy-check.nix
+checkConfigOutput '^true$' config.isLazy ./declare-lazyAttrsOf.nix ./attrsOf-lazy-check.nix
+checkConfigOutput '^true$' config.conditionalWorks ./declare-attrsOf.nix ./attrsOf-conditional-check.nix
+checkConfigOutput '^false$' config.conditionalWorks ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
+checkConfigOutput '^"empty"$' config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
+
+
+# Even with multiple assignments, a type error should be thrown if any of them aren't valid
+checkConfigError 'A definition for option .* is not of type .*' \
+ config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix
+
+## Freeform modules
+# Assigning without a declared option should work
+checkConfigOutput '^"24"$' config.value ./freeform-attrsOf.nix ./define-value-string.nix
+# Shorthand modules interpret `meta` and `class` as config items
+checkConfigOutput '^true$' options._module.args.value.result ./freeform-attrsOf.nix ./define-freeform-keywords-shorthand.nix
+# No freeform assignments shouldn't make it error
+checkConfigOutput '^{ }$' config ./freeform-attrsOf.nix
+# but only if the type matches
+checkConfigError 'A definition for option .* is not of type .*' config.value ./freeform-attrsOf.nix ./define-value-list.nix
+# and properties should be applied
+checkConfigOutput '^"yes"$' config.value ./freeform-attrsOf.nix ./define-value-string-properties.nix
+# Options should still be declarable, and be able to have a type that doesn't match the freeform type
+checkConfigOutput '^false$' config.enable ./freeform-attrsOf.nix ./define-value-string.nix ./declare-enable.nix
+checkConfigOutput '^"24"$' config.value ./freeform-attrsOf.nix ./define-value-string.nix ./declare-enable.nix
+# and this should work too with nested values
+checkConfigOutput '^false$' config.nest.foo ./freeform-attrsOf.nix ./freeform-nested.nix
+checkConfigOutput '^"bar"$' config.nest.bar ./freeform-attrsOf.nix ./freeform-nested.nix
+# Check whether a declared option can depend on an freeform-typed one
+checkConfigOutput '^null$' config.foo ./freeform-attrsOf.nix ./freeform-str-dep-unstr.nix
+checkConfigOutput '^"24"$' config.foo ./freeform-attrsOf.nix ./freeform-str-dep-unstr.nix ./define-value-string.nix
+# Check whether an freeform-typed value can depend on a declared option, this can only work with lazyAttrsOf
+checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.nix ./freeform-unstr-dep-str.nix
+checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix
+checkConfigOutput '^"24"$' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix
+# submodules in freeformTypes should have their locations annotated
+checkConfigOutput '/freeform-submodules.nix"$' config.fooDeclarations.0 ./freeform-submodules.nix
+# freeformTypes can get merged using `types.type`, including submodules
+checkConfigOutput '^10$' config.free.xxx.foo ./freeform-submodules.nix
+checkConfigOutput '^10$' config.free.yyy.bar ./freeform-submodules.nix
+
+## types.anything
+# Check that attribute sets are merged recursively
+checkConfigOutput '^null$' config.value.foo ./types-anything/nested-attrs.nix
+checkConfigOutput '^null$' config.value.l1.foo ./types-anything/nested-attrs.nix
+checkConfigOutput '^null$' config.value.l1.l2.foo ./types-anything/nested-attrs.nix
+checkConfigOutput '^null$' config.value.l1.l2.l3.foo ./types-anything/nested-attrs.nix
+# Attribute sets that are coercible to strings shouldn't be recursed into
+checkConfigOutput '^"foo"$' config.value.outPath ./types-anything/attrs-coercible.nix
+# Multiple lists aren't concatenated together
+checkConfigError 'The option .* has conflicting definitions' config.value ./types-anything/lists.nix
+# Check that all equalizable atoms can be used as long as all definitions are equal
+checkConfigOutput '^0$' config.value.int ./types-anything/equal-atoms.nix
+checkConfigOutput '^false$' config.value.bool ./types-anything/equal-atoms.nix
+checkConfigOutput '^""$' config.value.string ./types-anything/equal-atoms.nix
+checkConfigOutput '^/$' config.value.path ./types-anything/equal-atoms.nix
+checkConfigOutput '^null$' config.value.null ./types-anything/equal-atoms.nix
+checkConfigOutput '^0.1$' config.value.float ./types-anything/equal-atoms.nix
+# Functions can't be merged together
+checkConfigError "The option .value.multiple-lambdas.<function body>. has conflicting option types" config.applied.multiple-lambdas ./types-anything/functions.nix
+checkConfigOutput '^<LAMBDA>$' config.value.single-lambda ./types-anything/functions.nix
+checkConfigOutput '^null$' config.applied.merging-lambdas.x ./types-anything/functions.nix
+checkConfigOutput '^null$' config.applied.merging-lambdas.y ./types-anything/functions.nix
+# Check that all mk* modifiers are applied
+checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix
+checkConfigOutput '^{ }$' config.value.mkiftrue ./types-anything/mk-mods.nix
+checkConfigOutput '^1$' config.value.mkdefault ./types-anything/mk-mods.nix
+checkConfigOutput '^{ }$' config.value.mkmerge ./types-anything/mk-mods.nix
+checkConfigOutput '^true$' config.value.mkbefore ./types-anything/mk-mods.nix
+checkConfigOutput '^1$' config.value.nested.foo ./types-anything/mk-mods.nix
+checkConfigOutput '^"baz"$' config.value.nested.bar.baz ./types-anything/mk-mods.nix
+
+## types.functionTo
+checkConfigOutput '^"input is input"$' config.result ./functionTo/trivial.nix
+checkConfigOutput '^"a b"$' config.result ./functionTo/merging-list.nix
+checkConfigError 'A definition for option .fun.<function body>. is not of type .string.. Definition values:\n\s*- In .*wrong-type.nix' config.result ./functionTo/wrong-type.nix
+checkConfigOutput '^"b a"$' config.result ./functionTo/list-order.nix
+checkConfigOutput '^"a c"$' config.result ./functionTo/merging-attrs.nix
+checkConfigOutput '^"a bee"$' config.result ./functionTo/submodule-options.nix
+checkConfigOutput '^"fun.<function body>.a fun.<function body>.b"$' config.optionsResult ./functionTo/submodule-options.nix
+
+# moduleType
+checkConfigOutput '^"a b"$' config.resultFoo ./declare-variants.nix ./define-variant.nix
+checkConfigOutput '^"a b y z"$' config.resultFooBar ./declare-variants.nix ./define-variant.nix
+checkConfigOutput '^"a b c"$' config.resultFooFoo ./declare-variants.nix ./define-variant.nix
+
+## emptyValue's
+checkConfigOutput "[ ]" config.list.a ./emptyValues.nix
+checkConfigOutput "{ }" config.attrs.a ./emptyValues.nix
+checkConfigOutput "null" config.null.a ./emptyValues.nix
+checkConfigOutput "{ }" config.submodule.a ./emptyValues.nix
+# These types don't have empty values
+checkConfigError 'The option .int.a. is used but not defined' config.int.a ./emptyValues.nix
+checkConfigError 'The option .nonEmptyList.a. is used but not defined' config.nonEmptyList.a ./emptyValues.nix
+
+## types.raw
+checkConfigOutput "{ foo = <CODE>; }" config.unprocessedNesting ./raw.nix
+checkConfigOutput "10" config.processedToplevel ./raw.nix
+checkConfigError "The option .multiple. is defined multiple times" config.multiple ./raw.nix
+checkConfigOutput "bar" config.priorities ./raw.nix
+
+## Option collision
+checkConfigError \
+ 'The option .set. in module .*/declare-set.nix. would be a parent of the following options, but its type .attribute set of signed integer. does not support nested options.\n\s*- option[(]s[)] with prefix .set.enable. in module .*/declare-enable-nested.nix.' \
+ config.set \
+ ./declare-set.nix ./declare-enable-nested.nix
+
+# Test that types.optionType merges types correctly
+checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
+checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix
+
+# Test that types.optionType correctly annotates option locations
+checkConfigError 'The option .theOption.nested. in .other.nix. is already declared in .optionTypeFile.nix.' config.theOption.nested ./optionTypeFile.nix
+
+# Test that types.optionType leaves types untouched as long as they don't need to be merged
+checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survives-type-merge.nix
+
+# Anonymous submodules don't get nixed by import resolution/deduplication
+# because of an `extendModules` bug, issue 168767.
+checkConfigOutput '^1$' config.sub.specialisation.value ./extendModules-168767-imports.nix
+
+# Class checks, evalModules
+checkConfigOutput '^{ }$' config.ok.config ./class-check.nix
+checkConfigOutput '"nixos"' config.ok.class ./class-check.nix
+checkConfigError 'The module .*/module-class-is-darwin.nix was imported into nixos instead of darwin.' config.fail.config ./class-check.nix
+checkConfigError 'The module foo.nix#darwinModules.default was imported into nixos instead of darwin.' config.fail-anon.config ./class-check.nix
+
+# Class checks, submoduleWith
+checkConfigOutput '^{ }$' config.sub.nixosOk ./class-check.nix
+checkConfigError 'The module .*/module-class-is-darwin.nix was imported into nixos instead of darwin.' config.sub.nixosFail.config ./class-check.nix
+
+# submoduleWith type merge with different class
+checkConfigError 'A submoduleWith option is declared multiple times with conflicting class values "darwin" and "nixos".' config.sub.mergeFail.config ./class-check.nix
+
+# _type check
+checkConfigError 'Could not load a value as a module, because it is of type "flake", in file .*/module-imports-_type-check.nix' config.ok.config ./module-imports-_type-check.nix
+checkConfigOutput '^true$' "$@" config.enable ./declare-enable.nix ./define-enable-with-top-level-mkIf.nix
+checkConfigError 'Could not load a value as a module, because it is of type "configuration", in file .*/import-configuration.nix.*please only import the modules that make up the configuration.*' config ./import-configuration.nix
+
+# doRename works when `warnings` does not exist.
+checkConfigOutput '^1234$' config.c.d.e ./doRename-basic.nix
+# doRename adds a warning.
+checkConfigOutput '^"The option `a\.b. defined in `.*/doRename-warnings\.nix. has been renamed to `c\.d\.e.\."$' \
+ config.result \
+ ./doRename-warnings.nix
+
+# Anonymous modules get deduplicated by key
+checkConfigOutput '^"pear"$' config.once.raw ./merge-module-with-key.nix
+checkConfigOutput '^"pear\\npear"$' config.twice.raw ./merge-module-with-key.nix
+
+cat <<EOF
+====== module tests ======
+$pass Pass
+$fail Fail
+EOF
+
+if [ "$fail" -ne 0 ]; then
+ exit 1
+fi
+exit 0
diff --git a/tests/modules/adhoc-freeformType-survives-type-merge.nix b/tests/modules/adhoc-freeformType-survives-type-merge.nix
new file mode 100644
index 000000000..3cefb543c
--- /dev/null
+++ b/tests/modules/adhoc-freeformType-survives-type-merge.nix
@@ -0,0 +1,14 @@
+{ lib, ... }: {
+ options.dummy = lib.mkOption { type = lib.types.anything; default = {}; };
+ freeformType =
+ let
+ a = lib.types.attrsOf (lib.types.submodule { options.bar = lib.mkOption { }; });
+ in
+ # modifying types like this breaks type merging.
+ # This test makes sure that type merging is not performed when only a single declaration exists.
+ # Don't modify types in practice!
+ a // {
+ merge = loc: defs: { freeformItems = a.merge loc defs; };
+ };
+ config.foo.bar = "ok";
+}
diff --git a/tests/modules/alias-with-priority-can-override.nix b/tests/modules/alias-with-priority-can-override.nix
new file mode 100644
index 000000000..9a18c9d9f
--- /dev/null
+++ b/tests/modules/alias-with-priority-can-override.nix
@@ -0,0 +1,55 @@
+# This is a test to show that mkAliasOptionModule sets the priority correctly
+# for aliased options.
+#
+# This test shows that an alias with a high priority is able to override
+# a non-aliased option.
+
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options = {
+ # A simple boolean option that can be enabled or disabled.
+ enable = lib.mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = ''
+ Some descriptive text
+ '';
+ };
+
+ # mkAliasOptionModule sets warnings, so this has to be defined.
+ warnings = mkOption {
+ internal = true;
+ default = [];
+ type = types.listOf types.str;
+ example = [ "The `foo' service is deprecated and will go away soon!" ];
+ description = ''
+ This option allows modules to show warnings to users during
+ the evaluation of the system configuration.
+ '';
+ };
+ };
+
+ imports = [
+ # Create an alias for the "enable" option.
+ (mkAliasOptionModule [ "enableAlias" ] [ "enable" ])
+
+ # Disable the aliased option with a high priority so it
+ # should override the next import.
+ ( { config, lib, ... }:
+ {
+ enableAlias = lib.mkForce false;
+ }
+ )
+
+ # Enable the normal (non-aliased) option.
+ ( { config, lib, ... }:
+ {
+ enable = true;
+ }
+ )
+ ];
+}
diff --git a/tests/modules/alias-with-priority.nix b/tests/modules/alias-with-priority.nix
new file mode 100644
index 000000000..a35a06fc6
--- /dev/null
+++ b/tests/modules/alias-with-priority.nix
@@ -0,0 +1,55 @@
+# This is a test to show that mkAliasOptionModule sets the priority correctly
+# for aliased options.
+#
+# This test shows that an alias with a low priority is able to be overridden
+# with a non-aliased option.
+
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options = {
+ # A simple boolean option that can be enabled or disabled.
+ enable = lib.mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = ''
+ Some descriptive text
+ '';
+ };
+
+ # mkAliasOptionModule sets warnings, so this has to be defined.
+ warnings = mkOption {
+ internal = true;
+ default = [];
+ type = types.listOf types.str;
+ example = [ "The `foo' service is deprecated and will go away soon!" ];
+ description = ''
+ This option allows modules to show warnings to users during
+ the evaluation of the system configuration.
+ '';
+ };
+ };
+
+ imports = [
+ # Create an alias for the "enable" option.
+ (mkAliasOptionModule [ "enableAlias" ] [ "enable" ])
+
+ # Disable the aliased option, but with a default (low) priority so it
+ # should be able to be overridden by the next import.
+ ( { config, lib, ... }:
+ {
+ enableAlias = lib.mkDefault false;
+ }
+ )
+
+ # Enable the normal (non-aliased) option.
+ ( { config, lib, ... }:
+ {
+ enable = true;
+ }
+ )
+ ];
+}
diff --git a/tests/modules/attrsOf-conditional-check.nix b/tests/modules/attrsOf-conditional-check.nix
new file mode 100644
index 000000000..0f00ebca1
--- /dev/null
+++ b/tests/modules/attrsOf-conditional-check.nix
@@ -0,0 +1,7 @@
+{ lib, config, ... }: {
+ options.conditionalWorks = lib.mkOption {
+ default = ! config.value ? foo;
+ };
+
+ config.value.foo = lib.mkIf false "should not be defined";
+}
diff --git a/tests/modules/attrsOf-lazy-check.nix b/tests/modules/attrsOf-lazy-check.nix
new file mode 100644
index 000000000..ec5b418b1
--- /dev/null
+++ b/tests/modules/attrsOf-lazy-check.nix
@@ -0,0 +1,7 @@
+{ lib, config, ... }: {
+ options.isLazy = lib.mkOption {
+ default = ! config.value ? foo;
+ };
+
+ config.value.bar = throw "is not lazy";
+}
diff --git a/tests/modules/class-check.nix b/tests/modules/class-check.nix
new file mode 100644
index 000000000..293fd4abd
--- /dev/null
+++ b/tests/modules/class-check.nix
@@ -0,0 +1,76 @@
+{ lib, ... }: {
+ options = {
+ sub = {
+ nixosOk = lib.mkOption {
+ type = lib.types.submoduleWith {
+ class = "nixos";
+ modules = [ ];
+ };
+ };
+ # Same but will have bad definition
+ nixosFail = lib.mkOption {
+ type = lib.types.submoduleWith {
+ class = "nixos";
+ modules = [ ];
+ };
+ };
+
+ mergeFail = lib.mkOption {
+ type = lib.types.submoduleWith {
+ class = "nixos";
+ modules = [ ];
+ };
+ default = { };
+ };
+ };
+ };
+ imports = [
+ {
+ options = {
+ sub = {
+ mergeFail = lib.mkOption {
+ type = lib.types.submoduleWith {
+ class = "darwin";
+ modules = [ ];
+ };
+ };
+ };
+ };
+ }
+ ];
+ config = {
+ _module.freeformType = lib.types.anything;
+ ok =
+ lib.evalModules {
+ class = "nixos";
+ modules = [
+ ./module-class-is-nixos.nix
+ ];
+ };
+
+ fail =
+ lib.evalModules {
+ class = "nixos";
+ modules = [
+ ./module-class-is-nixos.nix
+ ./module-class-is-darwin.nix
+ ];
+ };
+
+ fail-anon =
+ lib.evalModules {
+ class = "nixos";
+ modules = [
+ ./module-class-is-nixos.nix
+ { _file = "foo.nix#darwinModules.default";
+ _class = "darwin";
+ config = {};
+ imports = [];
+ }
+ ];
+ };
+
+ sub.nixosOk = { _class = "nixos"; };
+ sub.nixosFail = { imports = [ ./module-class-is-darwin.nix ]; };
+ };
+}
diff --git a/tests/modules/declare-attrsOf.nix b/tests/modules/declare-attrsOf.nix
new file mode 100644
index 000000000..d19964064
--- /dev/null
+++ b/tests/modules/declare-attrsOf.nix
@@ -0,0 +1,13 @@
+{ lib, ... }:
+let
+ deathtrapArgs = lib.mapAttrs
+ (k: _: throw "The module system is too strict, accessing an unused option's ${k} mkOption-attribute.")
+ (lib.functionArgs lib.mkOption);
+in
+{
+ options.value = lib.mkOption {
+ type = lib.types.attrsOf lib.types.str;
+ default = {};
+ };
+ options.testing-laziness-so-don't-read-me = lib.mkOption deathtrapArgs;
+}
diff --git a/tests/modules/declare-attrsOfSub-any-enable.nix b/tests/modules/declare-attrsOfSub-any-enable.nix
new file mode 100644
index 000000000..986d07227
--- /dev/null
+++ b/tests/modules/declare-attrsOfSub-any-enable.nix
@@ -0,0 +1,29 @@
+{ lib, ... }:
+
+let
+ submod = { ... }: {
+ options = {
+ enable = lib.mkOption {
+ default = false;
+ example = true;
+ type = lib.types.bool;
+ description = ''
+ Some descriptive text
+ '';
+ };
+ };
+ };
+in
+
+{
+ options = {
+ attrsOfSub = lib.mkOption {
+ default = {};
+ example = {};
+ type = lib.types.attrsOf (lib.types.submodule [ submod ]);
+ description = ''
+ Some descriptive text
+ '';
+ };
+ };
+}
diff --git a/tests/modules/declare-bare-submodule-deep-option-duplicate.nix b/tests/modules/declare-bare-submodule-deep-option-duplicate.nix
new file mode 100644
index 000000000..06ad1f6e0
--- /dev/null
+++ b/tests/modules/declare-bare-submodule-deep-option-duplicate.nix
@@ -0,0 +1,10 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+in
+{
+ options.bare-submodule.deep = mkOption {
+ type = types.int;
+ default = 2;
+ };
+}
diff --git a/tests/modules/declare-bare-submodule-deep-option.nix b/tests/modules/declare-bare-submodule-deep-option.nix
new file mode 100644
index 000000000..06ad1f6e0
--- /dev/null
+++ b/tests/modules/declare-bare-submodule-deep-option.nix
@@ -0,0 +1,10 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+in
+{
+ options.bare-submodule.deep = mkOption {
+ type = types.int;
+ default = 2;
+ };
+}
diff --git a/tests/modules/declare-bare-submodule-nested-option.nix b/tests/modules/declare-bare-submodule-nested-option.nix
new file mode 100644
index 000000000..da125c84b
--- /dev/null
+++ b/tests/modules/declare-bare-submodule-nested-option.nix
@@ -0,0 +1,19 @@
+{ config, lib, ... }:
+let
+ inherit (lib) mkOption types;
+in
+{
+ options.bare-submodule = mkOption {
+ type = types.submoduleWith {
+ shorthandOnlyDefinesConfig = config.shorthandOnlyDefinesConfig;
+ modules = [
+ {
+ options.nested = mkOption {
+ type = types.int;
+ default = 1;
+ };
+ }
+ ];
+ };
+ };
+}
diff --git a/tests/modules/declare-bare-submodule.nix b/tests/modules/declare-bare-submodule.nix
new file mode 100644
index 000000000..5402f4ff5
--- /dev/null
+++ b/tests/modules/declare-bare-submodule.nix
@@ -0,0 +1,18 @@
+{ config, lib, ... }:
+let
+ inherit (lib) mkOption types;
+in
+{
+ options.bare-submodule = mkOption {
+ type = types.submoduleWith {
+ modules = [ ];
+ shorthandOnlyDefinesConfig = config.shorthandOnlyDefinesConfig;
+ };
+ default = {};
+ };
+
+ # config-dependent options: won't recommend, but useful for making this test parameterized
+ options.shorthandOnlyDefinesConfig = mkOption {
+ default = false;
+ };
+}
diff --git a/tests/modules/declare-coerced-value-unsound.nix b/tests/modules/declare-coerced-value-unsound.nix
new file mode 100644
index 000000000..7a017f24e
--- /dev/null
+++ b/tests/modules/declare-coerced-value-unsound.nix
@@ -0,0 +1,10 @@
+{ lib, ... }:
+
+{
+ options = {
+ value = lib.mkOption {
+ default = "12";
+ type = lib.types.coercedTo lib.types.str lib.toInt lib.types.ints.s8;
+ };
+ };
+}
diff --git a/tests/modules/declare-coerced-value.nix b/tests/modules/declare-coerced-value.nix
new file mode 100644
index 000000000..76b12ad53
--- /dev/null
+++ b/tests/modules/declare-coerced-value.nix
@@ -0,0 +1,10 @@
+{ lib, ... }:
+
+{
+ options = {
+ value = lib.mkOption {
+ default = 42;
+ type = lib.types.coercedTo lib.types.int builtins.toString lib.types.str;
+ };
+ };
+}
diff --git a/tests/modules/declare-either.nix b/tests/modules/declare-either.nix
new file mode 100644
index 000000000..5a0fa978a
--- /dev/null
+++ b/tests/modules/declare-either.nix
@@ -0,0 +1,5 @@
+{ lib, ... }: {
+ options.value = lib.mkOption {
+ type = lib.types.either lib.types.int lib.types.str;
+ };
+}
diff --git a/tests/modules/declare-enable-nested.nix b/tests/modules/declare-enable-nested.nix
new file mode 100644
index 000000000..c8da8273c
--- /dev/null
+++ b/tests/modules/declare-enable-nested.nix
@@ -0,0 +1,14 @@
+{ lib, ... }:
+
+{
+ options.set = {
+ enable = lib.mkOption {
+ default = false;
+ example = true;
+ type = lib.types.bool;
+ description = ''
+ Some descriptive text
+ '';
+ };
+ };
+}
diff --git a/tests/modules/declare-enable.nix b/tests/modules/declare-enable.nix
new file mode 100644
index 000000000..ebee243c7
--- /dev/null
+++ b/tests/modules/declare-enable.nix
@@ -0,0 +1,14 @@
+{ lib, ... }:
+
+{
+ options = {
+ enable = lib.mkOption {
+ default = false;
+ example = true;
+ type = lib.types.bool;
+ description = ''
+ Some descriptive text
+ '';
+ };
+ };
+}
diff --git a/tests/modules/declare-int-between-value.nix b/tests/modules/declare-int-between-value.nix
new file mode 100644
index 000000000..8b2624cc5
--- /dev/null
+++ b/tests/modules/declare-int-between-value.nix
@@ -0,0 +1,9 @@
+{ lib, ... }:
+
+{
+ options = {
+ value = lib.mkOption {
+ type = lib.types.ints.between (-21) 43;
+ };
+ };
+}
diff --git a/tests/modules/declare-int-positive-value-nested.nix b/tests/modules/declare-int-positive-value-nested.nix
new file mode 100644
index 000000000..72d2fb89f
--- /dev/null
+++ b/tests/modules/declare-int-positive-value-nested.nix
@@ -0,0 +1,9 @@
+{ lib, ... }:
+
+{
+ options.set = {
+ value = lib.mkOption {
+ type = lib.types.ints.positive;
+ };
+ };
+}
diff --git a/tests/modules/declare-int-positive-value.nix b/tests/modules/declare-int-positive-value.nix
new file mode 100644
index 000000000..6e48c6ac8
--- /dev/null
+++ b/tests/modules/declare-int-positive-value.nix
@@ -0,0 +1,9 @@
+{ lib, ... }:
+
+{
+ options = {
+ value = lib.mkOption {
+ type = lib.types.ints.positive;
+ };
+ };
+}
diff --git a/tests/modules/declare-int-unsigned-value.nix b/tests/modules/declare-int-unsigned-value.nix
new file mode 100644
index 000000000..05d0eff01
--- /dev/null
+++ b/tests/modules/declare-int-unsigned-value.nix
@@ -0,0 +1,9 @@
+{ lib, ... }:
+
+{
+ options = {
+ value = lib.mkOption {
+ type = lib.types.ints.unsigned;
+ };
+ };
+}
diff --git a/tests/modules/declare-lazyAttrsOf.nix b/tests/modules/declare-lazyAttrsOf.nix
new file mode 100644
index 000000000..1d9fec25f
--- /dev/null
+++ b/tests/modules/declare-lazyAttrsOf.nix
@@ -0,0 +1,6 @@
+{ lib, ... }: {
+ options.value = lib.mkOption {
+ type = lib.types.lazyAttrsOf (lib.types.str // { emptyValue.value = "empty"; });
+ default = {};
+ };
+}
diff --git a/tests/modules/declare-mkPackageOption.nix b/tests/modules/declare-mkPackageOption.nix
new file mode 100644
index 000000000..640b19a7b
--- /dev/null
+++ b/tests/modules/declare-mkPackageOption.nix
@@ -0,0 +1,19 @@
+{ lib, ... }: let
+ pkgs.hello = {
+ type = "derivation";
+ pname = "hello";
+ };
+in {
+ options = {
+ package = lib.mkPackageOption pkgs "hello" { };
+
+ undefinedPackage = lib.mkPackageOption pkgs "hello" {
+ default = null;
+ };
+
+ nullablePackage = lib.mkPackageOption pkgs "hello" {
+ nullable = true;
+ default = null;
+ };
+ };
+}
diff --git a/tests/modules/declare-oneOf.nix b/tests/modules/declare-oneOf.nix
new file mode 100644
index 000000000..df092a14f
--- /dev/null
+++ b/tests/modules/declare-oneOf.nix
@@ -0,0 +1,9 @@
+{ lib, ... }: {
+ options.value = lib.mkOption {
+ type = lib.types.oneOf [
+ lib.types.int
+ (lib.types.listOf lib.types.int)
+ lib.types.str
+ ];
+ };
+}
diff --git a/tests/modules/declare-set.nix b/tests/modules/declare-set.nix
new file mode 100644
index 000000000..853418531
--- /dev/null
+++ b/tests/modules/declare-set.nix
@@ -0,0 +1,12 @@
+{ lib, ... }:
+
+{
+ options.set = lib.mkOption {
+ default = { };
+ example = { a = 1; };
+ type = lib.types.attrsOf lib.types.int;
+ description = ''
+ Some descriptive text
+ '';
+ };
+}
diff --git a/tests/modules/declare-submodule-via-evalModules.nix b/tests/modules/declare-submodule-via-evalModules.nix
new file mode 100644
index 000000000..2841c64a0
--- /dev/null
+++ b/tests/modules/declare-submodule-via-evalModules.nix
@@ -0,0 +1,28 @@
+{ lib, ... }: {
+ options.submodule = lib.mkOption {
+ inherit (lib.evalModules {
+ modules = [
+ {
+ options.inner = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ }
+ ];
+ }) type;
+ default = {};
+ };
+
+ config.submodule = lib.mkMerge [
+ ({ lib, ... }: {
+ options.outer = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ })
+ {
+ inner = true;
+ outer = true;
+ }
+ ];
+}
diff --git a/tests/modules/declare-submoduleWith-modules.nix b/tests/modules/declare-submoduleWith-modules.nix
new file mode 100644
index 000000000..a8b82d176
--- /dev/null
+++ b/tests/modules/declare-submoduleWith-modules.nix
@@ -0,0 +1,28 @@
+{ lib, ... }: {
+ options.submodule = lib.mkOption {
+ type = lib.types.submoduleWith {
+ modules = [
+ {
+ options.inner = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ }
+ ];
+ };
+ default = {};
+ };
+
+ config.submodule = lib.mkMerge [
+ ({ lib, ... }: {
+ options.outer = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ })
+ {
+ inner = true;
+ outer = true;
+ }
+ ];
+}
diff --git a/tests/modules/declare-submoduleWith-noshorthand.nix b/tests/modules/declare-submoduleWith-noshorthand.nix
new file mode 100644
index 000000000..af3b4ba47
--- /dev/null
+++ b/tests/modules/declare-submoduleWith-noshorthand.nix
@@ -0,0 +1,13 @@
+{ lib, ... }: let
+ sub.options.config = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+in {
+ options.submodule = lib.mkOption {
+ type = lib.types.submoduleWith {
+ modules = [ sub ];
+ };
+ default = {};
+ };
+}
diff --git a/tests/modules/declare-submoduleWith-path.nix b/tests/modules/declare-submoduleWith-path.nix
new file mode 100644
index 000000000..477647f32
--- /dev/null
+++ b/tests/modules/declare-submoduleWith-path.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+ options.submodule = lib.mkOption {
+ type = lib.types.submoduleWith {
+ modules = [
+ ./declare-enable.nix
+ ];
+ };
+ default = {};
+ };
+
+ config.submodule = ./define-enable.nix;
+}
diff --git a/tests/modules/declare-submoduleWith-shorthand.nix b/tests/modules/declare-submoduleWith-shorthand.nix
new file mode 100644
index 000000000..63ac16293
--- /dev/null
+++ b/tests/modules/declare-submoduleWith-shorthand.nix
@@ -0,0 +1,14 @@
+{ lib, ... }: let
+ sub.options.config = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+in {
+ options.submodule = lib.mkOption {
+ type = lib.types.submoduleWith {
+ modules = [ sub ];
+ shorthandOnlyDefinesConfig = true;
+ };
+ default = {};
+ };
+}
diff --git a/tests/modules/declare-submoduleWith-special.nix b/tests/modules/declare-submoduleWith-special.nix
new file mode 100644
index 000000000..6b15c5bde
--- /dev/null
+++ b/tests/modules/declare-submoduleWith-special.nix
@@ -0,0 +1,17 @@
+{ lib, ... }: {
+ options.submodule = lib.mkOption {
+ type = lib.types.submoduleWith {
+ modules = [
+ ({ lib, ... }: {
+ options.foo = lib.mkOption {
+ default = lib.foo;
+ };
+ })
+ ];
+ specialArgs.lib = lib // {
+ foo = "foo";
+ };
+ };
+ default = {};
+ };
+}
diff --git a/tests/modules/declare-variants.nix b/tests/modules/declare-variants.nix
new file mode 100644
index 000000000..3ed6fa689
--- /dev/null
+++ b/tests/modules/declare-variants.nix
@@ -0,0 +1,9 @@
+{ lib, moduleType, ... }:
+let inherit (lib) mkOption types;
+in
+{
+ options.variants = mkOption {
+ type = types.lazyAttrsOf moduleType;
+ default = {};
+ };
+}
diff --git a/tests/modules/default.nix b/tests/modules/default.nix
new file mode 100644
index 000000000..5b0947104
--- /dev/null
+++ b/tests/modules/default.nix
@@ -0,0 +1,8 @@
+{ lib ? import ../.., modules ? [] }:
+
+{
+ inherit (lib.evalModules {
+ inherit modules;
+ specialArgs.modulesPath = ./.;
+ }) config options;
+}
diff --git a/tests/modules/deferred-module-error.nix b/tests/modules/deferred-module-error.nix
new file mode 100644
index 000000000..d48ae092e
--- /dev/null
+++ b/tests/modules/deferred-module-error.nix
@@ -0,0 +1,20 @@
+{ config, lib, ... }:
+let
+ inherit (lib) types mkOption setDefaultModuleLocation evalModules;
+ inherit (types) deferredModule lazyAttrsOf submodule str raw enum;
+in
+{
+ options = {
+ deferred = mkOption {
+ type = deferredModule;
+ };
+ result = mkOption {
+ default = (evalModules { modules = [ config.deferred ]; }).config.result;
+ };
+ };
+ config = {
+ deferred = { ... }:
+ # this should be an attrset, so this fails
+ true;
+ };
+}
diff --git a/tests/modules/deferred-module.nix b/tests/modules/deferred-module.nix
new file mode 100644
index 000000000..d03c60b02
--- /dev/null
+++ b/tests/modules/deferred-module.nix
@@ -0,0 +1,58 @@
+{ lib, ... }:
+let
+ inherit (lib) types mkOption setDefaultModuleLocation;
+ inherit (types) deferredModule lazyAttrsOf submodule str raw enum;
+in
+{
+ imports = [
+ # generic module, declaring submodules:
+ # - nodes.<name>
+ # - default
+ # where all nodes include the default
+ ({ config, ... }: {
+ _file = "generic.nix";
+ options.nodes = mkOption {
+ type = lazyAttrsOf (submodule { imports = [ config.default ]; });
+ default = {};
+ };
+ options.default = mkOption {
+ type = deferredModule;
+ default = { };
+ description = ''
+ Module that is included in all nodes.
+ '';
+ };
+ })
+
+ {
+ _file = "default-1.nix";
+ default = { config, ... }: {
+ options.settingsDict = lib.mkOption { type = lazyAttrsOf str; default = {}; };
+ options.bottom = lib.mkOption { type = enum []; };
+ };
+ }
+
+ {
+ _file = "default-a-is-b.nix";
+ default = ./define-settingsDict-a-is-b.nix;
+ }
+
+ {
+ _file = "nodes-foo.nix";
+ nodes.foo.settingsDict.b = "beta";
+ }
+
+ {
+ _file = "the-file-that-contains-the-bad-config.nix";
+ default.bottom = "bogus";
+ }
+
+ {
+ _file = "nodes-foo-c-is-a.nix";
+ nodes.foo = { config, ... }: {
+ settingsDict.c = config.settingsDict.a;
+ };
+ }
+
+ ];
+}
diff --git a/tests/modules/define-_module-args-custom.nix b/tests/modules/define-_module-args-custom.nix
new file mode 100644
index 000000000..e565fd215
--- /dev/null
+++ b/tests/modules/define-_module-args-custom.nix
@@ -0,0 +1,7 @@
+{ lib, ... }:
+
+{
+ config = {
+ _module.args.custom = true;
+ };
+}
diff --git a/tests/modules/define-attrsOfSub-bar-enable.nix b/tests/modules/define-attrsOfSub-bar-enable.nix
new file mode 100644
index 000000000..99c55d8b3
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-bar-enable.nix
@@ -0,0 +1,3 @@
+{
+ attrsOfSub.bar.enable = true;
+}
diff --git a/tests/modules/define-attrsOfSub-bar.nix b/tests/modules/define-attrsOfSub-bar.nix
new file mode 100644
index 000000000..2a33068a5
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-bar.nix
@@ -0,0 +1,3 @@
+{
+ attrsOfSub.bar = {};
+}
diff --git a/tests/modules/define-attrsOfSub-foo-enable-force.nix b/tests/modules/define-attrsOfSub-foo-enable-force.nix
new file mode 100644
index 000000000..c9ee36446
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-foo-enable-force.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+ attrsOfSub.foo.enable = lib.mkForce false;
+}
diff --git a/tests/modules/define-attrsOfSub-foo-enable-if.nix b/tests/modules/define-attrsOfSub-foo-enable-if.nix
new file mode 100644
index 000000000..0b3baddb5
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-foo-enable-if.nix
@@ -0,0 +1,5 @@
+{ config, lib, ... }:
+
+{
+ attrsOfSub.foo.enable = lib.mkIf config.enable true;
+}
diff --git a/tests/modules/define-attrsOfSub-foo-enable.nix b/tests/modules/define-attrsOfSub-foo-enable.nix
new file mode 100644
index 000000000..39cd63cef
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-foo-enable.nix
@@ -0,0 +1,3 @@
+{
+ attrsOfSub.foo.enable = true;
+}
diff --git a/tests/modules/define-attrsOfSub-foo-force-enable.nix b/tests/modules/define-attrsOfSub-foo-force-enable.nix
new file mode 100644
index 000000000..009da7c77
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-foo-force-enable.nix
@@ -0,0 +1,7 @@
+{ lib, ... }:
+
+{
+ attrsOfSub.foo = lib.mkForce {
+ enable = false;
+ };
+}
diff --git a/tests/modules/define-attrsOfSub-foo-if-enable.nix b/tests/modules/define-attrsOfSub-foo-if-enable.nix
new file mode 100644
index 000000000..93702dfa8
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-foo-if-enable.nix
@@ -0,0 +1,7 @@
+{ config, lib, ... }:
+
+{
+ attrsOfSub.foo = lib.mkIf config.enable {
+ enable = true;
+ };
+}
diff --git a/tests/modules/define-attrsOfSub-foo.nix b/tests/modules/define-attrsOfSub-foo.nix
new file mode 100644
index 000000000..e6bb531de
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-foo.nix
@@ -0,0 +1,3 @@
+{
+ attrsOfSub.foo = {};
+}
diff --git a/tests/modules/define-attrsOfSub-force-foo-enable.nix b/tests/modules/define-attrsOfSub-force-foo-enable.nix
new file mode 100644
index 000000000..5c02dd343
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-force-foo-enable.nix
@@ -0,0 +1,7 @@
+{ lib, ... }:
+
+{
+ attrsOfSub = lib.mkForce {
+ foo.enable = false;
+ };
+}
diff --git a/tests/modules/define-attrsOfSub-if-foo-enable.nix b/tests/modules/define-attrsOfSub-if-foo-enable.nix
new file mode 100644
index 000000000..a3fe6051d
--- /dev/null
+++ b/tests/modules/define-attrsOfSub-if-foo-enable.nix
@@ -0,0 +1,7 @@
+{ config, lib, ... }:
+
+{
+ attrsOfSub = lib.mkIf config.enable {
+ foo.enable = true;
+ };
+}
diff --git a/tests/modules/define-bare-submodule-values.nix b/tests/modules/define-bare-submodule-values.nix
new file mode 100644
index 000000000..00ede929e
--- /dev/null
+++ b/tests/modules/define-bare-submodule-values.nix
@@ -0,0 +1,4 @@
+{
+ bare-submodule.nested = 42;
+ bare-submodule.deep = 420;
+}
diff --git a/tests/modules/define-enable-abort.nix b/tests/modules/define-enable-abort.nix
new file mode 100644
index 000000000..85b58a567
--- /dev/null
+++ b/tests/modules/define-enable-abort.nix
@@ -0,0 +1,3 @@
+{
+ config.enable = abort "oops";
+}
diff --git a/tests/modules/define-enable-force.nix b/tests/modules/define-enable-force.nix
new file mode 100644
index 000000000..f4990a328
--- /dev/null
+++ b/tests/modules/define-enable-force.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+ enable = lib.mkForce false;
+}
diff --git a/tests/modules/define-enable-throw.nix b/tests/modules/define-enable-throw.nix
new file mode 100644
index 000000000..16a59b781
--- /dev/null
+++ b/tests/modules/define-enable-throw.nix
@@ -0,0 +1,3 @@
+{
+ config.enable = throw "oops";
+}
diff --git a/tests/modules/define-enable-with-custom-arg.nix b/tests/modules/define-enable-with-custom-arg.nix
new file mode 100644
index 000000000..7da74671d
--- /dev/null
+++ b/tests/modules/define-enable-with-custom-arg.nix
@@ -0,0 +1,7 @@
+{ lib, custom, ... }:
+
+{
+ config = {
+ enable = custom;
+ };
+}
diff --git a/tests/modules/define-enable-with-top-level-mkIf.nix b/tests/modules/define-enable-with-top-level-mkIf.nix
new file mode 100644
index 000000000..4909c16d8
--- /dev/null
+++ b/tests/modules/define-enable-with-top-level-mkIf.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+# I think this might occur more realistically in a submodule
+{
+ imports = [ (lib.mkIf true { enable = true; }) ];
+}
diff --git a/tests/modules/define-enable.nix b/tests/modules/define-enable.nix
new file mode 100644
index 000000000..7dc26010a
--- /dev/null
+++ b/tests/modules/define-enable.nix
@@ -0,0 +1,3 @@
+{
+ enable = true;
+}
diff --git a/tests/modules/define-force-attrsOfSub-foo-enable.nix b/tests/modules/define-force-attrsOfSub-foo-enable.nix
new file mode 100644
index 000000000..dafb2360e
--- /dev/null
+++ b/tests/modules/define-force-attrsOfSub-foo-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+lib.mkForce {
+ attrsOfSub.foo.enable = false;
+}
diff --git a/tests/modules/define-force-enable.nix b/tests/modules/define-force-enable.nix
new file mode 100644
index 000000000..978caa2a8
--- /dev/null
+++ b/tests/modules/define-force-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+lib.mkForce {
+ enable = false;
+}
diff --git a/tests/modules/define-freeform-keywords-shorthand.nix b/tests/modules/define-freeform-keywords-shorthand.nix
new file mode 100644
index 000000000..8de1ec6a7
--- /dev/null
+++ b/tests/modules/define-freeform-keywords-shorthand.nix
@@ -0,0 +1,15 @@
+{ config, ... }: {
+ class = { "just" = "data"; };
+ a = "one";
+ b = "two";
+ meta = "meta";
+
+ _module.args.result =
+ let r = builtins.removeAttrs config [ "_module" ];
+ in builtins.trace (builtins.deepSeq r r) (r == {
+ a = "one";
+ b = "two";
+ class = { "just" = "data"; };
+ meta = "meta";
+ });
+}
diff --git a/tests/modules/define-if-attrsOfSub-foo-enable.nix b/tests/modules/define-if-attrsOfSub-foo-enable.nix
new file mode 100644
index 000000000..6a8e32e80
--- /dev/null
+++ b/tests/modules/define-if-attrsOfSub-foo-enable.nix
@@ -0,0 +1,5 @@
+{ config, lib, ... }:
+
+lib.mkIf config.enable {
+ attrsOfSub.foo.enable = true;
+}
diff --git a/tests/modules/define-module-check.nix b/tests/modules/define-module-check.nix
new file mode 100644
index 000000000..5a0707c97
--- /dev/null
+++ b/tests/modules/define-module-check.nix
@@ -0,0 +1,3 @@
+{
+ _module.check = false;
+}
diff --git a/tests/modules/define-option-dependently-nested.nix b/tests/modules/define-option-dependently-nested.nix
new file mode 100644
index 000000000..69ee42555
--- /dev/null
+++ b/tests/modules/define-option-dependently-nested.nix
@@ -0,0 +1,16 @@
+{ lib, options, ... }:
+
+# Some modules may be distributed separately and need to adapt to other modules
+# that are distributed and versioned separately.
+{
+
+ # Always defined, but the value depends on the presence of an option.
+ config.set = {
+ value = if options ? set.enable then 360 else 7;
+ }
+ # Only define if possible.
+ // lib.optionalAttrs (options ? set.enable) {
+ enable = true;
+ };
+
+}
diff --git a/tests/modules/define-option-dependently.nix b/tests/modules/define-option-dependently.nix
new file mode 100644
index 000000000..ad85f99a9
--- /dev/null
+++ b/tests/modules/define-option-dependently.nix
@@ -0,0 +1,16 @@
+{ lib, options, ... }:
+
+# Some modules may be distributed separately and need to adapt to other modules
+# that are distributed and versioned separately.
+{
+
+ # Always defined, but the value depends on the presence of an option.
+ config = {
+ value = if options ? enable then 360 else 7;
+ }
+ # Only define if possible.
+ // lib.optionalAttrs (options ? enable) {
+ enable = true;
+ };
+
+}
diff --git a/tests/modules/define-settingsDict-a-is-b.nix b/tests/modules/define-settingsDict-a-is-b.nix
new file mode 100644
index 000000000..42363f45f
--- /dev/null
+++ b/tests/modules/define-settingsDict-a-is-b.nix
@@ -0,0 +1,3 @@
+{ config, ... }: {
+ settingsDict.a = config.settingsDict.b;
+}
diff --git a/tests/modules/define-shorthandOnlyDefinesConfig-true.nix b/tests/modules/define-shorthandOnlyDefinesConfig-true.nix
new file mode 100644
index 000000000..bd3a73dce
--- /dev/null
+++ b/tests/modules/define-shorthandOnlyDefinesConfig-true.nix
@@ -0,0 +1 @@
+{ shorthandOnlyDefinesConfig = true; }
diff --git a/tests/modules/define-submoduleWith-noshorthand.nix b/tests/modules/define-submoduleWith-noshorthand.nix
new file mode 100644
index 000000000..35e1607b6
--- /dev/null
+++ b/tests/modules/define-submoduleWith-noshorthand.nix
@@ -0,0 +1,3 @@
+{
+ submodule.config.config = true;
+}
diff --git a/tests/modules/define-submoduleWith-shorthand.nix b/tests/modules/define-submoduleWith-shorthand.nix
new file mode 100644
index 000000000..17df248db
--- /dev/null
+++ b/tests/modules/define-submoduleWith-shorthand.nix
@@ -0,0 +1,3 @@
+{
+ submodule.config = true;
+}
diff --git a/tests/modules/define-value-int-negative.nix b/tests/modules/define-value-int-negative.nix
new file mode 100644
index 000000000..a04122298
--- /dev/null
+++ b/tests/modules/define-value-int-negative.nix
@@ -0,0 +1,3 @@
+{
+ value = -23;
+}
diff --git a/tests/modules/define-value-int-positive.nix b/tests/modules/define-value-int-positive.nix
new file mode 100644
index 000000000..5803de172
--- /dev/null
+++ b/tests/modules/define-value-int-positive.nix
@@ -0,0 +1,3 @@
+{
+ value = 42;
+}
diff --git a/tests/modules/define-value-int-zero.nix b/tests/modules/define-value-int-zero.nix
new file mode 100644
index 000000000..68bb9f415
--- /dev/null
+++ b/tests/modules/define-value-int-zero.nix
@@ -0,0 +1,3 @@
+{
+ value = 0;
+}
diff --git a/tests/modules/define-value-list.nix b/tests/modules/define-value-list.nix
new file mode 100644
index 000000000..4831c1cc0
--- /dev/null
+++ b/tests/modules/define-value-list.nix
@@ -0,0 +1,3 @@
+{
+ value = [];
+}
diff --git a/tests/modules/define-value-string-arbitrary.nix b/tests/modules/define-value-string-arbitrary.nix
new file mode 100644
index 000000000..8e3abaf53
--- /dev/null
+++ b/tests/modules/define-value-string-arbitrary.nix
@@ -0,0 +1,3 @@
+{
+ value = "foobar";
+}
diff --git a/tests/modules/define-value-string-bigint.nix b/tests/modules/define-value-string-bigint.nix
new file mode 100644
index 000000000..f27e31985
--- /dev/null
+++ b/tests/modules/define-value-string-bigint.nix
@@ -0,0 +1,3 @@
+{
+ value = "1000";
+}
diff --git a/tests/modules/define-value-string-properties.nix b/tests/modules/define-value-string-properties.nix
new file mode 100644
index 000000000..972304c01
--- /dev/null
+++ b/tests/modules/define-value-string-properties.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+
+ imports = [{
+ value = lib.mkDefault "def";
+ }];
+
+ value = lib.mkMerge [
+ (lib.mkIf false "nope")
+ "yes"
+ ];
+
+}
diff --git a/tests/modules/define-value-string.nix b/tests/modules/define-value-string.nix
new file mode 100644
index 000000000..e7a166965
--- /dev/null
+++ b/tests/modules/define-value-string.nix
@@ -0,0 +1,3 @@
+{
+ value = "24";
+}
diff --git a/tests/modules/define-variant.nix b/tests/modules/define-variant.nix
new file mode 100644
index 000000000..423cb0e37
--- /dev/null
+++ b/tests/modules/define-variant.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }:
+let inherit (lib) types mkOption attrNames;
+in
+{
+ options = {
+ attrs = mkOption { type = types.attrsOf lib.types.int; };
+ result = mkOption { };
+ resultFoo = mkOption { };
+ resultFooBar = mkOption { };
+ resultFooFoo = mkOption { };
+ };
+ config = {
+ attrs.a = 1;
+ variants.foo.attrs.b = 1;
+ variants.bar.attrs.y = 1;
+ variants.foo.variants.bar.attrs.z = 1;
+ variants.foo.variants.foo.attrs.c = 3;
+ resultFoo = lib.concatMapStringsSep " " toString (attrNames config.variants.foo.attrs);
+ resultFooBar = lib.concatMapStringsSep " " toString (attrNames config.variants.foo.variants.bar.attrs);
+ resultFooFoo = lib.concatMapStringsSep " " toString (attrNames config.variants.foo.variants.foo.attrs);
+ };
+}
diff --git a/tests/modules/disable-declare-enable.nix b/tests/modules/disable-declare-enable.nix
new file mode 100644
index 000000000..a373ee7e5
--- /dev/null
+++ b/tests/modules/disable-declare-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+ disabledModules = [ ./declare-enable.nix ];
+}
diff --git a/tests/modules/disable-define-enable-string-path.nix b/tests/modules/disable-define-enable-string-path.nix
new file mode 100644
index 000000000..6429a6d63
--- /dev/null
+++ b/tests/modules/disable-define-enable-string-path.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+ disabledModules = [ (toString ./define-enable.nix) ];
+}
diff --git a/tests/modules/disable-define-enable.nix b/tests/modules/disable-define-enable.nix
new file mode 100644
index 000000000..0d84a7c3c
--- /dev/null
+++ b/tests/modules/disable-define-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+ disabledModules = [ ./define-enable.nix ];
+}
diff --git a/tests/modules/disable-enable-modules.nix b/tests/modules/disable-enable-modules.nix
new file mode 100644
index 000000000..c325f4e07
--- /dev/null
+++ b/tests/modules/disable-enable-modules.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+ disabledModules = [ "define-enable.nix" "declare-enable.nix" ];
+}
diff --git a/tests/modules/disable-module-bad-key.nix b/tests/modules/disable-module-bad-key.nix
new file mode 100644
index 000000000..f50d06f2f
--- /dev/null
+++ b/tests/modules/disable-module-bad-key.nix
@@ -0,0 +1,16 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+
+ moduleWithKey = { config, ... }: {
+ config = {
+ enable = true;
+ };
+ };
+in
+{
+ imports = [
+ ./declare-enable.nix
+ ];
+ disabledModules = [ { } ];
+}
diff --git a/tests/modules/disable-module-with-key.nix b/tests/modules/disable-module-with-key.nix
new file mode 100644
index 000000000..ea2a60aa8
--- /dev/null
+++ b/tests/modules/disable-module-with-key.nix
@@ -0,0 +1,34 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+
+ moduleWithKey = {
+ key = "disable-module-with-key.nix#moduleWithKey";
+ config = {
+ enable = true;
+ };
+ };
+in
+{
+ options = {
+ positive = mkOption {
+ type = types.submodule {
+ imports = [
+ ./declare-enable.nix
+ moduleWithKey
+ ];
+ };
+ default = {};
+ };
+ negative = mkOption {
+ type = types.submodule {
+ imports = [
+ ./declare-enable.nix
+ moduleWithKey
+ ];
+ disabledModules = [ moduleWithKey ];
+ };
+ default = {};
+ };
+ };
+}
diff --git a/tests/modules/disable-module-with-toString-key.nix b/tests/modules/disable-module-with-toString-key.nix
new file mode 100644
index 000000000..3f8c81904
--- /dev/null
+++ b/tests/modules/disable-module-with-toString-key.nix
@@ -0,0 +1,34 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+
+ moduleWithKey = {
+ key = 123;
+ config = {
+ enable = true;
+ };
+ };
+in
+{
+ options = {
+ positive = mkOption {
+ type = types.submodule {
+ imports = [
+ ./declare-enable.nix
+ moduleWithKey
+ ];
+ };
+ default = {};
+ };
+ negative = mkOption {
+ type = types.submodule {
+ imports = [
+ ./declare-enable.nix
+ moduleWithKey
+ ];
+ disabledModules = [ 123 ];
+ };
+ default = {};
+ };
+ };
+}
diff --git a/tests/modules/disable-recursive/bar.nix b/tests/modules/disable-recursive/bar.nix
new file mode 100644
index 000000000..4d9240a43
--- /dev/null
+++ b/tests/modules/disable-recursive/bar.nix
@@ -0,0 +1,5 @@
+{
+ imports = [
+ ../declare-enable.nix
+ ];
+}
diff --git a/tests/modules/disable-recursive/disable-bar.nix b/tests/modules/disable-recursive/disable-bar.nix
new file mode 100644
index 000000000..987b2802a
--- /dev/null
+++ b/tests/modules/disable-recursive/disable-bar.nix
@@ -0,0 +1,7 @@
+{
+
+ disabledModules = [
+ ./bar.nix
+ ];
+
+}
diff --git a/tests/modules/disable-recursive/disable-foo.nix b/tests/modules/disable-recursive/disable-foo.nix
new file mode 100644
index 000000000..5b68a3c46
--- /dev/null
+++ b/tests/modules/disable-recursive/disable-foo.nix
@@ -0,0 +1,7 @@
+{
+
+ disabledModules = [
+ ./foo.nix
+ ];
+
+}
diff --git a/tests/modules/disable-recursive/foo.nix b/tests/modules/disable-recursive/foo.nix
new file mode 100644
index 000000000..4d9240a43
--- /dev/null
+++ b/tests/modules/disable-recursive/foo.nix
@@ -0,0 +1,5 @@
+{
+ imports = [
+ ../declare-enable.nix
+ ];
+}
diff --git a/tests/modules/disable-recursive/main.nix b/tests/modules/disable-recursive/main.nix
new file mode 100644
index 000000000..48a3c6218
--- /dev/null
+++ b/tests/modules/disable-recursive/main.nix
@@ -0,0 +1,8 @@
+{
+ imports = [
+ ./foo.nix
+ ./bar.nix
+ ];
+
+ enable = true;
+}
diff --git a/tests/modules/doRename-basic.nix b/tests/modules/doRename-basic.nix
new file mode 100644
index 000000000..9d79fa4f2
--- /dev/null
+++ b/tests/modules/doRename-basic.nix
@@ -0,0 +1,11 @@
+{ lib, ... }: {
+ imports = [
+ (lib.doRename { from = ["a" "b"]; to = ["c" "d" "e"]; warn = true; use = x: x; visible = true; })
+ ];
+ options = {
+ c.d.e = lib.mkOption {};
+ };
+ config = {
+ a.b = 1234;
+ };
+}
diff --git a/tests/modules/doRename-warnings.nix b/tests/modules/doRename-warnings.nix
new file mode 100644
index 000000000..6f0f1e87e
--- /dev/null
+++ b/tests/modules/doRename-warnings.nix
@@ -0,0 +1,14 @@
+{ lib, config, ... }: {
+ imports = [
+ (lib.doRename { from = ["a" "b"]; to = ["c" "d" "e"]; warn = true; use = x: x; visible = true; })
+ ];
+ options = {
+ warnings = lib.mkOption { type = lib.types.listOf lib.types.str; };
+ c.d.e = lib.mkOption {};
+ result = lib.mkOption {};
+ };
+ config = {
+ a.b = 1234;
+ result = lib.concatStringsSep "%" config.warnings;
+ };
+}
diff --git a/tests/modules/emptyValues.nix b/tests/modules/emptyValues.nix
new file mode 100644
index 000000000..77825de32
--- /dev/null
+++ b/tests/modules/emptyValues.nix
@@ -0,0 +1,36 @@
+{ lib, ... }:
+let
+ inherit (lib) types;
+in {
+
+ options = {
+ int = lib.mkOption {
+ type = types.lazyAttrsOf types.int;
+ };
+ list = lib.mkOption {
+ type = types.lazyAttrsOf (types.listOf types.int);
+ };
+ nonEmptyList = lib.mkOption {
+ type = types.lazyAttrsOf (types.nonEmptyListOf types.int);
+ };
+ attrs = lib.mkOption {
+ type = types.lazyAttrsOf (types.attrsOf types.int);
+ };
+ null = lib.mkOption {
+ type = types.lazyAttrsOf (types.nullOr types.int);
+ };
+ submodule = lib.mkOption {
+ type = types.lazyAttrsOf (types.submodule {});
+ };
+ };
+
+ config = {
+ int.a = lib.mkIf false null;
+ list.a = lib.mkIf false null;
+ nonEmptyList.a = lib.mkIf false null;
+ attrs.a = lib.mkIf false null;
+ null.a = lib.mkIf false null;
+ submodule.a = lib.mkIf false null;
+ };
+
+}
diff --git a/tests/modules/extendModules-168767-imports.nix b/tests/modules/extendModules-168767-imports.nix
new file mode 100644
index 000000000..489e6b5a5
--- /dev/null
+++ b/tests/modules/extendModules-168767-imports.nix
@@ -0,0 +1,41 @@
+{ lib
+, extendModules
+, ...
+}:
+with lib;
+{
+ imports = [
+
+ {
+ options.sub = mkOption {
+ default = { };
+ type = types.submodule (
+ { config
+ , extendModules
+ , ...
+ }:
+ {
+ options.value = mkOption {
+ type = types.int;
+ };
+
+ options.specialisation = mkOption {
+ default = { };
+ inherit
+ (extendModules {
+ modules = [{
+ specialisation = mkOverride 0 { };
+ }];
+ })
+ type;
+ };
+ }
+ );
+ };
+ }
+
+ { config.sub.value = 1; }
+
+
+ ];
+}
diff --git a/tests/modules/freeform-attrsOf.nix b/tests/modules/freeform-attrsOf.nix
new file mode 100644
index 000000000..8cc577f38
--- /dev/null
+++ b/tests/modules/freeform-attrsOf.nix
@@ -0,0 +1,3 @@
+{ lib, ... }: {
+ freeformType = with lib.types; attrsOf (either str (attrsOf str));
+}
diff --git a/tests/modules/freeform-lazyAttrsOf.nix b/tests/modules/freeform-lazyAttrsOf.nix
new file mode 100644
index 000000000..36d6c0b13
--- /dev/null
+++ b/tests/modules/freeform-lazyAttrsOf.nix
@@ -0,0 +1,3 @@
+{ lib, ... }: {
+ freeformType = with lib.types; lazyAttrsOf (either str (lazyAttrsOf str));
+}
diff --git a/tests/modules/freeform-nested.nix b/tests/modules/freeform-nested.nix
new file mode 100644
index 000000000..b81fa7f0d
--- /dev/null
+++ b/tests/modules/freeform-nested.nix
@@ -0,0 +1,14 @@
+{ lib, ... }:
+let
+ deathtrapArgs = lib.mapAttrs
+ (k: _: throw "The module system is too strict, accessing an unused option's ${k} mkOption-attribute.")
+ (lib.functionArgs lib.mkOption);
+in
+{
+ options.nest.foo = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ options.nest.unused = lib.mkOption deathtrapArgs;
+ config.nest.bar = "bar";
+}
diff --git a/tests/modules/freeform-str-dep-unstr.nix b/tests/modules/freeform-str-dep-unstr.nix
new file mode 100644
index 000000000..a2dfbc80c
--- /dev/null
+++ b/tests/modules/freeform-str-dep-unstr.nix
@@ -0,0 +1,8 @@
+{ lib, config, ... }: {
+ options.foo = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ };
+
+ config.foo = lib.mkIf (config ? value) config.value;
+}
diff --git a/tests/modules/freeform-submodules.nix b/tests/modules/freeform-submodules.nix
new file mode 100644
index 000000000..3910435a7
--- /dev/null
+++ b/tests/modules/freeform-submodules.nix
@@ -0,0 +1,22 @@
+{ lib, options, ... }: with lib.types; {
+
+ options.fooDeclarations = lib.mkOption {
+ default = (options.free.type.getSubOptions [])._freeformOptions.foo.declarations;
+ };
+
+ options.free = lib.mkOption {
+ type = submodule {
+ config._module.freeformType = lib.mkMerge [
+ (attrsOf (submodule {
+ options.foo = lib.mkOption {};
+ }))
+ (attrsOf (submodule {
+ options.bar = lib.mkOption {};
+ }))
+ ];
+ };
+ };
+
+ config.free.xxx.foo = 10;
+ config.free.yyy.bar = 10;
+}
diff --git a/tests/modules/freeform-unstr-dep-str.nix b/tests/modules/freeform-unstr-dep-str.nix
new file mode 100644
index 000000000..549d89afe
--- /dev/null
+++ b/tests/modules/freeform-unstr-dep-str.nix
@@ -0,0 +1,8 @@
+{ lib, config, ... }: {
+ options.value = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ };
+
+ config.foo = lib.mkIf (config.value != null) config.value;
+}
diff --git a/tests/modules/functionTo/list-order.nix b/tests/modules/functionTo/list-order.nix
new file mode 100644
index 000000000..77a1a43a8
--- /dev/null
+++ b/tests/modules/functionTo/list-order.nix
@@ -0,0 +1,25 @@
+
+{ lib, config, ... }:
+let
+ inherit (lib) types;
+in {
+ options = {
+ fun = lib.mkOption {
+ type = types.functionTo (types.listOf types.str);
+ };
+
+ result = lib.mkOption {
+ type = types.str;
+ default = toString (config.fun {
+ a = "a";
+ b = "b";
+ c = "c";
+ });
+ };
+ };
+
+ config.fun = lib.mkMerge [
+ (input: lib.mkAfter [ input.a ])
+ (input: [ input.b ])
+ ];
+}
diff --git a/tests/modules/functionTo/merging-attrs.nix b/tests/modules/functionTo/merging-attrs.nix
new file mode 100644
index 000000000..97c015f92
--- /dev/null
+++ b/tests/modules/functionTo/merging-attrs.nix
@@ -0,0 +1,27 @@
+{ lib, config, ... }:
+let
+ inherit (lib) types;
+in {
+ options = {
+ fun = lib.mkOption {
+ type = types.functionTo (types.attrsOf types.str);
+ };
+
+ result = lib.mkOption {
+ type = types.str;
+ default = toString (lib.attrValues (config.fun {
+ a = "a";
+ b = "b";
+ c = "c";
+ }));
+ };
+ };
+
+ config.fun = lib.mkMerge [
+ (input: { inherit (input) a; })
+ (input: { inherit (input) b; })
+ (input: {
+ b = lib.mkForce input.c;
+ })
+ ];
+}
diff --git a/tests/modules/functionTo/merging-list.nix b/tests/modules/functionTo/merging-list.nix
new file mode 100644
index 000000000..15fcd2bdc
--- /dev/null
+++ b/tests/modules/functionTo/merging-list.nix
@@ -0,0 +1,24 @@
+{ lib, config, ... }:
+let
+ inherit (lib) types;
+in {
+ options = {
+ fun = lib.mkOption {
+ type = types.functionTo (types.listOf types.str);
+ };
+
+ result = lib.mkOption {
+ type = types.str;
+ default = toString (config.fun {
+ a = "a";
+ b = "b";
+ c = "c";
+ });
+ };
+ };
+
+ config.fun = lib.mkMerge [
+ (input: [ input.a ])
+ (input: [ input.b ])
+ ];
+}
diff --git a/tests/modules/functionTo/submodule-options.nix b/tests/modules/functionTo/submodule-options.nix
new file mode 100644
index 000000000..b884892ef
--- /dev/null
+++ b/tests/modules/functionTo/submodule-options.nix
@@ -0,0 +1,61 @@
+{ lib, config, options, ... }:
+let
+ inherit (lib) types;
+in
+{
+ imports = [
+
+ # fun.<function-body>.a
+ ({ ... }: {
+ options = {
+ fun = lib.mkOption {
+ type = types.functionTo (types.submodule {
+ options.a = lib.mkOption { default = "a"; };
+ });
+ };
+ };
+ })
+
+ # fun.<function-body>.b
+ ({ ... }: {
+ options = {
+ fun = lib.mkOption {
+ type = types.functionTo (types.submodule {
+ options.b = lib.mkOption { default = "b"; };
+ });
+ };
+ };
+ })
+ ];
+
+ options = {
+ result = lib.mkOption
+ {
+ type = types.str;
+ default = lib.concatStringsSep " " (lib.attrValues (config.fun (throw "shouldn't use input param")));
+ };
+
+ optionsResult = lib.mkOption
+ {
+ type = types.str;
+ default = lib.concatStringsSep " "
+ (lib.concatLists
+ (lib.mapAttrsToList
+ (k: v:
+ if k == "_module"
+ then [ ]
+ else [ (lib.showOption v.loc) ]
+ )
+ (
+ (options.fun.type.getSubOptions [ "fun" ])
+ )
+ )
+ );
+ };
+ };
+
+ config.fun = lib.mkMerge
+ [
+ (input: { b = "bee"; })
+ ];
+}
diff --git a/tests/modules/functionTo/trivial.nix b/tests/modules/functionTo/trivial.nix
new file mode 100644
index 000000000..0962a0cf8
--- /dev/null
+++ b/tests/modules/functionTo/trivial.nix
@@ -0,0 +1,17 @@
+{ lib, config, ... }:
+let
+ inherit (lib) types;
+in {
+ options = {
+ fun = lib.mkOption {
+ type = types.functionTo types.str;
+ };
+
+ result = lib.mkOption {
+ type = types.str;
+ default = config.fun "input";
+ };
+ };
+
+ config.fun = input: "input is ${input}";
+}
diff --git a/tests/modules/functionTo/wrong-type.nix b/tests/modules/functionTo/wrong-type.nix
new file mode 100644
index 000000000..fd65b7508
--- /dev/null
+++ b/tests/modules/functionTo/wrong-type.nix
@@ -0,0 +1,18 @@
+
+{ lib, config, ... }:
+let
+ inherit (lib) types;
+in {
+ options = {
+ fun = lib.mkOption {
+ type = types.functionTo types.str;
+ };
+
+ result = lib.mkOption {
+ type = types.str;
+ default = config.fun 0;
+ };
+ };
+
+ config.fun = input: input + 1;
+}
diff --git a/tests/modules/import-configuration.nix b/tests/modules/import-configuration.nix
new file mode 100644
index 000000000..a2a32bbee
--- /dev/null
+++ b/tests/modules/import-configuration.nix
@@ -0,0 +1,12 @@
+{ lib, ... }:
+let
+ myconf = lib.evalModules { modules = [ { } ]; };
+in
+{
+ imports = [
+ # We can't do this. A configuration is not equal to its set of a modules.
+ # Equating those would lead to a mess, as specialArgs, anonymous modules
+ # that can't be deduplicated, and possibly more come into play.
+ myconf
+ ];
+}
diff --git a/tests/modules/import-custom-arg.nix b/tests/modules/import-custom-arg.nix
new file mode 100644
index 000000000..3e687b661
--- /dev/null
+++ b/tests/modules/import-custom-arg.nix
@@ -0,0 +1,6 @@
+{ lib, custom, ... }:
+
+{
+ imports = []
+ ++ lib.optional custom ./define-enable-force.nix;
+}
diff --git a/tests/modules/import-from-store.nix b/tests/modules/import-from-store.nix
new file mode 100644
index 000000000..f5af22432
--- /dev/null
+++ b/tests/modules/import-from-store.nix
@@ -0,0 +1,11 @@
+{ lib, ... }:
+{
+
+ imports = [
+ "${builtins.toFile "drv" "{}"}"
+ ./declare-enable.nix
+ ./define-enable.nix
+ ];
+
+}
+
diff --git a/tests/modules/merge-module-with-key.nix b/tests/modules/merge-module-with-key.nix
new file mode 100644
index 000000000..21f00e6ef
--- /dev/null
+++ b/tests/modules/merge-module-with-key.nix
@@ -0,0 +1,49 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+
+ moduleWithoutKey = {
+ config = {
+ raw = "pear";
+ };
+ };
+
+ moduleWithKey = {
+ key = __curPos.file + "#moduleWithKey";
+ config = {
+ raw = "pear";
+ };
+ };
+
+ decl = {
+ options = {
+ raw = mkOption {
+ type = types.lines;
+ };
+ };
+ };
+in
+{
+ options = {
+ once = mkOption {
+ type = types.submodule {
+ imports = [
+ decl
+ moduleWithKey
+ moduleWithKey
+ ];
+ };
+ default = {};
+ };
+ twice = mkOption {
+ type = types.submodule {
+ imports = [
+ decl
+ moduleWithoutKey
+ moduleWithoutKey
+ ];
+ };
+ default = {};
+ };
+ };
+}
diff --git a/tests/modules/module-class-is-darwin.nix b/tests/modules/module-class-is-darwin.nix
new file mode 100644
index 000000000..bacf45626
--- /dev/null
+++ b/tests/modules/module-class-is-darwin.nix
@@ -0,0 +1,4 @@
+{
+ _class = "darwin";
+ config = {};
+}
diff --git a/tests/modules/module-class-is-nixos.nix b/tests/modules/module-class-is-nixos.nix
new file mode 100644
index 000000000..6d62feeda
--- /dev/null
+++ b/tests/modules/module-class-is-nixos.nix
@@ -0,0 +1,4 @@
+{
+ _class = "nixos";
+ config = {};
+}
diff --git a/tests/modules/module-imports-_type-check.nix b/tests/modules/module-imports-_type-check.nix
new file mode 100644
index 000000000..1e29c469d
--- /dev/null
+++ b/tests/modules/module-imports-_type-check.nix
@@ -0,0 +1,3 @@
+{
+ imports = [ { _type = "flake"; } ];
+}
diff --git a/tests/modules/optionTypeFile.nix b/tests/modules/optionTypeFile.nix
new file mode 100644
index 000000000..6015d59a7
--- /dev/null
+++ b/tests/modules/optionTypeFile.nix
@@ -0,0 +1,28 @@
+{ config, lib, ... }: {
+
+ _file = "optionTypeFile.nix";
+
+ options.theType = lib.mkOption {
+ type = lib.types.optionType;
+ };
+
+ options.theOption = lib.mkOption {
+ type = config.theType;
+ default = {};
+ };
+
+ config.theType = lib.mkMerge [
+ (lib.types.submodule {
+ options.nested = lib.mkOption {
+ type = lib.types.int;
+ };
+ })
+ (lib.types.submodule {
+ _file = "other.nix";
+ options.nested = lib.mkOption {
+ type = lib.types.str;
+ };
+ })
+ ];
+
+}
diff --git a/tests/modules/optionTypeMerging.nix b/tests/modules/optionTypeMerging.nix
new file mode 100644
index 000000000..74a620c46
--- /dev/null
+++ b/tests/modules/optionTypeMerging.nix
@@ -0,0 +1,27 @@
+{ config, lib, ... }: {
+
+ options.theType = lib.mkOption {
+ type = lib.types.optionType;
+ };
+
+ options.theOption = lib.mkOption {
+ type = config.theType;
+ };
+
+ config.theType = lib.mkMerge [
+ (lib.types.submodule {
+ options.int = lib.mkOption {
+ type = lib.types.int;
+ default = 10;
+ };
+ })
+ (lib.types.submodule {
+ options.str = lib.mkOption {
+ type = lib.types.str;
+ };
+ })
+ ];
+
+ config.theOption.str = "hello";
+
+}
diff --git a/tests/modules/raw.nix b/tests/modules/raw.nix
new file mode 100644
index 000000000..418e671ed
--- /dev/null
+++ b/tests/modules/raw.nix
@@ -0,0 +1,30 @@
+{ lib, ... }: {
+
+ options = {
+ processedToplevel = lib.mkOption {
+ type = lib.types.raw;
+ };
+ unprocessedNesting = lib.mkOption {
+ type = lib.types.raw;
+ };
+ multiple = lib.mkOption {
+ type = lib.types.raw;
+ };
+ priorities = lib.mkOption {
+ type = lib.types.raw;
+ };
+ };
+
+ config = {
+ processedToplevel = lib.mkIf true 10;
+ unprocessedNesting.foo = throw "foo";
+ multiple = lib.mkMerge [
+ "foo"
+ "foo"
+ ];
+ priorities = lib.mkMerge [
+ "foo"
+ (lib.mkForce "bar")
+ ];
+ };
+}
diff --git a/tests/modules/shorthand-meta.nix b/tests/modules/shorthand-meta.nix
new file mode 100644
index 000000000..8c9619e18
--- /dev/null
+++ b/tests/modules/shorthand-meta.nix
@@ -0,0 +1,19 @@
+{ lib, ... }:
+let
+ inherit (lib) types mkOption;
+in
+{
+ imports = [
+ ({ config, ... }: {
+ options = {
+ meta.foo = mkOption {
+ type = types.listOf types.str;
+ };
+ result = mkOption { default = lib.concatStringsSep " " config.meta.foo; };
+ };
+ })
+ {
+ meta.foo = [ "one" "two" ];
+ }
+ ];
+}
diff --git a/tests/modules/submoduleFiles.nix b/tests/modules/submoduleFiles.nix
new file mode 100644
index 000000000..c0d9b2cef
--- /dev/null
+++ b/tests/modules/submoduleFiles.nix
@@ -0,0 +1,21 @@
+{ lib, ... }: {
+ options.submodule = lib.mkOption {
+ default = {};
+ type = lib.types.submoduleWith {
+ modules = [ ({ options, ... }: {
+ options.value = lib.mkOption {};
+
+ options.internalFiles = lib.mkOption {
+ default = options.value.files;
+ };
+ })];
+ };
+ };
+
+ imports = [
+ {
+ _file = "the-file.nix";
+ submodule.value = 10;
+ }
+ ];
+}
diff --git a/tests/modules/types-anything/attrs-coercible.nix b/tests/modules/types-anything/attrs-coercible.nix
new file mode 100644
index 000000000..085cbd638
--- /dev/null
+++ b/tests/modules/types-anything/attrs-coercible.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config.value = {
+ outPath = "foo";
+ err = throw "err";
+ };
+
+}
diff --git a/tests/modules/types-anything/equal-atoms.nix b/tests/modules/types-anything/equal-atoms.nix
new file mode 100644
index 000000000..972711201
--- /dev/null
+++ b/tests/modules/types-anything/equal-atoms.nix
@@ -0,0 +1,26 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.int = 0;
+ value.bool = false;
+ value.string = "";
+ value.path = /.;
+ value.null = null;
+ value.float = 0.1;
+ }
+ {
+ value.int = 0;
+ value.bool = false;
+ value.string = "";
+ value.path = /.;
+ value.null = null;
+ value.float = 0.1;
+ }
+ ];
+
+}
diff --git a/tests/modules/types-anything/functions.nix b/tests/modules/types-anything/functions.nix
new file mode 100644
index 000000000..21edd4aff
--- /dev/null
+++ b/tests/modules/types-anything/functions.nix
@@ -0,0 +1,23 @@
+{ lib, config, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ options.applied = lib.mkOption {
+ default = lib.mapAttrs (name: fun: fun null) config.value;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.single-lambda = x: x;
+ value.multiple-lambdas = x: { inherit x; };
+ value.merging-lambdas = x: { inherit x; };
+ }
+ {
+ value.multiple-lambdas = x: [ x ];
+ value.merging-lambdas = y: { inherit y; };
+ }
+ ];
+
+}
diff --git a/tests/modules/types-anything/lists.nix b/tests/modules/types-anything/lists.nix
new file mode 100644
index 000000000..bd846afd3
--- /dev/null
+++ b/tests/modules/types-anything/lists.nix
@@ -0,0 +1,16 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value = [ null ];
+ }
+ {
+ value = [ null ];
+ }
+ ];
+
+}
diff --git a/tests/modules/types-anything/mk-mods.nix b/tests/modules/types-anything/mk-mods.nix
new file mode 100644
index 000000000..f84ad01df
--- /dev/null
+++ b/tests/modules/types-anything/mk-mods.nix
@@ -0,0 +1,44 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.mkiffalse = lib.mkIf false {};
+ }
+ {
+ value.mkiftrue = lib.mkIf true {};
+ }
+ {
+ value.mkdefault = lib.mkDefault 0;
+ }
+ {
+ value.mkdefault = 1;
+ }
+ {
+ value.mkmerge = lib.mkMerge [
+ {}
+ ];
+ }
+ {
+ value.mkbefore = lib.mkBefore true;
+ }
+ {
+ value.nested = lib.mkMerge [
+ {
+ foo = lib.mkDefault 0;
+ bar = lib.mkIf false 0;
+ }
+ (lib.mkIf true {
+ foo = lib.mkIf true (lib.mkForce 1);
+ bar = {
+ baz = lib.mkDefault "baz";
+ };
+ })
+ ];
+ }
+ ];
+
+}
diff --git a/tests/modules/types-anything/nested-attrs.nix b/tests/modules/types-anything/nested-attrs.nix
new file mode 100644
index 000000000..e57d33ef8
--- /dev/null
+++ b/tests/modules/types-anything/nested-attrs.nix
@@ -0,0 +1,22 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.foo = null;
+ }
+ {
+ value.l1.foo = null;
+ }
+ {
+ value.l1.l2.foo = null;
+ }
+ {
+ value.l1.l2.l3.foo = null;
+ }
+ ];
+
+}
diff --git a/tests/release.nix b/tests/release.nix
new file mode 100644
index 000000000..c3bf58db2
--- /dev/null
+++ b/tests/release.nix
@@ -0,0 +1,64 @@
+{ # The pkgs used for dependencies for the testing itself
+ # Don't test properties of pkgs.lib, but rather the lib in the parent directory
+ pkgs ? import ../.. {} // { lib = throw "pkgs.lib accessed, but the lib tests should use nixpkgs' lib path directly!"; },
+ nix ? pkgs.nix,
+ nixVersions ? [ pkgs.nixVersions.minimum nix pkgs.nixVersions.unstable ],
+}:
+
+let
+ testWithNix = nix:
+ pkgs.runCommand "nixpkgs-lib-tests-nix-${nix.version}" {
+ buildInputs = [
+ (import ./check-eval.nix)
+ (import ./maintainers.nix {
+ inherit pkgs;
+ lib = import ../.;
+ })
+ (import ./teams.nix {
+ inherit pkgs;
+ lib = import ../.;
+ })
+ (import ../path/tests {
+ inherit pkgs;
+ })
+ ];
+ nativeBuildInputs = [
+ nix
+ ];
+ strictDeps = true;
+ } ''
+ datadir="${nix}/share"
+ export TEST_ROOT=$(pwd)/test-tmp
+ export NIX_BUILD_HOOK=
+ export NIX_CONF_DIR=$TEST_ROOT/etc
+ export NIX_LOCALSTATE_DIR=$TEST_ROOT/var
+ export NIX_LOG_DIR=$TEST_ROOT/var/log/nix
+ export NIX_STATE_DIR=$TEST_ROOT/var/nix
+ export NIX_STORE_DIR=$TEST_ROOT/store
+ export PAGER=cat
+ cacheDir=$TEST_ROOT/binary-cache
+
+ mkdir -p $NIX_CONF_DIR
+ echo "experimental-features = nix-command" >> $NIX_CONF_DIR/nix.conf
+
+ nix-store --init
+
+ cp -r ${../.} lib
+ echo "Running lib/tests/modules.sh"
+ bash lib/tests/modules.sh
+
+ echo "Running lib/tests/filesystem.sh"
+ TEST_LIB=$PWD/lib bash lib/tests/filesystem.sh
+
+ echo "Running lib/tests/sources.sh"
+ TEST_LIB=$PWD/lib bash lib/tests/sources.sh
+
+ mkdir $out
+ echo success > $out/${nix.version}
+ '';
+
+in
+ pkgs.symlinkJoin {
+ name = "nixpkgs-lib-tests";
+ paths = map testWithNix nixVersions;
+ }
diff --git a/tests/sources.sh b/tests/sources.sh
new file mode 100755
index 000000000..cda77aa96
--- /dev/null
+++ b/tests/sources.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+set -euo pipefail
+shopt -s inherit_errexit
+
+# Use
+# || die
+die() {
+ echo >&2 "test case failed: " "$@"
+ exit 1
+}
+
+if test -n "${TEST_LIB:-}"; then
+ NIX_PATH=nixpkgs="$(dirname "$TEST_LIB")"
+else
+ NIX_PATH=nixpkgs="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.."; pwd)"
+fi
+export NIX_PATH
+
+work="$(mktemp -d)"
+clean_up() {
+ rm -rf "$work"
+}
+trap clean_up EXIT
+cd "$work"
+
+# Crudely unquotes a JSON string by just taking everything between the first and the second quote.
+# We're only using this for resulting /nix/store paths, which can't contain " anyways,
+# nor can they contain any other characters that would need to be escaped specially in JSON
+# This way we don't need to add a dependency on e.g. jq
+crudeUnquoteJSON() {
+ cut -d \" -f2
+}
+
+touch {README.md,module.o,foo.bar}
+
+dir="$(nix-instantiate --eval --strict --read-write-mode --json --expr '(with import <nixpkgs/lib>; "${
+ cleanSource ./.
+}")' | crudeUnquoteJSON)"
+(cd "$dir"; find) | sort -f | diff -U10 - <(cat <<EOF
+.
+./foo.bar
+./README.md
+EOF
+) || die "cleanSource 1"
+
+
+dir="$(nix-instantiate --eval --strict --read-write-mode --json --expr '(with import <nixpkgs/lib>; "${
+ cleanSourceWith { src = '"$work"'; filter = path: type: ! hasSuffix ".bar" path; }
+}")' | crudeUnquoteJSON)"
+(cd "$dir"; find) | sort -f | diff -U10 - <(cat <<EOF
+.
+./module.o
+./README.md
+EOF
+) || die "cleanSourceWith 1"
+
+dir="$(nix-instantiate --eval --strict --read-write-mode --json --expr '(with import <nixpkgs/lib>; "${
+ cleanSourceWith { src = cleanSource '"$work"'; filter = path: type: ! hasSuffix ".bar" path; }
+}")' | crudeUnquoteJSON)"
+(cd "$dir"; find) | sort -f | diff -U10 - <(cat <<EOF
+.
+./README.md
+EOF
+) || die "cleanSourceWith + cleanSource"
+
+echo >&2 tests ok
diff --git a/tests/systems.nix b/tests/systems.nix
new file mode 100644
index 000000000..2afe128c4
--- /dev/null
+++ b/tests/systems.nix
@@ -0,0 +1,42 @@
+# We assert that the new algorithmic way of generating these lists matches the
+# way they were hard-coded before.
+#
+# One might think "if we exhaustively test, what's the point of procedurally
+# calculating the lists anyway?". The answer is one can mindlessly update these
+# tests as new platforms become supported, and then just give the diff a quick
+# sanity check before committing :).
+let
+ lib = import ../default.nix;
+ mseteq = x: y: {
+ expr = lib.sort lib.lessThan x;
+ expected = lib.sort lib.lessThan y;
+ };
+in
+with lib.systems.doubles; lib.runTests {
+ testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ mmix ++ js ++ genode ++ redox);
+
+ testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-netbsd" "armv6l-none" "armv7a-linux" "armv7a-netbsd" "armv7l-linux" "armv7l-netbsd" "arm-none" "armv7a-darwin" ];
+ testarmv7 = mseteq armv7 [ "armv7a-darwin" "armv7a-linux" "armv7l-linux" "armv7a-netbsd" "armv7l-netbsd" ];
+ testi686 = mseteq i686 [ "i686-linux" "i686-freebsd13" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ];
+ testmips = mseteq mips [ "mips-linux" "mips64-linux" "mips64el-linux" "mipsel-linux" "mipsel-netbsd" ];
+ testmmix = mseteq mmix [ "mmix-mmixware" ];
+ testpower = mseteq power [ "powerpc-netbsd" "powerpc-none" "powerpc64-linux" "powerpc64le-linux" "powerpcle-none" ];
+ testriscv = mseteq riscv [ "riscv32-linux" "riscv64-linux" "riscv32-netbsd" "riscv64-netbsd" "riscv32-none" "riscv64-none" ];
+ testriscv32 = mseteq riscv32 [ "riscv32-linux" "riscv32-netbsd" "riscv32-none" ];
+ testriscv64 = mseteq riscv64 [ "riscv64-linux" "riscv64-netbsd" "riscv64-none" ];
+ tests390x = mseteq s390x [ "s390x-linux" "s390x-none" ];
+ testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd13" "x86_64-genode" "x86_64-redox" "x86_64-openbsd" "x86_64-netbsd" "x86_64-cygwin" "x86_64-solaris" "x86_64-windows" "x86_64-none" ];
+
+ testcygwin = mseteq cygwin [ "i686-cygwin" "x86_64-cygwin" ];
+ testdarwin = mseteq darwin [ "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin" ];
+ testfreebsd = mseteq freebsd [ "i686-freebsd13" "x86_64-freebsd13" ];
+ testgenode = mseteq genode [ "aarch64-genode" "i686-genode" "x86_64-genode" ];
+ testredox = mseteq redox [ "x86_64-redox" ];
+ testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */);
+ testillumos = mseteq illumos [ "x86_64-solaris" ];
+ testlinux = mseteq linux [ "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux" "i686-linux" "loongarch64-linux" "m68k-linux" "microblaze-linux" "microblazeel-linux" "mips-linux" "mips64-linux" "mips64el-linux" "mipsel-linux" "powerpc64-linux" "powerpc64le-linux" "riscv32-linux" "riscv64-linux" "s390-linux" "s390x-linux" "x86_64-linux" ];
+ testnetbsd = mseteq netbsd [ "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd" "i686-netbsd" "m68k-netbsd" "mipsel-netbsd" "powerpc-netbsd" "riscv32-netbsd" "riscv64-netbsd" "x86_64-netbsd" ];
+ testopenbsd = mseteq openbsd [ "i686-openbsd" "x86_64-openbsd" ];
+ testwindows = mseteq windows [ "i686-cygwin" "x86_64-cygwin" "i686-windows" "x86_64-windows" ];
+ testunix = mseteq unix (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin ++ redox);
+}
diff --git a/tests/teams.nix b/tests/teams.nix
new file mode 100644
index 000000000..8a0a5d272
--- /dev/null
+++ b/tests/teams.nix
@@ -0,0 +1,50 @@
+# to run these tests:
+# nix-build nixpkgs/lib/tests/teams.nix
+# If it builds, all tests passed
+{ pkgs ? import ../.. {}, lib ? pkgs.lib }:
+
+let
+ inherit (lib) types;
+
+ teamModule = { config, ... }: {
+ options = {
+ shortName = lib.mkOption {
+ type = types.str;
+ };
+ scope = lib.mkOption {
+ type = types.str;
+ };
+ enableFeatureFreezePing = lib.mkOption {
+ type = types.bool;
+ default = false;
+ };
+ members = lib.mkOption {
+ type = types.listOf (types.submodule
+ (import ./maintainer-module.nix { inherit lib; })
+ );
+ default = [];
+ };
+ githubTeams = lib.mkOption {
+ type = types.listOf types.str;
+ default = [];
+ };
+ };
+ };
+
+ checkTeam = team: uncheckedAttrs:
+ let
+ prefix = [ "lib" "maintainer-team" team ];
+ checkedAttrs = (lib.modules.evalModules {
+ inherit prefix;
+ modules = [
+ teamModule
+ {
+ _file = toString ../../maintainers/team-list.nix;
+ config = uncheckedAttrs;
+ }
+ ];
+ }).config;
+ in checkedAttrs;
+
+ checkedTeams = lib.mapAttrs checkTeam lib.teams;
+in pkgs.writeTextDir "maintainer-teams.json" (builtins.toJSON checkedTeams)
diff --git a/tests/test-to-plist-expected.plist b/tests/test-to-plist-expected.plist
new file mode 100644
index 000000000..df0528a60
--- /dev/null
+++ b/tests/test-to-plist-expected.plist
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>nested</key>
+ <dict>
+ <key>values</key>
+ <dict>
+ <key>attrs</key>
+ <dict>
+ <key>foo b/ar</key>
+ <string>baz</string>
+ </dict>
+ <key>bool</key>
+ <true/>
+ <key>emptyattrs</key>
+ <dict>
+
+ </dict>
+ <key>emptylist</key>
+ <array>
+
+ </array>
+ <key>emptystring</key>
+ <string></string>
+ <key>float</key>
+ <real>0.133700</real>
+ <key>int</key>
+ <integer>42</integer>
+ <key>list</key>
+ <array>
+ <integer>3</integer>
+ <integer>4</integer>
+ <string>test</string>
+ </array>
+ <key>newlinestring</key>
+ <string>
+</string>
+ <key>path</key>
+ <string>/foo</string>
+ <key>string</key>
+ <string>fn${o}"r\d</string>
+ </dict>
+ </dict>
+</dict>
+</plist> \ No newline at end of file