Skip to content

Commit 353bbb9

Browse files
authored
Merge pull request #1465 from DeusData/feat/ci-run-isolation
fix(ci): isolate local-CI runs per run id, and tidy what each run creates
2 parents 9b9b6ff + 75ac227 commit 353bbb9

5 files changed

Lines changed: 177 additions & 24 deletions

File tree

‎test-infrastructure/docker-compose.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,5 +319,13 @@ volumes:
319319
# native filesystem: object writes over the virtiofs bind mount are the
320320
# container legs' largest avoidable I/O cost, and the fixture cache makes
321321
# the network clone once-per-volume instead of once-per-container.
322+
# The build volume is the ONLY per-run-mutable one: concurrent legs writing
323+
# the same build dir clobber each other's objects and test-logs (a scheduler
324+
# then fails reading a suite log that another run replaced). run.sh gives each
325+
# run its own volume via CBM_CI_BUILD_VOLUME and removes it afterwards; the
326+
# default name preserves the single-run fast path. ccache and the fixture
327+
# cache stay SHARED on purpose — ccache is concurrency-safe and
328+
# content-verified, and sharing them is what keeps isolated runs fast.
322329
cbm-build:
330+
name: ${CBM_CI_BUILD_VOLUME:-cbm-build}
323331
cbm-fixture-cache:

‎test-infrastructure/run.sh‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,46 @@
5656
set -euo pipefail
5757

5858
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
59+
60+
# ── Per-run isolation (concurrent agents / multiple worktrees) ───────────────
61+
# Container names are already unique per `compose run --rm`, but the BUILD
62+
# volume was not: two legs running at once wrote the same /src/build, so one
63+
# run's objects and test-logs replaced the other's — after which the parallel
64+
# scheduler dies reading a suite log that another run had removed. Each run now
65+
# gets its own build volume, keyed by a unique run id. ccache and the fixture
66+
# cache stay SHARED on purpose: ccache is concurrency-safe and content-verified,
67+
# and sharing them is what keeps an isolated run fast rather than cold.
68+
#
69+
# CBM_CI_RUN_ID=<id> name/reuse a run (default: pid + epoch, unique)
70+
# CBM_CI_SHARED_BUILD=1 opt back into the single shared `cbm-build` volume
71+
# CBM_CI_KEEP=1 keep this run's build volume even on success
72+
RUN_ID="${CBM_CI_RUN_ID:-$$-$(date +%s)}"
73+
if [ "${CBM_CI_SHARED_BUILD:-0}" = "1" ]; then
74+
CBM_CI_BUILD_VOLUME="cbm-build"
75+
else
76+
CBM_CI_BUILD_VOLUME="cbm-build-${RUN_ID}"
77+
fi
78+
export CBM_CI_BUILD_VOLUME
79+
80+
# Tidy what this run generated. A FAILED run KEEPS its volume — those artifacts
81+
# are the post-mortem — and prints how to inspect and drop it. Successful runs
82+
# leave nothing behind. Shared-volume mode never removes anything.
83+
ci_cleanup() {
84+
local rc=$?
85+
if [ "${CBM_CI_BUILD_VOLUME}" = "cbm-build" ] || [ "${CBM_CI_KEEP:-0}" = "1" ]; then
86+
return $rc
87+
fi
88+
if [ $rc -eq 0 ]; then
89+
docker volume rm -f "${CBM_CI_BUILD_VOLUME}" >/dev/null 2>&1 || true
90+
else
91+
echo "run.sh: kept ${CBM_CI_BUILD_VOLUME} for post-mortem (run failed)" >&2
92+
echo " inspect: docker run --rm -v ${CBM_CI_BUILD_VOLUME}:/b alpine ls -R /b" >&2
93+
echo " drop: docker volume rm ${CBM_CI_BUILD_VOLUME}" >&2
94+
fi
95+
return $rc
96+
}
97+
trap ci_cleanup EXIT
98+
5999
COMPOSE="docker compose -f $ROOT/test-infrastructure/docker-compose.yml"
60100

