Skip to content

Commit 42ae385

Browse files
committed
e2e 290: "unchanged" is compared against the inherited value, not a pattern
Half one rejected a first PATH entry matching `*/subos/*/bin`. CI's own PATH already begins with one — the runner activates an xlings environment to get mcpp at all: MCPP: /home/runner/.xlings/subos/default/bin/mcpp so the test reported mcpp prepending something that was already there, and turned the shard red on a build that behaved correctly. A test for "did not change it" has to hold the before and the after side by side. Both halves now compare against `$PATH` as the test process had it: the plain project's child PATH must equal it byte for byte, and the declaring project's must be one new entry followed by exactly it. Verified under a PATH shaped like CI's, where the old criterion lied.
1 parent 3271d3d commit 42ae385

2 files changed

Lines changed: 54 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@
178178
| 287 | 交叉到 aarch64,断言 outline-atomics 辅助函数与 LSE 指令数,qemu 真跑 |
179179
| 288 | 无 OS 无 C 库,断言 c-abi 那一行的****``(不是断言它缺席),并在 qemu 里真启动 |
180180
| 289 | **一台宿主横扫四个目标** —— 这个体系本就是通用交叉构建,传统栈要六个 runner 的覆盖,这里一个循环 |
181-
| 290 | 声明把环境放到 `PATH` 前面,**而且只有声明会** —— 两个方向各一条断言 |
181+
| 290 | 声明把环境放到 `PATH` 前面,**而且只有声明会** —— 两半都对着**继承的那个值**比对,不是比对一个模式 |
182182
| 291 | `dynamic` 只在 C 库来自图时被拒 —— 且断言产物的 `DT_NEEDED` 而非只断言文案 |
183183

184184
- **⚠️ 上面这张表里的 285–289,此前一条都没在 CI 跑过。**

tests/e2e/290_the_declaration_puts_an_environment_in_front.sh

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# ⚠️ IT USED TO FIND WHATEVER THE MACHINE HAD, AND `command -v` CANNOT TELL THE
66
# DIFFERENCE. Measured 2026-08-25 with a build program that printed its own
7-
# PATH: mcpp's own environment appeared nowhere in it, and
7+
# PATH: the project's declared environment appeared nowhere in it, and
88
# `command -v qemu-system-riscv64` returned a shim that answers, when run,
99
#
1010
# [error] qemu-system-riscv64 is not installed in this subos (_)
@@ -13,11 +13,21 @@
1313
# environment the build could not reach.
1414
#
1515
# ⭐⭐ THIS FILE ASSERTS BOTH DIRECTIONS, BECAUSE ONLY ONE OF THEM IS THE
16-
# FEATURE. Prepending unconditionally would have passed the first half and is
17-
# the design that was withdrawn: a shared directory in front of every project
18-
# makes what a build sees depend on what else was installed on that machine.
19-
# The declaration is what puts it there, so a project that declares nothing
20-
# must come out byte-for-byte unchanged.
16+
# FEATURE. Prepending unconditionally would pass the "declared" half, and that
17+
# is the design that was withdrawn: a shared directory in front of every
18+
# project makes what a build sees depend on what else was installed on that
19+
# machine. The declaration is what puts it there, so a project that declares
20+
# nothing must come out unchanged.
21+
#
22+
# ⚠️⚠️ AND "UNCHANGED" IS COMPARED AGAINST THE INHERITED VALUE, NOT AGAINST A
23+
# PATTERN. The first version of this half rejected a first entry matching
24+
# `*/subos/*/bin` — and CI's own PATH already begins with one, because the
25+
# runner activates an xlings environment to get mcpp at all:
26+
#
27+
# MCPP: /home/runner/.xlings/subos/default/bin/mcpp
28+
#
29+
# so it reported mcpp prepending something that was already there. A test for
30+
# "did not change it" has to hold the before and the after side by side.
2131
set -e
2232

2333
MCPP="${MCPP:-mcpp}"
@@ -31,26 +41,15 @@ probe='import std;
3141
3242
int main() {
3343
const char* p = std::getenv("PATH");
34-
std::string_view path(p ? p : "");
35-
std::string_view first;
36-
for (auto part : std::views::split(path, '"'"':'"'"')) {
37-
first = std::string_view(part);
38-
break;
39-
}
40-
std::println("PROBE_FIRST={}", first);
41-
std::println("PROBE_ENTRIES={}", std::ranges::count(path, '"'"':'"'"') + 1);
44+
std::println("PROBE_PATH={}", p ? p : "");
4245
return 1;
4346
}'
4447

