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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
# Tests for CLI help formatting
# Copyright (C) 2025 Nguyễn Gia Phong
#
# This file is part of scadere.
#
# Scadere is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Scadere is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with scadere. If not, see <https://www.gnu.org/licenses/>.
from contextlib import redirect_stdout, suppress
from io import StringIO
from hypothesis import example, given
from pytest import fixture, mark, raises
from scadere import EXAMPLE_PREFIX, EXAMPLE_DESCRIPTION_PREFIX, NetLoc
from scadere.check import main as check
from scadere.listen import main as listen
@fixture(scope='session')
def help_string(request):
string = StringIO()
with suppress(SystemExit), redirect_stdout(string):
request.param(arguments=['--help'])
return string.getvalue()
@mark.parametrize('help_string', [check, listen], indirect=True)
def test_usage_prefix(help_string):
assert help_string.startswith('Usage: ')
@mark.parametrize('help_string', [check, listen], indirect=True)
def test_options_heading(help_string):
assert '\n\nOptions:\n' in help_string
@mark.parametrize('help_string', [check], indirect=True)
def test_one_or_more(help_string):
assert ' HOST[:PORT]...\n' in help_string
@mark.parametrize('help_string', [check], indirect=True)
@mark.parametrize('short,long,metavar', [('-d', '--days', 'DAYS'),
('-o', '--output', 'PATH')])
def test_long_option(help_string, short, long, metavar):
assert f'{short} {metavar}, {long}={metavar}' in help_string
@mark.parametrize('help_string', [check, listen], indirect=True)
def test_examples(help_string):
index = help_string.find('\n\nExamples:\n')
assert index >= 0
lines = help_string[index:].removeprefix('\n\nExamples:\n').splitlines()
assert EXAMPLE_DESCRIPTION_PREFIX.startswith(EXAMPLE_PREFIX)
assert not lines[0].startswith(EXAMPLE_DESCRIPTION_PREFIX)
assert lines[-1].startswith(EXAMPLE_DESCRIPTION_PREFIX)
must_be_desc = False
for line in lines:
if must_be_desc:
assert line.startswith(EXAMPLE_DESCRIPTION_PREFIX)
must_be_desc = False
else:
assert line.startswith(EXAMPLE_PREFIX)
must_be_desc = not line.startswith(EXAMPLE_DESCRIPTION_PREFIX)
@example('a.example:b', None) # string is unlikely to match .*:\D+
@example('a.example:98', None) # string is unlikely to match .*:\d+
@given(...)
def test_netloc(string: str, default_port: int | None):
netloc = NetLoc(default_port)
if ':' not in string:
assert netloc(string) == (string, default_port)
else:
hostname, port = string.rsplit(':', 1)
try:
port_number = int(port)
except ValueError:
with raises(ValueError):
netloc(string)
else:
assert netloc(string) == (hostname, port_number)
|