aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/tests/test_manpage.py
blob: ebd71c8d1638c979b498ead92157bc8324e0a843 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""
The man page is generated from the parser; these keep the generator honest.

An option added to an action must reach the page, every exit code must be
explained, and troff must read the result without a warning.
"""

import argparse
import os
import shutil
import subprocess

import pytest
from meshbay_cli import __main__ as cli
from meshbay_cli import errors, manpage


def _section(page: str, start: str) -> str:
    return page.split(start + "\n", 1)[1].split("\n.S", 1)[0]


def _entry(option: str) -> str:
    """How an option opens its entry: `.TP`, then the option in bold."""
    return f"\\fB{manpage._roff(option)}\\fR"


def test_every_action_and_option_is_in_its_section():
    page = manpage.render()
    parser = cli.build_parser()
    glob = _section(page, ".SH GLOBAL OPTIONS")
    for a in parser._actions:
        for option in a.option_strings:
            assert _entry(option) in glob, f"global {option} missing"
    sub = next(a for a in parser._actions if isinstance(a, argparse._SubParsersAction))
    assert list(sub.choices) == list(cli.ACTIONS)
    for name, sp in sub.choices.items():
        own = _section(page, f".SS {name}")
        for a in sp._actions:
            if isinstance(a, argparse._HelpAction):
                continue
            if a.option_strings and _entry(a.option_strings[0]) in glob:
                continue                               # a global one, listed once
            if a.option_strings:
                for option in a.option_strings:
                    assert _entry(option) in own, f"{name} {option} missing"
            elif a.choices:
                for choice in a.choices:
                    assert _entry(choice) in own, f"{name} {choice} missing"
            else:
                meta = a.metavar or a.dest.upper()
                assert f"\\fI{manpage._roff(meta)}\\fR" in own, f"{name} {meta} missing"


def test_every_exit_code_is_explained():
    codes = {v for k, v in vars(errors).items() if k.isupper() and isinstance(v, int)}
    assert codes | {0} == set(manpage.EXIT_CODES)


def test_names_and_text_cannot_break_the_page():
    assert manpage._roff(".hidden") == "\\&.hidden"
    assert manpage._roff("a\\b — c-d") == "a\\eb \\[u2014] c\\-d"


@pytest.mark.skipif(not shutil.which("groff"), reason="groff not installed")
def test_troff_reads_it_without_a_warning(tmp_path):
    page = tmp_path / "meshbay-cli.1"
    page.write_text(manpage.render(), encoding="ascii")    # nothing past ASCII left
    r = subprocess.run(["groff", "-man", "-Tutf8", "-ww", "-z", str(page)],
                       capture_output=True, text=True, env={**os.environ, "LC_ALL": "C"})
    assert r.returncode == 0 and not r.stderr, r.stderr