about summary refs log tree commit diff
path: root/nixpkgs/lib/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/lib/tests')
-rw-r--r--nixpkgs/lib/tests/check-eval.nix7
-rw-r--r--nixpkgs/lib/tests/maintainers.nix80
-rw-r--r--nixpkgs/lib/tests/misc.nix764
-rwxr-xr-xnixpkgs/lib/tests/modules.sh286
-rw-r--r--nixpkgs/lib/tests/modules/alias-with-priority-can-override.nix55
-rw-r--r--nixpkgs/lib/tests/modules/alias-with-priority.nix55
-rw-r--r--nixpkgs/lib/tests/modules/attrsOf-conditional-check.nix7
-rw-r--r--nixpkgs/lib/tests/modules/attrsOf-lazy-check.nix7
-rw-r--r--nixpkgs/lib/tests/modules/declare-attrsOf.nix6
-rw-r--r--nixpkgs/lib/tests/modules/declare-attrsOfSub-any-enable.nix29
-rw-r--r--nixpkgs/lib/tests/modules/declare-coerced-value-unsound.nix10
-rw-r--r--nixpkgs/lib/tests/modules/declare-coerced-value.nix10
-rw-r--r--nixpkgs/lib/tests/modules/declare-either.nix5
-rw-r--r--nixpkgs/lib/tests/modules/declare-enable-nested.nix14
-rw-r--r--nixpkgs/lib/tests/modules/declare-enable.nix14
-rw-r--r--nixpkgs/lib/tests/modules/declare-int-between-value.nix9
-rw-r--r--nixpkgs/lib/tests/modules/declare-int-positive-value-nested.nix9
-rw-r--r--nixpkgs/lib/tests/modules/declare-int-positive-value.nix9
-rw-r--r--nixpkgs/lib/tests/modules/declare-int-unsigned-value.nix9
-rw-r--r--nixpkgs/lib/tests/modules/declare-lazyAttrsOf.nix6
-rw-r--r--nixpkgs/lib/tests/modules/declare-oneOf.nix9
-rw-r--r--nixpkgs/lib/tests/modules/declare-submoduleWith-modules.nix28
-rw-r--r--nixpkgs/lib/tests/modules/declare-submoduleWith-noshorthand.nix13
-rw-r--r--nixpkgs/lib/tests/modules/declare-submoduleWith-path.nix12
-rw-r--r--nixpkgs/lib/tests/modules/declare-submoduleWith-shorthand.nix14
-rw-r--r--nixpkgs/lib/tests/modules/declare-submoduleWith-special.nix17
-rw-r--r--nixpkgs/lib/tests/modules/default.nix8
-rw-r--r--nixpkgs/lib/tests/modules/define-_module-args-custom.nix7
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-bar-enable.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-bar.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable-force.nix5
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable-if.nix5
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-foo-force-enable.nix7
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-foo-if-enable.nix7
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-foo.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-force-foo-enable.nix7
-rw-r--r--nixpkgs/lib/tests/modules/define-attrsOfSub-if-foo-enable.nix7
-rw-r--r--nixpkgs/lib/tests/modules/define-enable-force.nix5
-rw-r--r--nixpkgs/lib/tests/modules/define-enable-with-custom-arg.nix7
-rw-r--r--nixpkgs/lib/tests/modules/define-enable.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-force-attrsOfSub-foo-enable.nix5
-rw-r--r--nixpkgs/lib/tests/modules/define-force-enable.nix5
-rw-r--r--nixpkgs/lib/tests/modules/define-if-attrsOfSub-foo-enable.nix5
-rw-r--r--nixpkgs/lib/tests/modules/define-module-check.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-option-dependently-nested.nix16
-rw-r--r--nixpkgs/lib/tests/modules/define-option-dependently.nix16
-rw-r--r--nixpkgs/lib/tests/modules/define-submoduleWith-noshorthand.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-submoduleWith-shorthand.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-value-int-negative.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-value-int-positive.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-value-int-zero.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-value-list.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-value-string-arbitrary.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-value-string-bigint.nix3
-rw-r--r--nixpkgs/lib/tests/modules/define-value-string-properties.nix12
-rw-r--r--nixpkgs/lib/tests/modules/define-value-string.nix3
-rw-r--r--nixpkgs/lib/tests/modules/disable-declare-enable.nix5
-rw-r--r--nixpkgs/lib/tests/modules/disable-define-enable.nix5
-rw-r--r--nixpkgs/lib/tests/modules/disable-enable-modules.nix5
-rw-r--r--nixpkgs/lib/tests/modules/disable-recursive/bar.nix5
-rw-r--r--nixpkgs/lib/tests/modules/disable-recursive/disable-bar.nix7
-rw-r--r--nixpkgs/lib/tests/modules/disable-recursive/disable-foo.nix7
-rw-r--r--nixpkgs/lib/tests/modules/disable-recursive/foo.nix5
-rw-r--r--nixpkgs/lib/tests/modules/disable-recursive/main.nix8
-rw-r--r--nixpkgs/lib/tests/modules/freeform-attrsOf.nix3
-rw-r--r--nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix3
-rw-r--r--nixpkgs/lib/tests/modules/freeform-nested.nix7
-rw-r--r--nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix8
-rw-r--r--nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix8
-rw-r--r--nixpkgs/lib/tests/modules/functionTo/list-order.nix25
-rw-r--r--nixpkgs/lib/tests/modules/functionTo/merging-attrs.nix27
-rw-r--r--nixpkgs/lib/tests/modules/functionTo/merging-list.nix24
-rw-r--r--nixpkgs/lib/tests/modules/functionTo/trivial.nix17
-rw-r--r--nixpkgs/lib/tests/modules/functionTo/wrong-type.nix18
-rw-r--r--nixpkgs/lib/tests/modules/import-custom-arg.nix6
-rw-r--r--nixpkgs/lib/tests/modules/import-from-store.nix11
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix12
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix26
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/functions.nix23
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/lists.nix16
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/mk-mods.nix44
-rw-r--r--nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix22
-rw-r--r--nixpkgs/lib/tests/release.nix36
-rwxr-xr-xnixpkgs/lib/tests/sources.sh59
-rw-r--r--nixpkgs/lib/tests/systems.nix36
86 files changed, 2131 insertions, 0 deletions
diff --git a/nixpkgs/lib/tests/check-eval.nix b/nixpkgs/lib/tests/check-eval.nix
new file mode 100644
index 000000000000..8bd7b605a39b
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/maintainers.nix b/nixpkgs/lib/tests/maintainers.nix
new file mode 100644
index 000000000000..3cbfba569481
--- /dev/null
+++ b/nixpkgs/lib/tests/maintainers.nix
@@ -0,0 +1,80 @@
+# to run these tests (and the others)
+# nix-build nixpkgs/lib/tests/release.nix
+{ # The pkgs used for dependencies for the testing itself
+  pkgs
+, lib
+}:
+
+let
+  inherit (lib) types;
+
+  maintainerModule = { config, ... }: {
+    options = {
+      name = lib.mkOption {
+        type = types.str;
+      };
+      email = lib.mkOption {
+        type = types.str;
+      };
+      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.longkeyid = lib.mkOption { type = types.str; };
+          options.fingerprint = lib.mkOption { type = types.str; };
+        });
+        default = [];
+      };
+    };
+  };
+
+  checkMaintainer = handle: uncheckedAttrs:
+  let
+      prefix = [ "lib" "maintainers" handle ];
+      checkedAttrs = (lib.modules.evalModules {
+        inherit prefix;
+        modules = [
+          maintainerModule
+          {
+            _file = toString ../../maintainers/maintainer-list.nix;
+            config = uncheckedAttrs;
+          }
+        ];
+      }).config;
+
+      checkGithubId = 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"
+      '';
+    in lib.deepSeq checkedAttrs checkGithubId;
+
+  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/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix
new file mode 100644
index 000000000000..7b3a6b4e60b8
--- /dev/null
+++ b/nixpkgs/lib/tests/misc.nix
@@ -0,0 +1,764 @@
+# 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
+
+  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";
+  };
+
+  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" ];
+  };
+
+  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;";
+  };
+
+# 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];
+  };
+
+  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;
+  };
+
+
+# ATTRSETS
+
+  # 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
+    '';
+  };
+
+  /* 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 = builtins.currentSystem; };
+    in {
+    expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
+      int = 42;
+      float = 0.1337;
+      bool = true;
+      emptystring = "";
+      string = ''fno"rd'';
+      newlinestring = "\n";
+      path = /. + "/foo";
+      null_ = null;
+      function = x: x;
+      functionArgs = { arg ? 4, foo }: arg;
+      list = [ 3 4 function [ false ] ];
+      emptylist = [];
+      attrs = { foo = null; "foo bar" = "baz"; };
+      emptyattrs = {};
+      drv = deriv;
+    };
+    expected = rec {
+      int = "42";
+      float = "~0.133700";
+      bool = "true";
+      emptystring = ''""'';
+      string = ''"fno\"rd"'';
+      newlinestring = "\"\\n\"";
+      path = "/foo";
+      null_ = "null";
+      function = "<function>";
+      functionArgs = "<function, args: {arg?, foo}>";
+      list = "[ 3 4 ${function} [ false ] ]";
+      emptylist = "[ ]";
+      attrs = "{ foo = null; \"foo bar\" = \"baz\"; }";
+      emptyattrs = "{ }";
+      drv = "<derivation ${deriv.drvPath}>";
+    };
+  };
+
+  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;
+    };
+
+  testToPrettyMultiline = {
+    expr = mapAttrs (const (generators.toPretty { })) rec {
+      list = [ 3 4 [ false ] ];
+      attrs = { foo = null; bar.foo = "baz"; };
+      newlinestring = "\n";
+      multilinestring = ''
+        hello
+        there
+        test
+      '';
+      multilinestring' = ''
+        hello
+        there
+        test'';
+    };
+    expected = rec {
+      list = ''
+        [
+          3
+          4
+          [
+            false
+          ]
+        ]'';
+      attrs = ''
+        {
+          bar = {
+            foo = "baz";
+          };
+          foo = null;
+        }'';
+      newlinestring = "''\n  \n''";
+      multilinestring = ''
+        '''
+          hello
+          there
+          test
+        ''''';
+      multilinestring' = ''
+        '''
+          hello
+          there
+          test''''';
+
+    };
+  };
+
+  testToPrettyAllowPrettyValues = {
+    expr = generators.toPretty { allowPrettyValues = true; }
+             { __pretty = v: "«" + v + "»"; val = "foo"; };
+    expected  = "«foo»";
+  };
+
+
+# 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";
+  };
+
+  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 = [ [ "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; }
+    ];
+  };
+}
diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh
new file mode 100755
index 000000000000..b51db91f6b07
--- /dev/null
+++ b/nixpkgs/lib/tests/modules.sh
@@ -0,0 +1,286 @@
+#!/bin/sh
+#
+# 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.
+
+# 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" "$@"
+    fail=$((fail + 1))
+}
+
+checkConfigOutput() {
+    local outputContains=$1
+    shift;
+    if evalConfig "$@" 2>/dev/null | grep --silent "$outputContains" ; then
+        pass=$((pass + 1))
+        return 0;
+    else
+        echo 2>&1 "error: Expected result matching '$outputContains', while evaluating"
+        reportFailure "$@"
+        return 1
+    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 "$@"
+        return 1
+    else
+        if echo "$err" | grep -zP --silent "$errorContains" ; then
+            pass=$((pass + 1))
+            return 0;
+        else
+            echo 2>&1 "error: Expected error matching '$errorContains', while evaluating"
+            reportFailure "$@"
+            return 1
+        fi
+    fi
+}
+
+# Check boolean option.
+checkConfigOutput "false" config.enable ./declare-enable.nix
+checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./define-enable.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- 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- 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- 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
+checkConfigError "The option .*enable.* does not exist. Definition values:\n- 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
+
+# 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- In .*' "$@"
+checkConfigOutput "true" "$@" ./define-module-check.nix
+
+# Check coerced value.
+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- 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- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
+checkConfigError 'unrecognised JSON value' 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
+
+# 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 declare a value of type \`bool'\nrather 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
+
+## Paths should be allowed as values and work as expected
+checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.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- 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
+# No freeform assigments 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
+
+## 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- 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
+
+cat <<EOF
+====== module tests ======
+$pass Pass
+$fail Fail
+EOF
+
+if test $fail -ne 0; then
+    exit 1
+fi
+exit 0
diff --git a/nixpkgs/lib/tests/modules/alias-with-priority-can-override.nix b/nixpkgs/lib/tests/modules/alias-with-priority-can-override.nix
new file mode 100644
index 000000000000..9a18c9d9f613
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/alias-with-priority.nix b/nixpkgs/lib/tests/modules/alias-with-priority.nix
new file mode 100644
index 000000000000..a35a06fc6974
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/attrsOf-conditional-check.nix b/nixpkgs/lib/tests/modules/attrsOf-conditional-check.nix
new file mode 100644
index 000000000000..0f00ebca1559
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/attrsOf-lazy-check.nix b/nixpkgs/lib/tests/modules/attrsOf-lazy-check.nix
new file mode 100644
index 000000000000..ec5b418b15aa
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-attrsOf.nix b/nixpkgs/lib/tests/modules/declare-attrsOf.nix
new file mode 100644
index 000000000000..b3999de7e5fb
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/declare-attrsOf.nix
@@ -0,0 +1,6 @@
+{ lib, ... }: {
+  options.value = lib.mkOption {
+    type = lib.types.attrsOf lib.types.str;
+    default = {};
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/declare-attrsOfSub-any-enable.nix b/nixpkgs/lib/tests/modules/declare-attrsOfSub-any-enable.nix
new file mode 100644
index 000000000000..986d07227e13
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-coerced-value-unsound.nix b/nixpkgs/lib/tests/modules/declare-coerced-value-unsound.nix
new file mode 100644
index 000000000000..7a017f24e77d
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-coerced-value.nix b/nixpkgs/lib/tests/modules/declare-coerced-value.nix
new file mode 100644
index 000000000000..76b12ad53f00
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-either.nix b/nixpkgs/lib/tests/modules/declare-either.nix
new file mode 100644
index 000000000000..5a0fa978a138
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-enable-nested.nix b/nixpkgs/lib/tests/modules/declare-enable-nested.nix
new file mode 100644
index 000000000000..c8da8273cba1
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-enable.nix b/nixpkgs/lib/tests/modules/declare-enable.nix
new file mode 100644
index 000000000000..ebee243c7568
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-int-between-value.nix b/nixpkgs/lib/tests/modules/declare-int-between-value.nix
new file mode 100644
index 000000000000..8b2624cc5d65
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-int-positive-value-nested.nix b/nixpkgs/lib/tests/modules/declare-int-positive-value-nested.nix
new file mode 100644
index 000000000000..72d2fb89fc3b
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-int-positive-value.nix b/nixpkgs/lib/tests/modules/declare-int-positive-value.nix
new file mode 100644
index 000000000000..6e48c6ac8feb
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/declare-int-positive-value.nix
@@ -0,0 +1,9 @@
+{ lib, ... }:
+
+{
+  options = {
+    value = lib.mkOption {
+      type = lib.types.ints.positive;
+    };
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/declare-int-unsigned-value.nix b/nixpkgs/lib/tests/modules/declare-int-unsigned-value.nix
new file mode 100644
index 000000000000..05d0eff01c94
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/declare-int-unsigned-value.nix
@@ -0,0 +1,9 @@
+{ lib, ... }:
+
+{
+  options = {
+    value = lib.mkOption {
+      type = lib.types.ints.unsigned;
+    };
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/declare-lazyAttrsOf.nix b/nixpkgs/lib/tests/modules/declare-lazyAttrsOf.nix
new file mode 100644
index 000000000000..1d9fec25f908
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-oneOf.nix b/nixpkgs/lib/tests/modules/declare-oneOf.nix
new file mode 100644
index 000000000000..df092a14f81e
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-submoduleWith-modules.nix b/nixpkgs/lib/tests/modules/declare-submoduleWith-modules.nix
new file mode 100644
index 000000000000..a8b82d176881
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-submoduleWith-noshorthand.nix b/nixpkgs/lib/tests/modules/declare-submoduleWith-noshorthand.nix
new file mode 100644
index 000000000000..af3b4ba470ff
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-submoduleWith-path.nix b/nixpkgs/lib/tests/modules/declare-submoduleWith-path.nix
new file mode 100644
index 000000000000..477647f32121
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-submoduleWith-shorthand.nix b/nixpkgs/lib/tests/modules/declare-submoduleWith-shorthand.nix
new file mode 100644
index 000000000000..63ac16293e2b
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/declare-submoduleWith-special.nix b/nixpkgs/lib/tests/modules/declare-submoduleWith-special.nix
new file mode 100644
index 000000000000..6b15c5bde203
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/default.nix b/nixpkgs/lib/tests/modules/default.nix
new file mode 100644
index 000000000000..5b0947104198
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/default.nix
@@ -0,0 +1,8 @@
+{ lib ? import ../.., modules ? [] }:
+
+{
+  inherit (lib.evalModules {
+    inherit modules;
+    specialArgs.modulesPath = ./.;
+  }) config options;
+}
diff --git a/nixpkgs/lib/tests/modules/define-_module-args-custom.nix b/nixpkgs/lib/tests/modules/define-_module-args-custom.nix
new file mode 100644
index 000000000000..e565fd215a57
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-_module-args-custom.nix
@@ -0,0 +1,7 @@
+{ lib, ... }:
+
+{
+  config = {
+    _module.args.custom = true;
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-bar-enable.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-bar-enable.nix
new file mode 100644
index 000000000000..99c55d8b3608
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-attrsOfSub-bar-enable.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.bar.enable = true;
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-bar.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-bar.nix
new file mode 100644
index 000000000000..2a33068a5687
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-attrsOfSub-bar.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.bar = {};
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable-force.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable-force.nix
new file mode 100644
index 000000000000..c9ee36446f14
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable-force.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+  attrsOfSub.foo.enable = lib.mkForce false;
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable-if.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable-if.nix
new file mode 100644
index 000000000000..0b3baddb5ec0
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable.nix
new file mode 100644
index 000000000000..39cd63cef724
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-enable.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.foo.enable = true;
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-force-enable.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-force-enable.nix
new file mode 100644
index 000000000000..009da7c77cdd
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-force-enable.nix
@@ -0,0 +1,7 @@
+{ lib, ... }:
+
+{
+  attrsOfSub.foo = lib.mkForce {
+    enable = false;
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-if-enable.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo-if-enable.nix
new file mode 100644
index 000000000000..93702dfa86f3
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/define-attrsOfSub-foo.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo.nix
new file mode 100644
index 000000000000..e6bb531dedde
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-attrsOfSub-foo.nix
@@ -0,0 +1,3 @@
+{
+  attrsOfSub.foo = {};
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-force-foo-enable.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-force-foo-enable.nix
new file mode 100644
index 000000000000..5c02dd343146
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-attrsOfSub-force-foo-enable.nix
@@ -0,0 +1,7 @@
+{ lib, ... }:
+
+{
+  attrsOfSub = lib.mkForce {
+    foo.enable = false;
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/define-attrsOfSub-if-foo-enable.nix b/nixpkgs/lib/tests/modules/define-attrsOfSub-if-foo-enable.nix
new file mode 100644
index 000000000000..a3fe6051d41f
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/define-enable-force.nix b/nixpkgs/lib/tests/modules/define-enable-force.nix
new file mode 100644
index 000000000000..f4990a328631
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-enable-force.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+  enable = lib.mkForce false;
+}
diff --git a/nixpkgs/lib/tests/modules/define-enable-with-custom-arg.nix b/nixpkgs/lib/tests/modules/define-enable-with-custom-arg.nix
new file mode 100644
index 000000000000..7da74671d148
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-enable-with-custom-arg.nix
@@ -0,0 +1,7 @@
+{ lib, custom, ... }:
+
+{
+  config = {
+    enable = custom;
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/define-enable.nix b/nixpkgs/lib/tests/modules/define-enable.nix
new file mode 100644
index 000000000000..7dc26010ae59
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-enable.nix
@@ -0,0 +1,3 @@
+{
+  enable = true;
+}
diff --git a/nixpkgs/lib/tests/modules/define-force-attrsOfSub-foo-enable.nix b/nixpkgs/lib/tests/modules/define-force-attrsOfSub-foo-enable.nix
new file mode 100644
index 000000000000..dafb2360e1f1
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-force-attrsOfSub-foo-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+lib.mkForce {
+  attrsOfSub.foo.enable = false;
+}
diff --git a/nixpkgs/lib/tests/modules/define-force-enable.nix b/nixpkgs/lib/tests/modules/define-force-enable.nix
new file mode 100644
index 000000000000..978caa2a8c07
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-force-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+lib.mkForce {
+  enable = false;
+}
diff --git a/nixpkgs/lib/tests/modules/define-if-attrsOfSub-foo-enable.nix b/nixpkgs/lib/tests/modules/define-if-attrsOfSub-foo-enable.nix
new file mode 100644
index 000000000000..6a8e32e802a2
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/define-module-check.nix b/nixpkgs/lib/tests/modules/define-module-check.nix
new file mode 100644
index 000000000000..5a0707c975fa
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-module-check.nix
@@ -0,0 +1,3 @@
+{
+  _module.check = false;
+}
diff --git a/nixpkgs/lib/tests/modules/define-option-dependently-nested.nix b/nixpkgs/lib/tests/modules/define-option-dependently-nested.nix
new file mode 100644
index 000000000000..69ee4255534a
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/define-option-dependently.nix b/nixpkgs/lib/tests/modules/define-option-dependently.nix
new file mode 100644
index 000000000000..ad85f99a919f
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/define-submoduleWith-noshorthand.nix b/nixpkgs/lib/tests/modules/define-submoduleWith-noshorthand.nix
new file mode 100644
index 000000000000..35e1607b6f1c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-submoduleWith-noshorthand.nix
@@ -0,0 +1,3 @@
+{
+  submodule.config.config = true;
+}
diff --git a/nixpkgs/lib/tests/modules/define-submoduleWith-shorthand.nix b/nixpkgs/lib/tests/modules/define-submoduleWith-shorthand.nix
new file mode 100644
index 000000000000..17df248db8ef
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-submoduleWith-shorthand.nix
@@ -0,0 +1,3 @@
+{
+  submodule.config = true;
+}
diff --git a/nixpkgs/lib/tests/modules/define-value-int-negative.nix b/nixpkgs/lib/tests/modules/define-value-int-negative.nix
new file mode 100644
index 000000000000..a041222987ad
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-int-negative.nix
@@ -0,0 +1,3 @@
+{
+  value = -23;
+}
diff --git a/nixpkgs/lib/tests/modules/define-value-int-positive.nix b/nixpkgs/lib/tests/modules/define-value-int-positive.nix
new file mode 100644
index 000000000000..5803de172636
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-int-positive.nix
@@ -0,0 +1,3 @@
+{
+  value = 42;
+}
diff --git a/nixpkgs/lib/tests/modules/define-value-int-zero.nix b/nixpkgs/lib/tests/modules/define-value-int-zero.nix
new file mode 100644
index 000000000000..68bb9f415c3c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-int-zero.nix
@@ -0,0 +1,3 @@
+{
+  value = 0;
+}
diff --git a/nixpkgs/lib/tests/modules/define-value-list.nix b/nixpkgs/lib/tests/modules/define-value-list.nix
new file mode 100644
index 000000000000..4831c1cc09ba
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-list.nix
@@ -0,0 +1,3 @@
+{
+  value = [];
+}
diff --git a/nixpkgs/lib/tests/modules/define-value-string-arbitrary.nix b/nixpkgs/lib/tests/modules/define-value-string-arbitrary.nix
new file mode 100644
index 000000000000..8e3abaf536a0
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-string-arbitrary.nix
@@ -0,0 +1,3 @@
+{
+  value = "foobar";
+}
diff --git a/nixpkgs/lib/tests/modules/define-value-string-bigint.nix b/nixpkgs/lib/tests/modules/define-value-string-bigint.nix
new file mode 100644
index 000000000000..f27e31985c92
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-string-bigint.nix
@@ -0,0 +1,3 @@
+{
+  value = "1000";
+}
diff --git a/nixpkgs/lib/tests/modules/define-value-string-properties.nix b/nixpkgs/lib/tests/modules/define-value-string-properties.nix
new file mode 100644
index 000000000000..972304c01128
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/define-value-string.nix b/nixpkgs/lib/tests/modules/define-value-string.nix
new file mode 100644
index 000000000000..e7a166965a7a
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/define-value-string.nix
@@ -0,0 +1,3 @@
+{
+  value = "24";
+}
diff --git a/nixpkgs/lib/tests/modules/disable-declare-enable.nix b/nixpkgs/lib/tests/modules/disable-declare-enable.nix
new file mode 100644
index 000000000000..a373ee7e550e
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-declare-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+  disabledModules = [ ./declare-enable.nix ];
+}
diff --git a/nixpkgs/lib/tests/modules/disable-define-enable.nix b/nixpkgs/lib/tests/modules/disable-define-enable.nix
new file mode 100644
index 000000000000..0d84a7c3cb6c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-define-enable.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+  disabledModules = [ ./define-enable.nix ];
+}
diff --git a/nixpkgs/lib/tests/modules/disable-enable-modules.nix b/nixpkgs/lib/tests/modules/disable-enable-modules.nix
new file mode 100644
index 000000000000..c325f4e07431
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-enable-modules.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+  disabledModules = [ "define-enable.nix" "declare-enable.nix" ];
+}
diff --git a/nixpkgs/lib/tests/modules/disable-recursive/bar.nix b/nixpkgs/lib/tests/modules/disable-recursive/bar.nix
new file mode 100644
index 000000000000..4d9240a432df
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-recursive/bar.nix
@@ -0,0 +1,5 @@
+{
+  imports = [
+    ../declare-enable.nix
+  ];
+}
diff --git a/nixpkgs/lib/tests/modules/disable-recursive/disable-bar.nix b/nixpkgs/lib/tests/modules/disable-recursive/disable-bar.nix
new file mode 100644
index 000000000000..987b2802ae8c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-recursive/disable-bar.nix
@@ -0,0 +1,7 @@
+{
+
+  disabledModules = [
+    ./bar.nix
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/disable-recursive/disable-foo.nix b/nixpkgs/lib/tests/modules/disable-recursive/disable-foo.nix
new file mode 100644
index 000000000000..5b68a3c46105
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-recursive/disable-foo.nix
@@ -0,0 +1,7 @@
+{
+
+  disabledModules = [
+    ./foo.nix
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/disable-recursive/foo.nix b/nixpkgs/lib/tests/modules/disable-recursive/foo.nix
new file mode 100644
index 000000000000..4d9240a432df
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-recursive/foo.nix
@@ -0,0 +1,5 @@
+{
+  imports = [
+    ../declare-enable.nix
+  ];
+}
diff --git a/nixpkgs/lib/tests/modules/disable-recursive/main.nix b/nixpkgs/lib/tests/modules/disable-recursive/main.nix
new file mode 100644
index 000000000000..48a3c6218cf3
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/disable-recursive/main.nix
@@ -0,0 +1,8 @@
+{
+  imports = [
+    ./foo.nix
+    ./bar.nix
+  ];
+
+  enable = true;
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-attrsOf.nix b/nixpkgs/lib/tests/modules/freeform-attrsOf.nix
new file mode 100644
index 000000000000..8cc577f38a6c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-attrsOf.nix
@@ -0,0 +1,3 @@
+{ lib, ... }: {
+  freeformType = with lib.types; attrsOf (either str (attrsOf str));
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix b/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix
new file mode 100644
index 000000000000..36d6c0b13fca
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix
@@ -0,0 +1,3 @@
+{ lib, ... }: {
+  freeformType = with lib.types; lazyAttrsOf (either str (lazyAttrsOf str));
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-nested.nix b/nixpkgs/lib/tests/modules/freeform-nested.nix
new file mode 100644
index 000000000000..5da27f5a8b4f
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-nested.nix
@@ -0,0 +1,7 @@
+{ lib, ... }: {
+  options.nest.foo = lib.mkOption {
+    type = lib.types.bool;
+    default = false;
+  };
+  config.nest.bar = "bar";
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix b/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix
new file mode 100644
index 000000000000..a2dfbc80cfa6
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix b/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix
new file mode 100644
index 000000000000..549d89afecac
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/functionTo/list-order.nix b/nixpkgs/lib/tests/modules/functionTo/list-order.nix
new file mode 100644
index 000000000000..77a1a43a84f0
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/functionTo/merging-attrs.nix b/nixpkgs/lib/tests/modules/functionTo/merging-attrs.nix
new file mode 100644
index 000000000000..97c015f928ab
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/functionTo/merging-list.nix b/nixpkgs/lib/tests/modules/functionTo/merging-list.nix
new file mode 100644
index 000000000000..15fcd2bdcc42
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/functionTo/trivial.nix b/nixpkgs/lib/tests/modules/functionTo/trivial.nix
new file mode 100644
index 000000000000..0962a0cf893d
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/functionTo/wrong-type.nix b/nixpkgs/lib/tests/modules/functionTo/wrong-type.nix
new file mode 100644
index 000000000000..fd65b75088da
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/import-custom-arg.nix b/nixpkgs/lib/tests/modules/import-custom-arg.nix
new file mode 100644
index 000000000000..3e687b661c16
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/import-custom-arg.nix
@@ -0,0 +1,6 @@
+{ lib, custom, ... }:
+
+{
+  imports = []
+  ++ lib.optional custom ./define-enable-force.nix;
+}
diff --git a/nixpkgs/lib/tests/modules/import-from-store.nix b/nixpkgs/lib/tests/modules/import-from-store.nix
new file mode 100644
index 000000000000..f5af22432ce1
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/import-from-store.nix
@@ -0,0 +1,11 @@
+{ lib, ... }:
+{
+
+  imports = [
+    "${builtins.toFile "drv" "{}"}"
+    ./declare-enable.nix
+    ./define-enable.nix
+  ];
+
+}
+
diff --git a/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix b/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix
new file mode 100644
index 000000000000..085cbd638f17
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix b/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix
new file mode 100644
index 000000000000..972711201a09
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/types-anything/functions.nix b/nixpkgs/lib/tests/modules/types-anything/functions.nix
new file mode 100644
index 000000000000..21edd4aff9c4
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/types-anything/lists.nix b/nixpkgs/lib/tests/modules/types-anything/lists.nix
new file mode 100644
index 000000000000..bd846afd3d18
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix b/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix
new file mode 100644
index 000000000000..f84ad01df017
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix b/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix
new file mode 100644
index 000000000000..e57d33ef8717
--- /dev/null
+++ b/nixpkgs/lib/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/nixpkgs/lib/tests/release.nix b/nixpkgs/lib/tests/release.nix
new file mode 100644
index 000000000000..77e0e1af7555
--- /dev/null
+++ b/nixpkgs/lib/tests/release.nix
@@ -0,0 +1,36 @@
+{ # 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!"; }
+}:
+
+pkgs.runCommand "nixpkgs-lib-tests" {
+  buildInputs = [
+    pkgs.nix
+    (import ./check-eval.nix)
+    (import ./maintainers.nix {
+      inherit pkgs;
+      lib = import ../.;
+    })
+  ];
+} ''
+    datadir="${pkgs.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
+    nix-store --init
+
+    cp -r ${../.} lib
+    echo "Running lib/tests/modules.sh"
+    bash lib/tests/modules.sh
+
+    echo "Running lib/tests/sources.sh"
+    TEST_LIB=$PWD/lib bash lib/tests/sources.sh
+
+    touch $out
+''
diff --git a/nixpkgs/lib/tests/sources.sh b/nixpkgs/lib/tests/sources.sh
new file mode 100755
index 000000000000..71fee719cb21
--- /dev/null
+++ b/nixpkgs/lib/tests/sources.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Use
+#     || die
+die() {
+  echo >&2 "test case failed: " "$@"
+  exit 1
+}
+
+if test -n "${TEST_LIB:-}"; then
+  export NIX_PATH=nixpkgs="$(dirname "$TEST_LIB")"
+else
+  export NIX_PATH=nixpkgs="$(cd $(dirname ${BASH_SOURCE[0]})/../..; pwd)"
+fi
+
+work="$(mktemp -d)"
+clean_up() {
+  rm -rf "$work"
+}
+trap clean_up EXIT
+cd $work
+
+touch {README.md,module.o,foo.bar}
+
+# nix-instantiate doesn't write out the source, only computing the hash, so
+# this uses the experimental nix command instead.
+
+dir="$(nix eval --raw '(with import <nixpkgs/lib>; "${
+  cleanSource ./.
+}")')"
+(cd $dir; find) | sort -f | diff -U10 - <(cat <<EOF
+.
+./foo.bar
+./README.md
+EOF
+) || die "cleanSource 1"
+
+
+dir="$(nix eval --raw '(with import <nixpkgs/lib>; "${
+  cleanSourceWith { src = '"$work"'; filter = path: type: ! hasSuffix ".bar" path; }
+}")')"
+(cd $dir; find) | sort -f | diff -U10 - <(cat <<EOF
+.
+./module.o
+./README.md
+EOF
+) || die "cleanSourceWith 1"
+
+dir="$(nix eval --raw '(with import <nixpkgs/lib>; "${
+  cleanSourceWith { src = cleanSource '"$work"'; filter = path: type: ! hasSuffix ".bar" path; }
+}")')"
+(cd $dir; find) | sort -f | diff -U10 - <(cat <<EOF
+.
+./README.md
+EOF
+) || die "cleanSourceWith + cleanSource"
+
+echo >&2 tests ok
diff --git a/nixpkgs/lib/tests/systems.nix b/nixpkgs/lib/tests/systems.nix
new file mode 100644
index 000000000000..2646e792682b
--- /dev/null
+++ b/nixpkgs/lib/tests/systems.nix
@@ -0,0 +1,36 @@
+# 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" ];
+  testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ];
+  testmips = mseteq mips [ "mipsel-linux" "mipsel-netbsd" ];
+  testmmix = mseteq mmix [ "mmix-mmixware" ];
+  testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd" "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-freebsd" "x86_64-freebsd" ];
+  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" "mipsel-linux" "riscv32-linux" "riscv64-linux" "x86_64-linux" "powerpc64-linux" "powerpc64le-linux" "m68k-linux" "s390-linux" "s390x-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);
+}