#!/usr/bin/env bash # SPDX-License-Identifier: EUPL-1.2 # Copyright 2026 luoye-cpu # # scripts/ci/tests/test-toolchain-probe-mingw.sh — planted-failure cases for # the MinGW cross entry of scripts/dev/toolchain-probe.sh (issue #3307). # # Follows the stub pattern of scripts/ci/test-check-toolchain-claims.sh: # fake toolchain prefixes with a stub gcc printing a chosen thread model, # -dumpversion and -print-file-name answers, and a stub objdump printing a # crafted import table, so every refusal path runs without a real MinGW # cross toolchain. # # - Positive: win32-model driver + win32-flavoured runtime -> exit 0 # - Positive: posix-model driver + posix-flavoured runtime -> exit 0 # - Planted negative: win32-model driver + libwinpthread-importing runtime -> exit 1 # - Planted negative: posix-model driver + win32-flavoured runtime -> exit 1 # - Boundary: "13-win32" -dumpversion with resolvable DLLs -> exit 0 (no path # is rebuilt from the version string) # - Planted negative: "13-win32" -dumpversion with unresolvable DLLs -> exit 1 # - Boundary: no cross driver in the prefix -> exit 0, absent line # - Planted negative: driver prints no thread model -> exit 1 # - Planted negative: no objdump to read the import table -> exit 1 # - Large-output (PR #3317 review): a ~2 MiB import table with the # libwinpthread import early or late; win32 must refuse (B24) and posix # must pass. The old printf|grep -q form died of SIGPIPE (pipeline # status 141) on an early match and misreported the imports as "none"; # the here-string read does not. set -euo pipefail repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)" probe="${repo_root}/scripts/dev/toolchain-probe.sh" if [ ! -x "${probe}" ]; then echo "test-toolchain-probe-mingw: probe script missing or non-executable: ${probe}" >&2 exit 1 fi work="$(mktemp -d)" trap 'rm -rf "${work}"' EXIT INT TERM pass_count=0 fail_count=0 probe_out="" probe_rc=0 run_probe() { local prefix="$1" probe_rc=0 probe_out="$(MINGW_CROSS_PREFIX="${prefix}" "${probe}" 2>&1)" || probe_rc=$? } expect_rc() { local name="$1" expected="$2" if [ "${probe_rc}" -eq "${expected}" ]; then echo "PASS: ${name} (exit=${probe_rc})" pass_count=$((pass_count + 1)) else echo "FAIL: ${name} (got exit=${probe_rc}, expected ${expected})" echo "----- captured output -----" printf '%s\n' "${probe_out}" echo "---------------------------" fail_count=$((fail_count + 1)) fi } expect_grep() { local name="$1" pattern="$2" if printf '%s\n' "${probe_out}" | grep -qF -e "${pattern}"; then echo "PASS: ${name}" pass_count=$((pass_count + 1)) else echo "FAIL: ${name} (pattern not in probe output: ${pattern})" echo "----- captured output -----" printf '%s\n' "${probe_out}" echo "---------------------------" fail_count=$((fail_count + 1)) fi } expect_no_grep() { local name="$1" pattern="$2" if ! printf '%s\n' "${probe_out}" | grep -qF -e "${pattern}"; then echo "PASS: ${name}" pass_count=$((pass_count + 1)) else echo "FAIL: ${name} (unwanted pattern in probe output: ${pattern})" echo "----- captured output -----" printf '%s\n' "${probe_out}" echo "---------------------------" fail_count=$((fail_count + 1)) fi } # Precondition for the large-output cases: the stub objdump really emits a # multi-MB import table (the premise the SIGPIPE regression rides on). expect_large_stub() { local name="$1" dir="$2" bytes="" bytes="$("${dir}/bin/x86_64-w64-mingw32-objdump" -p "${dir}/runtime/libstdc++-6.dll" 2>/dev/null | wc -c)" bytes="${bytes//[[:space:]]/}" if [ "${bytes}" -gt 1000000 ]; then echo "PASS: ${name} (stub import table ${bytes} bytes)" pass_count=$((pass_count + 1)) else echo "FAIL: ${name} (stub import table only ${bytes} bytes, expected >1 MB)" fail_count=$((fail_count + 1)) fi } # build_mingw_stub # # [] # Plants a fake toolchain prefix: a stub cross driver answering --version, # -dumpversion, -v and -print-file-name=, fake runtime DLLs, and a stub # target objdump printing the crafted import table of the planted flavour. # large=early|late swaps in a ~2 MiB import table whose libwinpthread line # sits in the first bytes (early) or after the filler (late) — the shape # that made the old printf|grep -q read die of SIGPIPE under pipefail. build_mingw_stub() { local dir="$1" model="$2" dumpversion="$3" winpthread="$4" resolve="$5" local large="${6:-none}" local model_line="" if [ "${model}" != "none" ]; then model_line="Thread model: ${model}" fi mkdir -p "${dir}/bin" "${dir}/runtime" for f in libstdc++-6.dll libgcc_s_seh-1.dll libwinpthread-1.dll; do printf 'planted payload, not a PE: the stub objdump owns the import table\n' >"${dir}/runtime/${f}" done cat >"${dir}/bin/x86_64-w64-mingw32-gcc" <&2 exit 1 ;; esac EOF chmod +x "${dir}/bin/x86_64-w64-mingw32-gcc" cat >"${dir}/bin/x86_64-w64-mingw32-objdump" <&2 exit 1 fi case "\$2" in *libstdc++-6.dll) if [ "\${STUB_LARGE}" != none ]; then # ~2 MiB import table (29000 filler lines x 69 bytes): the # libwinpthread import in the first bytes (early) or after the # filler (late). With the old printf|grep -q form in the probe, an # early match lets grep exit first and the capture pipeline dies of # SIGPIPE under pipefail — status 141 read as "no match" (PR #3317). printf 'peer\n\tDLL Name: libstdc++-6.dll\n' if [ "\${STUB_LARGE}" = early ]; then printf '\tDLL Name: libwinpthread-1.dll\n' fi awk 'BEGIN { for (i = 0; i < 29000; i++) print "import-filler-line 0123456789012345678901234567890123456789012345"; }' if [ "\${STUB_LARGE}" = late ]; then printf '\tDLL Name: libwinpthread-1.dll\n' fi elif [ "\${STUB_WINPTHREAD}" = yes ]; then printf 'peer\n\tDLL Name: libstdc++-6.dll\n\tDLL Name: libwinpthread-1.dll\n' else printf 'peer\n\tDLL Name: libstdc++-6.dll\n' fi ;; *) printf 'peer\n\tDLL Name: libwinpthread-1.dll\n' ;; esac exit 0 EOF chmod +x "${dir}/bin/x86_64-w64-mingw32-objdump" } # ---------- Case 1: Positive — win32 model + win32-flavoured runtime ---------- build_mingw_stub "${work}/case1" win32 13.2.0 no yes run_probe "${work}/case1" expect_rc "positive: win32 driver + win32 runtime -> exit 0" 0 expect_grep "case1 block shows the thread model" "thread-model win32" expect_grep "case1 block shows the flavored dumpversion" "dumpversion 13.2.0" expect_grep "case1 resolves libstdc++-6.dll via -print-file-name" "runtime libstdc++-6.dll=" expect_grep "case1 resolves libgcc_s_seh-1.dll via -print-file-name" "runtime libgcc_s_seh-1.dll=" expect_grep "case1 pairing asserted" "pairing ok" expect_no_grep "case1 no refusal lines" "pairing-mismatch" expect_no_grep "case1 no unresolved lines" "runtime-unresolved" # ---------- Case 2: Planted negative — win32 model + posix-flavoured runtime (B24) ---------- build_mingw_stub "${work}/case2" win32 13.2.0 yes yes run_probe "${work}/case2" expect_rc "planted: win32 driver + libwinpthread-importing runtime -> exit 1" 1 expect_grep "case2 refusal names the pairing mismatch" "pairing-mismatch" expect_grep "case2 refusal names the thread model" "thread-model win32" expect_grep "case2 refusal names the B24 regression" "B24" # ---------- Case 3: Positive — posix model + posix-flavoured runtime ---------- build_mingw_stub "${work}/case3" posix 13.2.0 yes yes run_probe "${work}/case3" expect_rc "positive: posix driver + posix runtime -> exit 0" 0 expect_grep "case3 block shows the thread model" "thread-model posix" expect_grep "case3 pairing asserted" "pairing ok" # ---------- Case 4: Planted negative — posix model + win32-flavoured runtime ---------- build_mingw_stub "${work}/case4" posix 13.2.0 no yes run_probe "${work}/case4" expect_rc "planted: posix driver + runtime without libwinpthread -> exit 1" 1 expect_grep "case4 refusal names the pairing mismatch" "pairing-mismatch thread-model posix" # ---------- Case 5: Boundary — "13-win32" -dumpversion with resolvable DLLs ---------- build_mingw_stub "${work}/case5" win32 13-win32 no yes run_probe "${work}/case5" expect_rc "boundary: 13-win32 dumpversion with resolvable DLLs -> exit 0" 0 expect_grep "case5 block shows the flavored version string" "dumpversion 13-win32" expect_grep "case5 DLLs still resolved (no path rebuilt from -dumpversion)" "runtime libstdc++-6.dll=" expect_grep "case5 pairing still asserted" "pairing ok" # ---------- Case 6: Planted negative — "13-win32" -dumpversion, nothing resolvable ---------- build_mingw_stub "${work}/case6" win32 13-win32 no no run_probe "${work}/case6" expect_rc "planted: 13-win32 dumpversion with unresolvable DLLs -> exit 1" 1 expect_grep "case6 shows the flavored version string" "dumpversion 13-win32" expect_grep "case6 refuses the unresolved libstdc++-6.dll" "runtime-unresolved libstdc++-6.dll" expect_grep "case6 refuses the unresolved libgcc_s_seh-1.dll" "runtime-unresolved libgcc_s_seh-1.dll" expect_no_grep "case6 no pairing lines" "pairing" # ---------- Case 7: Boundary — no cross driver in the prefix -> absent, exit 0 ---------- mkdir -p "${work}/case7/bin" run_probe "${work}/case7" expect_rc "boundary: no cross driver -> exit 0 with an absent line" 0 expect_grep "case7 absent line names the driver" "x86_64-w64-mingw32-gcc absent searched=" expect_grep "case7 absent line names the searched prefix" "${work}/case7/bin" expect_no_grep "case7 no pairing lines" "pairing" # ---------- Case 8: Planted negative — driver prints no thread model ---------- build_mingw_stub "${work}/case8" none 13.2.0 no yes run_probe "${work}/case8" expect_rc "planted: no thread-model line -> exit 1" 1 expect_grep "case8 block shows the unknown model" "thread-model unknown" expect_grep "case8 refusal says the pairing is unverified" "pairing-unverified" expect_no_grep "case8 no green pairing line" "pairing ok" # ---------- Case 9: Planted negative — no objdump to read the import table ---------- build_mingw_stub "${work}/case9" win32 13.2.0 no yes rm -f "${work}/case9/bin/x86_64-w64-mingw32-objdump" run_probe "${work}/case9" # With a plain objdump on PATH the fake DLL is still unreadable, so both the # "no objdump found" and the "objdump exited non-zero" paths land here. expect_rc "planted: no readable import table -> exit 1" 1 expect_grep "case9 refusal says the pairing is unverified" "pairing-unverified" expect_no_grep "case9 no green pairing line" "pairing ok" # ---------- Case 10: Large-output — ~2 MiB table, libwinpthread early, win32 ---------- build_mingw_stub "${work}/case10" win32 13.2.0 yes yes early expect_large_stub "case10 stub emits a multi-MB import table (early)" "${work}/case10" run_probe "${work}/case10" expect_rc "large early: win32 driver + libwinpthread-importing runtime -> exit 1" 1 expect_grep "case10 refusal names the pairing mismatch" "pairing-mismatch thread-model win32" expect_grep "case10 refusal names the B24 regression" "B24" # ---------- Case 11: Large-output — ~2 MiB table, libwinpthread late, win32 ---------- build_mingw_stub "${work}/case11" win32 13.2.0 yes yes late expect_large_stub "case11 stub emits a multi-MB import table (late)" "${work}/case11" run_probe "${work}/case11" expect_rc "large late: win32 driver + libwinpthread-importing runtime -> exit 1" 1 expect_grep "case11 refusal names the pairing mismatch" "pairing-mismatch thread-model win32" expect_grep "case11 refusal names the B24 regression" "B24" # ---------- Case 12: Large-output — ~2 MiB table, libwinpthread early, posix ---------- build_mingw_stub "${work}/case12" posix 13.2.0 yes yes early expect_large_stub "case12 stub emits a multi-MB import table (early)" "${work}/case12" run_probe "${work}/case12" expect_rc "large early: posix driver + posix runtime -> exit 0" 0 expect_grep "case12 pairing asserted over a multi-MB table" "pairing ok thread-model posix" expect_no_grep "case12 no refusal lines" "pairing-mismatch" # ---------- Case 13: Large-output — ~2 MiB table, libwinpthread late, posix ---------- build_mingw_stub "${work}/case13" posix 13.2.0 yes yes late expect_large_stub "case13 stub emits a multi-MB import table (late)" "${work}/case13" run_probe "${work}/case13" expect_rc "large late: posix driver + posix runtime -> exit 0" 0 expect_grep "case13 pairing asserted over a multi-MB table" "pairing ok thread-model posix" expect_no_grep "case13 no refusal lines" "pairing-mismatch" echo "" echo "test-toolchain-probe-mingw: ${pass_count} passed, ${fail_count} failed" if [ "${fail_count}" -ne 0 ]; then exit 1 fi exit 0