cli: add __main__ guard so python -m cmos.cli actually runs

Without `if __name__ == "__main__": sys.exit(main())` at the bottom
of cli.py, `python -m cmos.cli format <path>` imports the module
but never invokes main(), so the process silently exits 0 with
empty stdout — indistinguishable from a successful run that
produced no output. Discovered during real-draft testing on
2026-04-11.

Adds a regression test that subprocesses the CLI with --help and
asserts on stdout content. argparse --help exits 0 in both broken
and fixed states; stdout content is the only discriminator.

Both invocation paths now work:
- uv run cmos format <path>                (pyproject script entry)
- uv run python -m cmos.cli format <path>  (module invocation)
This commit is contained in:
Mark Eaton
2026-04-11 17:45:18 -04:00
parent 1d970e84f9
commit 22404fbd6b
2 changed files with 37 additions and 0 deletions
+4
View File
@@ -84,3 +84,7 @@ def main(argv: list[str] | None = None) -> int:
sys.stdout.write(reformat_draft(text, concurrency=args.concurrency)) sys.stdout.write(reformat_draft(text, concurrency=args.concurrency))
return 0 return 0
return 2 return 2
if __name__ == "__main__":
sys.exit(main())
+33
View File
@@ -5,6 +5,8 @@ formatter so no API calls happen; the goal is to pin the glue logic, not the
LLM behavior. LLM behavior.
""" """
import subprocess
import sys
from pathlib import Path from pathlib import Path
import pytest import pytest
@@ -68,6 +70,37 @@ def test_reformat_draft_bibliography_heading_preserved():
assert "## Bibliography" in output assert "## Bibliography" in output
def test_python_dash_m_invocation_actually_runs_main():
"""Regression test: `python -m cmos.cli` must actually invoke main().
Without an `if __name__ == "__main__"` guard at the bottom of cli.py,
`python -m cmos.cli` imports the module body but never calls main(),
so the process silently exits 0 with empty stdout. That looks
indistinguishable from a successful run that produced no output —
the worst kind of bug, since callers assume the pipeline ran. This
test forces the guard to exist by invoking the CLI as a subprocess
with --help and asserting argparse actually fired.
"""
result = subprocess.run(
[sys.executable, "-m", "cmos.cli", "--help"],
capture_output=True,
text=True,
timeout=10,
)
# argparse --help exits 0 whether or not main() ran, so the
# discriminator is the stdout content. Without main(), stdout is empty.
assert result.returncode == 0, (
f"expected exit 0, got {result.returncode}; stderr={result.stderr!r}"
)
assert result.stdout, (
"stdout was empty — `python -m cmos.cli` likely silently exited "
"without invoking main(). Check that cli.py has an "
'`if __name__ == "__main__": sys.exit(main())` guard at the bottom.'
)
assert "format" in result.stdout
assert "usage" in result.stdout.lower()
def test_reformat_draft_preserves_order_under_concurrency(): def test_reformat_draft_preserves_order_under_concurrency():
# With concurrent execution the formatter is called on all entries in # With concurrent execution the formatter is called on all entries in
# parallel; the CLI must reassemble them in input order regardless of # parallel; the CLI must reassemble them in input order regardless of