Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions scripts/soak-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,17 @@ CBM_SOAK_MODE="${CBM_SOAK_MODE:-default}"
RESULTS_DIR="${RESULTS_DIR:-soak-results}"
mkdir -p "$RESULTS_DIR"

# Isolate daemon coordination from interactive CBM sessions and give this run
# a deterministic host-side daemon log. Wine needs a Windows-form cache path
# in the child environment while this Bash harness retains the host path.
# Every product process below must reach a daemon rendezvous this run owns.
# Only CBM_RUNTIME_DIR moves that rendezvous — a private CBM_CACHE_DIR alone
# still shares the operator's account daemon (#1691, #1696).
# shellcheck source=test-runtime.sh
source "$(dirname "${BASH_SOURCE[0]}")/test-runtime.sh"
cbm_test_runtime_init
trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT

# Give this run a deterministic host-side daemon log. Wine needs a Windows-form
# cache path in the child environment while this Bash harness retains the host
# path.
#
# On native Windows the cache CANNOT live under msys /tmp: the server's
# cache-private executable-identity check walks the cache path's ancestors
Expand Down Expand Up @@ -146,7 +154,7 @@ if [[ "$BINARY" == *.exe ]] && command -v cygpath >/dev/null 2>&1 &&
exit 1
fi
else
SOAK_CACHE_DIR_HOST=$(mktemp -d "${TMPDIR:-/tmp}/cbm-soak-cache.XXXXXX")
SOAK_CACHE_DIR_HOST="$CBM_TEST_CACHE_DIR_HOST"
fi
SOAK_CACHE_DIR_VALUE="$SOAK_CACHE_DIR_HOST"
if [[ "$BINARY" == *.exe ]] && command -v winepath >/dev/null 2>&1; then
Expand Down Expand Up @@ -206,7 +214,12 @@ soak_cleanup() {
if [ -f "$DAEMON_LOG" ]; then
cp "$DAEMON_LOG" "$RESULTS_DIR/cbm-daemon.log" 2>/dev/null || true
fi
rm -rf -- "$SOAK_PROJECT" "$SOAK_CACHE_DIR_HOST"
rm -rf -- "$SOAK_PROJECT"
# The helper stops this run's private daemon before removing its root and
# leaves the root behind for diagnosis when the daemon will not stop. The
# native-Windows cache sits under SOAK_WIN_ROOT, which goes only after the
# daemon check because the binary it probes with is the copy inside it.
cbm_test_runtime_cleanup "$BINARY"
[ -z "${SOAK_WIN_ROOT:-}" ] || rm -rf -- "$SOAK_WIN_ROOT"
}

