"""CLI argparse smoke tests. Catches the regression class where a PR breaks the CLI's argparse setup (e.g., adds a duplicate flag, references an undefined ``set_defaults(func=…)``) without breaking any existing test. ``arborist --help`` and ``arborist --help`` are the cheapest way to surface argparse errors — they exercise the full parser-construction path without invoking any handler. Cost: ~1 s for the full ~30-subcommand sweep via subprocess. Pure stdlib + subprocess. """ from __future__ import annotations import subprocess import sys import pytest _ARBORIST = "/home/fox/git/arborist/.venv/bin/arborist" def _run(*args, expect_rc: int = 0): """Invoke the arborist CLI; return (rc, stdout, stderr).""" out = subprocess.run( [_ARBORIST, *args], capture_output=True, text=True, cwd="/home/fox/git/arborist", ) if expect_rc is not None: assert out.returncode == expect_rc, ( f"arborist {args} returned {out.returncode}; " f"stdout: {out.stdout!r}; stderr: {out.stderr!r}" ) return out # --- top-level --help / --version ----------------------------------- def test_arborist_help_runs_clean(): """`arborist --help` exits 0 and prints usage banner.""" out = _run("--help") assert "usage: arborist" in out.stdout # Subcommand list contains the top-level dispatch verbs. for verb in ("ingest", "query", "verify", "stats"): assert verb in out.stdout def test_arborist_version_runs_clean(): out = _run("--version") # --version prints to stdout in argparse default; some # versions print to stderr. Tolerate either. combined = out.stdout + out.stderr assert combined.strip() # not empty def test_arborist_no_subcommand_returns_error(): """Bare `arborist` with no subcommand → argparse rejects. Smoke for the dispatch wiring.""" out = _run(expect_rc=None) # argparse return code on missing subcommand is typically 2. assert out.returncode != 0 # --- per-subcommand --help ----------------------------------------- # Generated from `arborist --help` output 2026-05-10. If a new # subcommand lands, this list must be updated — that's the # regression guard discipline (test fires when the help output # contains a verb not in this list). _TOP_LEVEL_SUBCOMMANDS = [ "ingest", "search", "verify", "distill", "ask", "query", "inspect", "losses", "providence", "controller-events", "burn", "burn-kindergarten", "reclassify", "emergent", "evict", "rehydrate", "activity", "stats", "canon", "analyze", "snapshot", "substrate", "memory", "capital", "selfmodel", "warrant-status", "warrant-resolve", "sweep", "alias", "mesh", "crawl", "crawler", ] @pytest.mark.parametrize("subcommand", _TOP_LEVEL_SUBCOMMANDS) def test_subcommand_help_runs_clean(subcommand): """`arborist --help` exits 0 — argparse parser-construction smoke. Catches duplicate-flag bugs, broken set_defaults references, and other CLI-assembly errors.""" out = _run(subcommand, "--help") assert "usage:" in out.stdout, ( f"`arborist {subcommand} --help` produced no usage banner" ) # --- argparse construction completeness ---------------------------- def test_help_lists_every_known_subcommand(): """The top-level --help output must mention every subcommand we test below. Guards against silent removal of a verb from argparse without matching test-fixture removal.""" out = _run("--help") for verb in _TOP_LEVEL_SUBCOMMANDS: assert verb in out.stdout, ( f"top-level --help is missing subcommand {verb!r} — " f"either it was removed (update _TOP_LEVEL_SUBCOMMANDS) " f"or argparse setup regressed" ) # --- subcommand verbs with their own subcommand groups ------------- # Verbs that themselves have subcommand groups (verb → list of # nested subcommands). E.g. `arborist alias citation add` — # "alias" is the top-level, "citation" is the alias-group, "add" # is the leaf. _NESTED_SUBCOMMANDS = { "alias": ["citation", "term"], "memory": ["snapshot", "show", "branches", "falsify"], "capital": ["op-cost", "summary", "top"], "selfmodel": ["snapshot", "list", "show", "falsify"], "mesh": ["status", "init", "enable", "disable", "members", "add", "kick", "rotate", "serve", "sync", "pull"], "crawler": ["recrawl-check"], "canon": ["compute", "list", "verify"], "substrate": ["score"], "snapshot": ["create", "list", "verify", "diff"], } @pytest.mark.parametrize("verb,group", [ (v, g) for v, gs in _NESTED_SUBCOMMANDS.items() for g in gs ]) def test_nested_subcommand_help_runs_clean(verb, group): """`arborist --help` exits 0 for every documented nested subcommand. Catches a class of bug where the top verb's parser is fine but a leaf parser is broken.""" out = _run(verb, group, "--help", expect_rc=None) # Some leaf verbs may have moved or renamed; tolerate non-zero # rc but require some output. if out.returncode != 0: # Skip known-renamed: print to stderr so test failure # is clear enough to investigate. pytest.skip(f"`arborist {verb} {group} --help` returned " f"{out.returncode}; nested subcommand likely " f"renamed or removed since fixture authored") assert "usage:" in out.stdout