forked from lix-project/lix
Previously, the garbage collector found runtime roots on Darwin by
shelling out to `lsof -n -w -F n` then parsing the result.
However, this requires an lsof binary and can be extremely slow.
The official Apple lsof returns in a reasonable amount of time,
about 250ms in my tests, but the lsof packaged in nixpkgs is quite slow,
taking about 40 seconds to run the command.
Using libproc directly is about the same speed as Apple lsof,
and allows us to reënable several tests that were disabled on Darwin.
Change-Id: Ifa0adda7984e13c15535693baba835aae79a3577
277 lines
6.9 KiB
Bash
277 lines
6.9 KiB
Bash
set -eu -o pipefail
|
||
|
||
if [[ -z "${COMMON_VARS_AND_FUNCTIONS_SH_SOURCED-}" ]]; then
|
||
|
||
COMMON_VARS_AND_FUNCTIONS_SH_SOURCED=1
|
||
|
||
export PS4='+(${BASH_SOURCE[0]-$0}:$LINENO) '
|
||
|
||
export TEST_ROOT=$(realpath ${TMPDIR:-/tmp}/nix-test)/${TEST_NAME:-default/tests\/functional//}
|
||
export NIX_STORE_DIR
|
||
if ! NIX_STORE_DIR=$(readlink -f $TEST_ROOT/store 2> /dev/null); then
|
||
# Maybe the build directory is symlinked.
|
||
export NIX_IGNORE_SYMLINK_STORE=1
|
||
NIX_STORE_DIR=$TEST_ROOT/store
|
||
fi
|
||
export NIX_LOCALSTATE_DIR=$TEST_ROOT/var
|
||
export NIX_LOG_DIR=$TEST_ROOT/var/log/nix
|
||
export NIX_STATE_DIR=$TEST_ROOT/var/nix
|
||
export NIX_CONF_DIR=$TEST_ROOT/etc
|
||
export NIX_DAEMON_SOCKET_PATH=$TEST_ROOT/dSocket
|
||
unset NIX_USER_CONF_FILES
|
||
export _NIX_TEST_SHARED=$TEST_ROOT/shared
|
||
if [[ -n $NIX_STORE ]]; then
|
||
export _NIX_TEST_NO_SANDBOX=1
|
||
fi
|
||
export _NIX_IN_TEST=$TEST_ROOT/shared
|
||
export NIX_REMOTE=${NIX_REMOTE_-}
|
||
unset NIX_PATH
|
||
export TEST_HOME=$TEST_ROOT/test-home
|
||
export HOME=$TEST_HOME
|
||
unset XDG_STATE_HOME
|
||
unset XDG_DATA_HOME
|
||
unset XDG_CONFIG_HOME
|
||
unset XDG_CONFIG_DIRS
|
||
unset XDG_CACHE_HOME
|
||
mkdir -p $TEST_HOME
|
||
|
||
export PATH=@bindir@:$PATH
|
||
if [[ -n "${NIX_CLIENT_PACKAGE:-}" ]]; then
|
||
export PATH="$NIX_CLIENT_PACKAGE/bin":$PATH
|
||
fi
|
||
DAEMON_PATH="$PATH"
|
||
if [[ -n "${NIX_DAEMON_PACKAGE:-}" ]]; then
|
||
DAEMON_PATH="${NIX_DAEMON_PACKAGE}/bin:$DAEMON_PATH"
|
||
fi
|
||
coreutils=@coreutils@
|
||
lsof=@lsof@
|
||
|
||
export dot=@dot@
|
||
export SHELL="@bash@"
|
||
export PAGER=cat
|
||
export busybox="@sandbox_shell@"
|
||
|
||
export version=@PACKAGE_VERSION@
|
||
export system=@system@
|
||
|
||
export BUILD_SHARED_LIBS=@BUILD_SHARED_LIBS@
|
||
|
||
export IMPURE_VAR1=foo
|
||
export IMPURE_VAR2=bar
|
||
|
||
cacheDir=$TEST_ROOT/binary-cache
|
||
|
||
readLink() {
|
||
ls -l "$1" | sed 's/.*->\ //'
|
||
}
|
||
|
||
clearProfiles() {
|
||
profiles="$HOME"/.local/state/nix/profiles
|
||
rm -rf "$profiles"
|
||
}
|
||
|
||
clearStore() {
|
||
echo "clearing store..."
|
||
chmod -R +w "$NIX_STORE_DIR"
|
||
rm -rf "$NIX_STORE_DIR"
|
||
mkdir "$NIX_STORE_DIR"
|
||
rm -rf "$NIX_STATE_DIR"
|
||
mkdir "$NIX_STATE_DIR"
|
||
clearProfiles
|
||
}
|
||
|
||
clearCache() {
|
||
rm -rf "$cacheDir"
|
||
}
|
||
|
||
clearCacheCache() {
|
||
rm -f $TEST_HOME/.cache/nix/binary-cache*
|
||
}
|
||
|
||
startDaemon() {
|
||
# Don’t start the daemon twice, as this would just make it loop indefinitely
|
||
if [[ "${_NIX_TEST_DAEMON_PID-}" != '' ]]; then
|
||
return
|
||
fi
|
||
# Start the daemon, wait for the socket to appear.
|
||
rm -f $NIX_DAEMON_SOCKET_PATH
|
||
PATH=$DAEMON_PATH nix --extra-experimental-features 'nix-command' daemon &
|
||
_NIX_TEST_DAEMON_PID=$!
|
||
export _NIX_TEST_DAEMON_PID
|
||
for ((i = 0; i < 300; i++)); do
|
||
if [[ -S $NIX_DAEMON_SOCKET_PATH ]]; then
|
||
DAEMON_STARTED=1
|
||
break;
|
||
fi
|
||
sleep 0.1
|
||
done
|
||
if [[ -z ${DAEMON_STARTED+x} ]]; then
|
||
fail "Didn’t manage to start the daemon"
|
||
fi
|
||
trap "killDaemon" EXIT
|
||
# Save for if daemon is killed
|
||
NIX_REMOTE_OLD=$NIX_REMOTE
|
||
export NIX_REMOTE=daemon
|
||
}
|
||
|
||
killDaemon() {
|
||
# Don’t fail trying to stop a non-existant daemon twice
|
||
if [[ "${_NIX_TEST_DAEMON_PID-}" == '' ]]; then
|
||
return
|
||
fi
|
||
kill $_NIX_TEST_DAEMON_PID
|
||
for i in {0..100}; do
|
||
kill -0 $_NIX_TEST_DAEMON_PID 2> /dev/null || break
|
||
sleep 0.1
|
||
done
|
||
kill -9 $_NIX_TEST_DAEMON_PID 2> /dev/null || true
|
||
wait $_NIX_TEST_DAEMON_PID || true
|
||
rm -f $NIX_DAEMON_SOCKET_PATH
|
||
# Indicate daemon is stopped
|
||
unset _NIX_TEST_DAEMON_PID
|
||
# Restore old nix remote
|
||
NIX_REMOTE=$NIX_REMOTE_OLD
|
||
trap "" EXIT
|
||
}
|
||
|
||
restartDaemon() {
|
||
[[ -z "${_NIX_TEST_DAEMON_PID:-}" ]] && return 0
|
||
|
||
killDaemon
|
||
startDaemon
|
||
}
|
||
|
||
if [[ $(uname) == Linux ]] && [[ -L /proc/self/ns/user ]] && unshare --user true; then
|
||
_canUseSandbox=1
|
||
fi
|
||
|
||
isDaemonNewer () {
|
||
[[ -n "${NIX_DAEMON_PACKAGE:-}" ]] || return 0
|
||
local requiredVersion="$1"
|
||
local daemonVersion=$($NIX_DAEMON_PACKAGE/bin/nix daemon --version | cut -d' ' -f3)
|
||
[[ $(nix eval --expr "builtins.compareVersions ''$daemonVersion'' ''$requiredVersion''") -ge 0 ]]
|
||
}
|
||
|
||
skipTest () {
|
||
echo "$1, skipping this test..." >&2
|
||
exit 99
|
||
}
|
||
|
||
requireDaemonNewerThan () {
|
||
isDaemonNewer "$1" || skipTest "Daemon is too old"
|
||
}
|
||
|
||
canUseSandbox() {
|
||
[[ ${_canUseSandbox-} ]]
|
||
}
|
||
|
||
requireSandboxSupport () {
|
||
canUseSandbox || skipTest "Sandboxing not supported"
|
||
}
|
||
|
||
requireGit() {
|
||
[[ $(type -p git) ]] || skipTest "Git not installed"
|
||
}
|
||
|
||
fail() {
|
||
echo "$1" >&2
|
||
exit 1
|
||
}
|
||
|
||
# Run a command failing if it didn't exit with the expected exit code.
|
||
#
|
||
# Has two advantages over the built-in `!`:
|
||
#
|
||
# 1. `!` conflates all non-0 codes. `expect` allows testing for an exact
|
||
# code.
|
||
#
|
||
# 2. `!` unexpectedly negates `set -e`, and cannot be used on individual
|
||
# pipeline stages with `set -o pipefail`. It only works on the entire
|
||
# pipeline, which is useless if we want, say, `nix ...` invocation to
|
||
# *fail*, but a grep on the error message it outputs to *succeed*.
|
||
expect() {
|
||
local expected res
|
||
expected="$1"
|
||
shift
|
||
"$@" && res=0 || res="$?"
|
||
if [[ $res -ne $expected ]]; then
|
||
echo "Expected exit code '$expected' but got '$res' from command ${*@Q}" >&2
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# Better than just doing `expect ... >&2` because the "Expected..."
|
||
# message below will *not* be redirected.
|
||
expectStderr() {
|
||
local expected res
|
||
expected="$1"
|
||
shift
|
||
"$@" 2>&1 && res=0 || res="$?"
|
||
if [[ $res -ne $expected ]]; then
|
||
echo "Expected exit code '$expected' but got '$res' from command ${*@Q}" >&2
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
needLocalStore() {
|
||
if [[ "$NIX_REMOTE" == "daemon" ]]; then
|
||
skipTest "Can’t run through the daemon ($1)"
|
||
fi
|
||
}
|
||
|
||
# Just to make it easy to find which tests should be fixed
|
||
buggyNeedLocalStore() {
|
||
needLocalStore "$1"
|
||
}
|
||
|
||
enableFeatures() {
|
||
local features="$1"
|
||
sed -i 's/experimental-features .*/& '"$features"'/' "$NIX_CONF_DIR"/nix.conf
|
||
}
|
||
|
||
set -x
|
||
|
||
onError() {
|
||
set +x
|
||
echo "$0: test failed at:" >&2
|
||
for ((i = 1; i < ${#BASH_SOURCE[@]}; i++)); do
|
||
if [[ -z ${BASH_SOURCE[i]} ]]; then break; fi
|
||
echo " ${FUNCNAME[i]} in ${BASH_SOURCE[i]}:${BASH_LINENO[i-1]}" >&2
|
||
done
|
||
}
|
||
|
||
# `grep -v` doesn't work well for exit codes. We want `!(exist line l. l
|
||
# matches)`. It gives us `exist line l. !(l matches)`.
|
||
#
|
||
# `!` normally doesn't work well with `set -e`, but when we wrap in a
|
||
# function it *does*.
|
||
grepInverse() {
|
||
! grep "$@"
|
||
}
|
||
|
||
# A shorthand, `> /dev/null` is a bit noisy.
|
||
#
|
||
# `grep -q` would seem to do this, no function necessary, but it is a
|
||
# bad fit with pipes and `set -o pipefail`: `-q` will exit after the
|
||
# first match, and then subsequent writes will result in broken pipes.
|
||
#
|
||
# Note that reproducing the above is a bit tricky as it depends on
|
||
# non-deterministic properties such as the timing between the match and
|
||
# the closing of the pipe, the buffering of the pipe, and the speed of
|
||
# the producer into the pipe. But rest assured we've seen it happen in
|
||
# CI reliably.
|
||
grepQuiet() {
|
||
grep "$@" > /dev/null
|
||
}
|
||
|
||
# The previous two, combined
|
||
grepQuietInverse() {
|
||
! grep "$@" > /dev/null
|
||
}
|
||
|
||
trap onError ERR
|
||
|
||
fi # COMMON_VARS_AND_FUNCTIONS_SH_SOURCED
|