Fix: Close verification gate review findings
This commit is contained in:
@@ -29,7 +29,8 @@ note() { findings+=("$1"); }
|
||||
scanner="$repo_dir/tests/server/scan-tracked-secrets.py"
|
||||
fixtures_dir="$repo_dir/tests/server/fixtures/secrets"
|
||||
|
||||
if ! python3 "$scanner" "$fixtures_dir/clean" compose.yml .env.example README.md; then
|
||||
if ! python3 "$scanner" "$fixtures_dir/clean" \
|
||||
compose.yml .env.example README.md signature-near-misses.txt; then
|
||||
note 'the clean secret-scanning fixture was rejected'
|
||||
fi
|
||||
|
||||
@@ -53,6 +54,10 @@ expect_leak .env.example '.env.example:1: API_TOKEN'
|
||||
expect_leak plain-list.yml 'plain-list.yml:4: API_TOKEN'
|
||||
expect_leak quoted-mapping.yml 'quoted-mapping.yml:4: API_TOKEN'
|
||||
expect_leak quoted-list.yml 'quoted-list.yml:4: API_TOKEN'
|
||||
expect_leak pem-private-key.txt 'pem-private-key.txt:1: private key'
|
||||
expect_leak anthropic-token.txt 'anthropic-token.txt:1: provider token'
|
||||
expect_leak github-token.txt 'github-token.txt:1: provider token'
|
||||
expect_leak slack-token.txt 'slack-token.txt:1: provider token'
|
||||
|
||||
mapfile -t tracked_server_files < <(git -C "$repo_dir" ls-files 'server/**' 'server/*')
|
||||
if ! output="$(python3 "$scanner" "$repo_dir" "${tracked_server_files[@]}" 2>&1)"; then
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-----BEGIN SYNTHETIC PUBLIC KEY-----
|
||||
sk-ant-
|
||||
ghp_0123456789ABCDEFGHI
|
||||
xoxb-
|
||||
@@ -0,0 +1 @@
|
||||
sk-ant-SYNTHETIC_FIXTURE_TOKEN
|
||||
@@ -0,0 +1 @@
|
||||
ghp_0123456789ABCDEFGHIJ
|
||||
@@ -0,0 +1 @@
|
||||
-----BEGIN SYNTHETIC PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
xoxb-SYNTHETIC_FIXTURE_TOKEN
|
||||
@@ -9,9 +9,23 @@ repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
fixture="$(mktemp -d)"
|
||||
output=""
|
||||
status=0
|
||||
background_cli_pid=""
|
||||
background_contract_pgid=""
|
||||
|
||||
cleanup() { rm -rf -- "$fixture"; }
|
||||
trap cleanup EXIT INT TERM
|
||||
cleanup() {
|
||||
trap - EXIT INT TERM
|
||||
if [[ "$background_cli_pid" =~ ^[1-9][0-9]*$ ]]; then
|
||||
kill -TERM "$background_cli_pid" 2>/dev/null || true
|
||||
wait "$background_cli_pid" 2>/dev/null || true
|
||||
fi
|
||||
if [[ "$background_contract_pgid" =~ ^[1-9][0-9]*$ ]]; then
|
||||
kill -KILL -- "-$background_contract_pgid" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf -- "$fixture"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'cleanup; exit 130' INT
|
||||
trap 'cleanup; exit 143' TERM
|
||||
|
||||
fail() { printf 'test runner: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
@@ -25,6 +39,12 @@ assert_not_contains() {
|
||||
[[ "$haystack" != *"$needle"* ]] || fail "expected output not to contain: $needle\n$haystack"
|
||||
}
|
||||
|
||||
assert_before() {
|
||||
local first="$1" second="$2" haystack="$3"
|
||||
[[ "$haystack" == *"$first"*"$second"* ]] \
|
||||
|| fail "expected '$first' before '$second':\n$haystack"
|
||||
}
|
||||
|
||||
assert_execution() {
|
||||
local expected="$1" actual
|
||||
actual="$(sort "$fixture/executions" 2>/dev/null || true)"
|
||||
@@ -33,6 +53,33 @@ assert_execution() {
|
||||
|
||||
reset_executions() { : > "$fixture/executions"; }
|
||||
|
||||
wait_for_file() {
|
||||
local path="$1" attempt
|
||||
for (( attempt = 0; attempt < 100; attempt++ )); do
|
||||
[[ -s "$path" ]] && return 0
|
||||
sleep 0.05
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_process_exit() {
|
||||
local pid="$1" attempt
|
||||
for (( attempt = 0; attempt < 100; attempt++ )); do
|
||||
kill -0 "$pid" 2>/dev/null || return 0
|
||||
sleep 0.05
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_path_removal() {
|
||||
local path="$1" attempt
|
||||
for (( attempt = 0; attempt < 100; attempt++ )); do
|
||||
[[ ! -e "$path" ]] && return 0
|
||||
sleep 0.05
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
run_panama() {
|
||||
output="$(cd "$fixture" && TMPDIR="$fixture" PANAMA_TEST_FIXTURE="$fixture" "$fixture/bin/panama" "$@" </dev/null 2>&1)"
|
||||
status=$?
|
||||
@@ -45,18 +92,39 @@ run_panama_with_timeout() {
|
||||
|
||||
run_panama_tty_default_no() {
|
||||
local command tty_stdout="$fixture/tty.stdout"
|
||||
local pty_state="$fixture/pty-state"
|
||||
mkdir -p "$pty_state"/{config,state,cache,data,runtime}
|
||||
chmod 700 "$pty_state/runtime"
|
||||
printf -v command 'cd %q && TMPDIR=%q PANAMA_TEST_FIXTURE=%q %q test composite > %q' \
|
||||
"$fixture" "$fixture" "$fixture" "$fixture/bin/panama" "$tty_stdout"
|
||||
output="$(python3 - "$command" <<'PY'
|
||||
output="$(
|
||||
HOME="$fixture/pty-home" \
|
||||
BASH_ENV="$fixture/pty-bash-env" \
|
||||
PANAMA_PTY_STARTUP_SENTINEL="$fixture/pty-startup-sourced" \
|
||||
python3 - "$command" "$fixture/pty-home" \
|
||||
"$pty_state/config" "$pty_state/state" "$pty_state/cache" \
|
||||
"$pty_state/data" "$pty_state/runtime" "$fixture" <<'PY'
|
||||
import errno
|
||||
import os
|
||||
import pty
|
||||
import sys
|
||||
|
||||
command = sys.argv[1]
|
||||
environment = os.environ.copy()
|
||||
environment.pop('BASH_ENV', None)
|
||||
environment.pop('ENV', None)
|
||||
environment.update({
|
||||
'HOME': sys.argv[2],
|
||||
'XDG_CONFIG_HOME': sys.argv[3],
|
||||
'XDG_STATE_HOME': sys.argv[4],
|
||||
'XDG_CACHE_HOME': sys.argv[5],
|
||||
'XDG_DATA_HOME': sys.argv[6],
|
||||
'XDG_RUNTIME_DIR': sys.argv[7],
|
||||
'TMPDIR': sys.argv[8],
|
||||
})
|
||||
pid, terminal = pty.fork()
|
||||
if pid == 0:
|
||||
os.execv('/bin/bash', ['bash', '-lc', command])
|
||||
os.execve('/bin/bash', ['bash', '--noprofile', '--norc', '-c', command], environment)
|
||||
|
||||
chunks = []
|
||||
replied = False
|
||||
@@ -78,7 +146,7 @@ _, child_status = os.waitpid(pid, 0)
|
||||
sys.stdout.buffer.write(b''.join(chunks))
|
||||
sys.exit(os.waitstatus_to_exitcode(child_status))
|
||||
PY
|
||||
)"
|
||||
)"
|
||||
status=$?
|
||||
}
|
||||
|
||||
@@ -88,6 +156,48 @@ assert_occurrences() {
|
||||
[[ "$actual" == "$expected" ]] || fail "expected $expected occurrence(s) of '$needle', got $actual\n$haystack"
|
||||
}
|
||||
|
||||
replace_manifest_line() {
|
||||
local original="$1" replacement="$2" line
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" == "$original" ]]; then
|
||||
[[ "$replacement" == __REMOVE__ ]] || printf '%s\n' "$replacement"
|
||||
else
|
||||
printf '%s\n' "$line"
|
||||
fi
|
||||
done <<<"$valid_manifest"
|
||||
}
|
||||
|
||||
swap_manifest_contract_paths() {
|
||||
local line
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
case "$line" in
|
||||
'live-compositor,live-desktop tests/composite-contract')
|
||||
printf '%s\n' 'live-compositor,live-desktop tests/desktop-contract'
|
||||
;;
|
||||
'live-desktop tests/desktop-contract')
|
||||
printf '%s\n' 'live-desktop tests/composite-contract'
|
||||
;;
|
||||
*) printf '%s\n' "$line" ;;
|
||||
esac
|
||||
done <<<"$valid_manifest"
|
||||
}
|
||||
|
||||
expect_manifest_rejection() {
|
||||
local label="$1" expected="$2" contents="$3"
|
||||
|
||||
printf '%s\n' "$contents" >"$fixture/tests/contracts.manifest"
|
||||
reset_executions
|
||||
run_panama test pass
|
||||
[[ $status -ne 0 ]] || fail "$label manifest unexpectedly allowed test execution"
|
||||
assert_contains "$expected" "$output"
|
||||
assert_execution ''
|
||||
|
||||
run_panama contracts config/subject
|
||||
[[ $status -ne 0 ]] || fail "$label manifest unexpectedly allowed contracts lookup"
|
||||
assert_contains "$expected" "$output"
|
||||
assert_execution ''
|
||||
}
|
||||
|
||||
mkdir -p "$fixture/bin" "$fixture/tests" "$fixture/config"
|
||||
cp "$repo_dir/bin/panama" "$fixture/bin/panama"
|
||||
chmod +x "$fixture/bin/panama"
|
||||
@@ -111,6 +221,8 @@ privileged tests/privileged-contract
|
||||
hermetic tests/stderr-contract
|
||||
EOF
|
||||
|
||||
valid_manifest="$(<"$fixture/tests/contracts.manifest")"
|
||||
|
||||
cat > "$fixture/tests/pass-contract" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'pass\n' >> "$PANAMA_TEST_FIXTURE/executions"
|
||||
@@ -135,7 +247,13 @@ EOF
|
||||
cat > "$fixture/tests/hang-contract" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'hang\n' >> "$PANAMA_TEST_FIXTURE/executions"
|
||||
trap 'printf terminated >"$PANAMA_TEST_FIXTURE/terminated"; exit 124' TERM
|
||||
printf '%s\n' "$BASHPID" > "$PANAMA_TEST_FIXTURE/hang.pid"
|
||||
finish() {
|
||||
printf 'terminated\n' >"$PANAMA_TEST_FIXTURE/terminated"
|
||||
exit "$1"
|
||||
}
|
||||
trap 'finish 130' INT
|
||||
trap 'finish 143' TERM
|
||||
while :; do sleep 1; done
|
||||
EOF
|
||||
|
||||
@@ -147,6 +265,7 @@ EOF
|
||||
cat > "$fixture/tests/desktop-contract" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'desktop\n' >> "$PANAMA_TEST_FIXTURE/executions"
|
||||
printf 'desktop fixture complete\n' >&2
|
||||
# config/subject
|
||||
EOF
|
||||
|
||||
@@ -172,6 +291,15 @@ chmod +x "$fixture/tests"/{composite,desktop,fail,hang,host,network,pass,privile
|
||||
# A PTY-backed default-no confirmation remains visible when stdout is redirected
|
||||
# but stdin and stderr are terminals. The fixture proves that one prompt gates
|
||||
# the selected composite capability set without running its contract.
|
||||
mkdir -p "$fixture/pty-home"
|
||||
for profile in .bash_profile .bashrc .profile; do
|
||||
cat >"$fixture/pty-home/$profile" <<'EOF'
|
||||
printf 'profile\n' >>"${PANAMA_PTY_STARTUP_SENTINEL:?}"
|
||||
EOF
|
||||
done
|
||||
cat >"$fixture/pty-bash-env" <<'EOF'
|
||||
printf 'BASH_ENV\n' >>"${PANAMA_PTY_STARTUP_SENTINEL:?}"
|
||||
EOF
|
||||
run_panama_tty_default_no
|
||||
[[ $status -ne 0 ]] || fail 'TTY default-no prompt unexpectedly ran the fixture'
|
||||
assert_execution ''
|
||||
@@ -179,6 +307,8 @@ assert_contains 'Run 1 contract(s) requiring: live-compositor live-desktop?' "$o
|
||||
assert_occurrences 'Run 1 contract(s) requiring:' "$output" 1
|
||||
assert_contains 'No contracts were run.' "$(<"$fixture/tty.stdout")"
|
||||
assert_not_contains 'Run 1 contract(s) requiring:' "$(<"$fixture/tty.stdout")"
|
||||
[[ ! -e "$fixture/pty-startup-sourced" ]] \
|
||||
|| fail 'PTY fixture sourced a shell profile or BASH_ENV'
|
||||
|
||||
# --safe must select hermetic entries from the manifest, not merely omit a
|
||||
# legacy desktop list. The failing and timed-out fixtures make the command
|
||||
@@ -204,6 +334,8 @@ assert_contains 'pass --allow live-desktop' "$output"
|
||||
run_panama test --allow live-desktop desktop
|
||||
[[ $status -eq 0 ]] || fail "explicit desktop grant failed: $output"
|
||||
assert_execution 'desktop'
|
||||
assert_contains 'Running desktop-contract [live-desktop]' "$output"
|
||||
assert_before 'Running desktop-contract [live-desktop]' 'desktop fixture complete' "$output"
|
||||
|
||||
reset_executions
|
||||
run_panama test --allow live-compositor --allow live-desktop composite
|
||||
@@ -229,6 +361,45 @@ assert_execution 'hang'
|
||||
[[ -f "$fixture/terminated" ]] || fail 'timed-out contract was not terminated with TERM'
|
||||
assert_contains 'timed out' "$output"
|
||||
|
||||
# INT/TERM ownership belongs to the exact public CLI PID, not a runner
|
||||
# subshell. The CLI must wait for the timeout process group and remove its
|
||||
# capture directory before returning the signal-derived status.
|
||||
reset_executions
|
||||
rm -f -- "$fixture/hang.pid" "$fixture/terminated"
|
||||
(
|
||||
cd "$fixture" || exit 1
|
||||
exec env TMPDIR="$fixture" PANAMA_TEST_FIXTURE="$fixture" \
|
||||
"$fixture/bin/panama" test hang
|
||||
) >"$fixture/exact-term.out" 2>&1 &
|
||||
background_cli_pid=$!
|
||||
wait_for_file "$fixture/hang.pid" \
|
||||
|| fail 'exact-PID TERM fixture never started the hang contract'
|
||||
hang_pid="$(<"$fixture/hang.pid")"
|
||||
background_contract_pgid="$(ps -o pgid= -p "$hang_pid" | tr -d '[:space:]')"
|
||||
[[ "$background_contract_pgid" =~ ^[1-9][0-9]*$ ]] \
|
||||
|| fail "could not resolve hang process group for PID $hang_pid"
|
||||
mapfile -t active_capture_dirs < <(
|
||||
find "$fixture" -mindepth 1 -maxdepth 1 -type d -name 'tmp.*' -print
|
||||
)
|
||||
(( ${#active_capture_dirs[@]} == 1 )) \
|
||||
|| fail "expected one active capture directory, got ${#active_capture_dirs[@]}"
|
||||
active_capture_dir="${active_capture_dirs[0]}"
|
||||
|
||||
kill -TERM "$background_cli_pid" \
|
||||
|| fail 'could not send TERM to the exact public CLI PID'
|
||||
term_status=0
|
||||
wait "$background_cli_pid" || term_status=$?
|
||||
background_cli_pid=""
|
||||
[[ "$term_status" -eq 143 ]] \
|
||||
|| fail "exact-PID TERM returned $term_status instead of 143: $(<"$fixture/exact-term.out")"
|
||||
wait_for_process_exit "$hang_pid" \
|
||||
|| fail "hang contract PID $hang_pid survived exact-PID TERM"
|
||||
background_contract_pgid=""
|
||||
wait_for_path_removal "$active_capture_dir" \
|
||||
|| fail "capture directory survived exact-PID TERM: $active_capture_dir"
|
||||
[[ -f "$fixture/terminated" ]] \
|
||||
|| fail 'exact-PID TERM did not reach the hang contract cleanup trap'
|
||||
|
||||
reset_executions
|
||||
run_panama test fail
|
||||
[[ $status -ne 0 ]] || fail 'failed contract unexpectedly passed'
|
||||
@@ -244,6 +415,19 @@ reset_executions
|
||||
run_panama test pass
|
||||
[[ $status -eq 0 ]] || fail "pass contract failed: $output"
|
||||
assert_not_contains 'pass stdout' "$output"
|
||||
assert_not_contains 'pass-contract [hermetic]' "$output"
|
||||
|
||||
reset_executions
|
||||
printf 'not a directory\n' >"$fixture/invalid-tmpdir"
|
||||
output="$(
|
||||
cd "$fixture" && \
|
||||
TMPDIR="$fixture/invalid-tmpdir" PANAMA_TEST_FIXTURE="$fixture" \
|
||||
"$fixture/bin/panama" test pass </dev/null 2>&1
|
||||
)"
|
||||
status=$?
|
||||
[[ $status -ne 0 ]] || fail 'invalid TMPDIR unexpectedly allowed contract execution'
|
||||
assert_contains 'Could not create contract capture directory.' "$output"
|
||||
assert_execution ''
|
||||
|
||||
reset_executions
|
||||
run_panama test --safe desktop
|
||||
@@ -258,10 +442,52 @@ assert_contains 'tests/desktop-contract [live-desktop]' "$output"
|
||||
assert_contains 'tests/composite-contract [live-compositor,live-desktop]' "$output"
|
||||
assert_contains 'tests/pass-contract [hermetic]' "$output"
|
||||
|
||||
# Both public manifest consumers fail closed on the complete format and
|
||||
# discovery set. Validation happens before selection, lookup, or contract
|
||||
# execution, so even a malformed entry unrelated to the requested pattern is
|
||||
# fatal and leaves the execution log empty.
|
||||
expect_manifest_rejection unknown-capability \
|
||||
'unknown capability hermetik on tests/pass-contract' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetik tests/pass-contract')"
|
||||
expect_manifest_rejection mixed-hermetic \
|
||||
'hermetic must appear alone on tests/pass-contract' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic,network tests/pass-contract')"
|
||||
expect_manifest_rejection duplicate-path \
|
||||
'duplicate path tests/pass-contract' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' $'hermetic tests/pass-contract\nhermetic tests/pass-contract')"
|
||||
expect_manifest_rejection stale-path \
|
||||
'stale manifest path tests/stale-contract' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic tests/stale-contract')"
|
||||
expect_manifest_rejection missing-contract \
|
||||
'missing contract tests/pass-contract' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' __REMOVE__)"
|
||||
expect_manifest_rejection extra-field \
|
||||
'manifest line is not exactly two fields' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic tests/pass-contract unexpected')"
|
||||
expect_manifest_rejection empty-capability \
|
||||
'empty capability on tests/pass-contract' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic, tests/pass-contract')"
|
||||
expect_manifest_rejection duplicate-capability \
|
||||
'duplicate capability hermetic on tests/pass-contract' \
|
||||
"$(replace_manifest_line 'hermetic tests/pass-contract' 'hermetic,hermetic tests/pass-contract')"
|
||||
expect_manifest_rejection unsorted-paths \
|
||||
'paths are not lexicographically sorted' \
|
||||
"$(swap_manifest_contract_paths)"
|
||||
expect_manifest_rejection uncommented-non-hermetic \
|
||||
'tests/desktop-contract is non-hermetic but lacks a directly preceding comment' \
|
||||
"$(replace_manifest_line '# Maps the live desktop.' __REMOVE__)"
|
||||
expect_manifest_rejection blank-comment \
|
||||
'tests/desktop-contract is non-hermetic but lacks a non-empty directly preceding comment' \
|
||||
"$(replace_manifest_line '# Maps the live desktop.' '#')"
|
||||
|
||||
printf '%s\n' "$valid_manifest" >"$fixture/tests/contracts.manifest"
|
||||
|
||||
mv "$fixture/tests/contracts.manifest" "$fixture/tests/contracts.manifest.missing"
|
||||
reset_executions
|
||||
run_panama test pass
|
||||
[[ $status -ne 0 ]] || fail 'missing manifest unexpectedly allowed test execution'
|
||||
assert_contains 'contracts.manifest' "$output"
|
||||
assert_execution ''
|
||||
mv "$fixture/tests/contracts.manifest.missing" "$fixture/tests/contracts.manifest"
|
||||
|
||||
capture_dirs="$(find "$fixture" -mindepth 1 -maxdepth 1 -type d -name 'tmp.*' -print)"
|
||||
|
||||
@@ -234,16 +234,116 @@ grep -qx 'install-packages' <<<"$ran_full" \
|
||||
# The fixture also covers a clean fast-forward, installer status propagation,
|
||||
# and the boundary between update and sync before forcing the conflict below.
|
||||
|
||||
# Make every ambient configuration source hostile before constructing the Git
|
||||
# fixtures. A hermetic fixture overrides these values with its own empty state;
|
||||
# consuming any of them either leaves a sentinel or prevents a commit.
|
||||
hostile="$tmp/hostile-environment"
|
||||
mkdir -p "$hostile/home" "$hostile/xdg-config" "$hostile/xdg-state" \
|
||||
"$hostile/xdg-cache" "$hostile/xdg-data" "$hostile/hooks" \
|
||||
"$hostile/template/hooks"
|
||||
for profile in .bash_profile .bashrc .profile; do
|
||||
cat >"$hostile/home/$profile" <<'EOF'
|
||||
printf 'profile\n' >>"${PANAMA_HOSTILE_PROFILE_SENTINEL:?}"
|
||||
EOF
|
||||
done
|
||||
cat >"$hostile/bash-env" <<'EOF'
|
||||
printf 'BASH_ENV\n' >>"${PANAMA_HOSTILE_BASH_ENV_SENTINEL:?}"
|
||||
EOF
|
||||
cat >"$hostile/hooks/pre-commit" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'global hook\n' >>"${PANAMA_HOSTILE_GIT_SENTINEL:?}"
|
||||
exit 97
|
||||
EOF
|
||||
cat >"$hostile/template/hooks/pre-commit" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
# PANAMA_HOSTILE_TEMPLATE_HOOK
|
||||
printf 'template hook\n' >>"${PANAMA_HOSTILE_TEMPLATE_SENTINEL:?}"
|
||||
exit 98
|
||||
EOF
|
||||
chmod +x "$hostile/hooks/pre-commit" "$hostile/template/hooks/pre-commit"
|
||||
cat >"$hostile/global.gitconfig" <<EOF
|
||||
[core]
|
||||
hooksPath = $hostile/hooks
|
||||
[commit]
|
||||
gpgSign = true
|
||||
[init]
|
||||
templateDir = $hostile/template
|
||||
EOF
|
||||
cp "$hostile/global.gitconfig" "$hostile/system.gitconfig"
|
||||
|
||||
export HOME="$hostile/home"
|
||||
export XDG_CONFIG_HOME="$hostile/xdg-config"
|
||||
export XDG_STATE_HOME="$hostile/xdg-state"
|
||||
export XDG_CACHE_HOME="$hostile/xdg-cache"
|
||||
export XDG_DATA_HOME="$hostile/xdg-data"
|
||||
export BASH_ENV="$hostile/bash-env"
|
||||
export PANAMA_HOSTILE_PROFILE_SENTINEL="$hostile/profile-sourced"
|
||||
export PANAMA_HOSTILE_BASH_ENV_SENTINEL="$hostile/bash-env-sourced"
|
||||
export PANAMA_HOSTILE_GIT_SENTINEL="$hostile/global-config-sourced"
|
||||
export PANAMA_HOSTILE_TEMPLATE_SENTINEL="$hostile/template-hook-sourced"
|
||||
export GIT_CONFIG_NOSYSTEM=0
|
||||
export GIT_CONFIG_SYSTEM="$hostile/system.gitconfig"
|
||||
export GIT_CONFIG_GLOBAL="$hostile/global.gitconfig"
|
||||
export GIT_CONFIG_COUNT=1
|
||||
export GIT_CONFIG_KEY_0=core.hooksPath
|
||||
export GIT_CONFIG_VALUE_0="$hostile/hooks"
|
||||
export GIT_TEMPLATE_DIR="$hostile/template"
|
||||
|
||||
prepare_cli_fixture_environment() {
|
||||
local root="$1"
|
||||
mkdir -p "$root/home" "$root/xdg-config" "$root/xdg-state" \
|
||||
"$root/xdg-cache" "$root/xdg-data" "$root/xdg-runtime" \
|
||||
"$root/empty-templates" "$root/empty-hooks"
|
||||
chmod 700 "$root/xdg-runtime"
|
||||
}
|
||||
|
||||
run_cli_fixture_environment() {
|
||||
local root="$1"
|
||||
shift
|
||||
env -u BASH_ENV -u ENV -u GIT_CONFIG_PARAMETERS \
|
||||
-u GIT_CONFIG_KEY_0 -u GIT_CONFIG_VALUE_0 \
|
||||
HOME="$root/home" \
|
||||
XDG_CONFIG_HOME="$root/xdg-config" \
|
||||
XDG_STATE_HOME="$root/xdg-state" \
|
||||
XDG_CACHE_HOME="$root/xdg-cache" \
|
||||
XDG_DATA_HOME="$root/xdg-data" \
|
||||
XDG_RUNTIME_DIR="$root/xdg-runtime" \
|
||||
GIT_CONFIG_NOSYSTEM=1 \
|
||||
GIT_CONFIG_SYSTEM=/dev/null \
|
||||
GIT_CONFIG_GLOBAL=/dev/null \
|
||||
GIT_CONFIG_COUNT=0 \
|
||||
GIT_TEMPLATE_DIR="$root/empty-templates" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
fixture_git() {
|
||||
local root="$1"
|
||||
shift
|
||||
run_cli_fixture_environment "$root" \
|
||||
git -c commit.gpgSign=false -c tag.gpgSign=false \
|
||||
-c core.hooksPath="$root/empty-hooks" "$@"
|
||||
}
|
||||
|
||||
configure_fixture_repo() {
|
||||
local root="$1" repository="$2"
|
||||
fixture_git "$root" -C "$repository" config user.email contract@panama || return 1
|
||||
fixture_git "$root" -C "$repository" config user.name contract || return 1
|
||||
fixture_git "$root" -C "$repository" config commit.gpgSign false || return 1
|
||||
fixture_git "$root" -C "$repository" config tag.gpgSign false || return 1
|
||||
fixture_git "$root" -C "$repository" config core.hooksPath "$root/empty-hooks" || return 1
|
||||
}
|
||||
|
||||
# Each fixture has the same three repositories as a real update: a bare remote,
|
||||
# a clone that publishes upstream changes, and the machine clone being updated.
|
||||
build_cli_fixture() (
|
||||
local root="$1"
|
||||
rm -rf "$root" || return 1
|
||||
mkdir -p "$root" || return 1
|
||||
git init -q --bare "$root/origin.git" || return 1
|
||||
git clone -q "$root/origin.git" "$root/upstream" 2>/dev/null || return 1
|
||||
git -C "$root/upstream" config user.email contract@panama || return 1
|
||||
git -C "$root/upstream" config user.name contract || return 1
|
||||
prepare_cli_fixture_environment "$root" || return 1
|
||||
fixture_git "$root" init -q --bare "$root/origin.git" || return 1
|
||||
fixture_git "$root" -C "$root/origin.git" config core.hooksPath "$root/empty-hooks" || return 1
|
||||
fixture_git "$root" clone -q "$root/origin.git" "$root/upstream" 2>/dev/null || return 1
|
||||
configure_fixture_repo "$root" "$root/upstream" || return 1
|
||||
|
||||
mkdir -p "$root/upstream/bin" || return 1
|
||||
cp "$panama" "$root/upstream/bin/panama" || return 1
|
||||
@@ -254,21 +354,20 @@ exit "${PANAMA_UPDATE_INSTALL_RC:-0}"
|
||||
EOF
|
||||
chmod +x "$root/upstream/bin/panama" "$root/upstream/install" || return 1
|
||||
printf 'one\n' >"$root/upstream/f" || return 1
|
||||
git -C "$root/upstream" add -A || return 1
|
||||
git -C "$root/upstream" commit -qm initial || return 1
|
||||
git -C "$root/upstream" push -qu origin HEAD || return 1
|
||||
fixture_git "$root" -C "$root/upstream" add -A || return 1
|
||||
fixture_git "$root" -C "$root/upstream" commit -qm initial || return 1
|
||||
fixture_git "$root" -C "$root/upstream" push -qu origin HEAD || return 1
|
||||
|
||||
git clone -q "$root/origin.git" "$root/machine" || return 1
|
||||
git -C "$root/machine" config user.email contract@panama || return 1
|
||||
git -C "$root/machine" config user.name contract || return 1
|
||||
fixture_git "$root" clone -q "$root/origin.git" "$root/machine" || return 1
|
||||
configure_fixture_repo "$root" "$root/machine" || return 1
|
||||
)
|
||||
|
||||
advance_upstream() (
|
||||
local root="$1" file="$2" contents="$3"
|
||||
printf '%s\n' "$contents" >"$root/upstream/$file" || return 1
|
||||
git -C "$root/upstream" add "$file" || return 1
|
||||
git -C "$root/upstream" commit -qm "update $file" || return 1
|
||||
git -C "$root/upstream" push -q || return 1
|
||||
fixture_git "$root" -C "$root/upstream" add "$file" || return 1
|
||||
fixture_git "$root" -C "$root/upstream" commit -qm "update $file" || return 1
|
||||
fixture_git "$root" -C "$root/upstream" push -q || return 1
|
||||
)
|
||||
|
||||
# This write fails before the later Git commands. The helper must return that
|
||||
@@ -285,26 +384,28 @@ fi
|
||||
|
||||
clean="$tmp/clean-update"
|
||||
if build_cli_fixture "$clean" && advance_upstream "$clean" release new; then
|
||||
machine_before="$(git -C "$clean/machine" rev-parse HEAD)"
|
||||
upstream_after="$(git -C "$clean/upstream" rev-parse HEAD)"
|
||||
machine_before="$(fixture_git "$clean" -C "$clean/machine" rev-parse HEAD)"
|
||||
upstream_after="$(fixture_git "$clean" -C "$clean/upstream" rev-parse HEAD)"
|
||||
[[ "$machine_before" != "$upstream_after" ]] \
|
||||
|| note 'the clean update fixture started current, so it cannot prove a fast-forward'
|
||||
|
||||
: >"$clean/install.log"
|
||||
update_status=0
|
||||
PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" \
|
||||
run_cli_fixture_environment "$clean" \
|
||||
PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" \
|
||||
"$clean/machine/bin/panama" update >"$clean/update.out" 2>&1 \
|
||||
|| update_status=$?
|
||||
[[ "$update_status" -eq 0 ]] \
|
||||
|| note "panama update failed on a clean clone with status $update_status"
|
||||
[[ "$(git -C "$clean/machine" rev-parse HEAD)" == "$upstream_after" ]] \
|
||||
[[ "$(fixture_git "$clean" -C "$clean/machine" rev-parse HEAD)" == "$upstream_after" ]] \
|
||||
|| note 'panama update did not fast-forward the clean machine clone'
|
||||
grep -qx -- '--upgrade' "$clean/install.log" \
|
||||
|| note 'panama update did not invoke the installer with --upgrade'
|
||||
|
||||
: >"$clean/install.log"
|
||||
update_status=0
|
||||
PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" PANAMA_UPDATE_INSTALL_RC=23 \
|
||||
run_cli_fixture_environment "$clean" \
|
||||
PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" PANAMA_UPDATE_INSTALL_RC=23 \
|
||||
"$clean/machine/bin/panama" update >"$clean/failing-update.out" 2>&1 \
|
||||
|| update_status=$?
|
||||
[[ "$update_status" -eq 23 ]] \
|
||||
@@ -316,7 +417,8 @@ if build_cli_fixture "$clean" && advance_upstream "$clean" release new; then
|
||||
printf 'local sync\n' >"$clean/machine/synced"
|
||||
sync_status=0
|
||||
printf 'y\ncontract sync\n' \
|
||||
| PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" \
|
||||
| run_cli_fixture_environment "$clean" \
|
||||
PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" \
|
||||
"$clean/machine/bin/panama" sync >"$clean/sync.out" 2>&1 \
|
||||
|| sync_status=$?
|
||||
[[ "$sync_status" -eq 0 ]] \
|
||||
@@ -335,19 +437,22 @@ if build_cli_fixture "$conflict"; then
|
||||
if advance_upstream "$conflict" f upstream; then
|
||||
: >"$conflict/install.log"
|
||||
conflict_status=0
|
||||
PANAMA_UPDATE_FIXTURE_LOG="$conflict/install.log" \
|
||||
run_cli_fixture_environment "$conflict" \
|
||||
PANAMA_UPDATE_FIXTURE_LOG="$conflict/install.log" \
|
||||
"$conflict/machine/bin/panama" update >"$conflict/update.out" 2>&1 \
|
||||
|| conflict_status=$?
|
||||
[[ "$conflict_status" -eq 0 ]] \
|
||||
|| note "panama update failed while recovering a stash conflict with status $conflict_status"
|
||||
[[ "$(<"$conflict/machine/f")" == upstream ]] \
|
||||
|| note 'panama update did not reset the conflicted file to the upstream version'
|
||||
if git -C "$conflict/machine" grep -qE '^(<<<<<<<|=======|>>>>>>>)' -- .; then
|
||||
if fixture_git "$conflict" -C "$conflict/machine" \
|
||||
grep -qE '^(<<<<<<<|=======|>>>>>>>)' -- .; then
|
||||
note 'panama update left conflict markers in the machine checkout'
|
||||
fi
|
||||
[[ -n "$(git -C "$conflict/machine" stash list)" ]] \
|
||||
[[ -n "$(fixture_git "$conflict" -C "$conflict/machine" stash list)" ]] \
|
||||
|| note 'panama update dropped the stash after its conflicted pop'
|
||||
recovered="$(git -C "$conflict/machine" show 'stash@{0}:f' 2>/dev/null)"
|
||||
recovered="$(fixture_git "$conflict" -C "$conflict/machine" \
|
||||
show 'stash@{0}:f' 2>/dev/null)"
|
||||
[[ "$recovered" == local ]] \
|
||||
|| note 'the stash left by panama update does not contain the local version'
|
||||
else
|
||||
@@ -357,6 +462,18 @@ else
|
||||
note 'the conflict update fixture could not be built'
|
||||
fi
|
||||
|
||||
for sentinel in profile-sourced bash-env-sourced global-config-sourced template-hook-sourced; do
|
||||
[[ ! -e "$hostile/$sentinel" ]] \
|
||||
|| note "the update Git fixture consumed hostile state: $sentinel"
|
||||
done
|
||||
template_copy="$(
|
||||
find "$tmp" -path "$hostile" -prune -o \
|
||||
-type f -path '*/hooks/pre-commit' \
|
||||
-exec grep -lF 'PANAMA_HOSTILE_TEMPLATE_HOOK' {} + 2>/dev/null
|
||||
)"
|
||||
[[ -z "$template_copy" ]] \
|
||||
|| note "the update Git fixture copied a hostile template hook: $template_copy"
|
||||
|
||||
if (( ${#findings[@]} > 0 )); then
|
||||
printf 'update command contract: %d finding(s)\n' "${#findings[@]}" >&2
|
||||
printf ' - %s\n' "${findings[@]}" >&2
|
||||
|
||||
Reference in New Issue
Block a user