Coverage for tests / test_extensions / test_base.py: 100.00%
100 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-11 11:48 +0100
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-11 11:48 +0100
1"""Tests for the base extension functionality."""
3from __future__ import annotations
5import sys
6from pathlib import Path
7from typing import TYPE_CHECKING, Any
9import pytest
11from griffe import (
12 Alias,
13 Extension,
14 GriffeLoader,
15 ObjectNode,
16 load_extensions,
17 temporary_visited_module,
18 temporary_visited_package,
19)
20from griffe._internal.models import Attribute, Class, Function, Module, Object, TypeAlias
22if TYPE_CHECKING:
23 import ast
25 from griffe import Attribute, Class, Function, Module, Object, ObjectNode, TypeAlias
28class AnalysisEventsTest(Extension): # noqa: D101
29 def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107
30 super().__init__()
31 self.records: list[str] = []
32 self.args = args
33 self.kwargs = kwargs
35 def on_attribute_instance(self, *, node: ast.AST | ObjectNode, attr: Attribute, **kwargs: Any) -> None: # noqa: D102,ARG002
36 self.records.append("on_attribute_instance")
38 def on_attribute_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002
39 self.records.append("on_attribute_node")
41 def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: # noqa: D102,ARG002
42 self.records.append("on_class_instance")
44 def on_class_members(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: # noqa: D102,ARG002
45 self.records.append("on_class_members")
47 def on_class_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002
48 self.records.append("on_class_node")
50 def on_function_instance(self, *, node: ast.AST | ObjectNode, func: Function, **kwargs: Any) -> None: # noqa: D102,ARG002
51 self.records.append("on_function_instance")
53 def on_function_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002
54 self.records.append("on_function_node")
56 def on_instance(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: # noqa: D102,ARG002
57 self.records.append("on_instance")
59 def on_members(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: # noqa: D102,ARG002
60 self.records.append("on_members")
62 def on_module_instance(self, *, node: ast.AST | ObjectNode, mod: Module, **kwargs: Any) -> None: # noqa: D102,ARG002
63 self.records.append("on_module_instance")
65 def on_module_members(self, *, node: ast.AST | ObjectNode, mod: Module, **kwargs: Any) -> None: # noqa: D102,ARG002
66 self.records.append("on_module_members")
68 def on_module_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002
69 self.records.append("on_module_node")
71 def on_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002
72 self.records.append("on_node")
74 def on_type_alias_instance(self, *, node: ast.AST | ObjectNode, type_alias: TypeAlias, **kwargs: Any) -> None: # noqa: D102,ARG002
75 self.records.append("on_type_alias_instance")
77 def on_type_alias_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002
78 self.records.append("on_type_alias_node")
80 def on_alias_instance(self, *, node: ast.AST | ObjectNode, alias: Alias, **kwargs: Any) -> None: # noqa: D102,ARG002
81 self.records.append("on_alias_instance")
84@pytest.mark.parametrize(
85 "extension",
86 [
87 # With module path.
88 "tests.test_extensions.test_base",
89 {"tests.test_extensions.test_base": {"option": 0}},
90 # With extension path.
91 "tests.test_extensions.test_base.AnalysisEventsTest",
92 {"tests.test_extensions.test_base.AnalysisEventsTest": {"option": 0}},
93 # With filepath.
94 "tests/test_extensions/test_base.py",
95 {"tests/test_extensions/test_base.py": {"option": 0}},
96 # With filepath and extension name.
97 "tests/test_extensions/test_base.py:AnalysisEventsTest",
98 {"tests/test_extensions/test_base.py:AnalysisEventsTest": {"option": 0}},
99 # With instance.
100 AnalysisEventsTest(option=0),
101 # With class.
102 AnalysisEventsTest,
103 # With absolute paths (esp. important to test for Windows).
104 Path("tests/test_extensions/test_base.py").absolute().as_posix(),
105 Path("tests/test_extensions/test_base.py:AnalysisEventsTest").absolute().as_posix(),
106 ],
107)
108def test_loading_extensions(extension: str | dict[str, dict[str, Any]] | Extension | type[Extension]) -> None:
109 """Test the extensions loading mechanisms.
111 Parameters:
112 extension: Extension specification (parametrized).
113 """
114 extensions = load_extensions(extension)
115 loaded: AnalysisEventsTest = extensions._extensions[0] # ty:ignore[invalid-assignment]
116 # We cannot use isinstance here,
117 # because loading from a filepath drops the parent `tests` package,
118 # resulting in a different object than the present ExtensionTest.
119 assert loaded.__class__.__name__ == "AnalysisEventsTest"
120 if isinstance(extension, (dict, AnalysisEventsTest)):
121 assert loaded.kwargs == {"option": 0}
124# YORE: EOL 3.11: Remove block.
125def test_analysis_events_without_type_aliases() -> None:
126 """Test analysis events triggering."""
127 extension = AnalysisEventsTest()
128 with temporary_visited_module(
129 """
130 import x
131 attr = 0
132 def func(): ...
133 class Class:
134 cattr = 1
135 def method(self): ...
136 """,
137 extensions=load_extensions(extension),
138 ):
139 pass
140 events = [
141 "on_alias_instance",
142 "on_attribute_instance",
143 "on_attribute_node",
144 "on_class_instance",
145 "on_class_members",
146 "on_class_node",
147 "on_function_instance",
148 "on_function_node",
149 "on_instance",
150 "on_members",
151 "on_module_instance",
152 "on_module_members",
153 "on_module_node",
154 "on_node",
155 ]
156 assert set(events) == set(extension.records)
159# YORE: EOL 3.11: Remove line.
160@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 type aliases")
161def test_analysis_events() -> None:
162 """Test analysis events triggering."""
163 extension = AnalysisEventsTest()
164 with temporary_visited_module(
165 """
166 import x
167 attr = 0
168 def func(): ...
169 class Class:
170 cattr = 1
171 def method(self): ...
172 type TypeAlias = list[int]
173 """,
174 extensions=load_extensions(extension),
175 ):
176 pass
177 events = [
178 "on_alias_instance",
179 "on_attribute_instance",
180 "on_attribute_node",
181 "on_class_instance",
182 "on_class_members",
183 "on_class_node",
184 "on_function_instance",
185 "on_function_node",
186 "on_instance",
187 "on_members",
188 "on_module_instance",
189 "on_module_members",
190 "on_module_node",
191 "on_node",
192 "on_type_alias_instance",
193 "on_type_alias_node",
194 ]
195 assert set(events) == set(extension.records)
198class LoadEventsTest(Extension): # noqa: D101
199 def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107
200 super().__init__()
201 self.records: list[str] = []
202 self.args = args
203 self.kwargs = kwargs
205 def on_alias(self, *, alias: Alias, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
206 self.records.append("on_alias")
208 def on_attribute(self, *, attr: Attribute, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
209 self.records.append("on_attribute")
211 def on_class(self, *, cls: Class, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
212 self.records.append("on_class")
214 def on_function(self, *, func: Function, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
215 self.records.append("on_function")
217 def on_module(self, *, mod: Module, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
218 self.records.append("on_module")
220 def on_object(self, *, obj: Object, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
221 self.records.append("on_object")
223 def on_package(self, *, pkg: Module, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
224 self.records.append("on_package")
226 def on_type_alias(self, *, type_alias: TypeAlias, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102
227 self.records.append("on_type_alias")
230# YORE: EOL 3.11: Remove block.
231def test_load_events_without_type_aliases() -> None:
232 """Test load events triggering."""
233 extension = LoadEventsTest()
234 with temporary_visited_package(
235 "pkg",
236 {
237 "__init__.py": """
238 import x
239 attr = 0
240 def func(): ...
241 class Class:
242 cattr = 1
243 def method(self): ...
244 """,
245 },
246 extensions=load_extensions(extension),
247 ):
248 pass
249 events = [
250 "on_alias",
251 "on_attribute",
252 "on_class",
253 "on_function",
254 "on_module",
255 "on_object",
256 "on_package",
257 ]
258 assert set(events) == set(extension.records)
261# YORE: EOL 3.11: Remove line.
262@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 type aliases")
263def test_load_events() -> None:
264 """Test load events triggering."""
265 extension = LoadEventsTest()
266 with temporary_visited_package(
267 "pkg",
268 {
269 "__init__.py": """
270 import x
271 attr = 0
272 def func(): ...
273 class Class:
274 cattr = 1
275 def method(self): ...
276 type TypeAlias = list[int]
277 """,
278 },
279 extensions=load_extensions(extension),
280 ):
281 pass
282 events = [
283 "on_alias",
284 "on_attribute",
285 "on_class",
286 "on_function",
287 "on_module",
288 "on_object",
289 "on_package",
290 "on_type_alias",
291 ]
292 assert set(events) == set(extension.records)