45-
# Returns "<first>|<entries>", or nothing if the program did not run.
48+
# Echoes the child's PATH, or nothing if the build program did not run.
4649
run_probe() {
47-
local dir="$1"
48-
local out
50+
local dir="$1" out
4951
out="$(cd "$dir" && "$MCPP" build 2>&1 || true)"
50-
local f e
51-
f="$(printf '%s\n' "$out" | grep -oP 'PROBE_FIRST=\K.*' | head -1)"
52-
e="$(printf '%s\n' "$out" | grep -oP 'PROBE_ENTRIES=\K[0-9]+' | head -1)"
53-
[ -n "$f" ] && printf '%s|%s\n' "$f" "$e"
52+
printf '%s\n' "$out" | grep -oP 'PROBE_PATH=\K.*' | head -1
5453
}
5554

5655
make_project() {
@@ -62,23 +61,26 @@ make_project() {
6261
printf '%s\n' "$probe" > "$dir/build.mcpp"
6362
}
6463

64+
# The value every assertion below is relative to. mcpp inherits this shell's
65+
# PATH, so this is exactly what an unchanged child would report.
66+
inherited="$PATH"
67+
6568
# ── Half one: a project that declared nothing ─────────────────────────────
6669
make_project "$work/plain" ""
6770
plain="$(run_probe "$work/plain")"
6871
if [ -z "$plain" ]; then
6972
echo "SKIP: the build program did not report — it may not have run here"
7073
exit 0
7174
fi
72-
plain_first="${plain%%|*}"
7375

74-
case "$plain_first" in
75-
*/subos/*/bin)
76-
echo "FAIL: a project that declared no environment got one in front anyway"
77-
echo " got: $plain_first"
78-
exit 1 ;;
79-
*)
80-
echo " ok a project that declares nothing keeps the PATH it was given" ;;
81-
esac
76+
if [ "$plain" = "$inherited" ]; then
77+
echo " ok a project that declares nothing gets the PATH mcpp was started with"
78+
else
79+
echo "FAIL: a project that declared no environment had its PATH changed"
80+
diff <(printf '%s\n' "$inherited" | tr ':' '\n') \
81+
<(printf '%s\n' "$plain" | tr ':' '\n') | head -6 | sed 's/^/ /'
82+
exit 1
83+
fi
8284

8385
# ── Half two: the same project, declaring one ─────────────────────────────
8486
#
@@ -93,25 +95,31 @@ if [ -z "$declared" ]; then
9395
echo "SKIP: the declaring project's build program did not report"
9496
exit 0
9597
fi
96-
declared_first="${declared%%|*}"
97-
declared_entries="${declared##*|}"
9898

99-
case "$declared_first" in
100-
*/subos/*/bin)
101-
echo " ok a project that declares one gets it first: $declared_first" ;;
102-
*)
103-
echo "FAIL: the declared environment is not at the front of PATH"
104-
echo " got: $declared_first"
105-
exit 1 ;;
99+
if [ "$declared" = "$inherited" ]; then
100+
echo "FAIL: the declaration changed nothing — the environment is not in front"
101+
echo " PATH: $(printf '%s' "$declared" | cut -c1-100)"
102+
exit 1
103+
fi
104+
105+
first="${declared%%:*}"
106+
case "$first" in
107+
*/subos/*/bin) echo " ok the declared environment is first: $first" ;;
108+
*) echo "FAIL: something other than a subos was prepended"
109+
echo " got: $first"
110+
exit 1 ;;
106111
esac
107112

108-
# ⭐ AND THE HOST IS STILL BEHIND IT. One entry means the inherited PATH was
109-
# replaced rather than extended, which would break every build program that
110-
# calls `git`, `python3` or a shell.
111-
if [ "${declared_entries:-1}" -gt 1 ]; then
112-
echo " ok the inherited PATH survives behind it ($declared_entries entries)"
113+
# ⭐ AND THE INHERITED VALUE IS STILL THERE, WHOLE, BEHIND IT. Prefixing means
114+
# the rest is untouched; a test that only checked the first entry would pass on
115+
# a PATH that had thrown everything else away, which would break every build
116+
# program that calls `git`, `python3` or a shell.
117+
if [ "${declared#*:}" = "$inherited" ]; then
118+
echo " ok and the inherited PATH follows it, unchanged"
113119
else
114-
echo "FAIL: PATH was replaced, not prefixed — only $declared_entries entry"
120+
echo "FAIL: the inherited PATH was not preserved behind the prefix"
121+
diff <(printf '%s\n' "$inherited" | tr ':' '\n') \
122+
<(printf '%s\n' "${declared#*:}" | tr ':' '\n') | head -6 | sed 's/^/ /'
115123
exit 1
116124
fi
117125

0 commit comments

Comments
 (0)