aboutsummaryrefslogtreecommitdiff
path: root/tests/functional/ca
diff options
context:
space:
mode:
Diffstat (limited to 'tests/functional/ca')
-rw-r--r--tests/functional/ca/build-cache.sh51
-rw-r--r--tests/functional/ca/build-dry.sh6
-rwxr-xr-xtests/functional/ca/build-with-garbage-path.sh21
-rw-r--r--tests/functional/ca/build.sh67
-rw-r--r--tests/functional/ca/common.sh5
-rwxr-xr-xtests/functional/ca/concurrent-builds.sh18
l---------tests/functional/ca/config.nix.in1
-rw-r--r--tests/functional/ca/content-addressed.nix100
-rw-r--r--tests/functional/ca/derivation-json.sh29
-rw-r--r--tests/functional/ca/duplicate-realisation-in-closure.sh26
-rw-r--r--tests/functional/ca/flake.nix3
-rwxr-xr-xtests/functional/ca/gc.sh10
-rw-r--r--tests/functional/ca/import-derivation.sh6
-rw-r--r--tests/functional/ca/local.mk28
-rw-r--r--tests/functional/ca/new-build-cmd.sh5
-rwxr-xr-xtests/functional/ca/nix-copy.sh31
-rwxr-xr-xtests/functional/ca/nix-run.sh7
-rwxr-xr-xtests/functional/ca/nix-shell.sh8
-rw-r--r--tests/functional/ca/nondeterministic.nix35
-rwxr-xr-xtests/functional/ca/post-hook.sh11
-rw-r--r--tests/functional/ca/racy.nix15
-rwxr-xr-xtests/functional/ca/recursive.sh9
-rw-r--r--tests/functional/ca/repl.sh5
-rwxr-xr-xtests/functional/ca/selfref-gc.sh11
-rw-r--r--tests/functional/ca/signatures.sh36
-rw-r--r--tests/functional/ca/substitute.sh71
-rw-r--r--tests/functional/ca/why-depends.sh5
27 files changed, 620 insertions, 0 deletions
diff --git a/tests/functional/ca/build-cache.sh b/tests/functional/ca/build-cache.sh
new file mode 100644
index 000000000..6a4080fec
--- /dev/null
+++ b/tests/functional/ca/build-cache.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+# The substituters didn't work prior to this time.
+requireDaemonNewerThan "2.18.0pre20230808"
+
+drv=$(nix-instantiate ./content-addressed.nix -A rootCA --arg seed 1)^out
+nix derivation show "$drv" --arg seed 1
+
+buildAttr () {
+ local derivationPath=$1
+ local seedValue=$2
+ shift; shift
+ local args=("./content-addressed.nix" "-A" "$derivationPath" --arg seed "$seedValue" "--no-out-link")
+ args+=("$@")
+ nix-build "${args[@]}"
+}
+
+copyAttr () {
+ local derivationPath=$1
+ local seedValue=$2
+ shift; shift
+ local args=("-f" "./content-addressed.nix" "$derivationPath" --arg seed "$seedValue")
+ args+=("$@")
+ # Note: to copy CA derivations, we need to copy the realisations, which
+ # currently requires naming the installables, not just the derivation output
+ # path.
+ nix copy --to file://$cacheDir "${args[@]}"
+}
+
+testRemoteCacheFor () {
+ local derivationPath=$1
+ clearCache
+ copyAttr "$derivationPath" 1
+ clearStore
+ # Check nothing gets built.
+ buildAttr "$derivationPath" 1 --option substituters file://$cacheDir --no-require-sigs |& grepQuietInverse " will be built:"
+}
+
+testRemoteCache () {
+ testRemoteCacheFor rootCA
+ testRemoteCacheFor dependentCA
+ testRemoteCacheFor dependentNonCA
+ testRemoteCacheFor dependentFixedOutput
+ testRemoteCacheFor dependentForBuildCA
+ testRemoteCacheFor dependentForBuildNonCA
+}
+
+clearStore
+testRemoteCache \ No newline at end of file
diff --git a/tests/functional/ca/build-dry.sh b/tests/functional/ca/build-dry.sh
new file mode 100644
index 000000000..9a72075ec
--- /dev/null
+++ b/tests/functional/ca/build-dry.sh
@@ -0,0 +1,6 @@
+source common.sh
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+
+cd .. && source build-dry.sh
+
diff --git a/tests/functional/ca/build-with-garbage-path.sh b/tests/functional/ca/build-with-garbage-path.sh
new file mode 100755
index 000000000..884cd2802
--- /dev/null
+++ b/tests/functional/ca/build-with-garbage-path.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+# Regression test for https://github.com/NixOS/nix/issues/4858
+
+source common.sh
+
+requireDaemonNewerThan "2.4pre20210621"
+
+# Get the output path of `rootCA`, and put some garbage instead
+outPath="$(nix-build ./content-addressed.nix -A rootCA --no-out-link)"
+nix-store --delete $(nix-store -q --referrers-closure "$outPath")
+touch "$outPath"
+
+# The build should correctly remove the garbage and put the expected path instead
+nix-build ./content-addressed.nix -A rootCA --no-out-link
+
+# Rebuild it. This shouldn’t overwrite the existing path
+oldInode=$(stat -c '%i' "$outPath")
+nix-build ./content-addressed.nix -A rootCA --no-out-link --arg seed 2
+newInode=$(stat -c '%i' "$outPath")
+[[ "$oldInode" == "$newInode" ]]
diff --git a/tests/functional/ca/build.sh b/tests/functional/ca/build.sh
new file mode 100644
index 000000000..e1a8a7625
--- /dev/null
+++ b/tests/functional/ca/build.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+drv=$(nix-instantiate ./content-addressed.nix -A rootCA --arg seed 1)^out
+nix derivation show "$drv" --arg seed 1
+
+buildAttr () {
+ local derivationPath=$1
+ local seedValue=$2
+ shift; shift
+ local args=("./content-addressed.nix" "-A" "$derivationPath" --arg seed "$seedValue" "--no-out-link")
+ args+=("$@")
+ nix-build "${args[@]}"
+}
+
+testDeterministicCA () {
+ [[ $(buildAttr rootCA 1) = $(buildAttr rootCA 2) ]]
+}
+
+testCutoffFor () {
+ local out1 out2
+ out1=$(buildAttr $1 1)
+ # The seed only changes the root derivation, and not it's output, so the
+ # dependent derivations should only need to be built once.
+ buildAttr rootCA 2
+ out2=$(buildAttr $1 2 -j0)
+ test "$out1" == "$out2"
+}
+
+testCutoff () {
+ # Don't directly build dependentCA, that way we'll make sure we don't rely on
+ # dependent derivations always being already built.
+ #testDerivation dependentCA
+ testCutoffFor transitivelyDependentCA
+ testCutoffFor dependentNonCA
+ testCutoffFor dependentFixedOutput
+}
+
+testGC () {
+ nix-instantiate ./content-addressed.nix -A rootCA --arg seed 5
+ nix-collect-garbage --option keep-derivations true
+ clearStore
+ buildAttr rootCA 1 --out-link $TEST_ROOT/rootCA
+ nix-collect-garbage
+ buildAttr rootCA 1 -j0
+}
+
+testNixCommand () {
+ clearStore
+ nix build --file ./content-addressed.nix --no-link
+}
+
+# Regression test for https://github.com/NixOS/nix/issues/4775
+testNormalization () {
+ clearStore
+ outPath=$(buildAttr rootCA 1)
+ test "$(stat -c %Y $outPath)" -eq 1
+}
+
+clearStore
+testNormalization
+testDeterministicCA
+clearStore
+testCutoff
+testGC
+testNixCommand
diff --git a/tests/functional/ca/common.sh b/tests/functional/ca/common.sh
new file mode 100644
index 000000000..b104b5a78
--- /dev/null
+++ b/tests/functional/ca/common.sh
@@ -0,0 +1,5 @@
+source ../common.sh
+
+enableFeatures "ca-derivations"
+
+restartDaemon
diff --git a/tests/functional/ca/concurrent-builds.sh b/tests/functional/ca/concurrent-builds.sh
new file mode 100755
index 000000000..b442619e2
--- /dev/null
+++ b/tests/functional/ca/concurrent-builds.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+# Ensure that we can’t build twice the same derivation concurrently.
+# Regression test for https://github.com/NixOS/nix/issues/5029
+
+source common.sh
+
+buggyNeedLocalStore "For some reason, this deadlocks with the daemon"
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+
+clearStore
+
+for i in {0..5}; do
+ nix build --no-link --file ./racy.nix &
+done
+
+wait
diff --git a/tests/functional/ca/config.nix.in b/tests/functional/ca/config.nix.in
new file mode 120000
index 000000000..af24ddb30
--- /dev/null
+++ b/tests/functional/ca/config.nix.in
@@ -0,0 +1 @@
+../config.nix.in \ No newline at end of file
diff --git a/tests/functional/ca/content-addressed.nix b/tests/functional/ca/content-addressed.nix
new file mode 100644
index 000000000..2559c562f
--- /dev/null
+++ b/tests/functional/ca/content-addressed.nix
@@ -0,0 +1,100 @@
+with import ./config.nix;
+
+let mkCADerivation = args: mkDerivation ({
+ __contentAddressed = true;
+ outputHashMode = "recursive";
+ outputHashAlgo = "sha256";
+} // args);
+in
+
+{ seed ? 0 }:
+# A simple content-addressed derivation.
+# The derivation can be arbitrarily modified by passing a different `seed`,
+# but the output will always be the same
+rec {
+ rootLegacy = mkDerivation {
+ name = "simple-input-addressed";
+ buildCommand = ''
+ set -x
+ echo "Building a legacy derivation"
+ mkdir -p $out
+ echo "Hello World" > $out/hello
+ '';
+ };
+ rootCA = mkCADerivation {
+ name = "rootCA";
+ outputs = [ "out" "dev" "foo" ];
+ buildCommand = ''
+ echo "building a CA derivation"
+ echo "The seed is ${toString seed}"
+ mkdir -p $out
+ echo ${rootLegacy}/hello > $out/dep
+ ln -s $out $out/self
+ # test symlinks at root
+ ln -s $out $dev
+ ln -s $out $foo
+ '';
+ };
+ dependentCA = mkCADerivation {
+ name = "dependent";
+ buildCommand = ''
+ echo "building a dependent derivation"
+ mkdir -p $out
+ cat ${rootCA}/self/dep
+ echo ${rootCA}/self/dep > $out/dep
+ '';
+ };
+ transitivelyDependentCA = mkCADerivation {
+ name = "transitively-dependent";
+ buildCommand = ''
+ echo "building transitively-dependent"
+ cat ${dependentCA}/dep
+ echo ${dependentCA} > $out
+ '';
+ };
+ dependentNonCA = mkDerivation {
+ name = "dependent-non-ca";
+ buildCommand = ''
+ echo "Didn't cut-off"
+ echo "building dependent-non-ca"
+ mkdir -p $out
+ echo ${rootCA}/non-ca-hello > $out/dep
+ '';
+ };
+ dependentForBuildCA = mkCADerivation {
+ name = "dependent-for-build-ca";
+ buildCommand = ''
+ echo "Depends on rootCA for building only"
+ mkdir -p $out
+ echo ${rootCA}
+ touch $out
+ '';
+ };
+ dependentForBuildNonCA = mkDerivation {
+ name = "dependent-for-build-non-ca";
+ buildCommand = ''
+ echo "Depends on rootCA for building only"
+ mkdir -p $out
+ echo ${rootCA}
+ touch $out
+ '';
+ };
+ dependentFixedOutput = mkDerivation {
+ name = "dependent-fixed-output";
+ outputHashMode = "recursive";
+ outputHash = "sha512-7aJcmSuEuYP5tGKcmGY8bRr/lrCjJlOxP2mIUjO/vMQeg6gx/65IbzRWES8EKiPDOs9z+wF30lEfcwxM/cT4pw==";
+ buildCommand = ''
+ cat ${dependentCA}/dep
+ echo foo > $out
+ '';
+ };
+ runnable = mkCADerivation rec {
+ name = "runnable-thing";
+ buildCommand = ''
+ mkdir -p $out/bin
+ echo ${rootCA} # Just to make it depend on it
+ echo "#! ${shell}" > $out/bin/${name}
+ chmod +x $out/bin/${name}
+ '';
+ };
+}
diff --git a/tests/functional/ca/derivation-json.sh b/tests/functional/ca/derivation-json.sh
new file mode 100644
index 000000000..c1480fd17
--- /dev/null
+++ b/tests/functional/ca/derivation-json.sh
@@ -0,0 +1,29 @@
+source common.sh
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+
+drvPath=$(nix-instantiate ../simple.nix)
+
+nix derivation show $drvPath | jq .[] > $TEST_HOME/simple.json
+
+drvPath2=$(nix derivation add < $TEST_HOME/simple.json)
+
+[[ "$drvPath" = "$drvPath2" ]]
+
+# Content-addressed derivations can be renamed.
+jq '.name = "foo"' < $TEST_HOME/simple.json > $TEST_HOME/foo.json
+drvPath3=$(nix derivation add --dry-run < $TEST_HOME/foo.json)
+# With --dry-run nothing is actually written
+[[ ! -e "$drvPath3" ]]
+
+# But the JSON is rejected without the experimental feature
+expectStderr 1 nix derivation add < $TEST_HOME/foo.json --experimental-features nix-command | grepQuiet "experimental Nix feature 'ca-derivations' is disabled"
+
+# Without --dry-run it is actually written
+drvPath4=$(nix derivation add < $TEST_HOME/foo.json)
+[[ "$drvPath4" = "$drvPath3" ]]
+[[ -e "$drvPath3" ]]
+
+# The modified derivation read back as JSON matches
+nix derivation show $drvPath3 | jq .[] > $TEST_HOME/foo-read.json
+diff $TEST_HOME/foo.json $TEST_HOME/foo-read.json
diff --git a/tests/functional/ca/duplicate-realisation-in-closure.sh b/tests/functional/ca/duplicate-realisation-in-closure.sh
new file mode 100644
index 000000000..da9cd8fb4
--- /dev/null
+++ b/tests/functional/ca/duplicate-realisation-in-closure.sh
@@ -0,0 +1,26 @@
+source ./common.sh
+
+requireDaemonNewerThan "2.4pre20210625"
+
+export REMOTE_STORE_DIR="$TEST_ROOT/remote_store"
+export REMOTE_STORE="file://$REMOTE_STORE_DIR"
+
+rm -rf $REMOTE_STORE_DIR
+clearStore
+
+# Build dep1 and push that to the binary cache.
+# This entails building (and pushing) current-time.
+nix copy --to "$REMOTE_STORE" -f nondeterministic.nix dep1
+clearStore
+sleep 2 # To make sure that `$(date)` will be different
+# Build dep2.
+# As we’ve cleared the cache, we’ll have to rebuild current-time. And because
+# the current time isn’t the same as before, this will yield a new (different)
+# realisation
+nix build -f nondeterministic.nix dep2 --no-link
+
+# Build something that depends both on dep1 and dep2.
+# If everything goes right, we should rebuild dep2 rather than fetch it from
+# the cache (because that would mean duplicating `current-time` in the closure),
+# and have `dep1 == dep2`.
+nix build --substituters "$REMOTE_STORE" -f nondeterministic.nix toplevel --no-require-sigs --no-link
diff --git a/tests/functional/ca/flake.nix b/tests/functional/ca/flake.nix
new file mode 100644
index 000000000..332c92a67
--- /dev/null
+++ b/tests/functional/ca/flake.nix
@@ -0,0 +1,3 @@
+{
+ outputs = { self }: import ./content-addressed.nix {};
+}
diff --git a/tests/functional/ca/gc.sh b/tests/functional/ca/gc.sh
new file mode 100755
index 000000000..e9b6c5ab5
--- /dev/null
+++ b/tests/functional/ca/gc.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+# Ensure that garbage collection works properly with ca derivations
+
+source common.sh
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+
+cd ..
+source gc.sh
diff --git a/tests/functional/ca/import-derivation.sh b/tests/functional/ca/import-derivation.sh
new file mode 100644
index 000000000..e98e0fbd0
--- /dev/null
+++ b/tests/functional/ca/import-derivation.sh
@@ -0,0 +1,6 @@
+source common.sh
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+
+cd .. && source import-derivation.sh
+
diff --git a/tests/functional/ca/local.mk b/tests/functional/ca/local.mk
new file mode 100644
index 000000000..fd87b8d1f
--- /dev/null
+++ b/tests/functional/ca/local.mk
@@ -0,0 +1,28 @@
+ca-tests := \
+ $(d)/build-with-garbage-path.sh \
+ $(d)/build.sh \
+ $(d)/build-cache.sh \
+ $(d)/concurrent-builds.sh \
+ $(d)/derivation-json.sh \
+ $(d)/duplicate-realisation-in-closure.sh \
+ $(d)/gc.sh \
+ $(d)/import-derivation.sh \
+ $(d)/new-build-cmd.sh \
+ $(d)/nix-copy.sh \
+ $(d)/nix-run.sh \
+ $(d)/nix-shell.sh \
+ $(d)/post-hook.sh \
+ $(d)/recursive.sh \
+ $(d)/repl.sh \
+ $(d)/selfref-gc.sh \
+ $(d)/signatures.sh \
+ $(d)/substitute.sh \
+ $(d)/why-depends.sh
+
+install-tests-groups += ca
+
+clean-files += \
+ $(d)/config.nix
+
+test-deps += \
+ tests/functional/ca/config.nix
diff --git a/tests/functional/ca/new-build-cmd.sh b/tests/functional/ca/new-build-cmd.sh
new file mode 100644
index 000000000..432d4d132
--- /dev/null
+++ b/tests/functional/ca/new-build-cmd.sh
@@ -0,0 +1,5 @@
+source common.sh
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+cd ..
+source ./build.sh
diff --git a/tests/functional/ca/nix-copy.sh b/tests/functional/ca/nix-copy.sh
new file mode 100755
index 000000000..7a8307a4e
--- /dev/null
+++ b/tests/functional/ca/nix-copy.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+export REMOTE_STORE_DIR="$TEST_ROOT/remote_store"
+export REMOTE_STORE="file://$REMOTE_STORE_DIR"
+
+ensureCorrectlyCopied () {
+ attrPath="$1"
+ nix build --store "$REMOTE_STORE" --file ./content-addressed.nix "$attrPath"
+}
+
+testOneCopy () {
+ clearStore
+ rm -rf "$REMOTE_STORE_DIR"
+
+ attrPath="$1"
+ nix copy --to $REMOTE_STORE "$attrPath" --file ./content-addressed.nix
+
+ ensureCorrectlyCopied "$attrPath"
+
+ # Ensure that we can copy back what we put in the store
+ clearStore
+ nix copy --from $REMOTE_STORE \
+ --file ./content-addressed.nix "$attrPath" \
+ --no-check-sigs
+}
+
+for attrPath in rootCA dependentCA transitivelyDependentCA dependentNonCA dependentFixedOutput; do
+ testOneCopy "$attrPath"
+done
diff --git a/tests/functional/ca/nix-run.sh b/tests/functional/ca/nix-run.sh
new file mode 100755
index 000000000..5f46518e8
--- /dev/null
+++ b/tests/functional/ca/nix-run.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+FLAKE_PATH=path:$PWD
+
+nix run --no-write-lock-file $FLAKE_PATH#runnable
diff --git a/tests/functional/ca/nix-shell.sh b/tests/functional/ca/nix-shell.sh
new file mode 100755
index 000000000..1c5a6639f
--- /dev/null
+++ b/tests/functional/ca/nix-shell.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+CONTENT_ADDRESSED=true
+cd ..
+source ./nix-shell.sh
+
diff --git a/tests/functional/ca/nondeterministic.nix b/tests/functional/ca/nondeterministic.nix
new file mode 100644
index 000000000..d6d099a3e
--- /dev/null
+++ b/tests/functional/ca/nondeterministic.nix
@@ -0,0 +1,35 @@
+with import ./config.nix;
+
+let mkCADerivation = args: mkDerivation ({
+ __contentAddressed = true;
+ outputHashMode = "recursive";
+ outputHashAlgo = "sha256";
+} // args);
+in
+
+rec {
+ currentTime = mkCADerivation {
+ name = "current-time";
+ buildCommand = ''
+ mkdir $out
+ echo $(date) > $out/current-time
+ '';
+ };
+ dep = seed: mkCADerivation {
+ name = "dep";
+ inherit seed;
+ buildCommand = ''
+ echo ${currentTime} > $out
+ '';
+ };
+ dep1 = dep 1;
+ dep2 = dep 2;
+ toplevel = mkCADerivation {
+ name = "toplevel";
+ buildCommand = ''
+ test ${dep1} == ${dep2}
+ touch $out
+ '';
+ };
+}
+
diff --git a/tests/functional/ca/post-hook.sh b/tests/functional/ca/post-hook.sh
new file mode 100755
index 000000000..705bde9d4
--- /dev/null
+++ b/tests/functional/ca/post-hook.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+requireDaemonNewerThan "2.4pre20210626"
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+cd ..
+source ./post-hook.sh
+
+
diff --git a/tests/functional/ca/racy.nix b/tests/functional/ca/racy.nix
new file mode 100644
index 000000000..555a15484
--- /dev/null
+++ b/tests/functional/ca/racy.nix
@@ -0,0 +1,15 @@
+# A derivation that would certainly fail if several builders tried to
+# build it at once.
+
+
+with import ./config.nix;
+
+mkDerivation {
+ name = "simple";
+ buildCommand = ''
+ mkdir $out
+ echo bar >> $out/foo
+ sleep 3
+ [[ "$(cat $out/foo)" == bar ]]
+ '';
+}
diff --git a/tests/functional/ca/recursive.sh b/tests/functional/ca/recursive.sh
new file mode 100755
index 000000000..cd6736b24
--- /dev/null
+++ b/tests/functional/ca/recursive.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+requireDaemonNewerThan "2.4pre20210623"
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+cd ..
+source ./recursive.sh
diff --git a/tests/functional/ca/repl.sh b/tests/functional/ca/repl.sh
new file mode 100644
index 000000000..3808c7cb2
--- /dev/null
+++ b/tests/functional/ca/repl.sh
@@ -0,0 +1,5 @@
+source common.sh
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+
+cd .. && source repl.sh
diff --git a/tests/functional/ca/selfref-gc.sh b/tests/functional/ca/selfref-gc.sh
new file mode 100755
index 000000000..248778894
--- /dev/null
+++ b/tests/functional/ca/selfref-gc.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+requireDaemonNewerThan "2.4pre20210626"
+
+enableFeatures "ca-derivations nix-command flakes"
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+cd ..
+source ./selfref-gc.sh
diff --git a/tests/functional/ca/signatures.sh b/tests/functional/ca/signatures.sh
new file mode 100644
index 000000000..eb18a4130
--- /dev/null
+++ b/tests/functional/ca/signatures.sh
@@ -0,0 +1,36 @@
+source common.sh
+
+clearStore
+clearCache
+
+nix-store --generate-binary-cache-key cache1.example.org $TEST_ROOT/sk1 $TEST_ROOT/pk1
+pk1=$(cat $TEST_ROOT/pk1)
+
+export REMOTE_STORE_DIR="$TEST_ROOT/remote_store"
+export REMOTE_STORE="file://$REMOTE_STORE_DIR"
+
+ensureCorrectlyCopied () {
+ attrPath="$1"
+ nix build --store "$REMOTE_STORE" --file ./content-addressed.nix "$attrPath"
+}
+
+testOneCopy () {
+ clearStore
+ rm -rf "$REMOTE_STORE_DIR"
+
+ attrPath="$1"
+ nix copy -vvvv --to $REMOTE_STORE "$attrPath" --file ./content-addressed.nix \
+ --secret-key-files "$TEST_ROOT/sk1" --show-trace
+
+ ensureCorrectlyCopied "$attrPath"
+
+ # Ensure that we can copy back what we put in the store
+ clearStore
+ nix copy --from $REMOTE_STORE \
+ --file ./content-addressed.nix "$attrPath" \
+ --trusted-public-keys $pk1
+}
+
+for attrPath in rootCA dependentCA transitivelyDependentCA dependentNonCA dependentFixedOutput; do
+ testOneCopy "$attrPath"
+done
diff --git a/tests/functional/ca/substitute.sh b/tests/functional/ca/substitute.sh
new file mode 100644
index 000000000..ea981adc4
--- /dev/null
+++ b/tests/functional/ca/substitute.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+
+# Ensure that binary substitution works properly with ca derivations
+
+source common.sh
+
+needLocalStore "“--no-require-sigs” can’t be used with the daemon"
+
+rm -rf $TEST_ROOT/binary_cache
+
+export REMOTE_STORE_DIR=$TEST_ROOT/binary_cache
+export REMOTE_STORE=file://$REMOTE_STORE_DIR
+
+buildDrvs () {
+ nix build --file ./content-addressed.nix -L --no-link "$@"
+}
+
+# Populate the remote cache
+clearStore
+nix copy --to $REMOTE_STORE --file ./content-addressed.nix
+
+# Restart the build on an empty store, ensuring that we don't build
+clearStore
+buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0 transitivelyDependentCA
+# Check that the thing we’ve just substituted has its realisation stored
+nix realisation info --file ./content-addressed.nix transitivelyDependentCA
+# Check that its dependencies have it too
+nix realisation info --file ./content-addressed.nix dependentCA
+# nix realisation info --file ./content-addressed.nix rootCA --outputs out
+
+if isDaemonNewer "2.13"; then
+ pushToStore="../push-to-store.sh"
+else
+ pushToStore="../push-to-store-old.sh"
+fi
+
+# Same thing, but
+# 1. With non-ca derivations
+# 2. Erasing the realisations on the remote store
+#
+# Even in that case, realising the derivations should still produce the right
+# realisations on the local store
+#
+# Regression test for #4725
+clearStore
+nix build --file ../simple.nix -L --no-link --post-build-hook "$pushToStore"
+clearStore
+rm -r "$REMOTE_STORE_DIR/realisations"
+nix build --file ../simple.nix -L --no-link --substitute --substituters "$REMOTE_STORE" --no-require-sigs -j0
+# There's no easy way to check whether a realisation is present on the local
+# store − short of manually querying the db, but the build environment doesn't
+# have the sqlite binary − so we instead push things again, and check that the
+# realisations have correctly been pushed to the remote store
+nix copy --to "$REMOTE_STORE" --file ../simple.nix
+if [[ -z "$(ls "$REMOTE_STORE_DIR/realisations")" ]]; then
+ echo "Realisations not rebuilt"
+ exit 1
+fi
+
+# Test the local realisation disk cache
+buildDrvs --post-build-hook "$pushToStore"
+clearStore
+# Add the realisations of rootCA to the cachecache
+clearCacheCache
+export _NIX_FORCE_HTTP=1
+buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0
+# Try rebuilding, but remove the realisations from the remote cache to force
+# using the cachecache
+clearStore
+rm $REMOTE_STORE_DIR/realisations/*
+buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0
diff --git a/tests/functional/ca/why-depends.sh b/tests/functional/ca/why-depends.sh
new file mode 100644
index 000000000..0c079f63b
--- /dev/null
+++ b/tests/functional/ca/why-depends.sh
@@ -0,0 +1,5 @@
+source common.sh
+
+export NIX_TESTS_CA_BY_DEFAULT=1
+
+cd .. && source why-depends.sh