164 lines
5.7 KiB
Python
164 lines
5.7 KiB
Python
"""Python UPM system tests — pip, uv, poetry, pipx via bare prebake."""
|
|
|
|
import os
|
|
import pytest
|
|
from probes import binary_exists
|
|
|
|
RUST_LOG = os.environ.get("RUST_LOG", "info")
|
|
|
|
|
|
def _yml(actions_yaml):
|
|
return """\
|
|
version: "1.0"
|
|
environment:
|
|
builder: "baremetal"
|
|
bootstrap:
|
|
user: "vulcan"
|
|
security:
|
|
drop_after: never
|
|
dependencies:
|
|
config:
|
|
repology_endpoint: none
|
|
system:
|
|
pacman:
|
|
packages:
|
|
user:
|
|
python:
|
|
""" + _indent(actions_yaml, 6)
|
|
|
|
|
|
def _indent(text, spaces):
|
|
pad = " " * spaces
|
|
return "\n".join(pad + line if line.strip() else line
|
|
for line in text.splitlines())
|
|
|
|
|
|
def _query(cmd, timeout=60):
|
|
import subprocess
|
|
try:
|
|
r = subprocess.run(cmd, capture_output=True, text=True,
|
|
timeout=timeout, env={**os.environ, "HOME": "/root"})
|
|
return {"success": r.returncode == 0, "stdout": r.stdout, "stderr": r.stderr}
|
|
except Exception as e:
|
|
return {"success": False, "stdout": "", "stderr": str(e)}
|
|
|
|
|
|
def _run_prebake(run_baker, config_path, timeout, operation, expected):
|
|
env_vars = {"RUST_LOG": RUST_LOG}
|
|
r = run_baker("prebake", config_path, timeout=timeout, env=env_vars)
|
|
full_log = "[stdout]\n{}\n[stderr]\n{}".format(r["stdout"], r["stderr"])
|
|
return r, full_log
|
|
|
|
|
|
def _check(r, full_log, operation, expected):
|
|
if r["success"]:
|
|
return
|
|
diagnosis = ""
|
|
try:
|
|
from llm import judge_log
|
|
j = judge_log(operation, expected, full_log)
|
|
diagnosis = "\n LLM diagnosis: [{}] {}".format(j.verdict, j.reason)
|
|
except Exception:
|
|
pass
|
|
pytest.fail("{} failed ({} chars):\n{}\n{}".format(
|
|
operation, len(full_log), full_log, diagnosis))
|
|
|
|
|
|
# ── pip ─────────────────────────────────────────────────────────────
|
|
|
|
def test_pip_install_package(run_baker, work_dir):
|
|
"""Install a package via pip."""
|
|
cfg = os.path.join(work_dir, "python.yml")
|
|
with open(cfg, "w") as f:
|
|
f.write(_yml("""\
|
|
- type: pip
|
|
packages: [requests]
|
|
index: "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
|
args: ["--break-system-packages"]
|
|
"""))
|
|
|
|
r, log = _run_prebake(run_baker, cfg, timeout=120,
|
|
operation="pip install requests", expected="requests module importable")
|
|
_check(r, log, "pip install", "pip installs requests successfully")
|
|
|
|
r2 = _query(["python3", "-c", "import requests"])
|
|
assert r2["success"], f"import requests failed: {r2['stderr']}"
|
|
|
|
|
|
# ── pip + venv ──────────────────────────────────────────────────────
|
|
|
|
def test_pip_venv_install(run_baker, work_dir):
|
|
"""Create venv and install inside it."""
|
|
cfg = os.path.join(work_dir, "python.yml")
|
|
with open(cfg, "w") as f:
|
|
f.write(_yml("""\
|
|
- type: pip
|
|
directory: "/tmp/test-venv"
|
|
index: "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
|
packages: [requests]
|
|
"""))
|
|
|
|
r, log = _run_prebake(run_baker, cfg, timeout=120,
|
|
operation="venv + pip install",
|
|
expected="requests installed inside venv")
|
|
_check(r, log, "pip venv install", "venv created, requests installed")
|
|
|
|
r2 = _query(["/tmp/test-venv/bin/python3", "-c", "import requests"])
|
|
assert r2["success"], f"import from venv failed: {r2['stderr']}"
|
|
|
|
|
|
# ── pip + requirements ──────────────────────────────────────────────
|
|
|
|
def test_pip_requirements_file(run_baker, work_dir):
|
|
"""Install from a requirements.txt file."""
|
|
req_path = os.path.join(work_dir, "requirements.txt")
|
|
with open(req_path, "w") as f:
|
|
f.write("requests\n")
|
|
|
|
cfg = os.path.join(work_dir, "python.yml")
|
|
with open(cfg, "w") as f:
|
|
f.write(_yml("""\
|
|
- type: pip
|
|
directory: "/tmp/test-venv2"
|
|
index: "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
|
manifest: "{}"
|
|
""".format(req_path)))
|
|
|
|
r, log = _run_prebake(run_baker, cfg, timeout=120,
|
|
operation="pip install -r requirements.txt",
|
|
expected="requests installed from requirements file")
|
|
_check(r, log, "pip -r requirements.txt", "pip installs from requirements file")
|
|
|
|
r2 = _query(["/tmp/test-venv2/bin/python3", "-c", "import requests"])
|
|
assert r2["success"], f"import from venv failed: {r2['stderr']}"
|
|
|
|
|
|
# ── multiple actions ────────────────────────────────────────────────
|
|
|
|
def test_multiple_actions(run_baker, work_dir):
|
|
"""Two pip actions in one config."""
|
|
cfg = os.path.join(work_dir, "python.yml")
|
|
with open(cfg, "w") as f:
|
|
f.write(_yml("""\
|
|
- type: pip
|
|
directory: "/tmp/test-venv-a"
|
|
index: "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
|
packages: [requests]
|
|
args: ["--break-system-packages"]
|
|
- type: pip
|
|
directory: "/tmp/test-venv-b"
|
|
index: "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
|
packages: [pytest]
|
|
"""))
|
|
|
|
r, log = _run_prebake(run_baker, cfg, timeout=180,
|
|
operation="two pip actions",
|
|
expected="both venvs have their packages")
|
|
_check(r, log, "multiple pip actions", "both requests and pytest installed")
|
|
|
|
r2 = _query(["/tmp/test-venv-a/bin/python3", "-c", "import requests"])
|
|
assert r2["success"], f"venv-a missing requests: {r2['stderr']}"
|
|
|
|
r3 = _query(["/tmp/test-venv-b/bin/python3", "-c", "import pytest"])
|
|
assert r3["success"], f"venv-b missing pytest: {r3['stderr']}"
|