Files
SeedPass/scripts/run_ci_tests.sh
T
thePR0M3TH3ANandClaude Opus 5 80a5c3cdb6 Locate the test interpreter via pythonLocation, not just PATH names
The previous attempt failed loudly with the resolved paths, which is what it
was built to do, and they said the rest: on Windows BOTH `python` and
`python3` are MSYS2's. The toolchain that must be on PATH so native wheels
can build is the very thing hiding the interpreter the tests need, and no
bare name can reach past it.

setup-python exports `pythonLocation` (here C:\hostedtoolcache\...\3.10.11)
without necessarily putting it first on PATH, so that is now tried before the
bare names, along with a last-resort candidate derived from wherever `pytest`
itself resolves — the determinism gate proves that one is reachable on this
runner. Selection is still by capability: the first candidate that can
actually import pytest wins, and a total failure prints every path it tried.
It also now echoes the interpreter it chose, so the next surprise of this
kind is one line of log rather than a deduction.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
2026-08-20 09:56:37 -04:00

103 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -eo pipefail
mkdir -p artifacts/coverage
if [[ "${TUI2_SMOKE_GATE:-0}" == "1" ]]; then
./scripts/tui2_check_smoke.sh
fi
# Pick an interpreter that can actually import pytest, not merely the first
# one named "python" on PATH. On Windows the MSYS2 toolchain is prepended to
# PATH so native wheels can build, and MSYS2 ships its own python.exe -- so a
# bare `python` resolved to that one and the whole suite died with "No module
# named pytest", after the determinism gate had already passed using the
# `pytest` executable, which MSYS2 does not provide.
candidates=()
[[ -x ".venv/bin/python" ]] && candidates+=(".venv/bin/python")
# setup-python exports pythonLocation but does not necessarily put it first on
# PATH. On Windows the MSYS2 toolchain is prepended so native wheels can
# build, and it ships its own python.exe AND python3 -- so BOTH bare names
# resolve to an interpreter with no pytest, and the toolchain that has to be
# on PATH is the very thing that hides the interpreter we want.
if [[ -n "${pythonLocation:-}" ]]; then
candidates+=("${pythonLocation}/python" "${pythonLocation}/python.exe" "${pythonLocation}/bin/python")
fi
candidates+=(python python3)
# Last resort: pytest itself is resolvable here (the determinism gate runs it
# that way), so its own directory locates a usable interpreter.
if command -v pytest >/dev/null 2>&1; then
pytest_dir="$(dirname "$(command -v pytest)")"
candidates+=("$pytest_dir/python" "$pytest_dir/python.exe" "$pytest_dir/../python" "$pytest_dir/../python.exe")
fi
py_bin=""
for candidate in "${candidates[@]}"; do
if "$candidate" -c "import pytest" >/dev/null 2>&1; then
py_bin="$candidate"
break
fi
done
if [[ -z "$py_bin" ]]; then
echo "no interpreter can import pytest; tried:" >&2
printf ' %s\n' "${candidates[@]}" >&2
exit 1
fi
echo "test interpreter: $py_bin"
if [[ "${DETERMINISM_GATE:-1}" == "1" ]]; then
./scripts/run_determinism_tests.sh
fi
pytest_args=(-vv)
if [[ -n "${STRESS_ARGS:-}" ]]; then
pytest_args+=(${STRESS_ARGS})
fi
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
pytest_args+=(-n 1)
fi
pytest_args+=(
--cov=src
--cov-report=xml
--cov-report=term-missing
--cov-report=json:artifacts/coverage/coverage.json
--cov-fail-under=20
src/tests
)
timeout_bin="timeout"
if ! command -v "$timeout_bin" >/dev/null 2>&1; then
if command -v gtimeout >/dev/null 2>&1; then
timeout_bin="gtimeout"
else
timeout_bin=""
fi
fi
if [[ -n "$timeout_bin" ]]; then
$timeout_bin 15m "$py_bin" -m pytest "${pytest_args[@]}" 2>&1 | tee pytest.log
status=${PIPESTATUS[0]}
else
echo "timeout command not found; running tests without timeout" >&2
"$py_bin" -m pytest "${pytest_args[@]}" 2>&1 | tee pytest.log
status=${PIPESTATUS[0]}
fi
if [[ $status -eq 124 ]]; then
echo "::error::Tests exceeded 15-minute limit"
tail -n 20 pytest.log
exit 1
fi
if [[ $status -eq 0 && "${CRITICAL_COVERAGE_GATE:-1}" == "1" ]]; then
"$py_bin" scripts/check_critical_coverage.py \
artifacts/coverage/coverage.json \
--json-output artifacts/coverage/critical_gate.full.json
fi
if [[ $status -eq 0 && "${TUI2_COVERAGE_GATE:-0}" == "1" ]]; then
./scripts/tui2_coverage_gate.sh
fi
if [[ $status -eq 0 && "${TUI3_COVERAGE_GATE:-1}" == "1" ]]; then
./scripts/tui3_coverage_gate.sh
fi
exit $status