Skip to content

Commit c83159e

Browse files
committed
fix(e2e): the Windows job's locale, not the guard, decided what 233 could read
`ci-windows-e2e` went red on 233 with UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 3037 from §7, whose `read_text()` names no encoding. Python then decodes with the LOCALE encoding — UTF-8 on the Linux/macOS runners, cp1252 on the Windows one — and every file this test reads (matrix.json, the engine adapters, the READMEs) contains non-ASCII. The crash is the benign half. cp1252 leaves only five byte values undefined, so the reads that DON'T hit one of them succeed and return mojibake: the regex matches nothing and the guard prints its success line while checking nothing. §1 has been reading matrix.json that way from the start and stayed green purely because its non-ASCII bytes missed those five. Same defect, opposite symptom — and the silent one is the symptom this whole test exists to catch, so it must not be the test's own failure mode. Every read now names utf-8, including the `subprocess(text=True)` that decoded git's stdout the same way. And the test exports PYTHONWARNDEFAULTENCODING / PYTHONWARNINGS=error::EncodingWarning so an unspecified encoding is a hard error on the FIRST machine that runs it — this class of bug should not be discoverable only on Windows. Verified both directions: with §7's encoding removed the test exits 1 locally (EncodingWarning), restored it exits 0. The injection asserts its own anchor first, because a sabotage that silently fails to apply reads exactly like a passing test.
1 parent 270bd92 commit c83159e

1 file changed

Lines changed: 37 additions & 9 deletions

File tree

tests/e2e/233_bench_matrix.sh

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@
1919
# 4. the workflow reads the file instead of repeating it.
2020
set -e
2121

