aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/build.sh4
-rw-r--r--tests/check.sh3
-rw-r--r--tests/eval.sh6
-rw-r--r--tests/fetchClosure.sh81
-rw-r--r--tests/fetchGit.sh6
-rw-r--r--tests/flakes/check.sh16
-rw-r--r--tests/flakes/flakes.sh5
-rw-r--r--tests/gc.sh17
-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.nix (renamed from tests/lang/eval-fail-bad-antiquote-2.nix)0
-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-fromTOML-timestamps.nix130
-rw-r--r--tests/lang/eval-okay-fromTOML-timestamps.exp1
-rw-r--r--tests/lang/eval-okay-fromTOML-timestamps.flags1
-rw-r--r--tests/lang/eval-okay-fromTOML-timestamps.nix130
-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-replacestrings.exp2
-rw-r--r--tests/lang/eval-okay-replacestrings.nix1
-rw-r--r--tests/linux-sandbox-cert-test.nix30
-rw-r--r--tests/linux-sandbox.sh45
-rw-r--r--tests/local.mk6
-rw-r--r--tests/nix-channel.sh2
-rw-r--r--tests/nix-collect-garbage-d.sh40
-rw-r--r--tests/nix-profile.sh16
-rw-r--r--tests/nixos/authorization.nix15
-rw-r--r--tests/nixos/nix-copy.nix10
-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/recursive.sh3
-rw-r--r--tests/signing.sh4
-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
36 files changed, 742 insertions, 36 deletions
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/eval.sh b/tests/eval.sh
index 066d8fc36..b81bb1e2c 100644
--- a/tests/eval.sh
+++ b/tests/eval.sh
@@ -35,3 +35,9 @@ nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
# Check that symlink cycles don't cause a hang.
ln -sfn cycle.nix $TEST_ROOT/cycle.nix
(! nix eval --file $TEST_ROOT/cycle.nix)
+
+# Check that relative symlinks are resolved correctly.
+mkdir -p $TEST_ROOT/xyzzy $TEST_ROOT/foo
+ln -sfn ../xyzzy $TEST_ROOT/foo/bar
+printf 123 > $TEST_ROOT/xyzzy/default.nix
+[[ $(nix eval --impure --expr "import $TEST_ROOT/foo/bar") = 123 ]]
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 865ca61b4..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;
@@ -72,6 +84,8 @@ cat > $flakeDir/flake.nix <<EOF
}
EOF
-checkRes=$(nix flake check --keep-going $flakeDir 2>&1 && fail "nix flake check should have failed" || true)
+nix flake check $flakeDir
+
+checkRes=$(nix flake check --all-systems --keep-going $flakeDir 2>&1 && fail "nix flake check --all-systems should have failed" || true)
echo "$checkRes" | grepQuiet "packages.system-1.default"
echo "$checkRes" | grepQuiet "packages.system-2.default"
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 98d6cb032..ad09a8b39 100644
--- a/tests/gc.sh
+++ b/tests/gc.sh
@@ -50,20 +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`
-# `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
- 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 ]]
-fi
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-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-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-fromTOML-timestamps.nix b/tests/lang/eval-fail-fromTOML-timestamps.nix
new file mode 100644
index 000000000..74cff9470
--- /dev/null
+++ b/tests/lang/eval-fail-fromTOML-timestamps.nix
@@ -0,0 +1,130 @@
+builtins.fromTOML ''
+ key = "value"
+ bare_key = "value"
+ bare-key = "value"
+ 1234 = "value"
+
+ "127.0.0.1" = "value"
+ "character encoding" = "value"
+ "ʎǝʞ" = "value"
+ 'key2' = "value"
+ 'quoted "value"' = "value"
+
+ name = "Orange"
+
+ physical.color = "orange"
+ physical.shape = "round"
+ site."google.com" = true
+
+ # This is legal according to the spec, but cpptoml doesn't handle it.
+ #a.b.c = 1
+ #a.d = 2
+
+ str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
+
+ int1 = +99
+ int2 = 42
+ int3 = 0
+ int4 = -17
+ int5 = 1_000
+ int6 = 5_349_221
+ int7 = 1_2_3_4_5
+
+ hex1 = 0xDEADBEEF
+ hex2 = 0xdeadbeef
+ hex3 = 0xdead_beef
+
+ oct1 = 0o01234567
+ oct2 = 0o755
+
+ bin1 = 0b11010110
+
+ flt1 = +1.0
+ flt2 = 3.1415
+ flt3 = -0.01
+ flt4 = 5e+22
+ flt5 = 1e6
+ flt6 = -2E-2
+ flt7 = 6.626e-34
+ flt8 = 9_224_617.445_991_228_313
+
+ bool1 = true
+ bool2 = false
+
+ odt1 = 1979-05-27T07:32:00Z
+ odt2 = 1979-05-27T00:32:00-07:00
+ odt3 = 1979-05-27T00:32:00.999999-07:00
+ odt4 = 1979-05-27 07:32:00Z
+ ldt1 = 1979-05-27T07:32:00
+ ldt2 = 1979-05-27T00:32:00.999999
+ ld1 = 1979-05-27
+ lt1 = 07:32:00
+ lt2 = 00:32:00.999999
+
+ arr1 = [ 1, 2, 3 ]
+ arr2 = [ "red", "yellow", "green" ]
+ arr3 = [ [ 1, 2 ], [3, 4, 5] ]
+ arr4 = [ "all", 'strings', """are the same""", ''''type'''']
+ arr5 = [ [ 1, 2 ], ["a", "b", "c"] ]
+
+ arr7 = [
+ 1, 2, 3
+ ]
+
+ arr8 = [
+ 1,
+ 2, # this is ok
+ ]
+
+ [table-1]
+ key1 = "some string"
+ key2 = 123
+
+
+ [table-2]
+ key1 = "another string"
+ key2 = 456
+
+ [dog."tater.man"]
+ type.name = "pug"
+
+ [a.b.c]
+ [ d.e.f ]
+ [ g . h . i ]
+ [ j . "ʞ" . 'l' ]
+ [x.y.z.w]
+
+ name = { first = "Tom", last = "Preston-Werner" }
+ point = { x = 1, y = 2 }
+ animal = { type.name = "pug" }
+
+ [[products]]
+ name = "Hammer"
+ sku = 738594937
+
+ [[products]]
+
+ [[products]]
+ name = "Nail"
+ sku = 284758393
+ color = "gray"
+
+ [[fruit]]
+ name = "apple"
+
+ [fruit.physical]
+ color = "red"
+ shape = "round"
+
+ [[fruit.variety]]
+ name = "red delicious"
+
+ [[fruit.variety]]
+ name = "granny smith"
+
+ [[fruit]]
+ name = "banana"
+
+ [[fruit.variety]]
+ name = "plantain"
+''
diff --git a/tests/lang/eval-okay-fromTOML-timestamps.exp b/tests/lang/eval-okay-fromTOML-timestamps.exp
new file mode 100644
index 000000000..08b3c69a6
--- /dev/null
+++ b/tests/lang/eval-okay-fromTOML-timestamps.exp
@@ -0,0 +1 @@
+{ "1234" = "value"; "127.0.0.1" = "value"; a = { b = { c = { }; }; }; arr1 = [ 1 2 3 ]; arr2 = [ "red" "yellow" "green" ]; arr3 = [ [ 1 2 ] [ 3 4 5 ] ]; arr4 = [ "all" "strings" "are the same" "type" ]; arr5 = [ [ 1 2 ] [ "a" "b" "c" ] ]; arr7 = [ 1 2 3 ]; arr8 = [ 1 2 ]; bare-key = "value"; bare_key = "value"; bin1 = 214; bool1 = true; bool2 = false; "character encoding" = "value"; d = { e = { f = { }; }; }; dog = { "tater.man" = { type = { name = "pug"; }; }; }; flt1 = 1; flt2 = 3.1415; flt3 = -0.01; flt4 = 5e+22; flt5 = 1e+06; flt6 = -0.02; flt7 = 6.626e-34; flt8 = 9.22462e+06; fruit = [ { name = "apple"; physical = { color = "red"; shape = "round"; }; variety = [ { name = "red delicious"; } { name = "granny smith"; } ]; } { name = "banana"; variety = [ { name = "plantain"; } ]; } ]; g = { h = { i = { }; }; }; hex1 = 3735928559; hex2 = 3735928559; hex3 = 3735928559; int1 = 99; int2 = 42; int3 = 0; int4 = -17; int5 = 1000; int6 = 5349221; int7 = 12345; j = { "ʞ" = { l = { }; }; }; key = "value"; key2 = "value"; ld1 = { _type = "timestamp"; value = "1979-05-27"; }; ldt1 = { _type = "timestamp"; value = "1979-05-27T07:32:00"; }; ldt2 = { _type = "timestamp"; value = "1979-05-27T00:32:00.999999"; }; lt1 = { _type = "timestamp"; value = "07:32:00"; }; lt2 = { _type = "timestamp"; value = "00:32:00.999999"; }; name = "Orange"; oct1 = 342391; oct2 = 493; odt1 = { _type = "timestamp"; value = "1979-05-27T07:32:00Z"; }; odt2 = { _type = "timestamp"; value = "1979-05-27T00:32:00-07:00"; }; odt3 = { _type = "timestamp"; value = "1979-05-27T00:32:00.999999-07:00"; }; odt4 = { _type = "timestamp"; value = "1979-05-27T07:32:00Z"; }; physical = { color = "orange"; shape = "round"; }; products = [ { name = "Hammer"; sku = 738594937; } { } { color = "gray"; name = "Nail"; sku = 284758393; } ]; "quoted \"value\"" = "value"; site = { "google.com" = true; }; str = "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."; table-1 = { key1 = "some string"; key2 = 123; }; table-2 = { key1 = "another string"; key2 = 456; }; x = { y = { z = { w = { animal = { type = { name = "pug"; }; }; name = { first = "Tom"; last = "Preston-Werner"; }; point = { x = 1; y = 2; }; }; }; }; }; "ʎǝʞ" = "value"; }
diff --git a/tests/lang/eval-okay-fromTOML-timestamps.flags b/tests/lang/eval-okay-fromTOML-timestamps.flags
new file mode 100644
index 000000000..9ed39dc6b
--- /dev/null
+++ b/tests/lang/eval-okay-fromTOML-timestamps.flags
@@ -0,0 +1 @@
+--extra-experimental-features parse-toml-timestamps
diff --git a/tests/lang/eval-okay-fromTOML-timestamps.nix b/tests/lang/eval-okay-fromTOML-timestamps.nix
new file mode 100644
index 000000000..74cff9470
--- /dev/null
+++ b/tests/lang/eval-okay-fromTOML-timestamps.nix
@@ -0,0 +1,130 @@
+builtins.fromTOML ''
+ key = "value"
+ bare_key = "value"
+ bare-key = "value"
+ 1234 = "value"
+
+ "127.0.0.1" = "value"
+ "character encoding" = "value"
+ "ʎǝʞ" = "value"
+ 'key2' = "value"
+ 'quoted "value"' = "value"
+
+ name = "Orange"
+
+ physical.color = "orange"
+ physical.shape = "round"
+ site."google.com" = true
+
+ # This is legal according to the spec, but cpptoml doesn't handle it.
+ #a.b.c = 1
+ #a.d = 2
+
+ str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
+
+ int1 = +99
+ int2 = 42
+ int3 = 0
+ int4 = -17
+ int5 = 1_000
+ int6 = 5_349_221
+ int7 = 1_2_3_4_5
+
+ hex1 = 0xDEADBEEF
+ hex2 = 0xdeadbeef
+ hex3 = 0xdead_beef
+
+ oct1 = 0o01234567
+ oct2 = 0o755
+
+ bin1 = 0b11010110
+
+ flt1 = +1.0
+ flt2 = 3.1415
+ flt3 = -0.01
+ flt4 = 5e+22
+ flt5 = 1e6
+ flt6 = -2E-2
+ flt7 = 6.626e-34
+ flt8 = 9_224_617.445_991_228_313
+
+ bool1 = true
+ bool2 = false
+
+ odt1 = 1979-05-27T07:32:00Z
+ odt2 = 1979-05-27T00:32:00-07:00
+ odt3 = 1979-05-27T00:32:00.999999-07:00
+ odt4 = 1979-05-27 07:32:00Z
+ ldt1 = 1979-05-27T07:32:00
+ ldt2 = 1979-05-27T00:32:00.999999
+ ld1 = 1979-05-27
+ lt1 = 07:32:00
+ lt2 = 00:32:00.999999
+
+ arr1 = [ 1, 2, 3 ]
+ arr2 = [ "red", "yellow", "green" ]
+ arr3 = [ [ 1, 2 ], [3, 4, 5] ]
+ arr4 = [ "all", 'strings', """are the same""", ''''type'''']
+ arr5 = [ [ 1, 2 ], ["a", "b", "c"] ]
+
+ arr7 = [
+ 1, 2, 3
+ ]
+
+ arr8 = [
+ 1,
+ 2, # this is ok
+ ]
+
+ [table-1]
+ key1 = "some string"
+ key2 = 123
+
+
+ [table-2]
+ key1 = "another string"
+ key2 = 456
+
+ [dog."tater.man"]
+ type.name = "pug"
+
+ [a.b.c]
+ [ d.e.f ]
+ [ g . h . i ]
+ [ j . "ʞ" . 'l' ]
+ [x.y.z.w]
+
+ name = { first = "Tom", last = "Preston-Werner" }
+ point = { x = 1, y = 2 }
+ animal = { type.name = "pug" }
+
+ [[products]]
+ name = "Hammer"
+ sku = 738594937
+
+ [[products]]
+
+ [[products]]
+ name = "Nail"
+ sku = 284758393
+ color = "gray"
+
+ [[fruit]]
+ name = "apple"
+
+ [fruit.physical]
+ color = "red"
+ shape = "round"
+
+ [[fruit.variety]]
+ name = "red delicious"
+
+ [[fruit.variety]]
+ name = "granny smith"
+
+ [[fruit]]
+ name = "banana"
+
+ [[fruit.variety]]
+ name = "plantain"
+''
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-replacestrings.exp b/tests/lang/eval-okay-replacestrings.exp
index 72e8274d8..eac67c5fe 100644
--- a/tests/lang/eval-okay-replacestrings.exp
+++ b/tests/lang/eval-okay-replacestrings.exp
@@ -1 +1 @@
-[ "faabar" "fbar" "fubar" "faboor" "fubar" "XaXbXcX" "X" "a_b" ]
+[ "faabar" "fbar" "fubar" "faboor" "fubar" "XaXbXcX" "X" "a_b" "fubar" ]
diff --git a/tests/lang/eval-okay-replacestrings.nix b/tests/lang/eval-okay-replacestrings.nix
index bd8031fc0..a803e6519 100644
--- a/tests/lang/eval-okay-replacestrings.nix
+++ b/tests/lang/eval-okay-replacestrings.nix
@@ -8,4 +8,5 @@ with builtins;
(replaceStrings [""] ["X"] "abc")
(replaceStrings [""] ["X"] "")
(replaceStrings ["-"] ["_"] "a-b")
+ (replaceStrings ["oo" "XX"] ["u" (throw "unreachable")] "foobar")
]
diff --git a/tests/linux-sandbox-cert-test.nix b/tests/linux-sandbox-cert-test.nix
new file mode 100644
index 000000000..2fc083ea9
--- /dev/null
+++ b/tests/linux-sandbox-cert-test.nix
@@ -0,0 +1,30 @@
+{ 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; }
+
+ 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
+
+ # 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 5a2cf7abd..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,14 +31,45 @@ 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 () {
+ 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
+cert=$TEST_ROOT/some-cert-file.pem
+echo -n "CERT_CONTENT" > $cert
+
+# No cert in sandbox when not a fixed-output derivation
+testCert missing normal "$cert"
+
+# No cert in sandbox when ssl-cert-file is empty
+testCert missing fixed-output ""
+
+# No cert in sandbox when ssl-cert-file is a nonexistent file
+testCert missing fixed-output "$nocert"
+
+# Cert in sandbox when ssl-cert-file is set to an existing file
+testCert present fixed-output "$cert"
diff --git a/tests/local.mk b/tests/local.mk
index 9cb81e1f0..2be1081f7 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -16,6 +16,7 @@ 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 \
@@ -135,7 +136,9 @@ 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
ifeq ($(HAVE_LIBCPUID), 1)
nix_tests += compute-levels.sh
@@ -153,6 +156,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/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 4ef5b484a..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 ])
@@ -157,17 +158,17 @@ error: An existing package already provides the following file:
To remove the existing package:
- nix profile remove path:${flake1Dir}
+ nix profile remove path:${flake1Dir}#packages.${system}.default
The new package can also be installed next to the existing one by assigning a different priority.
The conflicting packages have a priority of 5.
To prioritise the new package:
- nix profile install path:${flake2Dir} --priority 4
+ nix profile install path:${flake2Dir}#packages.${system}.default --priority 4
To prioritise the existing package:
- nix profile install path:${flake2Dir} --priority 6
+ nix profile install path:${flake2Dir}#packages.${system}.default --priority 6
EOF
)
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
@@ -177,3 +178,10 @@ nix profile install $flake2Dir --priority 0
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World2" ]]
# nix profile install $flake1Dir --priority 100
# [[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
+
+# Ensure that conflicts are handled properly even when the installables aren't
+# flake references.
+# Regression test for https://github.com/NixOS/nix/issues/8284
+clearProfiles
+nix profile install $(nix build $flake1Dir --no-link --print-out-paths)
+expect 1 nix profile install --impure --expr "(builtins.getFlake ''$flake2Dir'').packages.$system.default"
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/nix-copy.nix b/tests/nixos/nix-copy.nix
index ee8b77100..16c477bf9 100644
--- a/tests/nixos/nix-copy.nix
+++ b/tests/nixos/nix-copy.nix
@@ -23,6 +23,12 @@ in {
nix.settings.substituters = lib.mkForce [ ];
nix.settings.experimental-features = [ "nix-command" ];
services.getty.autologinUser = "root";
+ programs.ssh.extraConfig = ''
+ Host *
+ ControlMaster auto
+ ControlPath ~/.ssh/master-%h:%r@%n:%p
+ ControlPersist 15m
+ '';
};
server =
@@ -62,6 +68,10 @@ in {
client.wait_for_text("done")
server.succeed("nix-store --check-validity ${pkgA}")
+ # Check that ControlMaster is working
+ client.send_chars("nix copy --to ssh://server ${pkgA} >&2; echo done\n")
+ client.wait_for_text("done")
+
client.copy_from_host("key", "/root/.ssh/id_ed25519")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
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/recursive.sh b/tests/recursive.sh
index ffeb44e50..0bf00f8fa 100644
--- a/tests/recursive.sh
+++ b/tests/recursive.sh
@@ -1,8 +1,5 @@
source common.sh
-# FIXME
-if [[ $(uname) != Linux ]]; then skipTest "Not running Linux"; fi
-
enableFeatures 'recursive-nix'
restartDaemon
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/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;
+ }
+}