61101
usage() {
@@ -229,7 +269,9 @@ case "${1:-full}" in
229269
echo "=== Windows: binary version check (cross-compile + Wine) ==="
230270
$COMPOSE run --rm smoke-windows
231271
;;
232-
amd64)
272+
amd64 | test-amd64)
273+
# `--help` advertises `amd64|test-amd64`, but only `amd64` dispatched:
274+
# the documented spelling died with "unknown leg 'test-amd64'".
233275
echo "=== Linux amd64: test + build ==="
234276
$COMPOSE run --rm -e CBM_SKIP_PERF=1 test-amd64
235277
$COMPOSE run --rm build-amd64

‎test-infrastructure/vm/vm-run-tests.sh‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,25 @@ case "${1:-}" in
4646
esac
4747

4848
RUNNER="${CBM_VM_RUNNER:-}"
49-
LOG="${CBM_VM_TEST_LOG:-/tmp/win-test.log}"
49+
50+
# Per-run identity. The VM holds ONE checkout (/c/cbm), so two concurrent runs
51+
# shared a FIXED log path and the same build dir: the later run's output
52+
# replaced the earlier one's, and -PruneStale could delete a live run's temp
53+
# root out from under it. The run id namespaces the log and the build dir; the
54+
# protected temp root is already unique per run.
55+
CALLER_RUN_ID="${CBM_CI_RUN_ID:-}"
56+
RUN_ID="${CBM_CI_RUN_ID:-$$-$(date +%s)}"
57+
export CBM_CI_RUN_ID="$RUN_ID"
58+
LOG="${CBM_VM_TEST_LOG:-/tmp/win-test-${RUN_ID}.log}"
59+
60+
# A caller that sets CBM_CI_RUN_ID is declaring concurrency, so give that run
61+
# its own BUILD_DIR; the default single-run path keeps the shared one and its
62+
# incremental reuse (a per-run build dir on the VM costs a full rebuild).
63+
if [ -n "$CALLER_RUN_ID" ]; then
64+
BUILD_ARGS=("BUILD_DIR=build/vm-${RUN_ID}")
65+
else
66+
BUILD_ARGS=()
67+
fi
5068

