test: seal the fake PATH instead of prepending it
The old harness put .tests/fakebin at the front of $PATH, which fails OPEN: a package manager with no fake fell through to the real one, and an audit of this suite invoked the host's actual `brew`. The twelve one-line fakes are replaced by a single dispatcher every fake symlinks to, plus seal.sh, which builds the WHOLE of $PATH for a run — real binaries for the pure tools, fakes for anything that installs, downloads or needs root, and command-not-found for everything else. Runs go under `env -i` so they inherit nothing. Two audits ship with it: any `have`/`find_tool` probe the seal has never heard of is an error, and the four absolute paths find_tool probes are written to .shadowed so a test needing a tool to be genuinely absent can say so. The dispatcher also fails on demand (FAKE_FAIL), which is what makes the installers' failure paths reachable at all — the apt and npm one-at-a-time retries, note_fail, and cmd_install's non-zero exit had all been unreachable, and all three survived being deleted outright. _curl stands in for every remote the installer talks to and for nothing else: an unrecognised URL is a failed download, so a typo'd host shows up as the failure it would really be. The fzf fake records its argv, which is what makes the picker's bindings testable.
This commit is contained in:
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/bin/sh
|
||||
# One dispatcher; every fake in this directory is a symlink to it.
|
||||
#
|
||||
# The old version of this file did two things wrong, and both let real bugs
|
||||
# through:
|
||||
#
|
||||
# it always succeeded so no test could ever reach an installer's failure
|
||||
# path -- the apt and npm one-at-a-time retries, the
|
||||
# "did not install" report, and cmd_install's non-zero
|
||||
# exit were all unreachable, and all three survived
|
||||
# being deleted outright.
|
||||
# it failed OPEN the fakes were PREPENDED to $PATH, so a package
|
||||
# manager with no fake here fell through to the real
|
||||
# one. An audit of this suite invoked the host's real
|
||||
# `brew`. A fake that is missing must be an ERROR, not
|
||||
# a silent hand-off to the machine.
|
||||
#
|
||||
# The second is fixed by seal.sh, which builds a directory holding exactly the
|
||||
# tools a run may touch -- fakes for anything that installs, downloads or needs
|
||||
# root, real binaries for the pure ones -- and runs dotup under `env -i` with
|
||||
# that directory as the WHOLE of PATH. Anything not listed is command-not-found.
|
||||
#
|
||||
# ---------------------------------------------------------------- failure ---
|
||||
# FAKE_FAIL is a comma-separated list of injections:
|
||||
#
|
||||
# FAKE_FAIL=brew brew exits 1 every time
|
||||
# FAKE_FAIL=apt-get:batch apt-get fails only when asked for >1 package,
|
||||
# which is what a real "unable to locate package"
|
||||
# inside a batch looks like: the batch dies and
|
||||
# the one-at-a-time retry is the only thing that
|
||||
# saves the other thirty.
|
||||
# FAKE_FAIL=npm:batch the same shape for npm.
|
||||
# FAKE_FAIL=curl:deb curl fails only for the .deb download.
|
||||
#
|
||||
# Several may be combined: FAKE_FAIL=brew,apt-get:batch
|
||||
set -u
|
||||
me=${0##*/}
|
||||
printf '%s %s\n' "$me" "$*" >> "${DOTUP_TEST_LOG:?DOTUP_TEST_LOG unset}"
|
||||
|
||||
# The mode this run injects for THIS tool: "always", a tool-specific word, or
|
||||
# empty for "behave".
|
||||
mode=
|
||||
_oldifs=$IFS
|
||||
IFS=,
|
||||
for _e in ${FAKE_FAIL:-}; do
|
||||
case $_e in
|
||||
"$me") mode=always; break ;;
|
||||
"$me":*) mode=${_e#*:}; break ;;
|
||||
esac
|
||||
done
|
||||
IFS=$_oldifs
|
||||
[ "$mode" = always ] && exit 1
|
||||
|
||||
# Count the package-ish arguments of a batch call: everything after the
|
||||
# subcommand that is not a flag and not the flag's value.
|
||||
count_pkgs() {
|
||||
n=0
|
||||
for a in "$@"; do
|
||||
case $a in
|
||||
-*|install|update|show|get|tool|-g|-y|--*) continue ;;
|
||||
*=*) continue ;;
|
||||
esac
|
||||
n=$((n + 1))
|
||||
done
|
||||
printf '%s\n' "$n"
|
||||
}
|
||||
|
||||
case $me in
|
||||
apt-get)
|
||||
case ${1:-} in update) exit 0 ;; esac
|
||||
[ "$mode" = batch ] && [ "$(count_pkgs "$@")" -gt 1 ] && exit 1
|
||||
exit 0 ;;
|
||||
npm)
|
||||
[ "$mode" = batch ] && [ "$(count_pkgs "$@")" -gt 1 ] && exit 1
|
||||
exit 0 ;;
|
||||
apt-cache)
|
||||
# Stand in for a real apt cache. Two names are special, and both are real
|
||||
# behaviours of stock Ubuntu that the installer has to tell apart:
|
||||
#
|
||||
# gh genuinely absent -- `apt-cache policy` prints nothing. The
|
||||
# manifest's real example of a name that falls through to brew.
|
||||
# docker-ce present in the cache but with `Candidate: (none)`, because
|
||||
# something else Conflicts/Replaces it. `apt-cache show` exits
|
||||
# 0 for this, which is why the old probe kept it in the batch
|
||||
# and let apt refuse all thirty packages at once.
|
||||
case ${1:-} in
|
||||
policy)
|
||||
case ${2:-} in
|
||||
gh) exit 0 ;;
|
||||
docker-ce) printf '%s:\n Installed: (none)\n Candidate: (none)\n' "$2"; exit 0 ;;
|
||||
esac
|
||||
printf '%s:\n Installed: (none)\n Candidate: 1.0-fake\n' "$2"; exit 0 ;;
|
||||
esac
|
||||
case ${2:-} in gh) exit 100 ;; esac
|
||||
printf 'Package: %s\n' "${2:-}"; exit 0 ;;
|
||||
unzip)
|
||||
# `unzip -oq <zip> -d <dir>` -- produce the one member ensure_bws looks for.
|
||||
d=; prev=
|
||||
for a in "$@"; do [ "$prev" = -d ] && { d=$a; break; }; prev=$a; done
|
||||
[ -n "$d" ] || exit 1
|
||||
mkdir -p "$d" && printf '#!/bin/sh\necho "bws 2.1.0"\n' > "$d/bws" \
|
||||
&& chmod 755 "$d/bws" && exit 0
|
||||
exit 1 ;;
|
||||
dpkg)
|
||||
# install_deb fills %a in a gh: asset name from this.
|
||||
case ${1:-} in --print-architecture) echo amd64 ;; esac
|
||||
exit 0 ;;
|
||||
chezmoi)
|
||||
# `chezmoi init --apply --source DIR -c CFG URL`. Produce the shape the
|
||||
# caller checks for -- a source tree with a .git in it -- and NOTHING else,
|
||||
# at the caller's umask, so a missing `chmod -R go-rwx` is visible as a mode.
|
||||
src=; prev=
|
||||
for a in "$@"; do [ "$prev" = --source ] && { src=$a; break; }; prev=$a; done
|
||||
[ -n "$src" ] || exit 0
|
||||
mkdir -p "$src/.git" && : > "$src/.git/config" && : > "$src/README" || exit 1
|
||||
exit 0 ;;
|
||||
curl) . "$(dirname "$0")/_curl" ;;
|
||||
fzf)
|
||||
# The picker's own fzf, and the only fake here whose ARGV is the thing
|
||||
# under test. `--bind` strings are built across a dozen continued lines, so
|
||||
# one stray comment between them silently truncates the command and the
|
||||
# picker draws with two binds instead of seven. Dump argv one argument per
|
||||
# line -- a bind that lost its continuation cannot masquerade as one that
|
||||
# survived -- and behave like an accept: read the piped rows, print nothing.
|
||||
case ${1:-} in --version) echo "${FAKE_FZF_VERSION:-9.9.9} (fake)"; exit 0 ;; esac
|
||||
[ -n "${FAKE_FZF_ARGV:-}" ] && printf '%s\n' "$@" > "$FAKE_FZF_ARGV"
|
||||
cat > /dev/null
|
||||
exit "${FAKE_FZF_RC:-0}" ;;
|
||||
esac
|
||||
exit 0
|
||||
Reference in New Issue
Block a user