11#! /usr/bin/env bash
22# requires:
33# 171_bmi_staging_locked_dest.sh — mcpp#311: staging must survive a destination
4- # that another process is holding, and must fail LOUDLY when it genuinely can't.
4+ # another process is holding, and must fail LOUDLY when it genuinely can't.
55#
66# The reported failure: mcpp writes the staged std BMI path into
77# compile_commands.json so clangd can resolve `import std;`, clangd
88# memory-maps that file, and the old `Copy-Item -Force` staging step then tried
99# to overwrite it in place → Windows error 1224 (a file with a user-mapped
1010# section open cannot be replaced) → the whole build reported "build failed".
1111#
12- # Windows: reproduce it exactly, WITHOUT needing clangd — a background
13- # PowerShell holds a MemoryMappedFile on the staged BMI.
14- # POSIX: the closest analogue an ordinary test can create is an unwritable
15- # destination (chmod 444); the old `cp -f` silently unlinked and rewrote it,
16- # the new path must not need to write at all.
12+ # Windows: reproduce that exactly and WITHOUT clangd — a background PowerShell
13+ # holds a MemoryMappedFile on the staged BMI. The holder signals readiness
14+ # through a sentinel file, so a PowerShell that never started fails the test
15+ # instead of letting it pass for the wrong reason.
16+ #
17+ # The load-bearing assertion is platform-neutral and root-proof: an equivalent
18+ # destination must not be written AT ALL (same inode, same mtime, same size).
19+ # Permissions alone would not do — root ignores them, and the container e2e job
20+ # runs as root.
1721set -e
1822
1923TMP=$( mktemp -d)
@@ -41,18 +45,27 @@ DST="$(dirname "$NINJA")/$(echo "$EDGE" | awk '{print $2}')"
4145SRC=$( echo " $EDGE " | awk ' {print $NF}' )
4246[[ -f " $DST " ]] || { echo " FAIL: staged BMI missing at $DST " ; exit 1; }
4347
48+ identity () { stat -c ' %i %Y %s' " $1 " ; }
49+ BEFORE=$( identity " $DST " )
50+
4451# Dirty the staging edge without touching the shared cache's CONTENT.
4552touch " $SRC "
4653
4754HOLDER=" "
4855case " $( uname -s) " in
4956 MINGW* | MSYS* | CYGWIN* )
5057 WINDST=" $( cygpath -w " $DST " ) "
58+ READY=" $TMP /mapped.flag"
59+ WINREADY=" $( cygpath -w " $READY " ) "
5160 powershell -NoProfile -Command \
52- " \$ mm = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateFromFile('$WINDST ', [System.IO.FileMode]::Open); Start-Sleep -Seconds 90" &
61+ " \$ mm = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateFromFile('$WINDST ', [System.IO.FileMode]::Open); \
62+ Set-Content -Path '$WINREADY ' -Value 'mapped'; Start-Sleep -Seconds 120" &
5363 HOLDER=$!
54- # Give the mapping time to exist before the build races it.
55- sleep 3
64+ for _ in $( seq 1 40) ; do [[ -f " $READY " ]] && break ; sleep 1; done
65+ [[ -f " $READY " ]] || {
66+ kill " $HOLDER " 2> /dev/null || true
67+ echo " FAIL: could not map the staged BMI — the test would not have proven anything"
68+ exit 1; }
5669 echo " holding a user-mapped section on $WINDST (pid $HOLDER )"
5770 ;;
5871 * )
@@ -75,6 +88,12 @@ if [[ $rc -ne 0 ]]; then
7588fi
7689grep -q " stage --output" build2.log || {
7790 cat build2.log; echo " FAIL: the staging edge did not run" ; exit 1; }
91+
92+ # The real assertion: nothing was written. Not "the write succeeded anyway".
93+ AFTER=$( identity " $DST " )
94+ [[ " $BEFORE " == " $AFTER " ]] || {
95+ echo " FAIL: an equivalent destination was rewritten (was '$BEFORE ', now '$AFTER ')"
96+ exit 1; }
7897cmp " $SRC " " $DST " || { echo " FAIL: held destination no longer matches the cache" ; exit 1; }
7998
8099cd " $TMP "
0 commit comments