Skip to content

Commit c49061c

Browse files
ci: remove custom error collection and use pytest
modify tox.ini to run pytest in validate environment with -vv so that the nested dictionary comparison is actually shown
1 parent 021cdd1 commit c49061c

2 files changed

Lines changed: 71 additions & 94 deletions

File tree

tests/validate/test_facades.py

Lines changed: 70 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -21,96 +21,73 @@ class Versions(TypedDict, total=True):
2121

2222

2323
@pytest.fixture
24-
def project_root(request: pytest.FixtureRequest):
25-
return request.config.rootpath
26-
27-
28-
class TestFacades:
29-
def test_client_facades(self, project_root: Path) -> None:
30-
good_facades = self.generate_client_facades(project_root)
31-
client_facades = cast(ClientFacades, connection.client_facades)
32-
33-
errors: List[Tuple[str, Optional[List[int]], Optional[List[int]]]] = []
34-
all_names = sorted(set(connection.client_facades).union(good_facades))
35-
for name in all_names:
36-
expected = self._versions_from_facades(good_facades, name)
37-
actual = self._versions_from_facades(client_facades, name)
38-
if expected != actual:
39-
errors.append((name, expected, actual))
40-
41-
if errors:
42-
print('The following errors were found in connection.client_facades:')
43-
for name, expected, actual in errors:
44-
expected_msg = (
45-
f'should be {expected},'
46-
if expected is not None
47-
else 'should not be present,'
48-
)
49-
actual_msg = (
50-
f'not {actual}'
51-
if actual is not None
52-
else 'but is not present'
53-
)
54-
print(f' {name!r} {expected_msg} {actual_msg}')
55-
56-
assert not errors
57-
58-
@classmethod
59-
def generate_client_facades(cls, project_root: Path) -> ClientFacades:
60-
"""Return a client_facades dictionary from generated code under project_root.
61-
"""
62-
files_by_version: List[Tuple[int, Path]] = []
63-
# [(facade_version, Path), ...]
64-
for file in (project_root / 'juju' / 'client').glob('_client[0-9]*.py'):
65-
files_by_version.append((cls._version_from_filename(file), file))
66-
files_by_version.sort()
67-
68-
# _clientN.py files import * from _definitions
69-
# so we will ignore any names from there
70-
ignore = dir(importlib.import_module('juju.client._definitions'))
71-
72-
facades_by_version: Dict[int, Set[str]] = {}
73-
# {facade_version: {facade_name, ...}, ...}
74-
for version, file in files_by_version:
75-
module = cls._try_import(f'juju.client.{file.stem}')
76-
facades = {
77-
name.removesuffix("Facade")
78-
for name in dir(module)
79-
if not (name.startswith('_') or name in ignore)
80-
}
81-
facades_by_version[version] = facades
82-
83-
# client_facades in connection.py is sorted
84-
# so we sort facade names before constructing it
85-
first, *rest = facades_by_version.values()
86-
sorted_facade_names: list[str] = sorted(first.union(*rest))
87-
88-
client_facades: ClientFacades = {}
89-
# {facade_name: {'versions': [1, 2, 3, ...]}, ...}
90-
for name in sorted_facade_names:
91-
versions: List[int] = []
92-
for version, facades in facades_by_version.items():
93-
if name in facades:
94-
versions.append(version)
95-
client_facades[name] = {'versions': versions}
96-
return client_facades
97-
98-
@staticmethod
99-
def _try_import(module_name: str) -> Optional[ModuleType]:
100-
try:
101-
return importlib.import_module(module_name)
102-
except NameError as e:
103-
warnings.warn(f'error on importing {module_name}:\n{type(e).__name__}: {e}')
104-
return None
105-
106-
@staticmethod
107-
def _version_from_filename(path: Path) -> int:
108-
match = re.search('_client([0-9]+).py', path.name)
109-
assert match
110-
return int(match.group(1))
111-
112-
@staticmethod
113-
def _versions_from_facades(facades: ClientFacades, name: str) -> Optional[List[int]]:
114-
if name not in facades:
115-
return None
116-
return facades[name]['versions']
24+
def project_root(pytestconfig: pytest.Config) -> Path:
25+
return pytestconfig.rootpath
26+
27+
28+
def test_client_facades(project_root: Path) -> None:
29+
good_facades = make_client_facades_from_generated_code(project_root)
30+
client_facades = cast(ClientFacades, connection.client_facades)
31+
32+
assert {
33+
k: v['versions'] for k, v in client_facades.items()
34+
} == {
35+
k: v['versions'] for k, v in good_facades.items()
36+
}
37+
38+
39+
def make_client_facades_from_generated_code(project_root: Path) -> ClientFacades:
40+
"""Return a client_facades dictionary from generated code under project_root.
41+
"""
42+
files_by_version: List[Tuple[int, Path]] = []
43+
# [(facade_version, Path), ...]
44+
for file in (project_root / 'juju' / 'client').glob('_client[0-9]*.py'):
45+
files_by_version.append((_version_from_filename(file), file))
46+
files_by_version.sort()
47+
48+
# _clientN.py files import * from _definitions
49+
# so we will ignore any names from there
50+
ignore = dir(importlib.import_module('juju.client._definitions'))
51+
52+
facades_by_version: Dict[int, Set[str]] = {}
53+
# {facade_version: {facade_name, ...}, ...}
54+
for version, file in files_by_version:
55+
module = _try_import(f'juju.client.{file.stem}')
56+
facades = {
57+
name.removesuffix("Facade")
58+
for name in dir(module)
59+
if not (name.startswith('_') or name in ignore)
60+
}
61+
facades_by_version[version] = facades
62+
63+
# client_facades in connection.py is sorted
64+
# so we sort facade names before constructing it
65+
first, *rest = facades_by_version.values()
66+
sorted_facade_names: list[str] = sorted(first.union(*rest))
67+
68+
client_facades: ClientFacades = {}
69+
# {facade_name: {'versions': [1, 2, 3, ...]}, ...}
70+
for name in sorted_facade_names:
71+
versions: List[int] = []
72+
for version, facades in facades_by_version.items():
73+
if name in facades:
74+
versions.append(version)
75+
client_facades[name] = {'versions': versions}
76+
return client_facades
77+
78+
def _try_import(module_name: str) -> Optional[ModuleType]:
79+
try:
80+
return importlib.import_module(module_name)
81+
except NameError as e:
82+
warnings.warn(f'error on importing {module_name}:\n{type(e).__name__}: {e}')
83+
return None
84+
85+
def _version_from_filename(path: Path) -> int:
86+
match = re.search('_client([0-9]+).py', path.name)
87+
assert match
88+
return int(match.group(1))
89+
90+
def _versions_from_facades(facades: ClientFacades, name: str) -> Optional[List[int]]:
91+
if name not in facades:
92+
return None
93+
return facades[name]['versions']

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ commands =
105105

106106
[testenv:validate]
107107
envdir = {toxworkdir}/validate
108-
commands = python -m pytest --tb native -ra -v tests/validate
108+
commands = python -m pytest --tb native -ra -vv tests/validate
109109

110110
[testenv:example]
111111
envdir = {toxworkdir}/py3

0 commit comments

Comments
 (0)