diff options
Diffstat (limited to 'tests')
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 = ""test" 'test' < & >"; + }; + + 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 |