Twelve scripts retired, three domains registered. `reach` now covers nine. generate: `generate-brands` and `generate-corporations` were the second and third copies of the same 24-line build-if-missing-then-exec bash `tooling/atlas` carried, so they collapsed into `core.process.cargo_binary` rather than being ported. `import_economics` shelled out to the first of those, so it now calls that helper — `generated_brands.toml` comes back byte-identical, and the stamp registry swaps the retired wrapper for `core/process.py`. pr: `watchlist-diff` derives its watched set from `generator_sources.py` instead of restating it, so it cannot drift from the stamp check. dev: the environment scripts split decision from performing, per D-263's guarded-exec rule. `godot_plan()` and `worktree_plan()` decide what would happen; `install_godot()`, `install_rust()` and `setup_worktree()` do it. `tooling/test_environment.py` pins the version pin, both override precedences, the already-current skip, the platform refusal and both worktree refusals — none of them performed. `make setup` now installs reach first, since the targets that install rust and godot are reach verbs. Two live bugs found while porting: - The clerk read its decision index from `decisions/README.md`, a path that stopped existing when the DQR tree moved to `governance/`. Every clerk agent has been grepping blind; its prompt pointed at the same dead directory. - The conformance exec-check matched any `x.system()` regardless of receiver, so `platform.system()` read as `os.system()`. Narrowed and re-proved against a real mutant. `process.run` gains `input=`, `timeout=` and a `ProcessTimeout` subclass so a killed run stays distinguishable from a verdict. The pre-push hook no longer merges the clerk's stderr into its stdout — under streaming the last merged line is a JSONL event, which would read as an unrecognised verdict and block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
155 lines
6.1 KiB
Python
155 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""The `reach dev` environment decisions, exercised without performing them (T-1286).
|
|
|
|
`install-godot`, `install-rust` and `worktree-setup` were the three scripts the
|
|
CLI port stood to gain least from and risked most on. Nothing about them can be
|
|
checked by running them: a passing test would download a 60 MB archive, mutate
|
|
`~/bin`, or leave a git worktree behind. So the port split each one into a pure
|
|
decision (`godot_plan`, `worktree_plan`) and a thin performing half, and this
|
|
file pins the decisions.
|
|
|
|
That split is the whole claim of the port, which is why it gets a test rather
|
|
than a manual `--plan` run: an installer that cannot be tested has to be
|
|
trusted instead, and trusting an installer is how a working environment becomes
|
|
an unreproducible one.
|
|
|
|
Run: python3 tooling/test_environment.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
from tooling.core.errors import ReachError # noqa: E402
|
|
from tooling.domains.dev import environment # noqa: E402
|
|
|
|
|
|
def test_plan_names_the_pinned_version(failures: list[str]) -> None:
|
|
"""The default plan targets DEFAULT_GODOT_VERSION and builds a real URL."""
|
|
plan = environment.godot_plan()
|
|
if plan.wanted != environment.DEFAULT_GODOT_VERSION:
|
|
failures.append(
|
|
f"godot_plan() wanted {plan.wanted}, expected the pinned "
|
|
f"{environment.DEFAULT_GODOT_VERSION}"
|
|
)
|
|
if plan.wanted not in plan.url or plan.filename not in plan.url:
|
|
failures.append(f"the URL does not carry version and filename: {plan.url}")
|
|
if not plan.url.startswith("https://"):
|
|
failures.append(f"the download URL is not https: {plan.url}")
|
|
|
|
|
|
def test_explicit_version_overrides_the_pin(failures: list[str]) -> None:
|
|
plan = environment.godot_plan("4.9")
|
|
if plan.wanted != "4.9" or "4.9-stable" not in plan.url:
|
|
failures.append(f"an explicit version did not reach the URL: {plan.url}")
|
|
|
|
|
|
def test_env_var_overrides_the_pin(failures: list[str]) -> None:
|
|
"""GODOT_VERSION is how make setup passes the version through."""
|
|
before = os.environ.get("GODOT_VERSION")
|
|
os.environ["GODOT_VERSION"] = "4.7"
|
|
try:
|
|
if environment.godot_plan().wanted != "4.7":
|
|
failures.append("GODOT_VERSION was ignored by godot_plan()")
|
|
# An explicit argument still wins over the environment.
|
|
if environment.godot_plan("4.8").wanted != "4.8":
|
|
failures.append("an explicit version lost to GODOT_VERSION")
|
|
finally:
|
|
if before is None:
|
|
del os.environ["GODOT_VERSION"]
|
|
else:
|
|
os.environ["GODOT_VERSION"] = before
|
|
|
|
|
|
def test_already_current_is_decided_not_performed(failures: list[str]) -> None:
|
|
"""already_current compares installed against wanted — the skip decision."""
|
|
real = environment.godot_plan()
|
|
same = type(real)(
|
|
wanted="4.6", installed="4.6", platform_tag=real.platform_tag,
|
|
url=real.url, filename=real.filename,
|
|
)
|
|
differs = type(real)(
|
|
wanted="4.6", installed="4.5", platform_tag=real.platform_tag,
|
|
url=real.url, filename=real.filename,
|
|
)
|
|
absent = type(real)(
|
|
wanted="4.6", installed=None, platform_tag=real.platform_tag,
|
|
url=real.url, filename=real.filename,
|
|
)
|
|
if not same.already_current:
|
|
failures.append("a matching installed version was not treated as current")
|
|
if differs.already_current:
|
|
failures.append("a mismatched version was treated as current — install skipped")
|
|
if absent.already_current:
|
|
failures.append("a missing install was treated as current — install skipped")
|
|
|
|
|
|
def test_unsupported_platform_names_a_remedy(failures: list[str]) -> None:
|
|
"""An unknown platform must fail loudly, not build a URL that 404s."""
|
|
saved = dict(environment.PLATFORMS)
|
|
environment.PLATFORMS.clear()
|
|
try:
|
|
environment.godot_plan()
|
|
failures.append("an unsupported platform produced a plan instead of an error")
|
|
except ReachError as exc:
|
|
if not exc.fix:
|
|
failures.append("the unsupported-platform error carries no fix")
|
|
finally:
|
|
environment.PLATFORMS.update(saved)
|
|
|
|
|
|
def test_worktree_plan_targets_the_convention(failures: list[str]) -> None:
|
|
"""D-221: worktrees live at <root>/.worktrees/<branch>."""
|
|
target = environment.worktree_plan("some-unused-branch-name")
|
|
expected = environment.config.repo_root() / ".worktrees" / "some-unused-branch-name"
|
|
if target != expected:
|
|
failures.append(f"worktree_plan gave {target}, expected {expected}")
|
|
|
|
|
|
def test_worktree_plan_refuses_an_existing_tree(failures: list[str]) -> None:
|
|
"""Silently reusing a directory is how two branches share one tree."""
|
|
root = environment.config.repo_root()
|
|
with tempfile.TemporaryDirectory(dir=root / ".worktrees") as existing:
|
|
name = Path(existing).name
|
|
try:
|
|
environment.worktree_plan(name)
|
|
failures.append("worktree_plan accepted a branch whose directory exists")
|
|
except ReachError as exc:
|
|
if not exc.fix:
|
|
failures.append("the existing-worktree error carries no fix")
|
|
|
|
|
|
def main() -> int:
|
|
# .worktrees/ must exist for the collision test to place a directory in it.
|
|
(environment.config.repo_root() / ".worktrees").mkdir(exist_ok=True)
|
|
|
|
failures: list[str] = []
|
|
test_plan_names_the_pinned_version(failures)
|
|
test_explicit_version_overrides_the_pin(failures)
|
|
test_env_var_overrides_the_pin(failures)
|
|
test_already_current_is_decided_not_performed(failures)
|
|
test_unsupported_platform_names_a_remedy(failures)
|
|
test_worktree_plan_targets_the_convention(failures)
|
|
test_worktree_plan_refuses_an_existing_tree(failures)
|
|
|
|
if failures:
|
|
print("test_environment: FAIL", file=sys.stderr)
|
|
for failure in failures:
|
|
print(f" - {failure}", file=sys.stderr)
|
|
return 1
|
|
print(
|
|
"test_environment: OK — version pin, overrides, skip decision, "
|
|
"platform refusal and worktree placement, none of them performed"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|