Expand Down Expand Up @@ -807,7 +820,7 @@ if [ "$SKIP_CRASH" != "--skip-crash-test" ] && [ "$CBM_SOAK_MODE" != "query-leak
SERVER_PID=""
exec 3>&- 4<&-
if ! wait_for_daemon_stop "$DAEMON_STOP_COUNT"; then
echo "FAIL: last-session crash did not stop the shared daemon"
echo "FAIL: last-session crash did not stop the private daemon"
exit 1
fi

Expand Down Expand Up @@ -848,7 +861,7 @@ kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
SERVER_PID=""
if ! wait_for_daemon_stop "$FINAL_DAEMON_STOP_COUNT"; then
echo "FAIL: final frontend shutdown did not stop the shared daemon"
echo "FAIL: final frontend shutdown did not stop the private daemon"
PASS=false
fi

Expand Down
3 changes: 3 additions & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ bash "$ROOT/tests/test_ui_dev_proxy_security.sh"
echo "=== Step 0d: daemon soak recovery contract ==="
bash "$ROOT/tests/test_soak_daemon_recovery_contract.sh"

echo "=== Step 0d2: soak harness runtime isolation contract (#1696) ==="
bash "$ROOT/tests/test_soak_runtime_isolation_contract.sh"

echo "=== Step 0e: Windows launcher bundle contract ==="
bash "$ROOT/tests/test_windows_bundle_contract.sh"

Expand Down
82 changes: 82 additions & 0 deletions tests/test_soak_runtime_isolation_contract.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -euo pipefail

# Runtime-isolation contract for the soak harness (#1696, follow-up to #1691).
#
# scripts/soak-test.sh must be the only client of the daemon it measures: it
# asserts that a session crash and the final shutdown each stop "the daemon".
# It used to claim isolation from interactive sessions through a private
# CBM_CACHE_DIR, but only CBM_RUNTIME_DIR moves the daemon rendezvous
# (docs/CONFIGURATION.md), so the soak shared the operator's account daemon
# and either stopped it or was refused with a cache-root conflict. Drive the
# harness with an environment-probe fixture and require that no product
# process ever receives the caller's runtime or cache.

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT

fail() {
echo "FAIL: $*" >&2
exit 1
}

normalize_path() {
local path=${1%$'\r'}
if command -v cygpath >/dev/null 2>&1; then
cygpath -u "$path" 2>/dev/null && return 0
fi
printf '%s\n' "${path//\\//}"
}

ENV_PROBE="$WORKDIR/environment-probe"
cat > "$ENV_PROBE" <<'EOF'
#!/usr/bin/env bash
printf '%s\t%s\n' "${CBM_CACHE_DIR-}" "${CBM_RUNTIME_DIR-}" >> "$CBM_SOAK_ENV_PROBE"
[[ "${1-} ${2-}" == "daemon status" ]] && exit 1
exit 0
EOF
chmod +x "$ENV_PROBE"

CALLER_CACHE="$WORKDIR/caller-cache"
CALLER_RUNTIME="$WORKDIR/caller-runtime"
ENV_LOG="$WORKDIR/environment.log"
mkdir -p "$CALLER_CACHE" "$CALLER_RUNTIME"

# The fixture exits as soon as it has recorded its environment, so the soak
# fails at "server did not start"; only the environment it handed to the
# product is under test here. RESULTS_DIR keeps the soak's metrics out of cwd.
CBM_CACHE_DIR="$CALLER_CACHE" \
CBM_RUNTIME_DIR="$CALLER_RUNTIME" \
CBM_SOAK_ENV_PROBE="$ENV_LOG" \
RESULTS_DIR="$WORKDIR/results" \
"$ROOT/scripts/soak-test.sh" "$ENV_PROBE" 1 --skip-crash-test \
> "$WORKDIR/soak.out" 2>&1 || true

[[ -s "$ENV_LOG" ]] || fail "soak-test did not execute the environment-probe fixture"

CALLER_CACHE_NORMALIZED=$(normalize_path "$CALLER_CACHE")
CALLER_RUNTIME_NORMALIZED=$(normalize_path "$CALLER_RUNTIME")
private_root=""
while IFS=$'\t' read -r child_cache_raw child_runtime_raw; do
child_cache=$(normalize_path "$child_cache_raw")
child_runtime=$(normalize_path "$child_runtime_raw")
if [[ -z "$child_runtime" || "$child_runtime" == "$CALLER_RUNTIME_NORMALIZED" ]]; then
fail "soak-test exposed the caller CBM_RUNTIME_DIR to a product process"
fi
if [[ -z "$child_cache" || "$child_cache" == "$CALLER_CACHE_NORMALIZED" ]]; then
fail "soak-test exposed the caller CBM_CACHE_DIR to a product process"
fi
if [[ "${child_runtime%/*}" != "${child_cache%/*}" ||
"${child_runtime##*/}" != "runtime" || "${child_cache##*/}" != "cache" ]]; then
fail "soak runtime/cache were not isolated beneath one private root"
fi
if [[ -n "$private_root" && "$private_root" != "${child_runtime%/*}" ]]; then
fail "soak-test switched private roots mid-run"
fi
private_root="${child_runtime%/*}"
done < "$ENV_LOG"

[[ ! -e "$private_root" ]] || fail "soak-test left its private root behind: $private_root"

echo "PASS: soak harness isolates its daemon runtime and cache from the caller"
Loading