5169
[ $# -ge 1 ] || { echo "vm-run-tests: missing arguments. Please consult --help." >&2; exit 2; }
5270
if [ "$1" = "--soak" ]; then
@@ -130,13 +148,25 @@ if [ -n "$RUNNER" ]; then
130148
rc="${PIPESTATUS[0]}"
131149
fi
132150
elif [ "$1" = "--par" ]; then
133-
scripts/test.sh CC=clang CXX=clang++ 2>&1 | tee "$LOG"
151+
scripts/test.sh CC=clang CXX=clang++ \
152+
${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"} 2>&1 | tee "$LOG"
134153
rc="${PIPESTATUS[0]}"
135154
else
136-
scripts/test.sh --suites "$*" CC=clang CXX=clang++ 2>&1 | tee "$LOG"
155+
scripts/test.sh --suites "$*" CC=clang CXX=clang++ \
156+
${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"} 2>&1 | tee "$LOG"
137157
rc="${PIPESTATUS[0]}"
138158
fi
139159

160+
# Tidy this run's own build dir on success; keep it on failure as the
161+
# post-mortem. Only ever touches a dir this run created (concurrent mode).
162+
if [ -n "$CALLER_RUN_ID" ] && [ "${CBM_CI_KEEP:-0}" != "1" ]; then
163+
if [ "${rc:-1}" -eq 0 ]; then
164+
rm -rf "build/vm-${RUN_ID}"
165+
else
166+
echo "vm-run-tests: kept build/vm-${RUN_ID} and $LOG for post-mortem" >&2
167+
fi
168+
fi
169+
140170
if ! grep -Eq '[0-9]+ passed' "$LOG"; then
141171
echo "GUARD: test runner produced no completion summary — the suites did" \
142172
"not validly run; treating as failure (runner rc=$rc)" >&2

‎test-infrastructure/vm/win.sh‎

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,24 @@ ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
8888
[ -f "$CONFIG" ] && . "$CONFIG"
8989
HOST="${CBM_VM_HOST:?set CBM_VM_HOST in ~/.claude/cbm-vm/config}"
9090
USER_="${CBM_VM_USER:-test}"
91+
92+
# ── Per-run checkout (the VM's last shared-state hazard) ─────────────────────
93+
# The VM holds ONE working tree at /c/cbm, and `update`/`sync` REPLACE it
94+
# (`git reset --hard` + `clean -fdx`). A second session syncing its own branch
95+
# therefore swaps the code under a running leg — observed 2026-08-06, where a
96+
# gate run on main compiled another branch's files and died on a header that
97+
# does not exist on Windows. That is worse than a red: it is a confident wrong
98+
# answer about the wrong tree.
99+
#
100+
# Setting CBM_CI_RUN_ID gives this run its own checkout, cloned from /c/cbm.
101+
# A local clone hardlinks .git/objects, so it costs seconds and little disk.
102+
# Unset (the default) keeps the historical single-tree behaviour.
103+
VM_BASE_REPO="/c/cbm"
104+
if [ -n "${CBM_CI_RUN_ID:-}" ]; then
105+
VM_REPO="/c/cbm-run-${CBM_CI_RUN_ID}"
106+
else
107+
VM_REPO="$VM_BASE_REPO"
108+
fi
91109
HOST_KEY="${CBM_VM_HOST_KEY_SHA256:?set CBM_VM_HOST_KEY_SHA256 in ~/.claude/cbm-vm/config}"
92110
LOCAL_BRANCH="$(git -C "$ROOT" branch --show-current)"
93111
BRANCH="${CBM_VM_BRANCH:-${LOCAL_BRANCH:-main}}"
@@ -117,6 +135,38 @@ SCP=(scp "${SSH_OPTIONS[@]}")
117135

118136
vm() { local env="$1"; shift
119137
"${SSH[@]}" "C:\\msys64\\msys2_shell.cmd -defterm -no-start -${env} -c \"set -e -o pipefail; $*\""; }
138+
139+
# Create this run's own checkout if it does not exist yet. A LOCAL clone
140+
# hardlinks .git/objects, so it is seconds and near-zero disk rather than a
141+
# fresh network clone. No-op when running in the shared (default) tree.
142+
vm_ensure_run_checkout() {
143+
[ "$VM_REPO" = "$VM_BASE_REPO" ] && return 0
144+
# `git clone --local` points origin at the base PATH, so a later
145+
# `fetch origin <branch>` would resolve against whatever the base happens
146+
# to hold (i.e. another session's state) instead of GitHub. Inherit the
147+
# base's real origin URL so the isolated tree fetches independently.
148+
# The substitution is single-quoted here so it expands in the REMOTE shell
149+
# (same pattern as JOBS above); nested double quotes do not survive the
150+
# cmd.exe -> msys2_shell wrapper.
151+
local base_url_expr='$(git -C '"$VM_BASE_REPO"' remote get-url origin)'
152+
vm clangarm64 "[ -d '$VM_REPO/.git' ] || git clone --local '$VM_BASE_REPO' '$VM_REPO'"
153+
vm clangarm64 "git -C '$VM_REPO' remote set-url origin $base_url_expr"
154+
}
155+
156+
# The VM tree must be the one the caller means. A concurrent session running
157+
# `update`/`sync` in the SHARED tree swaps it mid-flight, and the leg then
158+
# reports a confident verdict about someone else's branch. Callers assert the
159+
# expected HEAD; a mismatch fails loudly instead of producing that verdict.
160+
vm_assert_head() {
161+
local want="$1" got
162+
got="$(vm clangarm64 "cd $VM_REPO && git rev-parse --verify HEAD" | tr -d '\r')"
163+
if [ "$got" != "$want" ]; then
164+
echo "FATAL: VM tree $VM_REPO is at $got, expected $want." >&2
165+
echo " Another session likely re-synced the shared checkout." >&2
166+
echo " Re-run with CBM_CI_RUN_ID set to get an isolated tree." >&2
167+
return 1
168+
fi
169+
}
120170
vm_cmd() { "${SSH[@]}" "$@"; } # plain cmd.exe (CI-shaped environment)
121171

122172
# A GitHub runner is ephemeral: every job starts on a fresh image with a
@@ -157,13 +207,25 @@ esac
157207
case "$cmd" in
158208
status)
159209
"${SSH[@]}" "echo VM_REACHABLE & ver"
160-
vm clangarm64 "cd /c/cbm 2>/dev/null && git log --oneline -1 && ls -la build/c/codebase-memory-mcp.exe build/c/test-runner.exe 2>/dev/null || echo 'repo/build missing — run provision-windows.sh'"
210+
vm clangarm64 "cd $VM_REPO 2>/dev/null && git log --oneline -1 && ls -la build/c/codebase-memory-mcp.exe build/c/test-runner.exe 2>/dev/null || echo 'repo/build missing — run provision-windows.sh'"
161211
;;
162212
update)
163-
vm clangarm64 "cd /c/cbm && git fetch origin ${BRANCH} && git reset --hard FETCH_HEAD && git clean -fdx && git log --oneline -1"
213+
vm_ensure_run_checkout
214+
vm clangarm64 "cd $VM_REPO && git fetch origin ${BRANCH} && git reset --hard FETCH_HEAD && git clean -fdx && git log --oneline -1"
164215
exec "$0" build
165216
;;
217+
drop-run-checkout)
218+
# Explicit tidy-up of THIS run's checkout (no-op in shared mode). The run
219+
# that created it removes it; nothing else may, since another session's
220+
# tree must never be deleted out from under it.
221+
if [ "$VM_REPO" = "$VM_BASE_REPO" ]; then
222+
echo "win.sh: shared checkout ($VM_BASE_REPO) — nothing to drop" >&2
223+
else
224+
vm clangarm64 "rm -rf '$VM_REPO' && echo dropped $VM_REPO"
225+
fi
226+
;;
166227
sync)
228+
vm_ensure_run_checkout
167229
local_head="$(git -C "$ROOT" rev-parse --verify HEAD)"
168230
WIN_MANIFEST="$(mktemp "${TMPDIR:-/tmp}/cbm-vm-manifest.XXXXXX")"
169231
WIN_ARCHIVE="$(mktemp "${TMPDIR:-/tmp}/cbm-vm-worktree.XXXXXX.tar")"
@@ -172,25 +234,25 @@ sync)
172234
cbm_vm_write_untracked_manifest "$ROOT" "$WIN_MANIFEST"
173235
COPYFILE_DISABLE=1 tar --no-xattrs --no-mac-metadata \
174236
-C "$ROOT" --null -T "$WIN_MANIFEST" -cf "$WIN_ARCHIVE"
175-
remote_head="$(vm clangarm64 "cd /c/cbm && git rev-parse --verify HEAD")"
237+
remote_head="$(vm clangarm64 "cd $VM_REPO && git rev-parse --verify HEAD")"
176238
remote_head="${remote_head//$'\r'/}"
177239
if [ "$remote_head" != "$local_head" ]; then
178240
echo "FATAL: Windows VM is at $remote_head, expected local HEAD $local_head; run win.sh update first." >&2
179241
exit 1
180242
fi
181243
vm clangarm64 \
182-
"cd /c/cbm && git reset --hard '$local_head' && git clean -fdx"
244+
"cd $VM_REPO && git reset --hard '$local_head' && git clean -fdx"
183245
if [ -s "$WIN_PATCH" ]; then
184246
"${SSH[@]}" \
185-
'C:\msys64\msys2_shell.cmd -defterm -no-start -clangarm64 -c "cd /c/cbm && git apply --binary --whitespace=nowarn -"' \
247+
'C:\msys64\msys2_shell.cmd -defterm -no-start -clangarm64 -c "cd $VM_REPO && git apply --binary --whitespace=nowarn -"' \
186248
<"$WIN_PATCH"
187249
fi
188250
if [ -s "$WIN_MANIFEST" ]; then
189251
"${SSH[@]}" \
190-
'C:\msys64\msys2_shell.cmd -defterm -no-start -clangarm64 -c "cd /c/cbm && tar -xf -"' \
252+
'C:\msys64\msys2_shell.cmd -defterm -no-start -clangarm64 -c "cd $VM_REPO && tar -xf -"' \
191253
<"$WIN_ARCHIVE"
192254
fi
193-
vm clangarm64 "cd /c/cbm && git status --short --branch"
255+
vm clangarm64 "cd $VM_REPO && git status --short --branch"
194256
win_cleanup
195257
exec "$0" build
196258
;;
@@ -201,7 +263,7 @@ build)
201263
# NOT via CC='ccache clang', which bypassed the verified-cache env layer.
202264
# The test-runner is no longer built here: the test leg (scripts/test.sh)
203265
# builds its own runner, same as CI's test jobs.
204-
vm clangarm64 "cd /c/cbm && scripts/build.sh CC=clang CXX=clang++ > /tmp/win-build.log 2>&1 && echo BUILD_OK || (echo BUILD_FAIL; tail -20 /tmp/win-build.log; exit 1)"
266+
vm clangarm64 "cd $VM_REPO && scripts/build.sh CC=clang CXX=clang++ > /tmp/win-build.log 2>&1 && echo BUILD_OK || (echo BUILD_FAIL; tail -20 /tmp/win-build.log; exit 1)"
205267
;;
206268
test)
207269
[ $# -ge 1 ] || { echo "usage: win.sh test <suite...>" >&2; exit 2; }
@@ -210,7 +272,12 @@ test)
210272
# protected per-user temp root via vm-run-tests.sh, which streams FULL
211273
# output and refuses to report success without the runner's completion
212274
# summary. A `| tail -40` here once hid 40 real Windows failures.
213-
vm clangarm64 "cd /c/cbm && bash test-infrastructure/vm/vm-run-tests.sh $*"
275+
# CBM_VM_EXPECT_HEAD=<sha> asserts the tree is the one the caller means
276+
# before spending a leg on it — cheap insurance against a concurrent
277+
# session having re-synced the shared checkout.
278+
[ -z "${CBM_VM_EXPECT_HEAD:-}" ] || vm_assert_head "$CBM_VM_EXPECT_HEAD"
279+
vm clangarm64 "cd $VM_REPO && bash test-infrastructure/vm/vm-run-tests.sh $*"
280+
[ -z "${CBM_VM_EXPECT_HEAD:-}" ] || vm_assert_head "$CBM_VM_EXPECT_HEAD"
214281
;;
215282
guards)
216283
# Match the Windows CI product build: a clean, embedded-UI product binary.
@@ -222,7 +289,7 @@ guards)
222289
# needs the official MSVC Node.js (/c/node, from provision-windows.sh):
223290
# MSVC-built npm native modules cannot resolve Node-API symbols against
224291
# MSYS2's mingw node.
225-
vm clangarm64 "export PATH=/c/node:\$PATH && cd /c/cbm && scripts/build.sh --with-ui CC=clang CXX=clang++ SANITIZE= BUILD_DIR=build/guards"
292+
vm clangarm64 "export PATH=/c/node:\$PATH && cd $VM_REPO && scripts/build.sh --with-ui CC=clang CXX=clang++ SANITIZE= BUILD_DIR=build/guards"
226293
# The guards themselves run through plain cmd/PowerShell, exactly like the
227294
# CI job: under the MSYS2 shell TMP is C:\msys64\tmp, whose ancestry
228295
# grants mutation rights to Authenticated Users — the daemon's
@@ -238,14 +305,14 @@ smoke-install)
238305
# build/c (including any test-runner) just as CI's separate jobs imply;
239306
# ccache keeps the rebuild to minutes — parity was the explicit user call
240307
# over incremental speed here.
241-
vm clangarm64 "cd /c/cbm && scripts/build.sh CC=clang CXX=clang++ && bash test-infrastructure/vm/vm-smoke.sh"
308+
vm clangarm64 "cd $VM_REPO && scripts/build.sh CC=clang CXX=clang++ && bash test-infrastructure/vm/vm-smoke.sh"
242309
;;
243310
smoke-artifact)
244311
# The release-archive flow with local bytes: canonical build → the ONE
245312
# package-release.sh → extract → vm-smoke.sh in artifact mode — the same
246313
# sequence _build.yml + _smoke.yml run remotely, so archive-layout bugs
247314
# surface here instead of in a release dry run.
248-
vm clangarm64 "cd /c/cbm && bash scripts/ci/smoke-artifact.sh windows arm64 CC=clang CXX=clang++"
315+
vm clangarm64 "cd $VM_REPO && bash scripts/ci/smoke-artifact.sh windows arm64 CC=clang CXX=clang++"
249316
;;
250317
soak)
251318
duration="${1:-10}"
@@ -256,7 +323,7 @@ soak)
256323
echo "usage: win.sh soak [positive-minutes]" >&2
257324
exit 2
258325
fi
259-
vm clangarm64 "cd /c/cbm && CBM_VM_TEST_LOG=/tmp/win-soak.log bash \
326+
vm clangarm64 "cd $VM_REPO && CBM_VM_TEST_LOG=/tmp/win-soak.log bash \
260327
test-infrastructure/vm/vm-run-tests.sh --soak '$duration'"
261328
;;
262329
sh)
@@ -273,11 +340,11 @@ ubsan-build)
273340
# Validated: UBSan needs no interceptors, so it builds, runs, AND reports
274341
# correctly under emulation. (ASan does NOT: no aarch64 runtime exists and
275342
# the x86_64 runtime faults in emulated process-init — ASan stays CI-only.)
276-
vm clang64 "cd /c/cbm && make -j${JOBS} -f Makefile.cbm CC=clang CXX=clang++ SANITIZE='-fsanitize=undefined -fno-omit-frame-pointer' build/c/test-runner > /tmp/win-ubsan-build.log 2>&1 && echo UBSAN_BUILD_OK || (echo UBSAN_BUILD_FAIL; tail -20 /tmp/win-ubsan-build.log; exit 1)"
343+
vm clang64 "cd $VM_REPO && make -j${JOBS} -f Makefile.cbm CC=clang CXX=clang++ SANITIZE='-fsanitize=undefined -fno-omit-frame-pointer' build/c/test-runner > /tmp/win-ubsan-build.log 2>&1 && echo UBSAN_BUILD_OK || (echo UBSAN_BUILD_FAIL; tail -20 /tmp/win-ubsan-build.log; exit 1)"
277344
;;
278345
ubsan-test)
279346
[ $# -ge 1 ] || { echo "usage: win.sh ubsan-test <suite...>" >&2; exit 2; }
280-
vm clang64 "cd /c/cbm && CBM_VM_TEST_LOG=/tmp/win-ubsan-test.log bash test-infrastructure/vm/vm-run-tests.sh $*"
347+
vm clang64 "cd $VM_REPO && CBM_VM_TEST_LOG=/tmp/win-ubsan-test.log bash test-infrastructure/vm/vm-run-tests.sh $*"
281348
;;
282349
trap-ubsan-build)
283350
# NATIVE ARM64 UBSan via trap mode. -fsanitize-trap=undefined needs NO
@@ -289,13 +356,13 @@ trap-ubsan-build)
289356
# fired, reproduce under the emulated `win.sh ubsan-build`/`ubsan-test`,
290357
# which carries the full runtime + message. BUILD_DIR isolated so it never
291358
# clobbers the plain test-runner.
292-
vm clangarm64 "cd /c/cbm && make -j${JOBS} -f Makefile.cbm CC='ccache clang' CXX='ccache clang++' SANITIZE='-fsanitize=undefined -fsanitize-trap=undefined -fstack-protector-strong -fno-omit-frame-pointer' BUILD_DIR=build/trap-ubsan build/trap-ubsan/test-runner > /tmp/win-trap-ubsan-build.log 2>&1 && echo TRAP_UBSAN_BUILD_OK || (echo TRAP_UBSAN_BUILD_FAIL; tail -20 /tmp/win-trap-ubsan-build.log; exit 1)"
359+
vm clangarm64 "cd $VM_REPO && make -j${JOBS} -f Makefile.cbm CC='ccache clang' CXX='ccache clang++' SANITIZE='-fsanitize=undefined -fsanitize-trap=undefined -fstack-protector-strong -fno-omit-frame-pointer' BUILD_DIR=build/trap-ubsan build/trap-ubsan/test-runner > /tmp/win-trap-ubsan-build.log 2>&1 && echo TRAP_UBSAN_BUILD_OK || (echo TRAP_UBSAN_BUILD_FAIL; tail -20 /tmp/win-trap-ubsan-build.log; exit 1)"
293360
;;
294361
trap-ubsan-test)
295362
[ $# -ge 1 ] || { echo "usage: win.sh trap-ubsan-test <suite...>" >&2; exit 2; }
296363
# A UB trap crashes the runner with SIGILL (exit 132); the harness reports
297364
# the failing suite so the emulated diagnosis loop can name the check.
298-
vm clangarm64 "cd /c/cbm && CBM_VM_RUNNER=build/trap-ubsan/test-runner CBM_VM_TEST_LOG=/tmp/win-trap-ubsan-test.log bash test-infrastructure/vm/vm-run-tests.sh $*"
365+
vm clangarm64 "cd $VM_REPO && CBM_VM_RUNNER=build/trap-ubsan/test-runner CBM_VM_TEST_LOG=/tmp/win-trap-ubsan-test.log bash test-infrastructure/vm/vm-run-tests.sh $*"
299366
;;
300367
pageheap)
301368
# OS-level heap verification (page-granular overflow/UAF detection) for the
@@ -318,7 +385,7 @@ test-par)
318385
# under the CI-shaped protected temp root with FULL output (this leg once
319386
# ran under the MSYS-shared /tmp and piped through `tail -25` — the same
320387
# truncated-blindness class that hid 40 Windows failures from `test`).
321-
vm clangarm64 "cd /c/cbm && CBM_VM_TEST_LOG=/tmp/win-test-par.log bash test-infrastructure/vm/vm-run-tests.sh --par"
388+
vm clangarm64 "cd $VM_REPO && CBM_VM_TEST_LOG=/tmp/win-test-par.log bash test-infrastructure/vm/vm-run-tests.sh --par"
322389
;;
323390
help | -h | --help)
324391
print_help

‎tests/test_windows_bundle_contract.sh‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,8 +537,14 @@ require(
537537
)
538538
require(
539539
sync_case is not None
540-
and 'remote_head="$(vm clangarm64 "cd /c/cbm && git rev-parse --verify HEAD")"'
541-
in sync_case.group("body")
540+
# Assert the PROPERTY, not one spelling of it: capture the remote HEAD into
541+
# a local variable and compare it here. The checkout path became a variable
542+
# (per-run isolation), so pinning the literal `/c/cbm` was asserting the
543+
# implementation rather than the contract it exists to protect.
544+
and re.search(
545+
r'remote_head="\$\(vm clangarm64 "cd \S+ && git rev-parse --verify HEAD"\)"',
546+
sync_case.group("body"),
547+
)
542548
and 'test \\"\\$(git rev-parse --verify HEAD)\\"' not in sync_case.group("body"),
543549
"win.sh sync must compare the remote HEAD locally instead of nesting shell quotes through "
544550
"cmd.exe",

0 commit comments

Comments
 (0)