Files
honey-biscuit-workshop/tests/upm/test_rust.py
T
Catty Steve fd18b547e5 feat(upm): implement Rust user package manager
Adds the `user.rust` UPM to workshop-engine for managing Rust
toolchains,
components, cross-compilation targets, and cargo operations:

- `rustup default <toolchain>` / `rustup component add` / `rustup target
  add`
- `cargo fetch` for dependency pre-fetch with optional `--locked`
- `cargo install` for global crate tools with registry support

Includes 275-line test suite (`tests/upm/test_rust.py`) covering the
full
lifecycle with optional network tests.
2026-04-28 12:20:05 +08:00

276 lines
9.2 KiB
Python

"""Rust UPM system tests driven by bare workshop-baker prebake.
These tests exercise the ``user.rust`` UPM through its full lifecycle:
1. sysdep injection (rustup via pacman, repology_endpoint: none)
2. rustup toolchain / component / target installation
3. cargo fetch (project dependency pre-fetch)
4. cargo install (global crate tools)
Observability
* ``RUST_LOG=info`` is injected into every baker invocation.
* On failure the *full* stdout + stderr is printed (no truncation).
* An LLM judge (``tests/llm/judge.py``) diagnoses failures with a
human-readable reason, cached per-log-hash to save tokens.
Network-dependent tests (toolchain download) are skipped by default.
Set ``RUST_UPM_NETWORK=1`` to run them.
"""
import os
import subprocess
import pytest
from probes import binary_exists
# ── helpers ─────────────────────────────────────────────────────────
RUST_LOG = os.environ.get("RUST_LOG", "info")
def _yml(user_section):
"""Build a minimal prebake.yml focused on the rust UPM."""
return """\
version: "1.0"
environment:
builder: "baremetal"
bootstrap:
user: "vulcan"
security:
drop_after: never
envvars:
prebake:
RUSTUP_DIST_SERVER: "https://mirrors.tuna.tsinghua.edu.cn/rustup"
dependencies:
config:
repology_endpoint: none
system:
pacman:
packages:
user:
rust:
""" + _indent(user_section, 6)
def _indent(text, spaces):
pad = " " * spaces
return "\n".join(pad + line if line.strip() else line
for line in text.splitlines())
def _cargo_toml(dir_path):
path = os.path.join(dir_path, "Cargo.toml")
with open(path, "w") as f:
f.write('[package]\nname = "test"\nversion = "0.1.0"\nedition = "2021"\n')
return path
def _query(cmd, timeout=60):
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):
"""Run prebake with RUST_LOG, return (result, full_log)."""
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):
"""Assert success; on failure call LLM judge and show full output."""
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)
)
# ── system dependency resolution ────────────────────────────────────
def test_repology_none_config(run_baker, work_dir):
cfg = os.path.join(work_dir, "rust.yml")
with open(cfg, "w") as f:
f.write(_yml("""\
cargo: {}
"""))
r, log = _run_prebake(run_baker, cfg, timeout=120,
operation="UPM sysdep: repology_endpoint=none",
expected="rustup gets installed via pacman")
_check(r, log, "repology-none sysdep resolution",
"pacman installs rustup, empty cargo config")
assert binary_exists("/usr/bin/rustup"), "rustup not installed via sysdep"
# ── rustup: toolchain & components ──────────────────────────────────
@pytest.mark.skipif(
"RUST_UPM_NETWORK" not in os.environ,
reason="set RUST_UPM_NETWORK=1 for tests that download toolchain/crates"
)
def test_toolchain_and_component(run_baker, work_dir):
cfg = os.path.join(work_dir, "rust.yml")
with open(cfg, "w") as f:
f.write(_yml("""\
toolchain: "stable"
components: ["rustfmt"]
"""))
r, log = _run_prebake(run_baker, cfg, timeout=600,
operation="rustup default stable + component add rustfmt",
expected="stable toolchain active, rustfmt installed")
_check(r, log, "toolchain + rustfmt installation",
"rustup default stable succeeds; rustfmt available")
assert binary_exists("/usr/bin/rustup"), "rustup not installed"
r2 = _query(["rustup", "which", "rustc"])
assert r2["success"], "rustc not found via rustup which"
assert os.path.isfile(r2["stdout"].strip()), "rustc binary missing"
# ── cross-compilation targets ───────────────────────────────────────
@pytest.mark.skipif(
"RUST_UPM_NETWORK" not in os.environ,
reason="set RUST_UPM_NETWORK=1 for tests that download toolchain/crates"
)
def test_cross_compile_target(run_baker, work_dir):
cfg = os.path.join(work_dir, "rust.yml")
with open(cfg, "w") as f:
f.write(_yml("""\
toolchain: "stable"
targets: ["wasm32-unknown-unknown"]
"""))
r, log = _run_prebake(run_baker, cfg, timeout=600,
operation="rustup target add wasm32-unknown-unknown",
expected="wasm32 target appears in installed list")
_check(r, log, "cross-compilation target installation",
"wasm32-unknown-unknown is installed")
r2 = _query(["rustup", "target", "list", "--installed"])
assert r2["success"], "rustup target list failed"
assert "wasm32-unknown-unknown" in r2["stdout"], "wasm32 target missing"
# ── cargo fetch ─────────────────────────────────────────────────────
@pytest.mark.skipif(
"RUST_UPM_NETWORK" not in os.environ,
reason="set RUST_UPM_NETWORK=1 for tests that download toolchain/crates"
)
def test_cargo_fetch(run_baker, work_dir):
manifest = _cargo_toml(work_dir)
cfg = os.path.join(work_dir, "rust.yml")
with open(cfg, "w") as f:
f.write(_yml("""\
toolchain: "stable"
cargo:
fetch: true
locked: true
manifests: ["{manifest}"]
""".format(manifest=manifest)))
r, log = _run_prebake(run_baker, cfg, timeout=600,
operation="cargo fetch on minimal Cargo.toml",
expected="~/.cargo/registry/cache populated")
_check(r, log, "cargo fetch dependency pre-fetch",
"cargo fetch succeeds, registry cache non-empty")
cache = os.path.expanduser("~/.cargo/registry/cache")
assert os.path.isdir(cache), "registry/cache missing"
assert len(os.listdir(cache)) > 0, "registry/cache empty after fetch"
# ── cargo install ───────────────────────────────────────────────────
@pytest.mark.skipif(
"RUST_UPM_NETWORK" not in os.environ,
reason="set RUST_UPM_NETWORK=1 for tests that download toolchain/crates"
)
def test_cargo_install(run_baker, work_dir):
cfg = os.path.join(work_dir, "rust.yml")
with open(cfg, "w") as f:
f.write(_yml("""\
toolchain: "stable"
cargo:
packages: ["cargo-audit"]
locked: true
"""))
r, log = _run_prebake(run_baker, cfg, timeout=1200,
operation="cargo install cargo-audit",
expected="cargo-audit binary in ~/.cargo/bin/")
_check(r, log, "cargo install crate",
"cargo-audit compiled and installed")
assert binary_exists(
os.path.expanduser("~/.cargo/bin/cargo-audit")
), "cargo-audit not installed"
# ── full flow ───────────────────────────────────────────────────────
@pytest.mark.skipif(
"RUST_UPM_NETWORK" not in os.environ,
reason="set RUST_UPM_NETWORK=1 for tests that download toolchain/crates"
)
def test_full_flow(run_baker, work_dir):
manifest = _cargo_toml(work_dir)
cfg = os.path.join(work_dir, "rust.yml")
with open(cfg, "w") as f:
f.write(_yml("""\
toolchain: "stable"
components: ["rustfmt"]
targets: ["wasm32-unknown-unknown"]
cargo:
fetch: true
locked: true
manifests: ["{manifest}"]
packages: ["cargo-watch"]
""".format(manifest=manifest)))
r, log = _run_prebake(run_baker, cfg, timeout=1200,
operation="full Rust UPM flow",
expected="all stages succeed")
_check(r, log, "full Rust UPM end-to-end",
"toolchain + cross target + cargo fetch + cargo install all pass")
assert binary_exists("/usr/bin/rustup")
r2 = _query(["rustup", "which", "rustc"])
assert r2["success"]
r3 = _query(["rustup", "target", "list", "--installed"])
assert "wasm32-unknown-unknown" in r3["stdout"]
cache = os.path.expanduser("~/.cargo/registry/cache")
assert os.path.isdir(cache) and len(os.listdir(cache)) > 0
assert binary_exists(os.path.expanduser("~/.cargo/bin/cargo-watch"))