tools: usage lines name wikitool; -h/--help acceptance checks become tests (#121)
Files changed: - CHANGES.md - VERSION - tools/chemenu/cli.py - tools/chemenu/tests/test_cli.py
This commit is contained in:
1 parent
b79f083cc1
commit
5ffab3accf
4 files changed
+93
-7
No files matched your search
@@ -142,6 +142,11 @@ def _pacify_real_fd(stream) -> None:
|
||||
# isn't a file) - nothing to redirect, same as the OSError case.
|
||||
pass
|
||||
|
||||
# What usage lines and "Try '... -h'" hints name. Without it Click takes
|
||||
# argv[0], which under `tools/wikitool` (`python -m chemenu.cli`) printed
|
||||
# `python -m chemenu.cli` - a command nobody should copy.
|
||||
PROG_NAME = "wikitool"
|
||||
|
||||
app = typer.Typer(
|
||||
help="wikitool - deterministic operations for Chemenu (see AGENTS.md).",
|
||||
no_args_is_help=True,
|
||||
@@ -300,7 +305,7 @@ def main() -> None:
|
||||
return
|
||||
if is_help:
|
||||
sys.argv = [sys.argv[0], *[a for a in argv if a != "--override-budget"]]
|
||||
app()
|
||||
app(prog_name=PROG_NAME)
|
||||
|
||||
|
||||
def _run_traced(command: str, args: list[str], charged: bool = False) -> None:
|
||||
@@ -322,7 +327,7 @@ def _run_traced(command: str, args: list[str], charged: bool = False) -> None:
|
||||
stderr_wrap = _BrokenPipeSwallow(real_stderr)
|
||||
sys.stdout, sys.stderr = stdout_wrap, stderr_wrap
|
||||
try:
|
||||
app()
|
||||
app(prog_name=PROG_NAME)
|
||||
except SystemExit as exc:
|
||||
code = exc.code
|
||||
exit_code = code if isinstance(code, int) else (0 if code is None else 1)
|
||||
|
||||
@@ -114,7 +114,7 @@ def test_a_write_cut_off_by_a_closed_pipe_is_not_recorded_as_an_error(monkeypatc
|
||||
stub = _FailingStream(fail_on=2) # first write succeeds, second hits EPIPE
|
||||
monkeypatch.setattr(sys, "stdout", stub)
|
||||
|
||||
def fake_app():
|
||||
def fake_app(**_kwargs):
|
||||
sys.stdout.write("line one\n")
|
||||
sys.stdout.write("line two\n") # truncated here, like `| head -1`
|
||||
raise SystemExit(0)
|
||||
@@ -137,7 +137,7 @@ def test_a_real_failure_is_still_recorded_as_one(monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("WIKI_TRACE_DIR", str(tmp_path))
|
||||
monkeypatch.setenv("WIKITOOL_SESSION_ID", "real-failure-unit")
|
||||
|
||||
def fake_app():
|
||||
def fake_app(**_kwargs):
|
||||
raise SystemExit(1)
|
||||
|
||||
monkeypatch.setattr(cli, "app", fake_app)
|
||||
@@ -157,7 +157,7 @@ def test_an_ordinary_call_restores_the_real_streams_afterwards(monkeypatch, tmp_
|
||||
monkeypatch.setenv("WIKITOOL_SESSION_ID", "restore-unit")
|
||||
real_stdout, real_stderr = sys.stdout, sys.stderr
|
||||
|
||||
def fake_app():
|
||||
def fake_app(**_kwargs):
|
||||
raise SystemExit(0)
|
||||
|
||||
monkeypatch.setattr(cli, "app", fake_app)
|
||||
@@ -165,3 +165,73 @@ def test_an_ordinary_call_restores_the_real_streams_afterwards(monkeypatch, tmp_
|
||||
cli._run_traced("lint", [])
|
||||
assert sys.stdout is real_stdout
|
||||
assert sys.stderr is real_stderr
|
||||
|
||||
|
||||
# --- help rendering from cli_contract (Gitea #121 B5) ---
|
||||
|
||||
import io # noqa: E402
|
||||
|
||||
from chemenu import cli_contract # noqa: E402
|
||||
|
||||
FRAME_CHARS = set("│╭╰─╯╮┃┏┗━┓┛")
|
||||
SECTION_ORDER = ("NAME", "SYNOPSIS", "PROPERTIES", "OPTIONS", "EXIT STATUS", "ON FAILURE", "NOTES")
|
||||
|
||||
|
||||
def _run(monkeypatch, args, tty=False):
|
||||
"""Drive the real app the way `main()` does, capturing both streams.
|
||||
`tty=True` makes stdout claim to be a terminal - what a `wikitool` run in
|
||||
an interactive shell sees, as opposed to an agent's pipe."""
|
||||
out, err = io.StringIO(), io.StringIO()
|
||||
if tty:
|
||||
out.isatty = lambda: True
|
||||
monkeypatch.setattr(sys, "stdout", out)
|
||||
monkeypatch.setattr(sys, "stderr", err)
|
||||
monkeypatch.setattr(sys, "argv", ["whatever-argv0-is", *args])
|
||||
with pytest.raises(SystemExit):
|
||||
cli.app(prog_name=cli.PROG_NAME)
|
||||
return out.getvalue(), err.getvalue()
|
||||
|
||||
|
||||
def test_publish_help_is_byte_identical_with_and_without_a_tty(monkeypatch):
|
||||
piped, _ = _run(monkeypatch, ["publish", "-h"])
|
||||
terminal, _ = _run(monkeypatch, ["publish", "-h"], tty=True)
|
||||
assert piped == terminal
|
||||
|
||||
|
||||
def test_publish_help_carries_the_record_in_man_page_order(monkeypatch):
|
||||
text, _ = _run(monkeypatch, ["publish", "-h"])
|
||||
assert text.startswith("NAME")
|
||||
positions = [text.index(section) for section in SECTION_ORDER]
|
||||
assert positions == sorted(positions)
|
||||
exit_block = text[text.index("EXIT STATUS"):text.index("ON FAILURE")]
|
||||
assert "\n 1 " in exit_block
|
||||
assert "\n 42 " in exit_block
|
||||
|
||||
|
||||
def test_every_command_renders_the_same_for_h_and_help_without_frames(monkeypatch):
|
||||
for path in sorted(cli_contract.all_records()):
|
||||
short, _ = _run(monkeypatch, [*path.split(" "), "-h"])
|
||||
long, _ = _run(monkeypatch, [*path.split(" "), "--help"])
|
||||
assert short == long, path
|
||||
assert short.startswith("NAME"), path
|
||||
assert not FRAME_CHARS & set(short), path
|
||||
|
||||
|
||||
def test_top_level_help_is_the_index_without_frames(monkeypatch):
|
||||
text, _ = _run(monkeypatch, ["-h"])
|
||||
assert text.startswith("Usage: wikitool")
|
||||
assert not FRAME_CHARS & set(text)
|
||||
non_idempotent = {
|
||||
line.split(" ")[0].strip() for line in text.splitlines() if " non-idempotent " in line
|
||||
}
|
||||
assert {
|
||||
"new", "log append", "publish", "upstream merge",
|
||||
"version bump", "version release", "migrate done",
|
||||
} <= non_idempotent
|
||||
|
||||
|
||||
def test_a_usage_error_is_unframed_and_names_wikitool(monkeypatch):
|
||||
out, err = _run(monkeypatch, ["publish", "--bogus"])
|
||||
text = out + err
|
||||
assert "Usage: wikitool publish" in text
|
||||
assert not FRAME_CHARS & set(text)
|
||||
Reference in new issue
Block a user