241 lines
4.8 KiB
Python
241 lines
4.8 KiB
Python
'''
|
|
Unit tests for `tractor.msg.pretty_struct`
|
|
private-field filtering in `pformat()`.
|
|
|
|
'''
|
|
import pytest
|
|
|
|
from tractor.msg.pretty_struct import (
|
|
Struct,
|
|
pformat,
|
|
iter_struct_ppfmt_lines,
|
|
)
|
|
from tractor.msg._codec import (
|
|
MsgDec,
|
|
mk_dec,
|
|
)
|
|
|
|
|
|
# ------ test struct definitions ------ #
|
|
|
|
class PublicOnly(Struct):
|
|
'''
|
|
All-public fields for baseline testing.
|
|
|
|
'''
|
|
name: str = 'alice'
|
|
age: int = 30
|
|
|
|
|
|
class PrivateOnly(Struct):
|
|
'''
|
|
Only underscore-prefixed (private) fields.
|
|
|
|
'''
|
|
_secret: str = 'hidden'
|
|
_internal: int = 99
|
|
|
|
|
|
class MixedFields(Struct):
|
|
'''
|
|
Mix of public and private fields.
|
|
|
|
'''
|
|
name: str = 'bob'
|
|
_hidden: int = 42
|
|
value: float = 3.14
|
|
_meta: str = 'internal'
|
|
|
|
|
|
class Inner(
|
|
Struct,
|
|
frozen=True,
|
|
):
|
|
'''
|
|
Frozen inner struct with a private field,
|
|
for nesting tests.
|
|
|
|
'''
|
|
x: int = 1
|
|
_secret: str = 'nope'
|
|
|
|
|
|
class Outer(Struct):
|
|
'''
|
|
Outer struct nesting an `Inner`.
|
|
|
|
'''
|
|
label: str = 'outer'
|
|
inner: Inner = Inner()
|
|
|
|
|
|
class EmptyStruct(Struct):
|
|
'''
|
|
Struct with zero fields.
|
|
|
|
'''
|
|
pass
|
|
|
|
|
|
# ------ tests ------ #
|
|
|
|
@pytest.mark.parametrize(
|
|
'struct_and_expected',
|
|
[
|
|
(
|
|
PublicOnly(),
|
|
{
|
|
'shown': ['name', 'age'],
|
|
'hidden': [],
|
|
},
|
|
),
|
|
(
|
|
MixedFields(),
|
|
{
|
|
'shown': ['name', 'value'],
|
|
'hidden': ['_hidden', '_meta'],
|
|
},
|
|
),
|
|
(
|
|
PrivateOnly(),
|
|
{
|
|
'shown': [],
|
|
'hidden': ['_secret', '_internal'],
|
|
},
|
|
),
|
|
],
|
|
ids=[
|
|
'all-public',
|
|
'mixed-pub-priv',
|
|
'all-private',
|
|
],
|
|
)
|
|
def test_field_visibility_in_pformat(
|
|
struct_and_expected: tuple[
|
|
Struct,
|
|
dict[str, list[str]],
|
|
],
|
|
):
|
|
'''
|
|
Verify `pformat()` shows public fields
|
|
and hides `_`-prefixed private fields.
|
|
|
|
'''
|
|
(
|
|
struct,
|
|
expected,
|
|
) = struct_and_expected
|
|
output: str = pformat(struct)
|
|
|
|
for field_name in expected['shown']:
|
|
assert field_name in output, (
|
|
f'{field_name!r} should appear in:\n'
|
|
f'{output}'
|
|
)
|
|
|
|
for field_name in expected['hidden']:
|
|
assert field_name not in output, (
|
|
f'{field_name!r} should NOT appear in:\n'
|
|
f'{output}'
|
|
)
|
|
|
|
|
|
def test_iter_ppfmt_lines_skips_private():
|
|
'''
|
|
Directly verify `iter_struct_ppfmt_lines()`
|
|
never yields tuples with `_`-prefixed field
|
|
names.
|
|
|
|
'''
|
|
struct = MixedFields()
|
|
lines: list[tuple[str, str]] = list(
|
|
iter_struct_ppfmt_lines(
|
|
struct,
|
|
field_indent=2,
|
|
)
|
|
)
|
|
# should have lines for public fields only
|
|
assert len(lines) == 2
|
|
|
|
for _prefix, line_content in lines:
|
|
field_name: str = (
|
|
line_content.split(':')[0].strip()
|
|
)
|
|
assert not field_name.startswith('_'), (
|
|
f'private field leaked: {field_name!r}'
|
|
)
|
|
|
|
|
|
def test_nested_struct_filters_inner_private():
|
|
'''
|
|
Verify that nested struct's private fields
|
|
are also filtered out during recursion.
|
|
|
|
'''
|
|
outer = Outer()
|
|
output: str = pformat(outer)
|
|
|
|
# outer's public field
|
|
assert 'label' in output
|
|
|
|
# inner's public field (recursed into)
|
|
assert 'x' in output
|
|
|
|
# inner's private field must be hidden
|
|
assert '_secret' not in output
|
|
|
|
|
|
def test_empty_struct_pformat():
|
|
'''
|
|
An empty struct should produce a valid
|
|
`pformat()` result with no field lines.
|
|
|
|
'''
|
|
output: str = pformat(EmptyStruct())
|
|
assert 'EmptyStruct(' in output
|
|
assert output.rstrip().endswith(')')
|
|
|
|
# no field lines => only struct header+footer
|
|
lines: list[tuple[str, str]] = list(
|
|
iter_struct_ppfmt_lines(
|
|
EmptyStruct(),
|
|
field_indent=2,
|
|
)
|
|
)
|
|
assert lines == []
|
|
|
|
|
|
def test_real_msgdec_pformat_hides_private():
|
|
'''
|
|
Verify `pformat()` on a real `MsgDec`
|
|
hides the `_dec` internal field.
|
|
|
|
NOTE: `MsgDec.__repr__` is custom and does
|
|
NOT call `pformat()`, so we call it directly.
|
|
|
|
'''
|
|
dec: MsgDec = mk_dec(spec=int)
|
|
output: str = pformat(dec)
|
|
|
|
# the private `_dec` field should be filtered
|
|
assert '_dec' not in output
|
|
|
|
# but the struct type name should be present
|
|
assert 'MsgDec(' in output
|
|
|
|
|
|
def test_pformat_repr_integration():
|
|
'''
|
|
Verify that `Struct.__repr__()` (which calls
|
|
`pformat()`) also hides private fields for
|
|
custom structs that do NOT override `__repr__`.
|
|
|
|
'''
|
|
mixed = MixedFields()
|
|
output: str = repr(mixed)
|
|
|
|
assert 'name' in output
|
|
assert 'value' in output
|
|
assert '_hidden' not in output
|
|
assert '_meta' not in output
|