From 5ef25c1c6a006a868c8d985efd744f37449670e5 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 30 Jul 2026 16:39:12 +0800 Subject: [PATCH 1/2] test(e2e): read the cache root from mcpp, not from a bash re-derivation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 170 asserted the staging source lived under ${MCPP_HOME:-$HOME/.mcpp}/build-cache/v1. That re-derivation is wrong for a SELF-CONTAINED install, where the unpacked tree itself is the home — the shape of every release tarball and every `xlings install mcpp`. Running the suite against the released 2026.7.30.2 binary therefore failed the test while the binary was correct: FAIL: std BMI cache is '/build-cache/v1/std/...', expected under '/home/speak/.mcpp/build-cache/v1/std' CI never saw it because the binary under test lives at target///bin/mcpp, which mcpp.home::root() deliberately disqualifies from self-contained detection. The root now comes from `mcpp cache dir`, which exists precisely so "where is the cache" has one answer. Verified passing against both shapes: the released tarball binary and a dev build. --- tests/e2e/170_bmi_staging_no_cascade.sh | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/e2e/170_bmi_staging_no_cascade.sh b/tests/e2e/170_bmi_staging_no_cascade.sh index 32c513ea..3d9c678e 100755 --- a/tests/e2e/170_bmi_staging_no_cascade.sh +++ b/tests/e2e/170_bmi_staging_no_cascade.sh @@ -9,7 +9,7 @@ # carried no `restat`, so every importer of `import std` rebuilt whenever # the cache-side BMI got a newer mtime (which happens on any cwd change # before the cache-root fix below). -# 2. The staging SOURCE must live under $MCPP_HOME/build-cache/v1/std. That cache +# 2. The staging SOURCE must live under the cache root mcpp itself reports. That cache # used to resolve its root through a private copy of the home logic that # knew neither %USERPROFILE% nor self-contained installs, so on Windows it # parked the cache in the current working directory as `.mcpp-bmi/`. @@ -56,12 +56,22 @@ DST=$(unescape_ninja "$(echo "$EDGE" | awk '{print $2}')") SRC=$(unescape_ninja "$(echo "$EDGE" | awk '{print $NF}')") echo "staging: $SRC -> $DST" -# ── invariant 2: the cache root is $MCPP_HOME/build-cache/v1/std, never a cwd-local -# dir (and never the pre-v1 $MCPP_HOME/bmi tree, which nothing reads now) ── -HOME_ROOT=$(norm_path "${MCPP_HOME:-$HOME/.mcpp}") +# ── invariant 2: the staging SOURCE lives under the tool's OWN cache root, and +# never in a cwd-local directory (nor in the pre-v1 bmi/ tree, which nothing +# reads now) ── +# +# The root is read from `mcpp cache dir` rather than re-derived here as +# ${MCPP_HOME:-$HOME/.mcpp}/build-cache/v1. That re-derivation is wrong for a +# SELF-CONTAINED install, where the unpacked tree itself is the home — which is +# the shape of every release tarball and every `xlings install mcpp`. Re-deriving +# it made this test fail against a released binary while the binary was right, +# and "where is the cache" having two answers in two places is the exact defect +# #311 spent a module (mcpp.home) removing. +CACHE_ROOT=$(norm_path "$("$MCPP" cache dir | head -1)") +[[ -n "$CACHE_ROOT" ]] || { echo "FAIL: mcpp cache dir printed nothing"; exit 1; } case "$(norm_path "$SRC")" in - "$HOME_ROOT"/build-cache/v1/std/*) ;; - *) echo "FAIL: std BMI cache is '$SRC', expected under '$HOME_ROOT/build-cache/v1/std'" + "$CACHE_ROOT"/std/*) ;; + *) echo "FAIL: std BMI cache is '$SRC', expected under '$CACHE_ROOT/std'" exit 1 ;; esac for leftover in "$TMP/.mcpp-bmi" "$TMP/app/.mcpp-bmi"; do From 68f18afd1f1cba7cc6baa588cbfcbce3ec842bf7 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 30 Jul 2026 16:57:05 +0800 Subject: [PATCH 2/2] test(e2e): accept either build verb where a dependency may come from the cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fallout from making the global cache work, and one of the two is a negative assertion my change silently weakened. 163 asserted `Compiling acme.widget`. What it tests is that the descriptor was found BY IDENTITY and the package joined the build; whether its objects were compiled here or served from the cache belongs to a different subsystem. Now that hits actually skip work, a sibling app dir under the same MCPP_HOME — 163 builds several — can legitimately supply them, which is how it failed on Windows CI: Cached acme.widget v1.38.1 (2 units) FAIL: 163_identity_first_resolution.sh 82 is the more important one. Its NEGATIVE check ("widget must not be pulled when the feature is inactive") grepped only `Compiling widget`, so a wrongly-pulled dependency whose objects happened to be cached would announce `Cached widget` and pass. Both directions now match either verb through one `pulled()` helper, so the positive cannot be too narrow and the negative cannot be too weak. 25 and 48 grep the ROOT package's Compiling line, which is never cached; left alone. --- tests/e2e/163_identity_first_resolution.sh | 11 ++++++++++- tests/e2e/82_feature_optional_deps.sh | 15 +++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/e2e/163_identity_first_resolution.sh b/tests/e2e/163_identity_first_resolution.sh index b1ad71e3..f94caf5b 100755 --- a/tests/e2e/163_identity_first_resolution.sh +++ b/tests/e2e/163_identity_first_resolution.sh @@ -78,7 +78,16 @@ EOF mkidx idx1 a/acme.widget.lua acme widget mkapp app1 acme ../idx1 acme widget (cd app1 && "$MCPP" build > out.txt 2>&1) || { cat app1/out.txt; exit 1; } -grep -q "Compiling acme.widget" app1/out.txt || { cat app1/out.txt; exit 1; } +# Either verb: what this test is about is that the descriptor was found BY +# IDENTITY and the package became part of the build. Whether its objects were +# compiled here or served from the global build cache is a different subsystem's +# business — and now that the cache actually works, a sibling app dir under the +# same MCPP_HOME (or a restored CI sandbox) can legitimately supply them. +grep -qE "(Compiling|Cached) acme\.widget" app1/out.txt || { + cat app1/out.txt + echo "FAIL: acme.widget was neither compiled nor served from cache" + exit 1 +} # xlings names the dir {namespace}-x-{literal name} — short name → acme-x-widget test -d "app1/.mcpp/.xlings/data/xpkgs/acme-x-widget" \ || { echo "FAIL: expected store dir acme-x-widget"; ls -R app1/.mcpp/.xlings/data/xpkgs 2>/dev/null; exit 1; } diff --git a/tests/e2e/82_feature_optional_deps.sh b/tests/e2e/82_feature_optional_deps.sh index f0275871..65603fcd 100755 --- a/tests/e2e/82_feature_optional_deps.sh +++ b/tests/e2e/82_feature_optional_deps.sh @@ -46,15 +46,22 @@ EOF cd app -# 1. Feature inactive → widget is NOT pulled (never resolved/compiled). +# Both directions match EITHER verb. A dependency that is in the graph announces +# itself as `Compiling` or, when the global build cache supplies its objects, as +# `Cached` — so "Compiling" alone is too narrow for the positive AND too weak for +# the negative: a wrongly-pulled dependency whose objects happened to be cached +# would print `Cached widget` and slip past a `Compiling`-only check. +pulled() { grep -qE '(Compiling|Cached) widget' "$1"; } + +# 1. Feature inactive → widget is NOT pulled (never resolved, never built). "$MCPP" build > b1.log 2>&1 || { cat b1.log; echo "FAIL: baseline build failed"; exit 1; } -if grep -q 'Compiling widget' b1.log; then +if pulled b1.log; then cat b1.log; echo "FAIL: widget must NOT be pulled when feature inactive"; exit 1 fi -# 2. Feature active → widget IS pulled and compiled like a normal dependency. +# 2. Feature active → widget IS pulled and built like a normal dependency. rm -rf target "$MCPP" build --features extra > b2.log 2>&1 || { cat b2.log; echo "FAIL: feature build failed (widget not pulled?)"; exit 1; } -grep -q 'Compiling widget' b2.log || { cat b2.log; echo "FAIL: widget was not pulled/compiled when feature active"; exit 1; } +pulled b2.log || { cat b2.log; echo "FAIL: widget was not pulled when feature active"; exit 1; } echo "OK"