jw-pkg/test/unit/python/jw/pkg/lib/ProjectConf/test.py
Jan Lindemann 64f1a73650
test: Fix mypy strict errors in unit tests
The unit test files below test/unit/python/jw/pkg/lib/ define Cmd and App
subclasses with unannotated methods: __init__(), add_arguments(), _run()
and close() lack signatures, the made class attribute lacks a type, and the
_cleanup() and exit_context() helpers are untyped, so a strict mypy run
over the test tree fails on them. Likewise, the ExecApp and version tests
carry list formatting that yapf rejects.

Annotate the test classes following the library conventions: parent is App
| Cmd, the parser is ArgumentParser, and args is Namespace, and mark every
overridden method with @override. Guard the imports that are only used for
annotations behind TYPE_CHECKING, and add the future annotations import so
they stay out of the runtime path. Then reformat the test tree with yapf.
That folds the opts list of the ExecApp test and re-indents the rejected
operator list of the version test.

Signed-off-by: Jan Lindemann <jan@janware.com>
Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.85.1
2026-09-13 15:42:29 +02:00

265 lines
7 KiB
Python

import os
import tempfile
from jw.pkg.lib.ProjectConf import ProjectConf
# -- Helper: create temp file and read it --
_tmpfiles: list[str] = []
def _load(text: str) -> ProjectConf:
with tempfile.NamedTemporaryFile(mode = 'w', suffix = '.conf', delete = False) as f:
f.write(text)
f.flush()
_tmpfiles.append(f.name)
return ProjectConf.read(f.name)
def _cleanup() -> None:
for _p in _tmpfiles:
os.unlink(_p)
del _p
_tmpfiles.clear()
# -- Basic construction --
pc = _load('[section]\nkey = value\n')
assert pc.get_str('section', 'key') == 'value'
# -- get_section returns raw section text --
pc = _load('[meta]\n# header comment\nkey = val\n\n')
section = pc.get_section('meta')
assert section is not None
assert '# header comment' in section
assert 'key = val' in section
# -- get_section returns None for missing section --
pc = _load('[section]\nkey = value\n')
assert pc.get_section('nonexistent') is None
# -- get_str returns unquoted value --
pc = _load('[section]\nkey = "hello world"\n')
assert pc.get_str('section', 'key') == 'hello world'
# -- get_str returns raw value when not quoted --
pc = _load('[section]\nkey = plain value\n')
assert pc.get_str('section', 'key') == 'plain value'
# -- get_str raises Error when key missing --
pc = _load('[section]\nother = val\n')
try:
pc.get_str('section', 'missing')
assert False, 'Should have raised'
except ProjectConf.Error:
pass
# -- get_str raises Error when section missing --
pc = _load('[section]\nkey = val\n')
try:
pc.get_str('nonexistent', 'key')
assert False, 'Should have raised'
except ProjectConf.Error:
pass
# -- get_str_or_none returns None for missing key --
pc = _load('[section]\nother = val\n')
assert pc.get_str_or_none('section', 'missing') is None
# -- get_str_or_none returns None for missing section --
pc = _load('[section]\nkey = val\n')
assert pc.get_str_or_none('nonexistent', 'key') is None
# -- get_str returns string with trailing whitespace stripped --
pc = _load('[section]\nkey = value \n')
assert pc.get_str('section', 'key') == 'value'
# -- Quoted value with spaces preserved --
pc = _load('[section]\nkey = " spaced "\n')
assert pc.get_str('section', 'key') == ' spaced '
# -- Quoted hash inside quotes is preserved --
pc = _load('[section]\nkey = "value # not a comment"\n')
assert pc.get_str('section', 'key') == 'value # not a comment'
# -- Inline comment outside quotes --
pc = _load('[section]\nkey = value # this is a comment\n')
assert pc.get_str('section', 'key') == 'value'
# -- Quoted value containing an inline comment delimiter --
pc = _load('[section]\nkey = "has # inside"\n')
assert pc.get_str('section', 'key') == 'has # inside'
# -- get_list returns split values --
pc = _load('[section]\nlist = a, b, c\n')
assert pc.get_list('section', 'list') == ['a', 'b', 'c']
# -- get_list returns single value --
pc = _load('[section]\nlist = single\n')
assert pc.get_list('section', 'list') == ['single']
# -- get_list_or_none returns None for missing key --
pc = _load('[section]\nother = val\n')
assert pc.get_list_or_none('section', 'missing') is None
# -- get_list raises Error when missing key --
pc = _load('[section]\nother = val\n')
try:
pc.get_list('section', 'missing')
assert False, 'Should have raised'
except ProjectConf.Error:
pass
# -- get_list handles quoted commas --
pc = _load('[section]\nlist = "a, b", c, "d, e"\n')
assert pc.get_list('section', 'list') == ['a, b', 'c', 'd, e']
# -- get_list returns empty list for empty value --
pc = _load('[section]\nlist = \n')
assert pc.get_list('section', 'list') == []
# -- get_list_or_none returns empty list for empty value --
pc = _load('[section]\nlist = \n')
assert pc.get_list_or_none('section', 'list') == []
# -- Unclosed quote in list raises --
pc = _load('[section]\nlist = "unclosed, b\n')
try:
pc.get_list('section', 'list')
assert False, 'Should have raised'
except ProjectConf.Error as exc:
assert 'Unclosed quote' in str(exc)
# -- Line continuation --
pc = _load('[section]\nkey = first \\\n second \\\n third\n')
assert pc.get_str('section', 'key') == 'first second third'
# -- Line continuation with inline comment at end --
pc = _load('[section]\nkey = first \\\n second # comment\n')
assert pc.get_str('section', 'key') == 'first second'
# -- get_section returns None for non-existent section --
pc = _load('[section]\nkey = val\n')
assert pc.get_section('does_not_exist') is None
# -- Multiple keys in same section --
pc = _load('[section]\nfirst = one\nsecond = two\nthird = three\n')
assert pc.get_str('section', 'first') == 'one'
assert pc.get_str('section', 'second') == 'two'
assert pc.get_str('section', 'third') == 'three'
# -- get_str_or_none does not raise for missing section --
pc = _load('[section]\nkey = val\n')
assert pc.get_str_or_none('nope', 'key') is None
# -- get_list_or_none does not raise for missing section --
pc = _load('[section]\nkey = val\n')
assert pc.get_list_or_none('nope', 'key') is None
# -- get_list_or_none does not raise for missing key --
pc = _load('[section]\nother = val\n')
assert pc.get_list_or_none('section', 'nope') is None
# -- Empty key raises --
pc = _load('[section]\n= value\n')
try:
pc.get_str('section', '')
assert False, 'Should have raised'
except ProjectConf.Error:
pass
# -- Not an assignment raises --
pc = _load('[section]\nnot an assignment\n')
try:
pc.get_str('section', 'not an assignment')
assert False, 'Should have raised'
except ProjectConf.Error:
pass
# -- Unfinished continuation raises --
pc = _load('[section]\nkey = value \\\n')
try:
pc.get_str('section', 'key')
assert False, 'Should have raised'
except ProjectConf.Error:
pass
# -- Error class is ValueError --
pc = _load('[section]\nkey = val\n')
try:
pc.get_str('missing', 'key')
except ValueError as e:
assert isinstance(e, ValueError)
else:
assert False, 'Should have raised ValueError'
# -- Whitespace around key and value --
pc = _load('[section]\n key = value \n')
assert pc.get_str('section', 'key') == 'value'
# -- Whitespace around key in list --
pc = _load('[section]\nlist = a , b , c \n')
assert pc.get_list('section', 'list') == ['a', 'b', 'c']
# -- Multiple sections --
pc = _load('[one]\nkey = val1\n[two]\nkey = val2\n')
assert pc.get_str('one', 'key') == 'val1'
assert pc.get_str('two', 'key') == 'val2'
# -- Section with only comments and blanks --
pc = _load('[empty]\n# just a comment\n\n')
section = pc.get_section('empty')
assert section is not None
assert pc.get_str_or_none('empty', 'key') is None
# -- Section with trailing comment on section header line --
pc = _load('[section] # inline comment\nkey = val\n')
assert pc.get_str('section', 'key') == 'val'
# -- Value that looks like a section header --
pc = _load('[section]\nnot_a_key = [bracket]\n')
assert pc.get_str('section', 'not_a_key') == '[bracket]'
# -- Multiple values with trailing backslash comments --
pc = _load('[section]\nlist = "a", "b", "c"\n')
assert pc.get_list('section', 'list') == ['a', 'b', 'c']
_cleanup()
print('All ProjectConf tests passed')