Files
settled-reach/tooling/test_planet_router.py
T
jpmschweitzerandClaude Opus 5.5 668772075c refactor(tooling): T-1288 — planet-gen becomes reach atlas planet
The 30-file tree moves under atlas as its third rung (D-243), ten verbs
fronting it. Each verb restates its module's options so `--help` describes
something; tooling/test_planet_router.py hands every declared option to the
module's own argparse and fails on drift, and now runs in make test-tooling.

The 2026-09-02 half of this move had converted the top-level imports and the
repo roots. Finishing it found what the half-move left:

- Lazy in-function imports, and all of sol_data/, still named siblings bare.
  They resolved only through sys.path.insert hacks, so under reach the first
  globe render in generate, batch or sol-import would have raised
  ModuleNotFoundError. Qualified; the hacks are gone.
- 247 print() calls and a stdout progress writer that fired once per 8 KB
  block. Report verbs (audit, quality) write through console.out, progress
  through console.event, and download progress is throttled to 10% steps
  so a job log is not tens of thousands of lines.
- Every error exit raises ReachError with a fix.

Two checks that could not fail:

- batch --verify-determinism printed a warning and exited 0 on a mismatch.
- import-provinces exited 0 with errors > 0.

Both now raise. The 271-body bake is only safe to re-run because the first
one holds.

sol-import --body is action="append" in the module but the router took one
value, so --body GJ0d --body GJ0e kept one. Now repeatable, and _flags repeats
list options.

test_conformance walked one level, so a nested group was reported as a verb
missing @command and its ten verbs were never checked. It recurses now;
proven by stripping @command from `planet quality` and watching it fail.

Stray PNGs from the 2026-09-03 runaway router-test run are parked in
.cache/t1288-stray-pngs/, not committed. Their reliefmaps differ from HEAD
while the heightmap regenerated byte-identical — filed as T-1291.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 16:08:02 +02:00

