Reuse a matching ARM64 Visual Studio environment instead of growing its paths on each initialization. Preserve Rust and Cargo homes before isolating bundle state. Hand the private Termux assembly directory to its container user and restore host ownership afterward. Verified targeted tests, real ARM64 SDK/OpenSSL execution, Rust compilation, and container facts publication. Full release packages were not rebuilt. The Windows x64 source-build timeout and the unchanged Termux linkage fixture failure remain unresolved.
79 lines
3.2 KiB
Python
79 lines
3.2 KiB
Python
"""Exercise the build mount's real Linux UID permissions, not Android binaries."""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
# Only the stdlib facts publisher runs here; the bionic tool bytes are never
|
|
# executed. A native Linux image keeps this kernel-permissions test off QEMU.
|
|
IMAGE = "python@sha256:ffb752e139c0a19692a43af8d8523b274222dd68eebad5d583b45c2201c6e30a"
|
|
|
|
|
|
@pytest.mark.platforms("linux")
|
|
@pytest.mark.parametrize("build_exit", [0, 23], ids=["success", "failed-build"])
|
|
def test_assembly_publishes_facts_as_termux_uid_and_returns_host_owned_output(tmp_path, build_exit):
|
|
if not shutil.which("docker"):
|
|
pytest.skip("Docker is required for the bind-mount ownership regression")
|
|
available = subprocess.run(
|
|
["docker", "info"], capture_output=True, timeout=15,
|
|
)
|
|
if available.returncode:
|
|
pytest.skip("Docker daemon is unavailable")
|
|
image = subprocess.run(
|
|
["docker", "image", "inspect", IMAGE], capture_output=True, timeout=15,
|
|
)
|
|
if image.returncode:
|
|
pytest.skip(f"Pre-pull the native Linux test image: docker pull {IMAGE}")
|
|
|
|
payload = tmp_path / "payload"
|
|
payload.mkdir()
|
|
for name in ("python", "uv"):
|
|
entry = payload / name
|
|
entry.mkdir()
|
|
(entry / "bytes").write_bytes(b"read-only staged tool bytes")
|
|
before = {path: (path.stat().st_uid, path.stat().st_gid, path.stat().st_mode)
|
|
for path in payload.rglob("*")}
|
|
result = subprocess.run(
|
|
["bash", "-c", r'''
|
|
set -euo pipefail
|
|
IMAGE="$1"; PAYLOAD_ABS="$2"; REPO="$3"
|
|
source "$REPO/scripts/termux/assembly_permissions.sh"
|
|
prepare_assembly
|
|
trap restore_assembly_owner EXIT
|
|
docker run --rm --user 1000:1000 --network none \
|
|
-v "$ASSEMBLY:/assembly" \
|
|
-v "$PAYLOAD_ABS/python:/assembly/tools/python:ro" \
|
|
-v "$PAYLOAD_ABS/uv:/assembly/tools/uv:ro" \
|
|
-v "$REPO:/app:ro" \
|
|
-e HERMES_HOME=/tmp/hermes \
|
|
"$IMAGE" sh -ec '
|
|
python -B /app/scripts/termux/build_environment.py prepare-tools \
|
|
--root /assembly --source-tools /assembly/tools
|
|
mkdir /assembly/venv /assembly/pm-runtime
|
|
umask 077
|
|
printf "private build output" > /assembly/pm-runtime/receipt
|
|
stat -c "%u:%g" /assembly/tools/facts.json > /assembly/writer
|
|
exit "$1"
|
|
' sh "$4"
|
|
''', "assembly-permissions-test", IMAGE, str(payload), str(ROOT), str(build_exit)],
|
|
cwd=ROOT, capture_output=True, text=True, timeout=90,
|
|
)
|
|
assert result.returncode == build_exit, result.stdout + result.stderr
|
|
assembly, = payload.glob(".environments-*")
|
|
facts = json.loads((assembly / "tools/facts.json").read_text())
|
|
assert set(facts["packages"]) == {"python", "uv"}
|
|
assert (assembly / "writer").read_text().strip() == "1000:1000"
|
|
assert (assembly / "pm-runtime/receipt").read_text() == "private build output"
|
|
for path in [assembly, *assembly.rglob("*")]:
|
|
info = path.stat()
|
|
assert (info.st_uid, info.st_gid) == (os.getuid(), os.getgid()), path
|
|
assert not info.st_mode & 0o022, path
|
|
assert {path: (path.stat().st_uid, path.stat().st_gid, path.stat().st_mode)
|
|
for path in before} == before
|
|
shutil.rmtree(assembly)
|