tools: usage lines name wikitool; -h/--help acceptance checks become tests (#121)
CI / verify (push) Failing after 1m18s
Release / release (push) Successful in 37s

Files changed:
- CHANGES.md
- VERSION
- tools/chemenu/cli.py
- tools/chemenu/tests/test_cli.py
This commit is contained in:
torben committed 2026-09-26 07:57:01 +02:00
1 parent b79f083cc1
commit 5ffab3accf
4 files changed
+93 -7

No files matched your search

+7 -2
View File
@@ -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)
+73 -3
View File
@@ -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)