|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Sandbox verification of mcpp 2026.9.30.2 (.agents/docs/2026-09-30-build- |
| 3 | +# wall-time-progress-count-and-hang-plan.md), run against the PUBLISHED release |
| 4 | +# inside an xlings sandbox: |
| 5 | +# |
| 6 | +# B64=$(base64 -w0 .agents/docs/2026-09-30-build-wall-time-verify.sh) |
| 7 | +# xlings subos new v9302 2>/dev/null || true |
| 8 | +# xlings subos use v9302 --sandbox --cmd "echo $B64 | base64 -d > /tmp/v.sh && VER=2026.9.30.2 bash /tmp/v.sh" |
| 9 | +# |
| 10 | +# VER selects the release under test. Running it with VER=2026.9.30.1 is the |
| 11 | +# control: every section marked CHANGE must fail there, and every other section |
| 12 | +# must pass on both. |
| 13 | +# |
| 14 | +# Every probe directory is removed at the start of its section, because the |
| 15 | +# sandbox's $HOME persists between runs of the same subos. A section that does |
| 16 | +# not run is reported as SKIP and counted separately from a pass. |
| 17 | +set -u |
| 18 | +VER="${VER:-2026.9.30.2}" |
| 19 | +W="$HOME/v9302" |
| 20 | +fails=0; passes=0; skips=0 |
| 21 | +pass() { echo "PASS $1"; passes=$((passes+1)); } |
| 22 | +fail() { echo "FAIL $1"; [ -n "${2:-}" ] && [ -f "$2" ] && tail -15 "$2"; fails=$((fails+1)); } |
| 23 | +skip() { echo "SKIP $1"; skips=$((skips+1)); } |
| 24 | + |
| 25 | +# ── 0. the release under test, from the published channel ────────────────── |
| 26 | +if [ -n "${MCPP_OVERRIDE:-}" ]; then |
| 27 | + MCPP="$MCPP_OVERRIDE" |
| 28 | +else |
| 29 | + xlings config --mirror CN >/dev/null 2>&1 || true |
| 30 | + xlings update >/dev/null 2>&1 || true |
| 31 | + xlings install "mcpp@$VER" -y > /tmp/v9302-install.log 2>&1 || true |
| 32 | + MCPP="$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp" |
| 33 | +fi |
| 34 | +if [ ! -x "$MCPP" ]; then |
| 35 | + echo "FATAL: mcpp $VER is not installable from the index"; tail -20 /tmp/v9302-install.log; exit 2 |
| 36 | +fi |
| 37 | +got=$("$MCPP" --version 2>&1 | head -1) |
| 38 | +case "$got" in *"$VER"*) pass "0 installed: $got";; *) fail "0 version: $got";; esac |
| 39 | +"$MCPP" self config --mirror CN >/dev/null 2>&1 || true |
| 40 | +mkdir -p "$W" |
| 41 | + |
| 42 | +module_file() { printf 'export module boost;\nexport int value() { return %s; }\n' "$2" > "$1"; } |
| 43 | +main_file() { printf '#include <cstdio>\nimport boost;\nint main() { std::printf("%%d\\n", value()); }\n' > "$1"; } |
| 44 | +bin_of() { find "$1" -path '*/bin/*' -name "$2" -type f | head -1; } |
| 45 | + |
| 46 | +# ── 1. CHANGE: #732, an artifacts program with its own module of one name ── |
| 47 | +rm -rf "$W/s1"; mkdir -p "$W/s1/app/src" "$W/s1/updater/src"; cd "$W/s1" |
| 48 | +module_file app/src/boost.cppm 1; main_file app/src/main.cpp |
| 49 | +module_file updater/src/boost.cppm 2; main_file updater/src/main.cpp |
| 50 | +printf '[package]\nname = "updater"\nversion = "0.1.0"\n\n[targets.updater]\nkind = "bin"\nmain = "src/main.cpp"\n' > updater/mcpp.toml |
| 51 | +printf '[package]\nname = "app"\nversion = "0.1.0"\n\n[dependencies]\nupdater = { path = "../updater", artifacts = ["updater"] }\n\n[targets.app]\nkind = "bin"\nmain = "src/main.cpp"\n' > app/mcpp.toml |
| 52 | +if (cd app && "$MCPP" build > ../s1.log 2>&1) \ |
| 53 | + && [ "$("$(bin_of app/target app)")" = 1 ] && [ "$("$(bin_of app/target updater)")" = 2 ]; then |
| 54 | + pass "1 CHANGE: the app and its artifacts updater each have their own module boost" |
| 55 | +else fail "1 CHANGE: two programs, one module name" s1.log; fi |
| 56 | + |
| 57 | +# ── 2. #732, two providers in one program are refused ─────────────────────── |
| 58 | +rm -rf "$W/s2"; mkdir -p "$W/s2/lib1/src" "$W/s2/lib2/src" "$W/s2/prog/src"; cd "$W/s2" |
| 59 | +module_file lib1/src/boost.cppm 1; module_file lib2/src/boost.cppm 2 |
| 60 | +for l in lib1 lib2; do printf '[package]\nname = "%s"\nversion = "0.1.0"\n' "$l" > $l/mcpp.toml; done |
| 61 | +printf 'int main() { return 0; }\n' > prog/src/main.cpp |
| 62 | +printf '[package]\nname = "prog"\nversion = "0.1.0"\n\n[dependencies]\nlib1 = { path = "../lib1" }\nlib2 = { path = "../lib2" }\n\n[targets.prog]\nkind = "bin"\nmain = "src/main.cpp"\n' > prog/mcpp.toml |
| 63 | +if (cd prog && "$MCPP" build > ../s2.log 2>&1); then fail "2 two providers in one program were accepted" s2.log |
| 64 | +elif grep -q "module 'boost' is provided by package" s2.log; then pass "2 two providers in one program are refused" |
| 65 | +else fail "2 the refusal does not name the module" s2.log; fi |
| 66 | + |
| 67 | +# ── 3. CHANGE: #732, two workspace members with one module name ───────────── |
| 68 | +rm -rf "$W/s3"; mkdir -p "$W/s3/one/src" "$W/s3/two/src"; cd "$W/s3" |
| 69 | +module_file one/src/boost.cppm 5; main_file one/src/main.cpp |
| 70 | +module_file two/src/boost.cppm 6; main_file two/src/main.cpp |
| 71 | +printf '[workspace]\nmembers = ["one", "two"]\n' > mcpp.toml |
| 72 | +for m in one two; do printf '[package]\nname = "%s"\nversion = "0.1.0"\n\n[targets.%s]\nkind = "bin"\nmain = "src/main.cpp"\n' "$m" "$m" > $m/mcpp.toml; done |
| 73 | +if "$MCPP" build --workspace > s3.log 2>&1 \ |
| 74 | + && [ "$("$(bin_of target one)")" = 5 ] && [ "$("$(bin_of target two)")" = 6 ]; then |
| 75 | + pass "3 CHANGE: two workspace members each have their own module boost" |
| 76 | +else fail "3 CHANGE: two workspace members, one module name" s3.log; fi |
| 77 | + |
| 78 | +# ── 4. index packages build and run with the release ─────────────────────── |
| 79 | +rm -rf "$W/s4"; mkdir -p "$W/s4/src"; cd "$W/s4" |
| 80 | +printf '[package]\nname = "eco"\nversion = "0.1.0"\n\n[dependencies]\n"compat.zlib" = "*"\n"mcpplibs.cmdline" = "*"\n\n[targets.eco]\nkind = "bin"\nmain = "src/main.cpp"\n' > mcpp.toml |
| 81 | +cat > src/main.cpp <<'EOF' |
| 82 | +#include <cstdio> |
| 83 | +#include <zlib.h> |
| 84 | +import mcpplibs.cmdline; |
| 85 | +int heavy(); |
| 86 | +int main() { std::printf("zlib %s\n", zlibVersion()); return heavy() == 0; } |
| 87 | +EOF |
| 88 | +# One unit that takes a few seconds to compile, so that section 5's build |
| 89 | +# outlives the half second before the status row is first drawn. |
| 90 | +cat > src/heavy.cpp <<'EOF' |
| 91 | +#include <format> |
| 92 | +#include <regex> |
| 93 | +#include <string> |
| 94 | +int heavy() { |
| 95 | + std::regex r("([a-z]+)-([0-9]+)"); |
| 96 | + std::smatch m; |
| 97 | + std::string s = std::format("{}-{}", "mcpp", 2026); |
| 98 | + return std::regex_match(s, m, r) ? static_cast<int>(m.size()) : 0; |
| 99 | +} |
| 100 | +EOF |
| 101 | +if "$MCPP" build > s4.log 2>&1 && "$(bin_of target eco)" | grep -q '^zlib '; then |
| 102 | + pass "4 compat.zlib and mcpplibs.cmdline from the index build and run" |
| 103 | +else fail "4 index packages" s4.log; fi |
| 104 | + |
| 105 | +# ── 5. CHANGE: the status row counts the work of the build ───────────────── |
| 106 | +# A pty makes the status row appear; the project of section 4 has a |
| 107 | +# dependency the global cache serves after its first build. |
| 108 | +cd "$W/s4" |
| 109 | +if command -v script > /dev/null 2>&1 && [ -f s4.log ] && grep -q 'Finished' s4.log; then |
| 110 | + "$MCPP" clean > /dev/null 2>&1 |
| 111 | + MCPP_PROGRESS=plain script -qefc "stty cols 160 rows 40; $MCPP build" s5.pty > /dev/null 2>&1 |
| 112 | + rows=$(sed 's/\x1b\[[0-9;?]*[A-Za-z]//g' s5.pty | tr '\r' '\n') |
| 113 | + # The units the cache placed, from the `Cached ... (N units)` lines; the |
| 114 | + # Building total must not include them (2026.9.30.1 counted each one). |
| 115 | + placed=$(echo "$rows" | grep -a '^ *Cached ' | grep -ao '[0-9]* units\?)' | grep -o '^[0-9]*' \ |
| 116 | + | awk '{s += $1} END {print s + 0}') |
| 117 | + building=$(echo "$rows" | grep -ao 'Building [0-9]*/[0-9]*' | head -1) |
| 118 | + total=${building##*/} |
| 119 | + if [ -n "$total" ] && [ "$placed" -gt 0 ] && [ "$total" -lt "$placed" ]; then |
| 120 | + pass "5 CHANGE: Building counts $total steps, not the $placed units placed from the cache" |
| 121 | + else fail "5 CHANGE: the status row (first Building: '$building', units placed: $placed)" s5.pty; fi |
| 122 | +else skip "5 no script(1) or section 4 did not build"; fi |
| 123 | + |
| 124 | +# ── 6. CHANGE: #744, the vendored xlings comes from the newest source ────── |
| 125 | +rm -rf "$W/s6"; mkdir -p "$W/s6/rel/bin" "$W/s6/rel/registry/bin" "$W/s6/pathbin"; cd "$W/s6" |
| 126 | +NINJA=$(ls "$HOME"/.mcpp/registry/data/xpkgs/xim-x-ninja/*/ninja 2>/dev/null | head -1) |
| 127 | +REAL="$HOME/.mcpp/registry/bin/xlings" |
| 128 | +if [ -n "$NINJA" ] && [ -x "$REAL" ]; then |
| 129 | + older=$("$NINJA" --version | head -1) |
| 130 | + export_home="$W/s6/home" |
| 131 | + cp "$MCPP" rel/bin/mcpp |
| 132 | + cp "$NINJA" rel/registry/bin/xlings |
| 133 | + cp "$REAL" pathbin/xlings |
| 134 | + MCPP_HOME="$export_home" MCPP_OFFLINE=1 rel/bin/mcpp self env > setup.log 2>&1 || true |
| 135 | + mkdir -p "$export_home/registry/bin"; rm -f "$export_home/registry/bin/xlings"; cp "$NINJA" "$export_home/registry/bin/xlings" |
| 136 | + env -u MCPP_VENDORED_XLINGS MCPP_HOME="$export_home" MCPP_OFFLINE=1 PATH="$W/s6/pathbin:/usr/bin:/bin" \ |
| 137 | + rel/bin/mcpp self env > s6.out 2> s6.err || true |
| 138 | + if grep -q "vendored xlings $older -> .* from PATH" s6.err; then |
| 139 | + pass "6 CHANGE: a newer xlings on PATH replaced the vendored one past an older released copy" |
| 140 | + else fail "6 CHANGE: #744" s6.err; fi |
| 141 | +else skip "6 no ninja payload or vendored xlings to stand in"; fi |
| 142 | + |
| 143 | +# ── 7. planning states its phases ────────────────────────────────────────── |
| 144 | +cd "$W/s4" && touch src/main.cpp |
| 145 | +if MCPP_VERBOSE=1 "$MCPP" build > s7.log 2>&1 && grep -q 'build/stage: plan scan' s7.log; then |
| 146 | + pass "7 CHANGE: planning states its phases under build/stage" |
| 147 | +else fail "7 CHANGE: the planning phase timers" s7.log; fi |
| 148 | + |
| 149 | +echo |
| 150 | +echo "RESULT: $passes passed, $fails failed, $skips skipped (mcpp $VER)" |
| 151 | +[ "$fails" -eq 0 ] |
0 commit comments