22+
# ⚠️ EVERY python read below MUST name its encoding, and this is what enforces it.
23+
#
24+
# `open()`, `read_text()` and `subprocess(text=True)` decode with the LOCALE
25+
# encoding, which is UTF-8 on the Linux and macOS runners and cp1252 on the
26+
# Windows one. Every file this test reads — matrix.json, the engine adapters,
27+
# the READMEs — contains non-ASCII, so on Windows the reads either raise
28+
#
29+
# UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 3037
30+
#
31+
# or, for the bytes cp1252 does happen to map, silently produce mojibake: the
32+
# regex then matches nothing and the guard reports success while guarding
33+
# nothing. That is the same "failure looks like success" shape this whole test
34+
# exists to catch, so it must not be the test's own failure mode.
35+
#
36+
# §1 read matrix.json without an encoding for a while and stayed green purely
37+
# because its non-ASCII bytes missed cp1252's five undefined ones; §7 hit 0x8f
38+
# and turned the whole Windows e2e job red. Both are the same defect.
39+
#
40+
# These two variables turn an unspecified encoding into a hard error, so the
41+
# next one fails on the FIRST machine that runs it rather than only on Windows.
42+
# Ignored by Python < 3.10, which predates EncodingWarning.
43+
export PYTHONWARNDEFAULTENCODING=1
44+
export PYTHONWARNINGS=error::EncodingWarning
45+
2246
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
2347
MATRIX="$ROOT/bench/matrix.json"
2448
WORKFLOW="$ROOT/.github/workflows/bench.yml"
@@ -34,7 +58,7 @@ SPEC="$ROOT/bench/SPEC.md"
3458
python3 - "$MATRIX" "$ROOT" <<'PY'
3559
import json, os, re, sys
3660
37-
m = json.load(open(sys.argv[1]))
61+
m = json.load(open(sys.argv[1], encoding="utf-8"))
3862
axes = m["axes"]
3963
fail = []
4064
@@ -186,7 +210,11 @@ tracked = subprocess.run(
186210
["git", "-C", root, "ls-files",
187211
"bench/projects/*/.xmake*", "bench/projects/*/build/*",
188212
"bench/projects/*/CMakeCache.txt", "bench/projects/*/bazel-*"],
189-
capture_output=True, text=True).stdout.split()
213+
# encoding pinned, not `text=True` alone: that decodes the child's stdout
214+
# with the LOCALE encoding, which on a Windows runner is cp1252. A path (or
215+
# any UTF-8 byte) then either raises or, worse, mojibakes into something
216+
# that no longer matches — a guard that silently stops guarding.
217+
capture_output=True, encoding="utf-8").stdout.split()
190218
if tracked:
191219
fail.append("engine scratch is tracked in git (machine-local state, and one of "
192220
f"these froze a fixed bug into CI): {tracked[:4]}"
@@ -211,7 +239,7 @@ if not re.match(r"^\d+(\.\d+)+$", str(m.get("reference_mcpp", ""))):
211239
# some other release, with every ratio still looking perfectly reasonable.
212240
xlings_pin = os.path.join(root, ".xlings.json")
213241
if os.path.isfile(xlings_pin):
214-
ws = json.load(open(xlings_pin)).get("workspace", {}).get("mcpp")
242+
ws = json.load(open(xlings_pin, encoding="utf-8")).get("workspace", {}).get("mcpp")
215243
if ws and ws != m.get("reference_mcpp"):
216244
fail.append(f"reference_mcpp={m.get('reference_mcpp')} but .xlings.json bootstraps "
217245
f"mcpp {ws} — the reference arm IS the bootstrapped binary, so these "
@@ -239,7 +267,7 @@ PY
239267
python3 - "$MATRIX" "$ROOT/bench/src/spec.cppm" "$ROOT/bench/src/registry.cppm" <<'PY'
240268
import json, re, sys
241269
242-
m = json.load(open(sys.argv[1]))
270+
m = json.load(open(sys.argv[1], encoding="utf-8"))
243271
spec = open(sys.argv[2], encoding="utf-8").read()
244272
registry = open(sys.argv[3], encoding="utf-8").read()
245273
fail = []
@@ -283,7 +311,7 @@ if not os.path.isfile(data):
283311
raise SystemExit(0)
284312
285313
truth = {}
286-
for c in json.load(open(data))["cells"]:
314+
for c in json.load(open(data, encoding="utf-8"))["cells"]:
287315
if c["status"] == "ok":
288316
truth.setdefault(c["engine"], {})[c["scenario"]] = round(c["median_s"], 2)
289317
default = next((k for k in truth if k.startswith("mcpp@") and "+" not in k), None)
@@ -332,7 +360,7 @@ if grep -qE '^\s*case ",\$want," in \*,(linux|macos|windows),\*\)' "$WORKFLOW";
332360
echo "FAIL: bench.yml still enumerates platforms inline; matrix.json owns that list"
333361
exit 1
334362
fi
335-
for img in $(python3 -c "import json,sys;print(' '.join(json.load(open(sys.argv[1]))['runners'].values()))" "$MATRIX"); do
363+
for img in $(python3 -c "import json,sys;print(' '.join(json.load(open(sys.argv[1], encoding='utf-8'))['runners'].values()))" "$MATRIX"); do
336364
if grep -q "runs-on: $img" "$WORKFLOW"; then
337365
echo "FAIL: bench.yml hard-codes runner image '$img'; it must come from matrix.json"
338366
exit 1
@@ -359,7 +387,7 @@ grep -q 'matrix.json' "$SPEC" \
359387
python3 - "$ROOT" <<'PY' || exit 1
360388
import json, pathlib, re, sys
361389
root = pathlib.Path(sys.argv[1])
362-
m = json.loads((root / "bench/matrix.json").read_text())
390+
m = json.loads((root / "bench/matrix.json").read_text(encoding="utf-8"))
363391
bad = []
364392
for c in m["cells"]:
365393
proj = c.get("project", "")
@@ -376,7 +404,7 @@ for c in m["cells"]:
376404
# failed exactly that way.
377405
if not f.exists():
378406
continue
379-
body = re.sub(r"#.*", "", f.read_text())
407+
body = re.sub(r"#.*", "", f.read_text(encoding="utf-8"))
380408
# ANY rule, not `cc_*` specifically. Every bazel rule instantiation
381409
# carries a `name =` attribute; `load()`, `package()` and
382410
# `exports_files()` do not. Matching `cc_binary|cc_library` was wrong:
@@ -420,7 +448,7 @@ for f in root.glob("*.cppm"):
420448
# sees the resolved path.
421449
if f.name == "engine.cppm":
422450
continue
423-
for n, line in enumerate(f.read_text().splitlines(), 1):
451+
for n, line in enumerate(f.read_text(encoding="utf-8").splitlines(), 1):
424452
code = line.split("//", 1)[0]
425453
if re.search(r'compiler\s*==\s*"(clang|gcc)"', code):
426454
bad.append(f"{f.name}:{n}: {line.strip()[:90]}")

0 commit comments

Comments
 (0)