aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/build-remote.sh1
-rw-r--r--tests/build.sh4
-rw-r--r--tests/check.sh3
-rw-r--r--tests/common.sh2
-rw-r--r--tests/common/vars-and-functions.sh.in2
-rw-r--r--tests/dependencies.sh3
-rw-r--r--tests/fetchClosure.sh81
-rw-r--r--tests/fetchGit.sh6
-rw-r--r--tests/flakes/check.sh12
-rw-r--r--tests/flakes/flakes.sh5
-rw-r--r--tests/gc.sh28
-rw-r--r--tests/hermetic.nix56
-rw-r--r--tests/lang-test-infra.sh86
-rwxr-xr-x[-rw-r--r--]tests/lang.sh124
-rw-r--r--tests/lang/empty.exp0
-rw-r--r--tests/lang/eval-fail-abort.err.exp10
-rw-r--r--tests/lang/eval-fail-antiquoted-path.err.exp1
-rw-r--r--tests/lang/eval-fail-assert.err.exp36
-rw-r--r--tests/lang/eval-fail-bad-antiquote-1.err.exp10
-rw-r--r--tests/lang/eval-fail-bad-antiquote-2.err.exp1
-rw-r--r--tests/lang/eval-fail-bad-antiquote-3.err.exp10
-rw-r--r--tests/lang/eval-fail-bad-string-interpolation-1.err.exp10
-rw-r--r--tests/lang/eval-fail-bad-string-interpolation-1.nix (renamed from tests/lang/eval-fail-bad-antiquote-1.nix)0
-rw-r--r--tests/lang/eval-fail-bad-string-interpolation-2.err.exp1
-rw-r--r--tests/lang/eval-fail-bad-string-interpolation-2.nix (renamed from tests/lang/eval-fail-bad-antiquote-2.nix)0
-rw-r--r--tests/lang/eval-fail-bad-string-interpolation-3.err.exp10
-rw-r--r--tests/lang/eval-fail-bad-string-interpolation-3.nix (renamed from tests/lang/eval-fail-bad-antiquote-3.nix)0
-rw-r--r--tests/lang/eval-fail-blackhole.err.exp18
-rw-r--r--tests/lang/eval-fail-deepseq.err.exp26
-rw-r--r--tests/lang/eval-fail-foldlStrict-strict-op-application.err.exp38
-rw-r--r--tests/lang/eval-fail-fromTOML-timestamps.err.exp12
-rw-r--r--tests/lang/eval-fail-hashfile-missing.err.exp19
-rw-r--r--tests/lang/eval-fail-list.err.exp10
-rw-r--r--tests/lang/eval-fail-list.nix1
-rw-r--r--tests/lang/eval-fail-missing-arg.err.exp16
-rw-r--r--tests/lang/eval-fail-nonexist-path.err.exp1
-rw-r--r--tests/lang/eval-fail-path-slash.err.exp8
-rw-r--r--tests/lang/eval-fail-recursion.err.exp16
-rw-r--r--tests/lang/eval-fail-recursion.nix1
-rw-r--r--tests/lang/eval-fail-remove.err.exp19
-rw-r--r--tests/lang/eval-fail-scope-5.err.exp36
-rw-r--r--tests/lang/eval-fail-seq.err.exp18
-rw-r--r--tests/lang/eval-fail-set-override.err.exp6
-rw-r--r--tests/lang/eval-fail-set-override.nix1
-rw-r--r--tests/lang/eval-fail-set.err.exp7
-rw-r--r--tests/lang/eval-fail-set.nix1
-rw-r--r--tests/lang/eval-fail-substring.err.exp12
-rw-r--r--tests/lang/eval-fail-to-path.err.exp14
-rw-r--r--tests/lang/eval-fail-undeclared-arg.err.exp17
-rw-r--r--tests/lang/eval-okay-fromjson.nix14
-rw-r--r--tests/lang/eval-okay-overrides.nix2
-rw-r--r--tests/lang/eval-okay-path-string-interpolation.exp (renamed from tests/lang/eval-okay-path-antiquotation.exp)0
-rw-r--r--tests/lang/eval-okay-path-string-interpolation.nix (renamed from tests/lang/eval-okay-path-antiquotation.nix)0
-rw-r--r--tests/lang/eval-okay-print.err.exp1
-rw-r--r--tests/lang/eval-okay-print.exp1
-rw-r--r--tests/lang/eval-okay-print.nix1
-rw-r--r--tests/lang/eval-okay-search-path.flags2
-rw-r--r--tests/lang/framework.sh33
-rw-r--r--tests/lang/parse-fail-dup-attrs-1.err.exp7
-rw-r--r--tests/lang/parse-fail-dup-attrs-2.err.exp7
-rw-r--r--tests/lang/parse-fail-dup-attrs-3.err.exp7
-rw-r--r--tests/lang/parse-fail-dup-attrs-4.err.exp7
-rw-r--r--tests/lang/parse-fail-dup-attrs-6.err.exp1
-rw-r--r--tests/lang/parse-fail-dup-attrs-7.err.exp7
-rw-r--r--tests/lang/parse-fail-dup-formals.err.exp6
-rw-r--r--tests/lang/parse-fail-eof-in-string.err.exp7
-rw-r--r--tests/lang/parse-fail-mixed-nested-attrs1.err.exp8
-rw-r--r--tests/lang/parse-fail-mixed-nested-attrs2.err.exp8
-rw-r--r--tests/lang/parse-fail-patterns-1.err.exp7
-rw-r--r--tests/lang/parse-fail-regression-20060610.err.exp8
-rw-r--r--tests/lang/parse-fail-undef-var-2.err.exp7
-rw-r--r--tests/lang/parse-fail-undef-var.err.exp7
-rw-r--r--tests/lang/parse-fail-utf8.err.exp6
-rw-r--r--tests/lang/parse-fail-utf8.nix (renamed from tests/lang/parse-fail-uft8.nix)0
-rw-r--r--tests/lang/parse-okay-1.exp1
-rw-r--r--tests/lang/parse-okay-crlf.exp1
-rw-r--r--tests/lang/parse-okay-dup-attrs-5.exp1
-rw-r--r--tests/lang/parse-okay-dup-attrs-6.exp1
-rw-r--r--tests/lang/parse-okay-mixed-nested-attrs-1.exp1
-rw-r--r--tests/lang/parse-okay-mixed-nested-attrs-2.exp1
-rw-r--r--tests/lang/parse-okay-mixed-nested-attrs-3.exp1
-rw-r--r--tests/lang/parse-okay-regression-20041027.exp1
-rw-r--r--tests/lang/parse-okay-regression-751.exp1
-rw-r--r--tests/lang/parse-okay-subversion.exp1
-rw-r--r--tests/lang/parse-okay-url.exp1
-rw-r--r--tests/linux-sandbox-cert-test.nix45
-rw-r--r--tests/linux-sandbox.sh27
-rw-r--r--tests/local.mk9
-rw-r--r--tests/misc.sh6
-rw-r--r--tests/nested-sandboxing.sh11
-rw-r--r--tests/nested-sandboxing/command.sh29
-rw-r--r--tests/nested-sandboxing/runner.nix24
-rw-r--r--tests/nix-channel.sh2
-rw-r--r--tests/nix-collect-garbage-d.sh40
-rw-r--r--tests/nix-profile.sh3
-rw-r--r--tests/nixos/authorization.nix15
-rw-r--r--tests/nixos/tarball-flakes.nix84
-rw-r--r--tests/plugins/plugintest.cc6
-rw-r--r--tests/read-only-store.sh42
-rw-r--r--tests/restricted.sh2
-rw-r--r--tests/signing.sh4
-rw-r--r--tests/supplementary-groups.sh37
-rw-r--r--tests/test-libstoreconsumer.sh6
-rw-r--r--tests/test-libstoreconsumer/README.md6
-rw-r--r--tests/test-libstoreconsumer/local.mk15
-rw-r--r--tests/test-libstoreconsumer/main.cc45
106 files changed, 1320 insertions, 102 deletions
diff --git a/tests/build-remote.sh b/tests/build-remote.sh
index 78e12b477..d2a2132c1 100644
--- a/tests/build-remote.sh
+++ b/tests/build-remote.sh
@@ -1,6 +1,7 @@
requireSandboxSupport
[[ $busybox =~ busybox ]] || skipTest "no busybox"
+# Avoid store dir being inside sandbox build-dir
unset NIX_STORE_DIR
unset NIX_STATE_DIR
diff --git a/tests/build.sh b/tests/build.sh
index 697aff0f9..8ae20f0df 100644
--- a/tests/build.sh
+++ b/tests/build.sh
@@ -129,3 +129,7 @@ nix build --impure -f multiple-outputs.nix --json e --no-link | jq --exit-status
(.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a_a", "b"]))
'
+
+# Make sure that `--stdin` works and does not apply any defaults
+printf "" | nix build --no-link --stdin --json | jq --exit-status '. == []'
+printf "%s\n" "$drv^*" | nix build --no-link --stdin --json | jq --exit-status '.[0]|has("drvPath")'
diff --git a/tests/check.sh b/tests/check.sh
index 645b90222..e13abf747 100644
--- a/tests/check.sh
+++ b/tests/check.sh
@@ -18,6 +18,9 @@ clearStore
nix-build dependencies.nix --no-out-link
nix-build dependencies.nix --no-out-link --check
+# Build failure exit codes (100, 104, etc.) are from
+# doc/manual/src/command-ref/status-build-failure.md
+
# check for dangling temporary build directories
# only retain if build fails and --keep-failed is specified, or...
# ...build is non-deterministic and --check and --keep-failed are both specified
diff --git a/tests/common.sh b/tests/common.sh
index 8941671d6..7b0922c9f 100644
--- a/tests/common.sh
+++ b/tests/common.sh
@@ -4,7 +4,7 @@ if [[ -z "${COMMON_SH_SOURCED-}" ]]; then
COMMON_SH_SOURCED=1
-source "$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")/common/vars-and-functions.sh"
+source "$(readlink -f "$(dirname "${BASH_SOURCE[0]-$0}")")/common/vars-and-functions.sh"
if [[ -n "${NIX_DAEMON_PACKAGE:-}" ]]; then
startDaemon
fi
diff --git a/tests/common/vars-and-functions.sh.in b/tests/common/vars-and-functions.sh.in
index a9e6c802f..dc7ce13cc 100644
--- a/tests/common/vars-and-functions.sh.in
+++ b/tests/common/vars-and-functions.sh.in
@@ -4,7 +4,7 @@ if [[ -z "${COMMON_VARS_AND_FUNCTIONS_SH_SOURCED-}" ]]; then
COMMON_VARS_AND_FUNCTIONS_SH_SOURCED=1
-export PS4='+(${BASH_SOURCE[0]}:$LINENO) '
+export PS4='+(${BASH_SOURCE[0]-$0}:$LINENO) '
export TEST_ROOT=$(realpath ${TMPDIR:-/tmp}/nix-test)/${TEST_NAME:-default}
export NIX_STORE_DIR
diff --git a/tests/dependencies.sh b/tests/dependencies.sh
index f9da0c6bc..d5cd30396 100644
--- a/tests/dependencies.sh
+++ b/tests/dependencies.sh
@@ -15,6 +15,9 @@ if test -n "$dot"; then
$dot < $TEST_ROOT/graph
fi
+# Test GraphML graph generation
+nix-store -q --graphml "$drvPath" > $TEST_ROOT/graphml
+
outPath=$(nix-store -rvv "$drvPath") || fail "build failed"
# Test Graphviz graph generation.
diff --git a/tests/fetchClosure.sh b/tests/fetchClosure.sh
index a207f647c..a02d1ce7a 100644
--- a/tests/fetchClosure.sh
+++ b/tests/fetchClosure.sh
@@ -5,6 +5,12 @@ enableFeatures "fetch-closure"
clearStore
clearCacheCache
+# Old daemons don't properly zero out the self-references when
+# calculating the CA hashes, so this breaks `nix store
+# make-content-addressed` which expects the client and the daemon to
+# compute the same hash
+requireDaemonNewerThan "2.16.0pre20230524"
+
# Initialize binary cache.
nonCaPath=$(nix build --json --file ./dependencies.nix --no-link | jq -r .[].outputs.out)
caPath=$(nix store make-content-addressed --json $nonCaPath | jq -r '.rewrites | map(.) | .[]')
@@ -27,20 +33,43 @@ clearStore
[ ! -e $nonCaPath ]
[ -e $caPath ]
+clearStore
+
+# The daemon will reject input addressed paths unless configured to trust the
+# cache key or the user. This behavior should be covered by another test, so we
+# skip this part when using the daemon.
if [[ "$NIX_REMOTE" != "daemon" ]]; then
- # In impure mode, we can use non-CA paths.
- [[ $(nix eval --raw --no-require-sigs --impure --expr "
+ # If we want to return a non-CA path, we have to be explicit about it.
+ expectStderr 1 nix eval --raw --no-require-sigs --expr "
+ builtins.fetchClosure {
+ fromStore = \"file://$cacheDir\";
+ fromPath = $nonCaPath;
+ }
+ " | grepQuiet -E "The .fromPath. value .* is input-addressed, but .inputAddressed. is set to .false."
+
+ # TODO: Should the closure be rejected, despite single user mode?
+ # [ ! -e $nonCaPath ]
+
+ [ ! -e $caPath ]
+
+ # We can use non-CA paths when we ask explicitly.
+ [[ $(nix eval --raw --no-require-sigs --expr "
builtins.fetchClosure {
fromStore = \"file://$cacheDir\";
fromPath = $nonCaPath;
+ inputAddressed = true;
}
") = $nonCaPath ]]
[ -e $nonCaPath ]
+ [ ! -e $caPath ]
+
fi
+[ ! -e $caPath ]
+
# 'toPath' set to empty string should fail but print the expected path.
expectStderr 1 nix eval -v --json --expr "
builtins.fetchClosure {
@@ -53,6 +82,10 @@ expectStderr 1 nix eval -v --json --expr "
# If fromPath is CA, then toPath isn't needed.
nix copy --to file://$cacheDir $caPath
+clearStore
+
+[ ! -e $caPath ]
+
[[ $(nix eval -v --raw --expr "
builtins.fetchClosure {
fromStore = \"file://$cacheDir\";
@@ -60,6 +93,8 @@ nix copy --to file://$cacheDir $caPath
}
") = $caPath ]]
+[ -e $caPath ]
+
# Check that URL query parameters aren't allowed.
clearStore
narCache=$TEST_ROOT/nar-cache
@@ -71,3 +106,45 @@ rm -rf $narCache
}
")
(! [ -e $narCache ])
+
+# If toPath is specified but wrong, we check it (only) when the path is missing.
+clearStore
+
+badPath=$(echo $caPath | sed -e 's!/store/................................-!/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-!')
+
+[ ! -e $badPath ]
+
+expectStderr 1 nix eval -v --raw --expr "
+ builtins.fetchClosure {
+ fromStore = \"file://$cacheDir\";
+ fromPath = $nonCaPath;
+ toPath = $badPath;
+ }
+" | grep "error: rewriting.*$nonCaPath.*yielded.*$caPath.*while.*$badPath.*was expected"
+
+[ ! -e $badPath ]
+
+# We only check it when missing, as a performance optimization similar to what we do for fixed output derivations. So if it's already there, we don't check it.
+# It would be nice for this to fail, but checking it would be too(?) slow.
+[ -e $caPath ]
+
+[[ $(nix eval -v --raw --expr "
+ builtins.fetchClosure {
+ fromStore = \"file://$cacheDir\";
+ fromPath = $badPath;
+ toPath = $caPath;
+ }
+") = $caPath ]]
+
+
+# However, if the output address is unexpected, we can report it
+
+
+expectStderr 1 nix eval -v --raw --expr "
+ builtins.fetchClosure {
+ fromStore = \"file://$cacheDir\";
+ fromPath = $caPath;
+ inputAddressed = true;
+ }
+" | grepQuiet 'error.*The store object referred to by.*fromPath.* at .* is not input-addressed, but .*inputAddressed.* is set to .*true.*'
+
diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh
index e2ccb0e97..418b4f63f 100644
--- a/tests/fetchGit.sh
+++ b/tests/fetchGit.sh
@@ -105,6 +105,8 @@ path2=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath")
[[ $(cat $path2/dir1/foo) = foo ]]
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).rev") = 0000000000000000000000000000000000000000 ]]
+[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).dirtyRev") = "${rev2}-dirty" ]]
+[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).dirtyShortRev") = "${rev2:0:7}-dirty" ]]
# ... unless we're using an explicit ref or rev.
path3=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"master\"; }).outPath")
@@ -119,6 +121,10 @@ git -C $repo commit -m 'Bla3' -a
path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchGit file://$repo).outPath")
[[ $path2 = $path4 ]]
+[[ $(nix eval --impure --expr "builtins.hasAttr \"rev\" (builtins.fetchGit $repo)") == "true" ]]
+[[ $(nix eval --impure --expr "builtins.hasAttr \"dirtyRev\" (builtins.fetchGit $repo)") == "false" ]]
+[[ $(nix eval --impure --expr "builtins.hasAttr \"dirtyShortRev\" (builtins.fetchGit $repo)") == "false" ]]
+
status=0
nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; rev = \"$rev2\"; narHash = \"sha256-B5yIPHhEm0eysJKEsO7nqxprh9vcblFxpJG11gXJus1=\"; }).outPath" || status=$?
[[ "$status" = "102" ]]
diff --git a/tests/flakes/check.sh b/tests/flakes/check.sh
index 34b82c61c..0433e5335 100644
--- a/tests/flakes/check.sh
+++ b/tests/flakes/check.sh
@@ -27,6 +27,18 @@ EOF
cat > $flakeDir/flake.nix <<EOF
{
+ outputs = { self, ... }: {
+ overlays.x86_64-linux.foo = final: prev: {
+ };
+ };
+}
+EOF
+
+checkRes=$(nix flake check $flakeDir 2>&1 && fail "nix flake check --all-systems should have failed" || true)
+echo "$checkRes" | grepQuiet "error: overlay is not a function, but a set instead"
+
+cat > $flakeDir/flake.nix <<EOF
+{
outputs = { self }: {
nixosModules.foo = {
a.b.c = 123;
diff --git a/tests/flakes/flakes.sh b/tests/flakes/flakes.sh
index f2e216435..128f759ea 100644
--- a/tests/flakes/flakes.sh
+++ b/tests/flakes/flakes.sh
@@ -95,11 +95,16 @@ json=$(nix flake metadata flake1 --json | jq .)
[[ $(echo "$json" | jq -r .lastModified) = $(git -C $flake1Dir log -n1 --format=%ct) ]]
hash1=$(echo "$json" | jq -r .revision)
+echo foo > $flake1Dir/foo
+git -C $flake1Dir add $flake1Dir/foo
+[[ $(nix flake metadata flake1 --json --refresh | jq -r .dirtyRevision) == "$hash1-dirty" ]]
+
echo -n '# foo' >> $flake1Dir/flake.nix
flake1OriginalCommit=$(git -C $flake1Dir rev-parse HEAD)
git -C $flake1Dir commit -a -m 'Foo'
flake1NewCommit=$(git -C $flake1Dir rev-parse HEAD)
hash2=$(nix flake metadata flake1 --json --refresh | jq -r .revision)
+[[ $(nix flake metadata flake1 --json --refresh | jq -r .dirtyRevision) == "null" ]]
[[ $hash1 != $hash2 ]]
# Test 'nix build' on a flake.
diff --git a/tests/gc.sh b/tests/gc.sh
index 95669e25c..ad09a8b39 100644
--- a/tests/gc.sh
+++ b/tests/gc.sh
@@ -50,31 +50,3 @@ if test -e $outPath/foobar; then false; fi
# Check that the store is empty.
rmdir $NIX_STORE_DIR/.links
rmdir $NIX_STORE_DIR
-
-## Test `nix-collect-garbage -d`
-testCollectGarbageD () {
- clearProfiles
- # Run two `nix-env` commands, should create two generations of
- # the profile
- nix-env -f ./user-envs.nix -i foo-1.0
- nix-env -f ./user-envs.nix -i foo-2.0pre1
- [[ $(nix-env --list-generations | wc -l) -eq 2 ]]
-
- # Clear the profile history. There should be only one generation
- # left
- nix-collect-garbage -d
- [[ $(nix-env --list-generations | wc -l) -eq 1 ]]
-}
-# `nix-env` doesn't work with CA derivations, so let's ignore that bit if we're
-# using them
-if [[ -z "${NIX_TESTS_CA_BY_DEFAULT:-}" ]]; then
- testCollectGarbageD
-
- # Run the same test, but forcing the profiles at their legacy location under
- # /nix/var/nix.
- #
- # Regression test for #8294
- rm ~/.nix-profile
- ln -s $NIX_STATE_DIR/profiles/per-user/me ~/.nix-profile
- testCollectGarbageD
-fi
diff --git a/tests/hermetic.nix b/tests/hermetic.nix
new file mode 100644
index 000000000..4c9d7a51f
--- /dev/null
+++ b/tests/hermetic.nix
@@ -0,0 +1,56 @@
+{ busybox, seed }:
+
+with import ./config.nix;
+
+let
+ contentAddressedByDefault = builtins.getEnv "NIX_TESTS_CA_BY_DEFAULT" == "1";
+ caArgs = if contentAddressedByDefault then {
+ __contentAddressed = true;
+ outputHashMode = "recursive";
+ outputHashAlgo = "sha256";
+ } else {};
+
+ mkDerivation = args:
+ derivation ({
+ inherit system;
+ builder = busybox;
+ args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")];
+ } // removeAttrs args ["builder" "meta" "passthru"]
+ // caArgs)
+ // { meta = args.meta or {}; passthru = args.passthru or {}; };
+
+ input1 = mkDerivation {
+ shell = busybox;
+ name = "hermetic-input-1";
+ buildCommand = "echo hi-input1 seed=${toString seed}; echo FOO > $out";
+ };
+
+ input2 = mkDerivation {
+ shell = busybox;
+ name = "hermetic-input-2";
+ buildCommand = "echo hi; echo BAR > $out";
+ };
+
+ input3 = mkDerivation {
+ shell = busybox;
+ name = "hermetic-input-3";
+ buildCommand = ''
+ echo hi-input3
+ read x < ${input2}
+ echo $x BAZ > $out
+ '';
+ };
+
+in
+
+ mkDerivation {
+ shell = busybox;
+ name = "hermetic";
+ passthru = { inherit input1 input2 input3; };
+ buildCommand =
+ ''
+ read x < ${input1}
+ read y < ${input3}
+ echo "$x $y" > $out
+ '';
+ }
diff --git a/tests/lang-test-infra.sh b/tests/lang-test-infra.sh
new file mode 100644
index 000000000..30da8977b
--- /dev/null
+++ b/tests/lang-test-infra.sh
@@ -0,0 +1,86 @@
+# Test the function for lang.sh
+source common.sh
+
+source lang/framework.sh
+
+# We are testing this, so don't want outside world to affect us.
+unset _NIX_TEST_ACCEPT
+
+# We'll only modify this in subshells so we don't need to reset it.
+badDiff=0
+
+# matches non-empty
+echo Hi! > "$TEST_ROOT/got"
+cp "$TEST_ROOT/got" "$TEST_ROOT/expected"
+(
+ diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/expected"
+ (( "$badDiff" == 0 ))
+)
+
+# matches empty, non-existant file is the same as empty file
+echo -n > "$TEST_ROOT/got"
+(
+ diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/does-not-exist"
+ (( "$badDiff" == 0 ))
+)
+
+# doesn't matches non-empty, non-existant file is the same as empty file
+echo Hi! > "$TEST_ROOT/got"
+(
+ diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/does-not-exist"
+ (( "$badDiff" == 1 ))
+)
+
+# doesn't match, `badDiff` set, file unchanged
+echo Hi! > "$TEST_ROOT/got"
+echo Bye! > "$TEST_ROOT/expected"
+(
+ diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/expected"
+ (( "$badDiff" == 1 ))
+)
+[[ "$(echo Bye! )" == $(< "$TEST_ROOT/expected") ]]
+
+# _NIX_TEST_ACCEPT=1 matches non-empty
+echo Hi! > "$TEST_ROOT/got"
+cp "$TEST_ROOT/got" "$TEST_ROOT/expected"
+(
+ _NIX_TEST_ACCEPT=1 diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/expected"
+ (( "$badDiff" == 0 ))
+)
+
+# _NIX_TEST_ACCEPT doesn't match, `badDiff=1` set, file changed (was previously non-empty)
+echo Hi! > "$TEST_ROOT/got"
+echo Bye! > "$TEST_ROOT/expected"
+(
+ _NIX_TEST_ACCEPT=1 diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/expected"
+ (( "$badDiff" == 1 ))
+)
+[[ "$(echo Hi! )" == $(< "$TEST_ROOT/expected") ]]
+# second time succeeds
+(
+ diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/expected"
+ (( "$badDiff" == 0 ))
+)
+
+# _NIX_TEST_ACCEPT matches empty, non-existant file not created
+echo -n > "$TEST_ROOT/got"
+(
+ _NIX_TEST_ACCEPT=1 diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/does-not-exists"
+ (( "$badDiff" == 0 ))
+)
+[[ ! -f "$TEST_ROOT/does-not-exist" ]]
+
+# _NIX_TEST_ACCEPT doesn't match, output empty, file deleted
+echo -n > "$TEST_ROOT/got"
+echo Bye! > "$TEST_ROOT/expected"
+badDiff=0
+(
+ _NIX_TEST_ACCEPT=1 diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/expected"
+ (( "$badDiff" == 1 ))
+)
+[[ ! -f "$TEST_ROOT/expected" ]]
+# second time succeeds
+(
+ diffAndAcceptInner test "$TEST_ROOT/got" "$TEST_ROOT/expected"
+ (( "$badDiff" == 0 ))
+)
diff --git a/tests/lang.sh b/tests/lang.sh
index 8170cb39d..75dbbc38e 100644..100755
--- a/tests/lang.sh
+++ b/tests/lang.sh
@@ -1,5 +1,17 @@
source common.sh
+set -o pipefail
+
+source lang/framework.sh
+
+# specialize function a bit
+function diffAndAccept() {
+ local -r testName="$1"
+ local -r got="lang/$testName.$2"
+ local -r expected="lang/$testName.$3"
+ diffAndAcceptInner "$testName" "$got" "$expected"
+}
+
export TEST_VAR=foo # for eval-okay-getenv.nix
export NIX_REMOTE=dummy://
export NIX_STORE_DIR=/nix/store
@@ -20,63 +32,115 @@ nix-instantiate --eval -E 'let x = { repeating = x; tracing = builtins.trace x t
set +x
-fail=0
+badDiff=0
+badExitCode=0
for i in lang/parse-fail-*.nix; do
echo "parsing $i (should fail)";
- i=$(basename $i .nix)
- if ! expect 1 nix-instantiate --parse - < lang/$i.nix; then
+ i=$(basename "$i" .nix)
+ if expectStderr 1 nix-instantiate --parse - < "lang/$i.nix" > "lang/$i.err"
+ then
+ diffAndAccept "$i" err err.exp
+ else
echo "FAIL: $i shouldn't parse"
- fail=1
+ badExitCode=1
fi
done
for i in lang/parse-okay-*.nix; do
echo "parsing $i (should succeed)";
- i=$(basename $i .nix)
- if ! expect 0 nix-instantiate --parse - < lang/$i.nix > lang/$i.out; then
+ i=$(basename "$i" .nix)
+ if
+ expect 0 nix-instantiate --parse - < "lang/$i.nix" \
+ 1> "lang/$i.out" \
+ 2> "lang/$i.err"
+ then
+ sed "s!$(pwd)!/pwd!g" "lang/$i.out" "lang/$i.err"
+ diffAndAccept "$i" out exp
+ diffAndAccept "$i" err err.exp
+ else
echo "FAIL: $i should parse"
- fail=1
+ badExitCode=1
fi
done
for i in lang/eval-fail-*.nix; do
echo "evaluating $i (should fail)";
- i=$(basename $i .nix)
- if ! expect 1 nix-instantiate --eval lang/$i.nix; then
+ i=$(basename "$i" .nix)
+ if
+ expectStderr 1 nix-instantiate --show-trace "lang/$i.nix" \
+ | sed "s!$(pwd)!/pwd!g" > "lang/$i.err"
+ then
+ diffAndAccept "$i" err err.exp
+ else
echo "FAIL: $i shouldn't evaluate"
- fail=1
+ badExitCode=1
fi
done
for i in lang/eval-okay-*.nix; do
echo "evaluating $i (should succeed)";
- i=$(basename $i .nix)
+ i=$(basename "$i" .nix)
- if test -e lang/$i.exp; then
- flags=
- if test -e lang/$i.flags; then
- flags=$(cat lang/$i.flags)
- fi
- if ! expect 0 env NIX_PATH=lang/dir3:lang/dir4 HOME=/fake-home nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out; then
+ if test -e "lang/$i.exp.xml"; then
+ if expect 0 nix-instantiate --eval --xml --no-location --strict \
+ "lang/$i.nix" > "lang/$i.out.xml"
+ then
+ diffAndAccept "$i" out.xml exp.xml
+ else
echo "FAIL: $i should evaluate"
- fail=1
- elif ! diff <(< lang/$i.out sed -e "s|$(pwd)|/pwd|g") lang/$i.exp; then
- echo "FAIL: evaluation result of $i not as expected"
- fail=1
+ badExitCode=1
+ fi
+ elif test ! -e "lang/$i.exp-disabled"; then
+ declare -a flags=()
+ if test -e "lang/$i.flags"; then
+ read -r -a flags < "lang/$i.flags"
fi
- fi
- if test -e lang/$i.exp.xml; then
- if ! expect 0 nix-instantiate --eval --xml --no-location --strict \
- lang/$i.nix > lang/$i.out.xml; then
+ if
+ expect 0 env \
+ NIX_PATH=lang/dir3:lang/dir4 \
+ HOME=/fake-home \
+ nix-instantiate "${flags[@]}" --eval --strict "lang/$i.nix" \
+ 1> "lang/$i.out" \
+ 2> "lang/$i.err"
+ then
+ sed -i "s!$(pwd)!/pwd!g" "lang/$i.out" "lang/$i.err"
+ diffAndAccept "$i" out exp
+ diffAndAccept "$i" err err.exp
+ else
echo "FAIL: $i should evaluate"
- fail=1
- elif ! cmp -s lang/$i.out.xml lang/$i.exp.xml; then
- echo "FAIL: XML evaluation result of $i not as expected"
- fail=1
+ badExitCode=1
fi
fi
done
-exit $fail
+if test -n "${_NIX_TEST_ACCEPT-}"; then
+ if (( "$badDiff" )); then
+ echo 'Output did mot match, but accepted output as the persisted expected output.'
+ echo 'That means the next time the tests are run, they should pass.'
+ else
+ echo 'NOTE: Environment variable _NIX_TEST_ACCEPT is defined,'
+ echo 'indicating the unexpected output should be accepted as the expected output going forward,'
+ echo 'but no tests had unexpected output so there was no expected output to update.'
+ fi
+ if (( "$badExitCode" )); then
+ exit "$badExitCode"
+ else
+ skipTest "regenerating golden masters"
+ fi
+else
+ if (( "$badDiff" )); then
+ echo ''
+ echo 'You can rerun this test with:'
+ echo ''
+ echo ' _NIX_TEST_ACCEPT=1 make tests/lang.sh.test'
+ echo ''
+ echo 'to regenerate the files containing the expected output,'
+ echo 'and then view the git diff to decide whether a change is'
+ echo 'good/intentional or bad/unintentional.'
+ echo 'If the diff contains arbitrary or impure information,'
+ echo 'please improve the normalization that the test applies to the output.'
+ fi
+ exit $(( "$badExitCode" + "$badDiff" ))
+fi
diff --git a/tests/lang/empty.exp b/tests/lang/empty.exp
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/lang/empty.exp
diff --git a/tests/lang/eval-fail-abort.err.exp b/tests/lang/eval-fail-abort.err.exp
new file mode 100644
index 000000000..345232d3f
--- /dev/null
+++ b/tests/lang/eval-fail-abort.err.exp
@@ -0,0 +1,10 @@
+error:
+ … while calling the 'abort' builtin
+
+ at /pwd/lang/eval-fail-abort.nix:1:14:
+
+ 1| if true then abort "this should fail" else 1
+ | ^
+ 2|
+
+ error: evaluation aborted with the following error message: 'this should fail'
diff --git a/tests/lang/eval-fail-antiquoted-path.err.exp b/tests/lang/eval-fail-antiquoted-path.err.exp
new file mode 100644
index 000000000..425deba42
--- /dev/null
+++ b/tests/lang/eval-fail-antiquoted-path.err.exp
@@ -0,0 +1 @@
+error: getting attributes of path ‘PWD/lang/fnord’: No such file or directory
diff --git a/tests/lang/eval-fail-assert.err.exp b/tests/lang/eval-fail-assert.err.exp
new file mode 100644
index 000000000..aeecd8167
--- /dev/null
+++ b/tests/lang/eval-fail-assert.err.exp
@@ -0,0 +1,36 @@
+error:
+ … while evaluating the attribute 'body'
+
+ at /pwd/lang/eval-fail-assert.nix:4:3:
+
+ 3|
+ 4| body = x "x";
+ | ^
+ 5| }
+
+ … from call site
+
+ at /pwd/lang/eval-fail-assert.nix:4:10:
+
+ 3|
+ 4| body = x "x";
+ | ^
+ 5| }
+
+ … while calling 'x'
+
+ at /pwd/lang/eval-fail-assert.nix:2:7:
+
+ 1| let {
+ 2| x = arg: assert arg == "y"; 123;
+ | ^
+ 3|
+
+ error: assertion '(arg == "y")' failed
+
+ at /pwd/lang/eval-fail-assert.nix:2:12:
+
+ 1| let {
+ 2| x = arg: assert arg == "y"; 123;
+ | ^
+ 3|
diff --git a/tests/lang/eval-fail-bad-antiquote-1.err.exp b/tests/lang/eval-fail-bad-antiquote-1.err.exp
new file mode 100644
index 000000000..cf94f53bc
--- /dev/null
+++ b/tests/lang/eval-fail-bad-antiquote-1.err.exp
@@ -0,0 +1,10 @@
+error:
+ … while evaluating a path segment
+
+ at /pwd/lang/eval-fail-bad-antiquote-1.nix:1:2:
+
+ 1| "${x: x}"
+ | ^
+ 2|
+
+ error: cannot coerce a function to a string
diff --git a/tests/lang/eval-fail-bad-antiquote-2.err.exp b/tests/lang/eval-fail-bad-antiquote-2.err.exp
new file mode 100644
index 000000000..c8fe39d12
--- /dev/null
+++ b/tests/lang/eval-fail-bad-antiquote-2.err.exp
@@ -0,0 +1 @@
+error: operation 'addToStoreFromDump' is not supported by store 'dummy'
diff --git a/tests/lang/eval-fail-bad-antiquote-3.err.exp b/tests/lang/eval-fail-bad-antiquote-3.err.exp
new file mode 100644
index 000000000..fbefbc826
--- /dev/null
+++ b/tests/lang/eval-fail-bad-antiquote-3.err.exp
@@ -0,0 +1,10 @@
+error:
+ … while evaluating a path segment
+
+ at /pwd/lang/eval-fail-bad-antiquote-3.nix:1:3:
+
+ 1| ''${x: x}''
+ | ^
+ 2|
+
+ error: cannot coerce a function to a string
diff --git a/tests/lang/eval-fail-bad-string-interpolation-1.err.exp b/tests/lang/eval-fail-bad-string-interpolation-1.err.exp
new file mode 100644
index 000000000..eb73e9a52
--- /dev/null
+++ b/tests/lang/eval-fail-bad-string-interpolation-1.err.exp
@@ -0,0 +1,10 @@
+error:
+ … while evaluating a path segment
+
+ at /pwd/lang/eval-fail-bad-string-interpolation-1.nix:1:2:
+
+ 1| "${x: x}"
+ | ^
+ 2|
+
+ error: cannot coerce a function to a string
diff --git a/tests/lang/eval-fail-bad-antiquote-1.nix b/tests/lang/eval-fail-bad-string-interpolation-1.nix
index ffe9c983c..ffe9c983c 100644
--- a/tests/lang/eval-fail-bad-antiquote-1.nix
+++ b/tests/lang/eval-fail-bad-string-interpolation-1.nix
diff --git a/tests/lang/eval-fail-bad-string-interpolation-2.err.exp b/tests/lang/eval-fail-bad-string-interpolation-2.err.exp
new file mode 100644
index 000000000..c8fe39d12
--- /dev/null
+++ b/tests/lang/eval-fail-bad-string-interpolation-2.err.exp
@@ -0,0 +1 @@
+error: operation 'addToStoreFromDump' is not supported by store 'dummy'
diff --git a/tests/lang/eval-fail-bad-antiquote-2.nix b/tests/lang/eval-fail-bad-string-interpolation-2.nix
index 3745235ce..3745235ce 100644
--- a/tests/lang/eval-fail-bad-antiquote-2.nix
+++ b/tests/lang/eval-fail-bad-string-interpolation-2.nix
diff --git a/tests/lang/eval-fail-bad-string-interpolation-3.err.exp b/tests/lang/eval-fail-bad-string-interpolation-3.err.exp
new file mode 100644
index 000000000..ac14f329b
--- /dev/null
+++ b/tests/lang/eval-fail-bad-string-interpolation-3.err.exp
@@ -0,0 +1,10 @@
+error:
+ … while evaluating a path segment
+
+ at /pwd/lang/eval-fail-bad-string-interpolation-3.nix:1:3:
+
+ 1| ''${x: x}''
+ | ^
+ 2|
+
+ error: cannot coerce a function to a string
diff --git a/tests/lang/eval-fail-bad-antiquote-3.nix b/tests/lang/eval-fail-bad-string-interpolation-3.nix
index 65b9d4f50..65b9d4f50 100644
--- a/tests/lang/eval-fail-bad-antiquote-3.nix
+++ b/tests/lang/eval-fail-bad-string-interpolation-3.nix
diff --git a/tests/lang/eval-fail-blackhole.err.exp b/tests/lang/eval-fail-blackhole.err.exp
new file mode 100644
index 000000000..f0618d8ac
--- /dev/null
+++ b/tests/lang/eval-fail-blackhole.err.exp
@@ -0,0 +1,18 @@
+error:
+ … while evaluating the attribute 'body'
+
+ at /pwd/lang/eval-fail-blackhole.nix:2:3:
+
+ 1| let {
+ 2| body = x;
+ | ^
+ 3| x = y;
+
+ error: infinite recursion encountered
+
+ at /pwd/lang/eval-fail-blackhole.nix:3:7:
+
+ 2| body = x;
+ 3| x = y;
+ | ^
+ 4| y = x;
diff --git a/tests/lang/eval-fail-deepseq.err.exp b/tests/lang/eval-fail-deepseq.err.exp
new file mode 100644
index 000000000..5e204ba73
--- /dev/null
+++ b/tests/lang/eval-fail-deepseq.err.exp
@@ -0,0 +1,26 @@
+error:
+ … while calling the 'deepSeq' builtin
+
+ at /pwd/lang/eval-fail-deepseq.nix:1:1:
+
+ 1| builtins.deepSeq { x = abort "foo"; } 456
+ | ^
+ 2|
+
+ … while evaluating the attribute 'x'
+
+ at /pwd/lang/eval-fail-deepseq.nix:1:20:
+
+ 1| builtins.deepSeq { x = abort "foo"; } 456
+ | ^
+ 2|
+
+ … while calling the 'abort' builtin
+
+ at /pwd/lang/eval-fail-deepseq.nix:1:24:
+
+ 1| builtins.deepSeq { x = abort "foo"; } 456
+ | ^
+ 2|
+
+ error: evaluation aborted with the following error message: 'foo'
diff --git a/tests/lang/eval-fail-foldlStrict-strict-op-application.err.exp b/tests/lang/eval-fail-foldlStrict-strict-op-application.err.exp
new file mode 100644
index 000000000..0069285fb
--- /dev/null
+++ b/tests/lang/eval-fail-foldlStrict-strict-op-application.err.exp
@@ -0,0 +1,38 @@
+error:
+ … while calling the 'foldl'' builtin
+
+ at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:2:1:
+
+ 1| # Tests that the result of applying op is forced even if the value is never used
+ 2| builtins.foldl'
+ | ^
+ 3| (_: f: f null)
+
+ … while calling anonymous lambda
+
+ at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:3:7:
+
+ 2| builtins.foldl'
+ 3| (_: f: f null)
+ | ^
+ 4| null
+
+ … from call site
+
+ at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:3:10:
+
+ 2| builtins.foldl'
+ 3| (_: f: f null)
+ | ^
+ 4| null
+
+ … while calling anonymous lambda
+
+ at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:5:6:
+
+ 4| null
+ 5| [ (_: throw "Not the final value, but is still forced!") (_: 23) ]
+ | ^
+ 6|
+
+ error: Not the final value, but is still forced!
diff --git a/tests/lang/eval-fail-fromTOML-timestamps.err.exp b/tests/lang/eval-fail-fromTOML-timestamps.err.exp
new file mode 100644
index 000000000..f6bd19f5a
--- /dev/null
+++ b/tests/lang/eval-fail-fromTOML-timestamps.err.exp
@@ -0,0 +1,12 @@
+error:
+ … while calling the 'fromTOML' builtin
+
+ at /pwd/lang/eval-fail-fromTOML-timestamps.nix:1:1:
+
+ 1| builtins.fromTOML ''
+ | ^
+ 2| key = "value"
+
+ error: while parsing a TOML string: Dates and times are not supported
+
+ at «none»:0: (source not available)
diff --git a/tests/lang/eval-fail-hashfile-missing.err.exp b/tests/lang/eval-fail-hashfile-missing.err.exp
new file mode 100644
index 000000000..8e77dec1e
--- /dev/null
+++ b/tests/lang/eval-fail-hashfile-missing.err.exp
@@ -0,0 +1,19 @@
+error:
+ … while calling the 'toString' builtin
+
+ at /pwd/lang/eval-fail-hashfile-missing.nix:4:3:
+
+ 3| in
+ 4| toString (builtins.concatLists (map (hash: map (builtins.hashFile hash) paths) ["md5" "sha1" "sha256" "sha512"]))
+ | ^
+ 5|
+
+ … while evaluating the first argument passed to builtins.toString
+
+ at «none»:0: (source not available)
+
+ … while calling the 'hashFile' builtin
+
+ at «none»:0: (source not available)
+
+ error: opening file '/pwd/lang/this-file-is-definitely-not-there-7392097': No such file or directory
diff --git a/tests/lang/eval-fail-list.err.exp b/tests/lang/eval-fail-list.err.exp
new file mode 100644
index 000000000..24d682118
--- /dev/null
+++ b/tests/lang/eval-fail-list.err.exp
@@ -0,0 +1,10 @@
+error:
+ … while evaluating one of the elements to concatenate
+
+ at /pwd/lang/eval-fail-list.nix:1:2:
+
+ 1| 8++1
+ | ^
+ 2|
+
+ error: value is an integer while a list was expected
diff --git a/tests/lang/eval-fail-list.nix b/tests/lang/eval-fail-list.nix
new file mode 100644
index 000000000..fa749f2f7
--- /dev/null
+++ b/tests/lang/eval-fail-list.nix
@@ -0,0 +1 @@
+8++1
diff --git a/tests/lang/eval-fail-missing-arg.err.exp b/tests/lang/eval-fail-missing-arg.err.exp
new file mode 100644
index 000000000..61fabf0d5
--- /dev/null
+++ b/tests/lang/eval-fail-missing-arg.err.exp
@@ -0,0 +1,16 @@
+error:
+ … from call site
+
+ at /pwd/lang/eval-fail-missing-arg.nix:1:1:
+
+ 1| ({x, y, z}: x + y + z) {x = "foo"; z = "bar";}
+ | ^
+ 2|
+
+ error: function 'anonymous lambda' called without required argument 'y'
+
+ at /pwd/lang/eval-fail-missing-arg.nix:1:2:
+
+ 1| ({x, y, z}: x + y + z) {x = "foo"; z = "bar";}
+ | ^
+ 2|
diff --git a/tests/lang/eval-fail-nonexist-path.err.exp b/tests/lang/eval-fail-nonexist-path.err.exp
new file mode 100644
index 000000000..c8fe39d12
--- /dev/null
+++ b/tests/lang/eval-fail-nonexist-path.err.exp
@@ -0,0 +1 @@
+error: operation 'addToStoreFromDump' is not supported by store 'dummy'
diff --git a/tests/lang/eval-fail-path-slash.err.exp b/tests/lang/eval-fail-path-slash.err.exp
new file mode 100644
index 000000000..f0011c97f
--- /dev/null
+++ b/tests/lang/eval-fail-path-slash.err.exp
@@ -0,0 +1,8 @@
+error: path has a trailing slash
+
+ at /pwd/lang/eval-fail-path-slash.nix:6:12:
+
+ 5| # and https://nixos.org/nix-dev/2016-June/020829.html
+ 6| /nix/store/
+ | ^
+ 7|
diff --git a/tests/lang/eval-fail-recursion.err.exp b/tests/lang/eval-fail-recursion.err.exp
new file mode 100644
index 000000000..af64133cb
--- /dev/null
+++ b/tests/lang/eval-fail-recursion.err.exp
@@ -0,0 +1,16 @@
+error:
+ … in the right operand of the update (//) operator
+
+ at /pwd/lang/eval-fail-recursion.nix:1:12:
+
+ 1| let a = {} // a; in a.foo
+ | ^
+ 2|
+
+ error: infinite recursion encountered
+
+ at /pwd/lang/eval-fail-recursion.nix:1:15:
+
+ 1| let a = {} // a; in a.foo
+ | ^
+ 2|
diff --git a/tests/lang/eval-fail-recursion.nix b/tests/lang/eval-fail-recursion.nix
new file mode 100644
index 000000000..075b5ed06
--- /dev/null
+++ b/tests/lang/eval-fail-recursion.nix
@@ -0,0 +1 @@
+let a = {} // a; in a.foo
diff --git a/tests/lang/eval-fail-remove.err.exp b/tests/lang/eval-fail-remove.err.exp
new file mode 100644
index 000000000..e82cdac98
--- /dev/null
+++ b/tests/lang/eval-fail-remove.err.exp
@@ -0,0 +1,19 @@
+error:
+ … while evaluating the attribute 'body'
+
+ at /pwd/lang/eval-fail-remove.nix:4:3:
+
+ 3|
+ 4| body = (removeAttrs attrs ["x"]).x;
+ | ^
+ 5| }
+
+ error: attribute 'x' missing
+
+ at /pwd/lang/eval-fail-remove.nix:4:10:
+
+ 3|
+ 4| body = (removeAttrs attrs ["x"]).x;
+ | ^
+ 5| }
+ Did you mean y?
diff --git a/tests/lang/eval-fail-scope-5.err.exp b/tests/lang/eval-fail-scope-5.err.exp
new file mode 100644
index 000000000..22b6166f8
--- /dev/null
+++ b/tests/lang/eval-fail-scope-5.err.exp
@@ -0,0 +1,36 @@
+error:
+ … while evaluating the attribute 'body'
+
+ at /pwd/lang/eval-fail-scope-5.nix:8:3:
+
+ 7|
+ 8| body = f {};
+ | ^
+ 9|
+
+ … from call site
+
+ at /pwd/lang/eval-fail-scope-5.nix:8:10:
+
+ 7|
+ 8| body = f {};
+ | ^
+ 9|
+
+ … while calling 'f'
+
+ at /pwd/lang/eval-fail-scope-5.nix:6:7:
+
+ 5|
+ 6| f = {x ? y, y ? x}: x + y;
+ | ^
+ 7|
+
+ error: infinite recursion encountered
+
+ at /pwd/lang/eval-fail-scope-5.nix:6:12:
+
+ 5|
+ 6| f = {x ? y, y ? x}: x + y;
+ | ^
+ 7|
diff --git a/tests/lang/eval-fail-seq.err.exp b/tests/lang/eval-fail-seq.err.exp
new file mode 100644
index 000000000..33a7e9491
--- /dev/null
+++ b/tests/lang/eval-fail-seq.err.exp
@@ -0,0 +1,18 @@
+error:
+ … while calling the 'seq' builtin
+
+ at /pwd/lang/eval-fail-seq.nix:1:1:
+
+ 1| builtins.seq (abort "foo") 2
+ | ^
+ 2|
+
+ … while calling the 'abort' builtin
+
+ at /pwd/lang/eval-fail-seq.nix:1:15:
+
+ 1| builtins.seq (abort "foo") 2
+ | ^
+ 2|
+
+ error: evaluation aborted with the following error message: 'foo'
diff --git a/tests/lang/eval-fail-set-override.err.exp b/tests/lang/eval-fail-set-override.err.exp
new file mode 100644
index 000000000..beb29d678
--- /dev/null
+++ b/tests/lang/eval-fail-set-override.err.exp
@@ -0,0 +1,6 @@
+error:
+ … while evaluating the `__overrides` attribute
+
+ at «none»:0: (source not available)
+
+ error: value is an integer while a set was expected
diff --git a/tests/lang/eval-fail-set-override.nix b/tests/lang/eval-fail-set-override.nix
new file mode 100644
index 000000000..03551c186
--- /dev/null
+++ b/tests/lang/eval-fail-set-override.nix
@@ -0,0 +1 @@
+rec { __overrides = 1; }
diff --git a/tests/lang/eval-fail-set.err.exp b/tests/lang/eval-fail-set.err.exp
new file mode 100644
index 000000000..0d0140508
--- /dev/null
+++ b/tests/lang/eval-fail-set.err.exp
@@ -0,0 +1,7 @@
+error: undefined variable 'x'
+
+ at /pwd/lang/eval-fail-set.nix:1:3:
+
+ 1| 8.x
+ | ^
+ 2|
diff --git a/tests/lang/eval-fail-set.nix b/tests/lang/eval-fail-set.nix
new file mode 100644
index 000000000..c6b7980b6
--- /dev/null
+++ b/tests/lang/eval-fail-set.nix
@@ -0,0 +1 @@
+8.x
diff --git a/tests/lang/eval-fail-substring.err.exp b/tests/lang/eval-fail-substring.err.exp
new file mode 100644
index 000000000..dc26a00bd
--- /dev/null
+++ b/tests/lang/eval-fail-substring.err.exp
@@ -0,0 +1,12 @@
+error:
+ … while calling the 'substring' builtin
+
+ at /pwd/lang/eval-fail-substring.nix:1:1:
+
+ 1| builtins.substring (builtins.sub 0 1) 1 "x"
+ | ^
+ 2|
+
+ error: negative start position in 'substring'
+
+ at «none»:0: (source not available)
diff --git a/tests/lang/eval-fail-to-path.err.exp b/tests/lang/eval-fail-to-path.err.exp
new file mode 100644
index 000000000..43ed2bdfc
--- /dev/null
+++ b/tests/lang/eval-fail-to-path.err.exp
@@ -0,0 +1,14 @@
+error:
+ … while calling the 'toPath' builtin
+
+ at /pwd/lang/eval-fail-to-path.nix:1:1:
+
+ 1| builtins.toPath "foo/bar"
+ | ^
+ 2|
+
+ … while evaluating the first argument passed to builtins.toPath
+
+ at «none»:0: (source not available)
+
+ error: string 'foo/bar' doesn't represent an absolute path
diff --git a/tests/lang/eval-fail-undeclared-arg.err.exp b/tests/lang/eval-fail-undeclared-arg.err.exp
new file mode 100644
index 000000000..30db743c7
--- /dev/null
+++ b/tests/lang/eval-fail-undeclared-arg.err.exp
@@ -0,0 +1,17 @@
+error:
+ … from call site
+
+ at /pwd/lang/eval-fail-undeclared-arg.nix:1:1:
+
+ 1| ({x, z}: x + z) {x = "foo"; y = "bla"; z = "bar";}
+ | ^
+ 2|
+
+ error: function 'anonymous lambda' called with unexpected argument 'y'
+
+ at /pwd/lang/eval-fail-undeclared-arg.nix:1:2:
+
+ 1| ({x, z}: x + z) {x = "foo"; y = "bla"; z = "bar";}
+ | ^
+ 2|
+ Did you mean one of x or z?
diff --git a/tests/lang/eval-okay-fromjson.nix b/tests/lang/eval-okay-fromjson.nix
index e1c0f86cc..4c526b9ae 100644
--- a/tests/lang/eval-okay-fromjson.nix
+++ b/tests/lang/eval-okay-fromjson.nix
@@ -11,9 +11,12 @@ builtins.fromJSON
"Width": 200,
"Height": 250
},
+ "Animated" : false,
+ "IDs": [116, 943, 234, 38793, true ,false,null, -100],
+ "Escapes": "\"\\\/\t\n\r\t",
"Subtitle" : false,
- "Latitude": 46.2051,
- "Longitude": 6.0723
+ "Latitude": 37.7668,
+ "Longitude": -122.3959
}
}
''
@@ -28,8 +31,11 @@ builtins.fromJSON
Width = 200;
Height = 250;
};
+ Animated = false;
+ IDs = [ 116 943 234 38793 true false null (0-100) ];
+ Escapes = "\"\\\/\t\n\r\t"; # supported in JSON but not Nix: \b\f
Subtitle = false;
- Latitude = 46.2051;
- Longitude = 6.0723;
+ Latitude = 37.7668;
+ Longitude = -122.3959;
};
}
diff --git a/tests/lang/eval-okay-overrides.nix b/tests/lang/eval-okay-overrides.nix
index 358742b36..719bdc9c0 100644
--- a/tests/lang/eval-okay-overrides.nix
+++ b/tests/lang/eval-okay-overrides.nix
@@ -1,6 +1,6 @@
let
- overrides = { a = 2; };
+ overrides = { a = 2; b = 3; };
in (rec {
__overrides = overrides;
diff --git a/tests/lang/eval-okay-path-antiquotation.exp b/tests/lang/eval-okay-path-string-interpolation.exp
index 5b8ea0243..5b8ea0243 100644
--- a/tests/lang/eval-okay-path-antiquotation.exp
+++ b/tests/lang/eval-okay-path-string-interpolation.exp
diff --git a/tests/lang/eval-okay-path-antiquotation.nix b/tests/lang/eval-okay-path-string-interpolation.nix
index 497d7c1c7..497d7c1c7 100644
--- a/tests/lang/eval-okay-path-antiquotation.nix
+++ b/tests/lang/eval-okay-path-string-interpolation.nix
diff --git a/tests/lang/eval-okay-print.err.exp b/tests/lang/eval-okay-print.err.exp
new file mode 100644
index 000000000..3fc99be3e
--- /dev/null
+++ b/tests/lang/eval-okay-print.err.exp
@@ -0,0 +1 @@
+trace: [ <CODE> ]
diff --git a/tests/lang/eval-okay-print.exp b/tests/lang/eval-okay-print.exp
new file mode 100644
index 000000000..0d960fb70
--- /dev/null
+++ b/tests/lang/eval-okay-print.exp
@@ -0,0 +1 @@
+[ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ [ «repeated» ] ] ]
diff --git a/tests/lang/eval-okay-print.nix b/tests/lang/eval-okay-print.nix
new file mode 100644
index 000000000..d36ba4da3
--- /dev/null
+++ b/tests/lang/eval-okay-print.nix
@@ -0,0 +1 @@
+with builtins; trace [(1+1)] [ null toString (deepSeq "x") (a: a) (let x=[x]; in x) ]
diff --git a/tests/lang/eval-okay-search-path.flags b/tests/lang/eval-okay-search-path.flags
index a28e68210..dfad1c611 100644
--- a/tests/lang/eval-okay-search-path.flags
+++ b/tests/lang/eval-okay-search-path.flags
@@ -1 +1 @@
--I lang/dir1 -I lang/dir2 -I dir5=lang/dir3 \ No newline at end of file
+-I lang/dir1 -I lang/dir2 -I dir5=lang/dir3
diff --git a/tests/lang/framework.sh b/tests/lang/framework.sh
new file mode 100644
index 000000000..516bff8ad
--- /dev/null
+++ b/tests/lang/framework.sh
@@ -0,0 +1,33 @@
+# Golden test support
+#
+# Test that the output of the given test matches what is expected. If
+# `_NIX_TEST_ACCEPT` is non-empty also update the expected output so
+# that next time the test succeeds.
+function diffAndAcceptInner() {
+ local -r testName=$1
+ local -r got="$2"
+ local -r expected="$3"
+
+ # Absence of expected file indicates empty output expected.
+ if test -e "$expected"; then
+ local -r expectedOrEmpty="$expected"
+ else
+ local -r expectedOrEmpty=lang/empty.exp
+ fi
+
+ # Diff so we get a nice message
+ if ! diff --unified "$got" "$expectedOrEmpty"; then
+ echo "FAIL: evaluation result of $testName not as expected"
+ badDiff=1
+ fi
+
+ # Update expected if `_NIX_TEST_ACCEPT` is non-empty.
+ if test -n "${_NIX_TEST_ACCEPT-}"; then
+ cp "$got" "$expected"
+ # Delete empty expected files to avoid bloating the repo with
+ # empty files.
+ if ! test -s "$expected"; then
+ rm "$expected"
+ fi
+ fi
+}
diff --git a/tests/lang/parse-fail-dup-attrs-1.err.exp b/tests/lang/parse-fail-dup-attrs-1.err.exp
new file mode 100644
index 000000000..4fe6b7a1f
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-1.err.exp
@@ -0,0 +1,7 @@
+error: attribute 'x' already defined at «stdin»:1:3
+
+ at «stdin»:3:3:
+
+ 2| y = 456;
+ 3| x = 789;
+ | ^
diff --git a/tests/lang/parse-fail-dup-attrs-2.err.exp b/tests/lang/parse-fail-dup-attrs-2.err.exp
new file mode 100644
index 000000000..3aba2891f
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-2.err.exp
@@ -0,0 +1,7 @@
+error: attribute 'x' already defined at «stdin»:9:5
+
+ at «stdin»:10:17:
+
+ 9| x = 789;
+ 10| inherit (as) x;
+ | ^
diff --git a/tests/lang/parse-fail-dup-attrs-3.err.exp b/tests/lang/parse-fail-dup-attrs-3.err.exp
new file mode 100644
index 000000000..3aba2891f
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-3.err.exp
@@ -0,0 +1,7 @@
+error: attribute 'x' already defined at «stdin»:9:5
+
+ at «stdin»:10:17:
+
+ 9| x = 789;
+ 10| inherit (as) x;
+ | ^
diff --git a/tests/lang/parse-fail-dup-attrs-4.err.exp b/tests/lang/parse-fail-dup-attrs-4.err.exp
new file mode 100644
index 000000000..ff68446a1
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-4.err.exp
@@ -0,0 +1,7 @@
+error: attribute 'services.ssh.port' already defined at «stdin»:2:3
+
+ at «stdin»:3:3:
+
+ 2| services.ssh.port = 22;
+ 3| services.ssh.port = 23;
+ | ^
diff --git a/tests/lang/parse-fail-dup-attrs-6.err.exp b/tests/lang/parse-fail-dup-attrs-6.err.exp
new file mode 100644
index 000000000..74823fc25
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-6.err.exp
@@ -0,0 +1 @@
+error: attribute ‘services.ssh’ at (string):3:3 already defined at (string):2:3
diff --git a/tests/lang/parse-fail-dup-attrs-7.err.exp b/tests/lang/parse-fail-dup-attrs-7.err.exp
new file mode 100644
index 000000000..512a499ca
--- /dev/null
+++ b/tests/lang/parse-fail-dup-attrs-7.err.exp
@@ -0,0 +1,7 @@
+error: attribute 'x' already defined at «stdin»:6:12
+
+ at «stdin»:7:12:
+
+ 6| inherit x;
+ 7| inherit x;
+ | ^
diff --git a/tests/lang/parse-fail-dup-formals.err.exp b/tests/lang/parse-fail-dup-formals.err.exp
new file mode 100644
index 000000000..1d566fb33
--- /dev/null
+++ b/tests/lang/parse-fail-dup-formals.err.exp
@@ -0,0 +1,6 @@
+error: duplicate formal function argument 'x'
+
+ at «stdin»:1:8:
+
+ 1| {x, y, x}: x
+ | ^
diff --git a/tests/lang/parse-fail-eof-in-string.err.exp b/tests/lang/parse-fail-eof-in-string.err.exp
new file mode 100644
index 000000000..f9fa72312
--- /dev/null
+++ b/tests/lang/parse-fail-eof-in-string.err.exp
@@ -0,0 +1,7 @@
+error: syntax error, unexpected end of file, expecting '"'
+
+ at «stdin»:3:5:
+
+ 2| # Note that this file must not end with a newline.
+ 3| a 1"$
+ | ^
diff --git a/tests/lang/parse-fail-mixed-nested-attrs1.err.exp b/tests/lang/parse-fail-mixed-nested-attrs1.err.exp
new file mode 100644
index 000000000..32f776795
--- /dev/null
+++ b/tests/lang/parse-fail-mixed-nested-attrs1.err.exp
@@ -0,0 +1,8 @@
+error: attribute 'z' already defined at «stdin»:3:16
+
+ at «stdin»:2:3:
+
+ 1| {
+ 2| x.z = 3;
+ | ^
+ 3| x = { y = 3; z = 3; };
diff --git a/tests/lang/parse-fail-mixed-nested-attrs2.err.exp b/tests/lang/parse-fail-mixed-nested-attrs2.err.exp
new file mode 100644
index 000000000..0437cd50c
--- /dev/null
+++ b/tests/lang/parse-fail-mixed-nested-attrs2.err.exp
@@ -0,0 +1,8 @@
+error: attribute 'y' already defined at «stdin»:3:9
+
+ at «stdin»:2:3:
+
+ 1| {
+ 2| x.y.y = 3;
+ | ^
+ 3| x = { y.y= 3; z = 3; };
diff --git a/tests/lang/parse-fail-patterns-1.err.exp b/tests/lang/parse-fail-patterns-1.err.exp
new file mode 100644
index 000000000..634a04aaa
--- /dev/null
+++ b/tests/lang/parse-fail-patterns-1.err.exp
@@ -0,0 +1,7 @@
+error: duplicate formal function argument 'args'
+
+ at «stdin»:1:1:
+
+ 1| args@{args, x, y, z}: x
+ | ^
+ 2|
diff --git a/tests/lang/parse-fail-regression-20060610.err.exp b/tests/lang/parse-fail-regression-20060610.err.exp
new file mode 100644
index 000000000..167d01e85
--- /dev/null
+++ b/tests/lang/parse-fail-regression-20060610.err.exp
@@ -0,0 +1,8 @@
+error: undefined variable 'gcc'
+
+ at «stdin»:8:12:
+
+ 7|
+ 8| body = ({
+ | ^
+ 9| inherit gcc;
diff --git a/tests/lang/parse-fail-undef-var-2.err.exp b/tests/lang/parse-fail-undef-var-2.err.exp
new file mode 100644
index 000000000..77c96bbd2
--- /dev/null
+++ b/tests/lang/parse-fail-undef-var-2.err.exp
@@ -0,0 +1,7 @@
+error: syntax error, unexpected ':', expecting '}'
+
+ at «stdin»:3:13:
+
+ 2|
+ 3| f = {x, y :
+ | ^
diff --git a/tests/lang/parse-fail-undef-var.err.exp b/tests/lang/parse-fail-undef-var.err.exp
new file mode 100644
index 000000000..48e88747f
--- /dev/null
+++ b/tests/lang/parse-fail-undef-var.err.exp
@@ -0,0 +1,7 @@
+error: undefined variable 'y'
+
+ at «stdin»:1:4:
+
+ 1| x: y
+ | ^
+ 2|
diff --git a/tests/lang/parse-fail-utf8.err.exp b/tests/lang/parse-fail-utf8.err.exp
new file mode 100644
index 000000000..6087479a3
--- /dev/null
+++ b/tests/lang/parse-fail-utf8.err.exp
@@ -0,0 +1,6 @@
+error: syntax error, unexpected invalid token, expecting end of file
+
+ at «stdin»:1:5:
+
+ 1| 123 Ã
+ | ^
diff --git a/tests/lang/parse-fail-uft8.nix b/tests/lang/parse-fail-utf8.nix
index 34948d48a..34948d48a 100644
--- a/tests/lang/parse-fail-uft8.nix
+++ b/tests/lang/parse-fail-utf8.nix
diff --git a/tests/lang/parse-okay-1.exp b/tests/lang/parse-okay-1.exp
new file mode 100644
index 000000000..d5ab5f18a
--- /dev/null
+++ b/tests/lang/parse-okay-1.exp
@@ -0,0 +1 @@
+({ x, y, z }: ((x + y) + z))
diff --git a/tests/lang/parse-okay-crlf.exp b/tests/lang/parse-okay-crlf.exp
new file mode 100644
index 000000000..4213609fc
--- /dev/null
+++ b/tests/lang/parse-okay-crlf.exp
@@ -0,0 +1 @@
+rec { foo = "multi\nline\n string\n test\r"; x = y; y = 123; z = 456; }
diff --git a/tests/lang/parse-okay-dup-attrs-5.exp b/tests/lang/parse-okay-dup-attrs-5.exp
new file mode 100644
index 000000000..88b0b036f
--- /dev/null
+++ b/tests/lang/parse-okay-dup-attrs-5.exp
@@ -0,0 +1 @@
+{ services = { ssh = { enable = true; port = 23; }; }; }
diff --git a/tests/lang/parse-okay-dup-attrs-6.exp b/tests/lang/parse-okay-dup-attrs-6.exp
new file mode 100644
index 000000000..88b0b036f
--- /dev/null
+++ b/tests/lang/parse-okay-dup-attrs-6.exp
@@ -0,0 +1 @@
+{ services = { ssh = { enable = true; port = 23; }; }; }
diff --git a/tests/lang/parse-okay-mixed-nested-attrs-1.exp b/tests/lang/parse-okay-mixed-nested-attrs-1.exp
new file mode 100644
index 000000000..89c66f760
--- /dev/null
+++ b/tests/lang/parse-okay-mixed-nested-attrs-1.exp
@@ -0,0 +1 @@
+{ x = { q = 3; y = 3; z = 3; }; }
diff --git a/tests/lang/parse-okay-mixed-nested-attrs-2.exp b/tests/lang/parse-okay-mixed-nested-attrs-2.exp
new file mode 100644
index 000000000..89c66f760
--- /dev/null
+++ b/tests/lang/parse-okay-mixed-nested-attrs-2.exp
@@ -0,0 +1 @@
+{ x = { q = 3; y = 3; z = 3; }; }
diff --git a/tests/lang/parse-okay-mixed-nested-attrs-3.exp b/tests/lang/parse-okay-mixed-nested-attrs-3.exp
new file mode 100644
index 000000000..b89a59734
--- /dev/null
+++ b/tests/lang/parse-okay-mixed-nested-attrs-3.exp
@@ -0,0 +1 @@
+{ services = { httpd = { enable = true; }; ssh = { enable = true; port = 123; }; }; }
diff --git a/tests/lang/parse-okay-regression-20041027.exp b/tests/lang/parse-okay-regression-20041027.exp
new file mode 100644
index 000000000..9df7219e4
--- /dev/null
+++ b/tests/lang/parse-okay-regression-20041027.exp
@@ -0,0 +1 @@
+({ fetchurl, stdenv }: ((stdenv).mkDerivation { name = "libXi-6.0.1"; src = (fetchurl { md5 = "7e935a42428d63a387b3c048be0f2756"; url = "http://freedesktop.org/~xlibs/release/libXi-6.0.1.tar.bz2"; }); }))
diff --git a/tests/lang/parse-okay-regression-751.exp b/tests/lang/parse-okay-regression-751.exp
new file mode 100644
index 000000000..e2ed886fe
--- /dev/null
+++ b/tests/lang/parse-okay-regression-751.exp
@@ -0,0 +1 @@
+(let const = (a: "const"); in ((const { x = "q"; })))
diff --git a/tests/lang/parse-okay-subversion.exp b/tests/lang/parse-okay-subversion.exp
new file mode 100644
index 000000000..4168ee8bf
--- /dev/null
+++ b/tests/lang/parse-okay-subversion.exp
@@ -0,0 +1 @@
+({ fetchurl, localServer ? false, httpServer ? false, sslSupport ? false, pythonBindings ? false, javaSwigBindings ? false, javahlBindings ? false, stdenv, openssl ? null, httpd ? null, db4 ? null, expat, swig ? null, j2sdk ? null }: assert (expat != null); assert (localServer -> (db4 != null)); assert (httpServer -> ((httpd != null) && ((httpd).expat == expat))); assert (sslSupport -> ((openssl != null) && (httpServer -> ((httpd).openssl == openssl)))); assert (pythonBindings -> ((swig != null) && (swig).pythonSupport)); assert (javaSwigBindings -> ((swig != null) && (swig).javaSupport)); assert (javahlBindings -> (j2sdk != null)); ((stdenv).mkDerivation { builder = /foo/bar; db4 = (if localServer then db4 else null); inherit expat ; inherit httpServer ; httpd = (if httpServer then httpd else null); j2sdk = (if javaSwigBindings then (swig).j2sdk else (if javahlBindings then j2sdk else null)); inherit javaSwigBindings ; inherit javahlBindings ; inherit localServer ; name = "subversion-1.1.1"; openssl = (if sslSupport then openssl else null); patches = (if javahlBindings then [ (/javahl.patch) ] else [ ]); python = (if pythonBindings then (swig).python else null); inherit pythonBindings ; src = (fetchurl { md5 = "a180c3fe91680389c210c99def54d9e0"; url = "http://subversion.tigris.org/tarballs/subversion-1.1.1.tar.bz2"; }); inherit sslSupport ; swig = (if (pythonBindings || javaSwigBindings) then swig else null); }))
diff --git a/tests/lang/parse-okay-url.exp b/tests/lang/parse-okay-url.exp
new file mode 100644
index 000000000..e5f0829b0
--- /dev/null
+++ b/tests/lang/parse-okay-url.exp
@@ -0,0 +1 @@
+[ ("x:x") ("https://svn.cs.uu.nl:12443/repos/trace/trunk") ("http://www2.mplayerhq.hu/MPlayer/releases/fonts/font-arial-iso-8859-1.tar.bz2") ("http://losser.st-lab.cs.uu.nl/~armijn/.nix/gcc-3.3.4-static-nix.tar.gz") ("http://fpdownload.macromedia.com/get/shockwave/flash/english/linux/7.0r25/install_flash_player_7_linux.tar.gz") ("https://ftp5.gwdg.de/pub/linux/archlinux/extra/os/x86_64/unzip-6.0-14-x86_64.pkg.tar.zst") ("ftp://ftp.gtk.org/pub/gtk/v1.2/gtk+-1.2.10.tar.gz") ]
diff --git a/tests/linux-sandbox-cert-test.nix b/tests/linux-sandbox-cert-test.nix
index 2b86dad2e..2fc083ea9 100644
--- a/tests/linux-sandbox-cert-test.nix
+++ b/tests/linux-sandbox-cert-test.nix
@@ -1,29 +1,30 @@
-{ fixed-output }:
+{ mode }:
with import ./config.nix;
-mkDerivation ({
- name = "ssl-export";
- buildCommand = ''
- # Add some indirection, otherwise grepping into the debug output finds the string.
- report () { echo CERT_$1_IN_SANDBOX; }
+mkDerivation (
+ {
+ name = "ssl-export";
+ buildCommand = ''
+ # Add some indirection, otherwise grepping into the debug output finds the string.
+ report () { echo CERT_$1_IN_SANDBOX; }
- if [ -f /etc/ssl/certs/ca-certificates.crt ]; then
- content=$(</etc/ssl/certs/ca-certificates.crt)
- if [ "$content" == CERT_CONTENT ]; then
- report present
+ if [ -f /etc/ssl/certs/ca-certificates.crt ]; then
+ content=$(</etc/ssl/certs/ca-certificates.crt)
+ if [ "$content" == CERT_CONTENT ]; then
+ report present
+ fi
+ else
+ report missing
fi
- else
- report missing
- fi
- # Always fail, because we do not want to bother with fixed-output
- # derivations being cached, and do not want to compute the right hash.
- false;
- '';
-} // (
- if fixed-output == "fixed-output"
- then { outputHash = "sha256:0000000000000000000000000000000000000000000000000000000000000000"; }
- else { }
-))
+ # Always fail, because we do not want to bother with fixed-output
+ # derivations being cached, and do not want to compute the right hash.
+ false;
+ '';
+ } // {
+ fixed-output = { outputHash = "sha256:0000000000000000000000000000000000000000000000000000000000000000"; };
+ normal = { };
+ }.${mode}
+)
diff --git a/tests/linux-sandbox.sh b/tests/linux-sandbox.sh
index 45f0ce7a4..ff7d257bd 100644
--- a/tests/linux-sandbox.sh
+++ b/tests/linux-sandbox.sh
@@ -11,6 +11,8 @@ requireSandboxSupport
# otherwise things get complicated (e.g. if it's in /bin, do we need
# /lib as well?).
if [[ ! $SHELL =~ /nix/store ]]; then skipTest "Shell is not from Nix store"; fi
+# An alias to automatically bind-mount the $SHELL on nix-build invocations
+nix-sandbox-build () { nix-build --no-out-link --sandbox-paths /nix/store "$@"; }
chmod -R u+w $TEST_ROOT/store0 || true
rm -rf $TEST_ROOT/store0
@@ -18,7 +20,7 @@ rm -rf $TEST_ROOT/store0
export NIX_STORE_DIR=/my/store
export NIX_REMOTE=$TEST_ROOT/store0
-outPath=$(nix-build dependencies.nix --no-out-link --sandbox-paths /nix/store)
+outPath=$(nix-sandbox-build dependencies.nix)
[[ $outPath =~ /my/store/.*-dependencies ]]
@@ -29,24 +31,31 @@ nix store ls -R -l $outPath | grep foobar
nix store cat $outPath/foobar | grep FOOBAR
# Test --check without hash rewriting.
-nix-build dependencies.nix --no-out-link --check --sandbox-paths /nix/store
+nix-sandbox-build dependencies.nix --check
# Test that sandboxed builds with --check and -K can move .check directory to store
-nix-build check.nix -A nondeterministic --sandbox-paths /nix/store --no-out-link
+nix-sandbox-build check.nix -A nondeterministic
-(! nix-build check.nix -A nondeterministic --sandbox-paths /nix/store --no-out-link --check -K 2> $TEST_ROOT/log)
-if grepQuiet 'error: renaming' $TEST_ROOT/log; then false; fi
+# `100 + 4` means non-determinstic, see doc/manual/src/command-ref/status-build-failure.md
+expectStderr 104 nix-sandbox-build check.nix -A nondeterministic --check -K > $TEST_ROOT/log
+grepQuietInverse 'error: renaming' $TEST_ROOT/log
grepQuiet 'may not be deterministic' $TEST_ROOT/log
# Test that sandboxed builds cannot write to /etc easily
-(! nix-build -E 'with import ./config.nix; mkDerivation { name = "etc-write"; buildCommand = "echo > /etc/test"; }' --no-out-link --sandbox-paths /nix/store)
+# `100` means build failure without extra info, see doc/manual/src/command-ref/status-build-failure.md
+expectStderr 100 nix-sandbox-build -E 'with import ./config.nix; mkDerivation { name = "etc-write"; buildCommand = "echo > /etc/test"; }' |
+ grepQuiet "/etc/test: Permission denied"
## Test mounting of SSL certificates into the sandbox
testCert () {
- (! nix-build linux-sandbox-cert-test.nix --argstr fixed-output "$2" --no-out-link --sandbox-paths /nix/store --option ssl-cert-file "$3" 2> $TEST_ROOT/log)
- cat $TEST_ROOT/log
- grepQuiet "CERT_${1}_IN_SANDBOX" $TEST_ROOT/log
+ expectation=$1 # "missing" | "present"
+ mode=$2 # "normal" | "fixed-output"
+ certFile=$3 # a string that can be the path to a cert file
+ # `100` means build failure without extra info, see doc/manual/src/command-ref/status-build-failure.md
+ [ "$mode" == fixed-output ] && ret=1 || ret=100
+ expectStderr $ret nix-sandbox-build linux-sandbox-cert-test.nix --argstr mode "$mode" --option ssl-cert-file "$certFile" |
+ grepQuiet "CERT_${expectation}_IN_SANDBOX"
}
nocert=$TEST_ROOT/no-cert-file.pem
diff --git a/tests/local.mk b/tests/local.mk
index 9e340e2e2..df20f3dd7 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -16,9 +16,11 @@ nix_tests = \
flakes/flake-in-submodule.sh \
ca/gc.sh \
gc.sh \
+ nix-collect-garbage-d.sh \
remote-store.sh \
legacy-ssh-store.sh \
lang.sh \
+ lang-test-infra.sh \
experimental-features.sh \
fetchMercurial.sh \
gc-auto.sh \
@@ -93,6 +95,7 @@ nix_tests = \
misc.sh \
dump-db.sh \
linux-sandbox.sh \
+ supplementary-groups.sh \
build-dry.sh \
structured-attrs.sh \
shell.sh \
@@ -134,7 +137,10 @@ nix_tests = \
flakes/show.sh \
impure-derivations.sh \
path-from-hash-part.sh \
- toString-path.sh
+ test-libstoreconsumer.sh \
+ toString-path.sh \
+ read-only-store.sh \
+ nested-sandboxing.sh
ifeq ($(HAVE_LIBCPUID), 1)
nix_tests += compute-levels.sh
@@ -152,6 +158,7 @@ test-deps += \
tests/common/vars-and-functions.sh \
tests/config.nix \
tests/ca/config.nix \
+ tests/test-libstoreconsumer/test-libstoreconsumer \
tests/dyn-drv/config.nix
ifeq ($(BUILD_SHARED_LIBS), 1)
diff --git a/tests/misc.sh b/tests/misc.sh
index 60d58310e..af96d20bd 100644
--- a/tests/misc.sh
+++ b/tests/misc.sh
@@ -24,3 +24,9 @@ eval_stdin_res=$(echo 'let a = {} // a; in a.foo' | nix-instantiate --eval -E -
echo $eval_stdin_res | grep "at «stdin»:1:15:"
echo $eval_stdin_res | grep "infinite recursion encountered"
+# Attribute path errors
+expectStderr 1 nix-instantiate --eval -E '{}' -A '"x' | grepQuiet "missing closing quote in selection path"
+expectStderr 1 nix-instantiate --eval -E '[]' -A 'x' | grepQuiet "should be a set"
+expectStderr 1 nix-instantiate --eval -E '{}' -A '1' | grepQuiet "should be a list"
+expectStderr 1 nix-instantiate --eval -E '{}' -A '.' | grepQuiet "empty attribute name"
+expectStderr 1 nix-instantiate --eval -E '[]' -A '1' | grepQuiet "out of range"
diff --git a/tests/nested-sandboxing.sh b/tests/nested-sandboxing.sh
new file mode 100644
index 000000000..d9fa788aa
--- /dev/null
+++ b/tests/nested-sandboxing.sh
@@ -0,0 +1,11 @@
+source common.sh
+# This test is run by `tests/nested-sandboxing/runner.nix` in an extra layer of sandboxing.
+[[ -d /nix/store ]] || skipTest "running this test without Nix's deps being drawn from /nix/store is not yet supported"
+
+requireSandboxSupport
+
+source ./nested-sandboxing/command.sh
+
+expectStderr 100 runNixBuild badStoreUrl 2 | grepQuiet '`sandbox-build-dir` must not contain'
+
+runNixBuild goodStoreUrl 5
diff --git a/tests/nested-sandboxing/command.sh b/tests/nested-sandboxing/command.sh
new file mode 100644
index 000000000..69366486c
--- /dev/null
+++ b/tests/nested-sandboxing/command.sh
@@ -0,0 +1,29 @@
+export NIX_BIN_DIR=$(dirname $(type -p nix))
+# TODO Get Nix and its closure more flexibly
+export EXTRA_SANDBOX="/nix/store $(dirname $NIX_BIN_DIR)"
+
+badStoreUrl () {
+ local altitude=$1
+ echo $TEST_ROOT/store-$altitude
+}
+
+goodStoreUrl () {
+ local altitude=$1
+ echo $("badStoreUrl" "$altitude")?store=/foo-$altitude
+}
+
+# The non-standard sandbox-build-dir helps ensure that we get the same behavior
+# whether this test is being run in a derivation as part of the nix build or
+# being manually run by a developer outside a derivation
+runNixBuild () {
+ local storeFun=$1
+ local altitude=$2
+ nix-build \
+ --no-substitute --no-out-link \
+ --store "$("$storeFun" "$altitude")" \
+ --extra-sandbox-paths "$EXTRA_SANDBOX" \
+ ./nested-sandboxing/runner.nix \
+ --arg altitude "$((altitude - 1))" \
+ --argstr storeFun "$storeFun" \
+ --sandbox-build-dir /build-non-standard
+}
diff --git a/tests/nested-sandboxing/runner.nix b/tests/nested-sandboxing/runner.nix
new file mode 100644
index 000000000..9a5822c88
--- /dev/null
+++ b/tests/nested-sandboxing/runner.nix
@@ -0,0 +1,24 @@
+{ altitude, storeFun }:
+
+with import ../config.nix;
+
+mkDerivation {
+ name = "nested-sandboxing";
+ busybox = builtins.getEnv "busybox";
+ EXTRA_SANDBOX = builtins.getEnv "EXTRA_SANDBOX";
+ buildCommand = if altitude == 0 then ''
+ echo Deep enough! > $out
+ '' else ''
+ cp -r ${../common} ./common
+ cp ${../common.sh} ./common.sh
+ cp ${../config.nix} ./config.nix
+ cp -r ${./.} ./nested-sandboxing
+
+ export PATH=${builtins.getEnv "NIX_BIN_DIR"}:$PATH
+
+ source common.sh
+ source ./nested-sandboxing/command.sh
+
+ runNixBuild ${storeFun} ${toString altitude} >> $out
+ '';
+}
diff --git a/tests/nix-channel.sh b/tests/nix-channel.sh
index dbb3114f1..b5d935004 100644
--- a/tests/nix-channel.sh
+++ b/tests/nix-channel.sh
@@ -8,6 +8,7 @@ rm -f $TEST_HOME/.nix-channels $TEST_HOME/.nix-profile
nix-channel --add http://foo/bar xyzzy
nix-channel --list | grepQuiet http://foo/bar
nix-channel --remove xyzzy
+[[ $(nix-channel --list-generations | wc -l) == 1 ]]
[ -e $TEST_HOME/.nix-channels ]
[ "$(cat $TEST_HOME/.nix-channels)" = '' ]
@@ -38,6 +39,7 @@ ln -s dependencies.nix $TEST_ROOT/nixexprs/default.nix
# Test the update action.
nix-channel --add file://$TEST_ROOT/foo
nix-channel --update
+[[ $(nix-channel --list-generations | wc -l) == 2 ]]
# Do a query.
nix-env -qa \* --meta --xml --out-path > $TEST_ROOT/meta.xml
diff --git a/tests/nix-collect-garbage-d.sh b/tests/nix-collect-garbage-d.sh
new file mode 100644
index 000000000..bf30f8938
--- /dev/null
+++ b/tests/nix-collect-garbage-d.sh
@@ -0,0 +1,40 @@
+source common.sh
+
+clearStore
+
+## Test `nix-collect-garbage -d`
+
+# TODO make `nix-env` doesn't work with CA derivations, and make
+# `ca/nix-collect-garbage-d.sh` wrapper.
+
+testCollectGarbageD () {
+ clearProfiles
+ # Run two `nix-env` commands, should create two generations of
+ # the profile
+ nix-env -f ./user-envs.nix -i foo-1.0 "$@"
+ nix-env -f ./user-envs.nix -i foo-2.0pre1 "$@"
+ [[ $(nix-env --list-generations "$@" | wc -l) -eq 2 ]]
+
+ # Clear the profile history. There should be only one generation
+ # left
+ nix-collect-garbage -d
+ [[ $(nix-env --list-generations "$@" | wc -l) -eq 1 ]]
+}
+
+testCollectGarbageD
+
+# Run the same test, but forcing the profiles an arbitrary location.
+rm ~/.nix-profile
+ln -s $TEST_ROOT/blah ~/.nix-profile
+testCollectGarbageD
+
+# Run the same test, but forcing the profiles at their legacy location under
+# /nix/var/nix.
+#
+# Note that we *don't* use the default profile; `nix-collect-garbage` will
+# need to check the legacy conditional unconditionally not just follow
+# `~/.nix-profile` to pass this test.
+#
+# Regression test for #8294
+rm ~/.nix-profile
+testCollectGarbageD --profile "$NIX_STATE_DIR/profiles/per-user/me"
diff --git a/tests/nix-profile.sh b/tests/nix-profile.sh
index 9da3f802b..7c478a0cd 100644
--- a/tests/nix-profile.sh
+++ b/tests/nix-profile.sh
@@ -47,8 +47,9 @@ cp ./config.nix $flake1Dir/
# Test upgrading from nix-env.
nix-env -f ./user-envs.nix -i foo-1.0
-nix profile list | grep '0 - - .*-foo-1.0'
+nix profile list | grep -A2 'Index:.*0' | grep 'Store paths:.*foo-1.0'
nix profile install $flake1Dir -L
+nix profile list | grep -A4 'Index:.*1' | grep 'Locked flake URL:.*narHash'
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
[ -e $TEST_HOME/.nix-profile/share/man ]
(! [ -e $TEST_HOME/.nix-profile/include ])
diff --git a/tests/nixos/authorization.nix b/tests/nixos/authorization.nix
index 7e8744dd9..fdeae06ed 100644
--- a/tests/nixos/authorization.nix
+++ b/tests/nixos/authorization.nix
@@ -75,5 +75,20 @@
su --login bob -c '(! nix-store --verify --repair 2>&1)' | tee diag 1>&2
grep -F "you are not privileged to repair paths" diag
""")
+
+ machine.succeed("""
+ set -x
+ su --login mallory -c '
+ nix-store --generate-binary-cache-key cache1.example.org sk1 pk1
+ (! nix store sign --key-file sk1 ${pathFour} 2>&1)' | tee diag 1>&2
+ grep -F "cannot open connection to remote store 'daemon'" diag
+ """)
+
+ machine.succeed("""
+ su --login bob -c '
+ nix-store --generate-binary-cache-key cache1.example.org sk1 pk1
+ nix store sign --key-file sk1 ${pathFour}
+ '
+ """)
'';
}
diff --git a/tests/nixos/tarball-flakes.nix b/tests/nixos/tarball-flakes.nix
new file mode 100644
index 000000000..1d43a5d04
--- /dev/null
+++ b/tests/nixos/tarball-flakes.nix
@@ -0,0 +1,84 @@
+{ lib, config, nixpkgs, ... }:
+
+let
+ pkgs = config.nodes.machine.nixpkgs.pkgs;
+
+ root = pkgs.runCommand "nixpkgs-flake" {}
+ ''
+ mkdir -p $out/stable
+
+ set -x
+ dir=nixpkgs-${nixpkgs.shortRev}
+ cp -prd ${nixpkgs} $dir
+ # Set the correct timestamp in the tarball.
+ find $dir -print0 | xargs -0 touch -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${builtins.substring 12 2 nixpkgs.lastModifiedDate} --
+ tar cfz $out/stable/${nixpkgs.rev}.tar.gz $dir --hard-dereference
+
+ echo 'Redirect "/latest.tar.gz" "/stable/${nixpkgs.rev}.tar.gz"' > $out/.htaccess
+
+ echo 'Header set Link "<http://localhost/stable/${nixpkgs.rev}.tar.gz?rev=${nixpkgs.rev}&revCount=1234>; rel=\"immutable\""' > $out/stable/.htaccess
+ '';
+in
+
+{
+ name = "tarball-flakes";
+
+ nodes =
+ {
+ machine =
+ { config, pkgs, ... }:
+ { networking.firewall.allowedTCPPorts = [ 80 ];
+
+ services.httpd.enable = true;
+ services.httpd.adminAddr = "foo@example.org";
+ services.httpd.extraConfig = ''
+ ErrorLog syslog:local6
+ '';
+ services.httpd.virtualHosts."localhost" =
+ { servedDirs =
+ [ { urlPath = "/";
+ dir = root;
+ }
+ ];
+ };
+
+ virtualisation.writableStore = true;
+ virtualisation.diskSize = 2048;
+ virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
+ virtualisation.memorySize = 4096;
+ nix.settings.substituters = lib.mkForce [ ];
+ nix.extraOptions = "experimental-features = nix-command flakes";
+ };
+ };
+
+ testScript = { nodes }: ''
+ # fmt: off
+ import json
+
+ start_all()
+
+ machine.wait_for_unit("httpd.service")
+
+ out = machine.succeed("nix flake metadata --json http://localhost/latest.tar.gz")
+ print(out)
+ info = json.loads(out)
+
+ # Check that we got redirected to the immutable URL.
+ assert info["locked"]["url"] == "http://localhost/stable/${nixpkgs.rev}.tar.gz"
+
+ # Check that we got the rev and revCount attributes.
+ assert info["revision"] == "${nixpkgs.rev}"
+ assert info["revCount"] == 1234
+
+ # Check that fetching with rev/revCount/narHash succeeds.
+ machine.succeed("nix flake metadata --json http://localhost/latest.tar.gz?rev=" + info["revision"])
+ machine.succeed("nix flake metadata --json http://localhost/latest.tar.gz?revCount=" + str(info["revCount"]))
+ machine.succeed("nix flake metadata --json http://localhost/latest.tar.gz?narHash=" + info["locked"]["narHash"])
+
+ # Check that fetching fails if we provide incorrect attributes.
+ machine.fail("nix flake metadata --json http://localhost/latest.tar.gz?rev=493300eb13ae6fb387fbd47bf54a85915acc31c0")
+ machine.fail("nix flake metadata --json http://localhost/latest.tar.gz?revCount=789")
+ machine.fail("nix flake metadata --json http://localhost/latest.tar.gz?narHash=sha256-tbudgBSg+bHWHiHnlteNzN8TUvI80ygS9IULh4rklEw=")
+ '';
+
+}
diff --git a/tests/plugins/plugintest.cc b/tests/plugins/plugintest.cc
index 04b791021..e02fd68d5 100644
--- a/tests/plugins/plugintest.cc
+++ b/tests/plugins/plugintest.cc
@@ -21,4 +21,8 @@ static void prim_anotherNull (EvalState & state, const PosIdx pos, Value ** args
v.mkBool(false);
}
-static RegisterPrimOp rp("anotherNull", 0, prim_anotherNull);
+static RegisterPrimOp rp({
+ .name = "anotherNull",
+ .arity = 0,
+ .fun = prim_anotherNull,
+});
diff --git a/tests/read-only-store.sh b/tests/read-only-store.sh
new file mode 100644
index 000000000..d63920c19
--- /dev/null
+++ b/tests/read-only-store.sh
@@ -0,0 +1,42 @@
+source common.sh
+
+enableFeatures "read-only-local-store"
+
+needLocalStore "cannot open store read-only when daemon has already opened it writeable"
+
+clearStore
+
+happy () {
+ # We can do a read-only query just fine with a read-only store
+ nix --store local?read-only=true path-info $dummyPath
+
+ # We can "write" an already-present store-path a read-only store, because no IO is actually required
+ nix-store --store local?read-only=true --add dummy
+}
+## Testing read-only mode without forcing the underlying store to actually be read-only
+
+# Make sure the command fails when the store doesn't already have a database
+expectStderr 1 nix-store --store local?read-only=true --add dummy | grepQuiet "database does not exist, and cannot be created in read-only mode"
+
+# Make sure the store actually has a current-database, with at least one store object
+dummyPath=$(nix-store --add dummy)
+
+# Try again and make sure we fail when adding a item not already in the store
+expectStderr 1 nix-store --store local?read-only=true --add eval.nix | grepQuiet "attempt to write a readonly database"
+
+# Test a few operations that should work with the read-only store in its current state
+happy
+
+## Testing read-only mode with an underlying store that is actually read-only
+
+# Ensure store is actually read-only
+chmod -R -w $TEST_ROOT/store
+chmod -R -w $TEST_ROOT/var
+
+# Make sure we fail on add operations on the read-only store
+# This is only for adding files that are not *already* in the store
+expectStderr 1 nix-store --add eval.nix | grepQuiet "error: opening lock file '$(readlink -e $TEST_ROOT)/var/nix/db/big-lock'"
+expectStderr 1 nix-store --store local?read-only=true --add eval.nix | grepQuiet "Permission denied"
+
+# Test the same operations from before should again succeed
+happy
diff --git a/tests/restricted.sh b/tests/restricted.sh
index 776893a56..17f310a4b 100644
--- a/tests/restricted.sh
+++ b/tests/restricted.sh
@@ -49,3 +49,5 @@ output="$(nix eval --raw --restrict-eval -I "$traverseDir" \
2>&1 || :)"
echo "$output" | grep "is forbidden"
echo "$output" | grepInverse -F restricted-secret
+
+expectStderr 1 nix-instantiate --restrict-eval true ./dependencies.nix | grepQuiet "forbidden in restricted mode"
diff --git a/tests/signing.sh b/tests/signing.sh
index 9b673c609..942b51630 100644
--- a/tests/signing.sh
+++ b/tests/signing.sh
@@ -84,6 +84,10 @@ info=$(nix path-info --store file://$cacheDir --json $outPath2)
# Copying to a diverted store should fail due to a lack of signatures by trusted keys.
chmod -R u+w $TEST_ROOT/store0 || true
rm -rf $TEST_ROOT/store0
+
+# Fails or very flaky only on GHA + macOS:
+# expectStderr 1 nix copy --to $TEST_ROOT/store0 $outPath | grepQuiet -E 'cannot add path .* because it lacks a signature by a trusted key'
+# but this works:
(! nix copy --to $TEST_ROOT/store0 $outPath)
# But succeed if we supply the public keys.
diff --git a/tests/supplementary-groups.sh b/tests/supplementary-groups.sh
new file mode 100644
index 000000000..d18fb2414
--- /dev/null
+++ b/tests/supplementary-groups.sh
@@ -0,0 +1,37 @@
+source common.sh
+
+requireSandboxSupport
+[[ $busybox =~ busybox ]] || skipTest "no busybox"
+if ! command -p -v unshare; then skipTest "Need unshare"; fi
+needLocalStore "The test uses --store always so we would just be bypassing the daemon"
+
+unshare --mount --map-root-user bash <<EOF
+ source common.sh
+
+ # Avoid store dir being inside sandbox build-dir
+ unset NIX_STORE_DIR
+ unset NIX_STATE_DIR
+
+ setLocalStore () {
+ export NIX_REMOTE=\$TEST_ROOT/\$1
+ mkdir -p \$NIX_REMOTE
+ }
+
+ cmd=(nix-build ./hermetic.nix --arg busybox "$busybox" --arg seed 1 --no-out-link)
+
+ # Fails with default setting
+ # TODO better error
+ setLocalStore store1
+ expectStderr 1 "\${cmd[@]}" | grepQuiet "unable to start build process"
+
+ # Fails with `require-drop-supplementary-groups`
+ # TODO better error
+ setLocalStore store2
+ NIX_CONFIG='require-drop-supplementary-groups = true' \
+ expectStderr 1 "\${cmd[@]}" | grepQuiet "unable to start build process"
+
+ # Works without `require-drop-supplementary-groups`
+ setLocalStore store3
+ NIX_CONFIG='require-drop-supplementary-groups = false' \
+ "\${cmd[@]}"
+EOF
diff --git a/tests/test-libstoreconsumer.sh b/tests/test-libstoreconsumer.sh
new file mode 100644
index 000000000..8a77cf5a1
--- /dev/null
+++ b/tests/test-libstoreconsumer.sh
@@ -0,0 +1,6 @@
+source common.sh
+
+drv="$(nix-instantiate simple.nix)"
+cat "$drv"
+out="$(./test-libstoreconsumer/test-libstoreconsumer "$drv")"
+cat "$out/hello" | grep -F "Hello World!"
diff --git a/tests/test-libstoreconsumer/README.md b/tests/test-libstoreconsumer/README.md
new file mode 100644
index 000000000..ded69850f
--- /dev/null
+++ b/tests/test-libstoreconsumer/README.md
@@ -0,0 +1,6 @@
+
+A very simple C++ consumer of the libstore library.
+
+ - Keep it simple. Library consumers expect something simple.
+ - No build hook, or any other reinvocations.
+ - No more global state than necessary.
diff --git a/tests/test-libstoreconsumer/local.mk b/tests/test-libstoreconsumer/local.mk
new file mode 100644
index 000000000..edc140723
--- /dev/null
+++ b/tests/test-libstoreconsumer/local.mk
@@ -0,0 +1,15 @@
+programs += test-libstoreconsumer
+
+test-libstoreconsumer_DIR := $(d)
+
+# do not install
+test-libstoreconsumer_INSTALL_DIR :=
+
+test-libstoreconsumer_SOURCES := \
+ $(wildcard $(d)/*.cc) \
+
+test-libstoreconsumer_CXXFLAGS += -I src/libutil -I src/libstore
+
+test-libstoreconsumer_LIBS = libstore libutil
+
+test-libstoreconsumer_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) $(LOWDOWN_LIBS)
diff --git a/tests/test-libstoreconsumer/main.cc b/tests/test-libstoreconsumer/main.cc
new file mode 100644
index 000000000..31b6d8ef1
--- /dev/null
+++ b/tests/test-libstoreconsumer/main.cc
@@ -0,0 +1,45 @@
+#include "globals.hh"
+#include "store-api.hh"
+#include "build-result.hh"
+#include <iostream>
+
+using namespace nix;
+
+int main (int argc, char **argv)
+{
+ try {
+ if (argc != 2) {
+ std::cerr << "Usage: " << argv[0] << " store/path/to/something.drv\n";
+ return 1;
+ }
+
+ std::string drvPath = argv[1];
+
+ initLibStore();
+
+ auto store = nix::openStore();
+
+ // build the derivation
+
+ std::vector<DerivedPath> paths {
+ DerivedPath::Built {
+ .drvPath = store->parseStorePath(drvPath),
+ .outputs = OutputsSpec::Names{"out"}
+ }
+ };
+
+ const auto results = store->buildPathsWithResults(paths, bmNormal, store);
+
+ for (const auto & result : results) {
+ for (const auto & [outputName, realisation] : result.builtOutputs) {
+ std::cout << store->printStorePath(realisation.outPath) << "\n";
+ }
+ }
+
+ return 0;
+
+ } catch (const std::exception & e) {
+ std::cerr << "Error: " << e.what() << "\n";
+ return 1;
+ }
+}