fix(termux): classify bionic from its interpreter and run the Linux tests
The POSIX runner had a blank line after exec env, so it printed the environment and never ran pytest. Keep the command attached and prove failure propagation. The actual payload records ANDROID_API_LEVEL in sysconfig; use that instead of looking for text in a guessed libc file. Rebuild unproven runtime-library extracts from verified archives.
This commit is contained in:
3
.github/workflows/termux-verify.yml
vendored
3
.github/workflows/termux-verify.yml
vendored
@@ -44,7 +44,8 @@ jobs:
|
||||
tests/test_termux_python_linkage.py tests/test_termux_retag_wheel.py \
|
||||
tests/test_termux_wheelhouse_cache.py tests/test_termux_runtime_libs.py \
|
||||
tests/test_termux_launchers.py tests/test_pm_bionic.py \
|
||||
tests/pm/test_stage_only.py tests/pm/test_deb_safety.py tests/test_stage_apt_repo.py
|
||||
tests/pm/test_stage_only.py tests/pm/test_deb_safety.py tests/test_stage_apt_repo.py \
|
||||
tests/test_run_tests_shell.py
|
||||
|
||||
native-linkage:
|
||||
name: Reproduce and verify bionic Python linkage
|
||||
|
||||
17
pm/store.py
17
pm/store.py
@@ -76,17 +76,12 @@ def _is_bionic_libc() -> bool:
|
||||
"""
|
||||
if sys.platform == "android":
|
||||
return True
|
||||
# Termux python reports sys.platform == "linux"; ask the C runtime.
|
||||
try:
|
||||
with open("/system/lib/libc.so", "rb") as f:
|
||||
return b"Bionic" in f.read(4096)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
with open("/lib/libc.so", "rb") as f:
|
||||
return b"Bionic" in f.read(4096)
|
||||
except OSError:
|
||||
return False
|
||||
# Termux's pre-3.13 interpreter reports Linux but records its Android
|
||||
# build target in sysconfig. This also works in a container without
|
||||
# the phone's /system mount or a recognizable string in libc's header.
|
||||
import sysconfig
|
||||
|
||||
return bool(sysconfig.get_config_var("ANDROID_API_LEVEL"))
|
||||
|
||||
|
||||
def current_target() -> str:
|
||||
|
||||
@@ -213,5 +213,4 @@ fi
|
||||
echo "▶ posix: per-file parallel suite via run_tests_parallel.py"
|
||||
echo " (TZ=UTC LANG=C.UTF-8 PYTHONHASHSEED=0; clean env)"
|
||||
exec env -i "${HERMETIC_ENV[@]}" \
|
||||
|
||||
"$PYTHON" "$SCRIPT_DIR/run_tests_parallel.py" "$@"
|
||||
|
||||
@@ -41,6 +41,13 @@ def main() -> int:
|
||||
ap.add_argument("--output", type=Path, required=True)
|
||||
ap.add_argument("--built-wheel", type=Path)
|
||||
args = ap.parse_args()
|
||||
sys.path.insert(0, str(args.repo))
|
||||
from pm.store import current_target
|
||||
|
||||
target = current_target()
|
||||
print("PAYLOAD_TARGET", target, "ANDROID_API_LEVEL", sysconfig.get_config_var("ANDROID_API_LEVEL"), flush=True)
|
||||
if target != "linux-arm64-bionic":
|
||||
raise RuntimeError(f"payload interpreter misclassified: {target}")
|
||||
prefix = Path("/data/data/com.termux/files/usr")
|
||||
python = args.payload / "python" / prefix.relative_to("/") / "bin/python3.11"
|
||||
uv = args.payload / "uv" / prefix.relative_to("/") / "bin/uv"
|
||||
|
||||
@@ -112,22 +112,12 @@ def _ensure_extracted(work: Path, name: str, row: dict) -> Path:
|
||||
scratch = work / "dl"
|
||||
scratch.mkdir(parents=True, exist_ok=True)
|
||||
archive = scratch / f"{name}.deb"
|
||||
marker = extract / ".deb-sha256"
|
||||
|
||||
archive_ok = False
|
||||
if archive.exists():
|
||||
try:
|
||||
archive_ok = _sha256_file(archive) == row["sha256"]
|
||||
except OSError:
|
||||
archive_ok = False
|
||||
extraction_ok = (
|
||||
any(extract.glob(f"{PREFIX_REL}/lib/*.so*"))
|
||||
and marker.is_file()
|
||||
and marker.read_text(encoding="utf-8").strip() == row["sha256"]
|
||||
)
|
||||
if extraction_ok and archive_ok:
|
||||
return extract
|
||||
|
||||
if not archive_ok:
|
||||
Download([Source(row["url"], archive, row["sha256"])],
|
||||
partials_dir=scratch).run()
|
||||
@@ -135,7 +125,6 @@ def _ensure_extracted(work: Path, name: str, row: dict) -> Path:
|
||||
shutil.rmtree(extract)
|
||||
extract.mkdir(parents=True, exist_ok=True)
|
||||
_LibDeb(name).unpack(archive, extract, "linux-arm64-bionic")
|
||||
marker.write_text(row["sha256"], encoding="utf-8")
|
||||
return extract
|
||||
|
||||
|
||||
@@ -156,8 +145,8 @@ def stage(payload: Path, table: dict) -> Path:
|
||||
f"{len(list(out.glob('*.so*')))} .so* -> {out}")
|
||||
return out
|
||||
|
||||
# Miss: rebuild the merged dir COMPLETELY from scratch. Nothing from
|
||||
# a previous partial or corrupted state survives.
|
||||
# A cache miss rebuilds from verified archives, never scratch extracts.
|
||||
manifest_path.unlink(missing_ok=True)
|
||||
if out.exists():
|
||||
shutil.rmtree(out)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
29
tests/test_run_tests_shell.py
Normal file
29
tests/test_run_tests_shell.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""The canonical shell must run tests and propagate a failing test's status."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.platforms("posix")
|
||||
def test_shell_runner_executes_tests_and_propagates_failure(tmp_path):
|
||||
case = tmp_path / "test_runner_canary.py"
|
||||
marker = tmp_path / "executed"
|
||||
case.write_text(
|
||||
"from pathlib import Path\n"
|
||||
"def test_canary():\n"
|
||||
f" Path({str(marker)!r}).write_text('executed')\n"
|
||||
" assert False, 'runner failure propagation canary'\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
result = subprocess.run(
|
||||
["bash", str(root / "scripts/run_tests.sh"), "-j", "1", str(case)],
|
||||
cwd=tmp_path, capture_output=True, text=True, timeout=180,
|
||||
env={**os.environ, "HERMES_PYTHON": sys.executable, "HERMES_TEST_FILE_RETRIES": "0"},
|
||||
)
|
||||
assert marker.read_text(encoding="utf-8") == "executed", result.stdout + result.stderr
|
||||
assert result.returncode != 0, result.stdout + result.stderr
|
||||
assert "runner failure propagation canary" in result.stdout + result.stderr
|
||||
Reference in New Issue
Block a user