nexa / tests /test_cli.py
DBax127's picture
Claude Opus 5
ascii: make the fallback reachable, and the clone survive the gateway
3432acf
Raw History Blame Contribute Delete
13.2 kB
"""The exit-code contract, and the banner that should appear exactly once.
Exit codes are three-valued on purpose: 0 worked, 1 the command ran and
reported findings, 2 it was called wrong. Before that split, `nexa ask` with no
question printed help and returned 0 -- so `nexa ask odoo "$Q" || exit 1`
reported success for an empty $Q. Every row of the table below is a contract,
and the comments mark the ones that used to return the wrong number.
Most of these call main() in-process: it returns the code rather than exiting,
so the assertion is direct, it runs in milliseconds, and a coverage run can
actually see the branches being taken. A handful go through a real subprocess at
the bottom, because in-process calls would never catch a broken __main__ block.
"""
import os
import subprocess
import sys
import pytest
import nexa
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OK, FINDINGS, MISUSE = 0, 1, 2
@pytest.fixture(autouse=True)
def fresh_banner(monkeypatch):
"""banner(once=True) is process-global, and every test here is a new run."""
monkeypatch.setattr(nexa, "_BANNER_SHOWN", False)
@pytest.fixture(autouse=True)
def in_repo(monkeypatch):
"""nexa resolves the corpus relative to its own file, but check and index
take paths from the caller's working directory."""
monkeypatch.chdir(ROOT)
def main(*args):
return nexa.main(list(args))
def shell(*args):
proc = subprocess.run([sys.executable, os.path.join(ROOT, "nexa.py")] + list(args),
capture_output=True, text=True, encoding="utf-8",
errors="replace", cwd=ROOT)
return proc.returncode, (proc.stdout or "")
CASES = [
([], OK, "bare invocation prints the command list"),
(["version"], OK, "machine-readable, and the one command with no banner"),
(["--version"], OK, "the flag spelling"),
(["help"], OK, "help with no topic is the command list"),
(["help", "ask"], OK, "help for a real command"),
(["help", "bogus"], MISUSE, "was 1: an unknown topic is misuse, not a finding"),
(["bogus"], MISUSE, "was 1"),
(["ask"], MISUSE, "was 0 -- the bug that motivated the split"),
(["ask", "odoo"], MISUSE, "was 0: a stack with no question is still misuse"),
(["run"], MISUSE, "was 0"),
(["run", "odoo"], MISUSE, "was 0"),
(["index"], MISUSE, "was 0"),
(["check"], MISUSE, "was 0"),
(["check", "no_such_file_here.py"], MISUSE, "was 1: a bad path is a bad argument"),
(["rules"], OK, "the whole corpus"),
(["rules", "onchange"], OK, "a term that matches"),
(["rules", "zzzznomatch"], FINDINGS, "ran fine, found nothing -- NOT misuse"),
(["rules", "--stack", "odoo"], OK, "the one stack, named"),
(["rules", "--full", "odoo.onchange-is-not-validation"], OK, "a real id"),
(["rules", "--full", "no.such.rule"], MISUSE, "was 1: an id that does not exist"),
(["validate"], OK, "corpus is schema-valid"),
(["crosscheck"], OK, "informational, never fails the build"),
(["coverage"], OK, "reports its own blind spots"),
(["adversarial"], OK, "all three probes"),
(["adversarial", "evasion"], OK, "one probe by name"),
]
@pytest.mark.parametrize("args,expected,why", CASES,
ids=[" ".join(c[0]) or "(none)" for c in CASES])
def test_exit_code(args, expected, why, capsys):
assert main(*args) == expected, "{0}: {1}".format(" ".join(args) or "(none)", why)
def test_empty_search_and_bad_id_are_different_codes(capsys):
"""The distinction the three-valued scheme exists for: 'I looked and found
nothing' is a result; 'that id does not exist' is a mistake."""
assert main("rules", "zzzznomatch") == FINDINGS
assert main("rules", "--full", "no.such.rule") == MISUSE
def test_usage_error_still_shows_the_help_text(capsys):
"""Exiting non-zero is no reason to withhold the thing the user needs.
Asserted against the usage string the command actually declares, rather
than a copy of it: this used to spell out the stack list and went red when
a fourth stack was added, which is not what it is testing.
"""
assert main("ask") == MISUSE
assert nexa.COMMANDS["ask"][1] in capsys.readouterr().out
def test_unknown_command_points_back_at_the_command_list(capsys):
for args in (["bogus"], ["help", "bogus"]):
nexa._BANNER_SHOWN = False
main(*args)
out = capsys.readouterr().out
assert "unknown command" in out
assert "for the command list" in out, "both spellings give the same hint"
def test_commands_table_and_dispatch_agree(capsys):
"""A command listed in help with no dispatch branch would print its help and
then fall through to 'unknown command'."""
for name in nexa.COMMANDS:
nexa._BANNER_SHOWN = False
assert main("help", name) == OK
assert "unknown command" not in capsys.readouterr().out
# ---------------------------------------------------------------- the banner
def test_banner_prints_once_per_process(capsys):
nexa.banner(subtitle=False, once=True)
nexa.banner(subtitle=False, once=True)
assert capsys.readouterr().out.count("NEXA") == 1
def test_banner_without_once_always_prints(capsys):
nexa.banner(subtitle=False)
nexa.banner(subtitle=False)
assert capsys.readouterr().out.count("NEXA") == 2
def test_every_command_wears_the_banner(capsys):
for args in (["rules"], ["validate"], ["coverage"], ["adversarial"], ["bogus"]):
nexa._BANNER_SHOWN = False
main(*args)
assert "NEXA" in capsys.readouterr().out, "{0} printed no banner".format(args)
def test_version_is_the_one_command_without_a_banner(capsys):
main("version")
out = capsys.readouterr().out
assert out.strip() == "nexa {0}".format(nexa.VERSION)
# ------------------------------------------------------ the real entry point
def test_subprocess_entry_point_exits_with_the_returned_code():
"""In-process tests never execute the __main__ block, so sys.exit(main())
and the KeyboardInterrupt handler around it would go unchecked."""
assert shell("version")[0] == OK
assert shell("bogus")[0] == MISUSE
assert shell("ask")[0] == MISUSE
def test_subprocess_version_is_parseable():
code, out = shell("version")
assert code == OK
assert out.strip() == "nexa {0}".format(nexa.VERSION)
def test_subprocess_output_is_clean_when_not_a_terminal():
"""Piped output must carry no escape codes: COLOR is gated on isatty, and a
redirect that captured raw ANSI would make `nexa rules > file` unreadable."""
code, out = shell("rules", "onchange")
assert code == OK
assert "\033" not in out
# --------------------------------------------- the wordmark and its underbar
def _art_rows(art):
"""The art's rows, indentation intact, without the blank lines the triple-
quoted literal starts and ends with."""
return [row for row in art.splitlines() if row.strip()]
def test_both_spellings_are_the_same_height():
"""banner() bands by row index, so a fallback of a different height would
paint the bar as a letterform and vice versa."""
assert len(_art_rows(nexa.BANNER_UNICODE)) == len(_art_rows(nexa.BANNER_ASCII))
# The wordmark, and the bar under it.
assert len(_art_rows(nexa.BANNER_UNICODE)) == nexa.BANNER_ROWS + 1
def test_the_bar_is_as_wide_as_the_wordmark():
"""The bar is light thrown by the sign. One that overhangs the letters, or
falls short of them, reads as a rule somebody drew underneath instead."""
for art in (nexa.BANNER_UNICODE, nexa.BANNER_ASCII):
widths = set(len(row) for row in _art_rows(art))
assert len(widths) == 1, art
def test_the_ascii_fallback_is_actually_ascii():
"""It exists for a console on a legacy code page. A block-drawing character
surviving into it defeats the entire point."""
assert nexa.BANNER_ASCII.isascii()
def test_glyphs_lists_every_non_ascii_character_the_module_can_print():
"""GLYPHS is what _enable_utf8 probes a declared encoding with.
A glyph added to the UI and not to GLYPHS makes the probe pass on an
encoding that cannot hold it, and the character reaches the terminal
anyway -- the exact crash the fallback exists to prevent, reintroduced one
character at a time. Asserted against the source rather than against the
constants, because those are already degraded to ASCII when UNICODE is
False and would have nothing to say in the run that matters.
Comments are excluded. They are never printed, and the ones describing the
bar name the very characters this is looking for.
"""
import io
import tokenize
with open(os.path.join(ROOT, "nexa.py"), encoding="utf-8") as handle:
source = handle.read()
printable = set()
for token in tokenize.generate_tokens(io.StringIO(source).readline):
if token.type == tokenize.COMMENT:
continue
printable.update(ch for ch in token.string if ord(ch) > 127)
assert printable, "the scan found nothing, so it is not reading the art"
missing = sorted(printable - set(nexa.GLYPHS))
assert not missing, "used but not in nexa.GLYPHS: " + " ".join(missing)
def _cube(n):
"""An xterm-256 cube index as its (r, g, b) coordinates, each 0-5."""
n -= 16
return (n // 36, (n % 36) // 6, n % 6)
def test_the_ramp_sweeps_cyan_to_magenta():
"""Endpoints, asserted against the NUMBERS rather than the escape codes.
Under NO_COLOR every code is the empty string, so a test written against
codes would pass for any ramp at all, which is worse than not testing it.
"""
assert nexa.BANNER_RAMP[0] == 51, "starts on cyan"
assert nexa.BANNER_RAMP[-1] == 201, "ends on magenta"
assert nexa.BANNER_BAR == 214, "amber, what the rest of the CLI warns in"
assert nexa.BANNER_BAR not in nexa.BANNER_RAMP, (
"the bar contrasts with the wordmark rather than continuing it")
def test_the_steps_between_are_an_interpolation_not_a_shortlist():
"""The whole claim of a ramp. Cyan to magenta the short way round holds
blue at maximum while red rises and green falls; a colour picked because
somebody liked it breaks one of those three, and the gradient develops a
kink no amount of squinting at a screenshot reliably catches."""
steps = [_cube(n) for n in nexa.BANNER_RAMP]
assert all(b == 5 for _r, _g, b in steps), steps
reds = [r for r, _g, _b in steps]
greens = [g for _r, g, _b in steps]
assert reds == sorted(reds), "red must rise monotonically"
assert greens == sorted(greens, reverse=True), "green must fall monotonically"
assert len(set(nexa.BANNER_RAMP)) == len(nexa.BANNER_RAMP), (
"two rows sharing a colour is a ramp with a step missing")
def test_the_readme_shows_the_banner_that_ships():
"""The README drifted silently once already -- it was still showing an
outlined NX after the art had changed twice, and that stale block reached
two public remotes before anybody noticed. A wordmark in the README is a
claim about what the command prints, so it is the same art or it is wrong."""
path = os.path.join(ROOT, "README.md")
with open(path, encoding="utf-8") as handle:
readme = handle.read()
for row in _art_rows(nexa.BANNER_UNICODE):
assert row in readme, "README is missing this row of the wordmark: " + row
def test_the_rendered_palette_is_built_from_the_ramp():
"""And the codes that reach the terminal come from those numbers rather than
from a second, hand-kept list that can drift from them."""
assert nexa.WORDMARK_GRADIENT == [nexa.c(n) for n in nexa.BANNER_RAMP]
assert nexa.BAR_COLOUR == nexa.c(nexa.BANNER_BAR)
def test_the_wordmark_row_count_matches_the_hue_count():
"""One hue per letterform row. If they drift, the banding wraps and two rows
share a colour."""
assert len(nexa.WORDMARK_GRADIENT) == nexa.BANNER_ROWS
def test_the_banner_prints_once_in_a_real_run():
"""`python nexa.py` leaves the name `nexa` unclaimed, so nexa_cmds doing
`import nexa` loaded a SECOND copy of the module -- with its own COLOR, its
own UNICODE and its own _BANNER_SHOWN. banner(once=True) then printed twice
on `doctor` and `build`, each copy believing it was the first.
Only a subprocess can see this. Under pytest the module is imported once
under its own name, the two never diverge, and the test above passes while
the CLI prints two banners.
"""
_, out = shell("doctor")
assert out.count("validation intelligence") == 1, out
def test_the_banner_says_whose_work_it_is(capsys):
"""The byline rides under the wordmark on every command that prints one,
including the short form. Read from the same two constants the README
footer and LICENSE spell out, so the three cannot drift apart."""
nexa.banner(subtitle=False)
out = capsys.readouterr().out
assert "created by " + nexa.AUTHOR in out
assert nexa.YEAR in out
def test_version_stays_bare(capsys):
"""The byline must not reach the one line a script parses."""
assert nexa.main(["version"]) == 0
assert capsys.readouterr().out.strip() == "nexa " + nexa.VERSION