184 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""`atlas planet` declares its options twice — this stops them drifting (T-1288).
The router restates ten argument surfaces that already exist in the modules'
own argparse parsers. That duplication buys real `--help` for an agent, which
a passthrough could not, but it creates the obvious failure: the router grows
an option the module has never heard of, and the mismatch only shows up when
someone runs the command with that flag.
So every option the router declares is handed to the module's own parser here.
A parser rejects an unknown option with SystemExit(2), which is exactly the
signal wanted — no output comparison, no fixtures, no running the generators.
Nothing here executes a generator. Each parser is invoked directly, so a full
pass costs milliseconds and never touches the atlas DB.
Run: python3 tooling/test_planet_router.py
"""
from __future__ import annotations
import argparse
import contextlib
import io
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))
import typer # noqa: E402
from tooling.domains.atlas.planet import router as planet_router # noqa: E402
# verb -> (module attribute, positional arguments the parser requires)
IMPLEMENTATIONS = {
"generate": ("generate", ["body.json"]),
"batch": ("batch", []),
"scaffold": ("scaffold_bodies", ["index.json"]),
"import-heightmaps": ("import_heightmaps", []),
"import-provinces": ("import_province_boundaries", []),
"terrain-reference": ("populate_terrain_reference", []),
"sol-import": ("sol_import", []),
"sol-name-fixes": ("sol_name_fixes", []),
"audit": ("atlas_cohesion_audit", []),
"quality": ("atlas_quality_analysis", []),
}
# A representative value per click param type, so the parser sees a well-formed
# pair. Keyed on the type's `name` because these are ParamType INSTANCES, not
# classes — keying on the class matches nothing and every int option then gets
# the string "x" and reads as a parser rejection.
SAMPLE = {"int": "4", "str": "x", "path": "x", "filename": "x", "float": "1.0"}
# Options whose module-side parser restricts the value. The router cannot send
# a placeholder to these, so the test sends a real one.
CONSTRAINED = {
("generate", "--render-mode"): "cartographic",
("sol-import", "--render-mode"): "cartographic",
}
def _router_options(verb: str) -> list[tuple[str, type]]:
"""The long options the router declares for a verb, with their types."""
command = typer.main.get_command(planet_router.app).commands[verb] # type: ignore[attr-defined]
found: list[tuple[str, type]] = []
for param in command.params:
for opt in getattr(param, "opts", []):
if opt.startswith("--") and opt != "--help":
found.append((opt, param.type))
return found
class _Parsed(BaseException):
"""Raised the instant a parser accepts, to stop before any work happens."""
def _parser_accepts(module, argv: list[str]) -> tuple[bool, str]:
"""Feed argv to the module's own parser, and stop the moment it accepts.
`main()` builds its parser and then does the work, so simply calling it
would generate planets. Patching `parse_args` lets the module construct its
real parser — the thing under test — and aborts on the line after it
succeeds. A rejection still raises SystemExit(2) from inside argparse.
Found the hard way: the first version of this test called `main()` and let
it run, and spent two minutes generating bodies for GJ_1005A before it was
stopped.
"""
real = argparse.ArgumentParser.parse_args
def stop_after_parsing(self, args=None, namespace=None):
real(self, args, namespace)
raise _Parsed
buffer = io.StringIO()
argparse.ArgumentParser.parse_args = stop_after_parsing
try:
with contextlib.redirect_stderr(buffer), contextlib.redirect_stdout(buffer):
module.main(argv)
except _Parsed:
return True, ""
except SystemExit as exc:
if exc.code == 2: # argparse's "bad arguments"
return False, buffer.getvalue().strip()
return True, ""
except BaseException:
# Failed before reaching parse_args — an import guard, a missing DB.
# Not this test's business either way.
return True, ""
finally:
argparse.ArgumentParser.parse_args = real
return True, ""
def test_every_option_is_known_to_its_parser(failures: list[str]) -> None:
import importlib
for verb, (module_name, positionals) in IMPLEMENTATIONS.items():
module = importlib.import_module(f"tooling.domains.atlas.planet.{module_name}")
for opt, param_type in _router_options(verb):
argv = list(positionals)
argv.append(opt)
type_name = getattr(param_type, "name", "text")
if type_name != "boolean":
argv.append(CONSTRAINED.get((verb, opt), SAMPLE.get(type_name, "x")))
ok, err = _parser_accepts(module, argv)
if not ok:
failures.append(
f"`reach atlas planet {verb} {opt}` — {module_name}.py's parser "
f"does not accept it: {err.splitlines()[-1] if err else 'rejected'}"
)
def test_every_verb_has_an_implementation(failures: list[str]) -> None:
"""A verb missing from IMPLEMENTATIONS would be silently unchecked."""
declared = set(typer.main.get_command(planet_router.app).commands) # type: ignore[attr-defined]
covered = set(IMPLEMENTATIONS)
for verb in sorted(declared - covered):
failures.append(
f"`reach atlas planet {verb}` is not in IMPLEMENTATIONS — add it, or "
"its options are never checked against a parser"
)
for verb in sorted(covered - declared):
failures.append(f"IMPLEMENTATIONS names '{verb}', which the router does not declare")
def test_flags_builder_drops_defaults(failures: list[str]) -> None:
"""None and False must not reach argv — the module owns its defaults."""
built = planet_router._flags(system=None, force=False, body="GJ380c", limit=7, dry_run=True)
if "--system" in built or "--force" in built:
failures.append(f"_flags passed an unset option through: {built}")
if built != ["--body", "GJ380c", "--limit", "7", "--dry-run"]:
failures.append(f"_flags built unexpected argv: {built}")
# sol-import's --body is action="append"; a list must repeat the flag, or
# `--body GJ0d --body GJ0e` silently keeps only one of them.
repeated = planet_router._flags(body=["GJ0d", "GJ0e"])
if repeated != ["--body", "GJ0d", "--body", "GJ0e"]:
failures.append(f"_flags did not repeat a list option: {repeated}")
def main() -> int:
failures: list[str] = []
test_every_verb_has_an_implementation(failures)
test_flags_builder_drops_defaults(failures)
test_every_option_is_known_to_its_parser(failures)
if failures:
print("test_planet_router: FAIL", file=sys.stderr)
for failure in failures:
print(f" - {failure}", file=sys.stderr)
return 1
total = sum(len(_router_options(v)) for v in IMPLEMENTATIONS)
print(
f"test_planet_router: OK — {len(IMPLEMENTATIONS)} verbs, {total} options, "
"every one accepted by the module's own parser"
)
return 0
if __name__ == "__main__":
sys.exit(main())