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

1"""Tests for the base extension functionality.""" 

2 

3from __future__ import annotations 

4 

5import sys 

6from pathlib import Path 

7from typing import TYPE_CHECKING, Any 

8 

9import pytest 

10 

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 

21 

22if TYPE_CHECKING: 

23 import ast 

24 

25 from griffe import Attribute, Class, Function, Module, Object, ObjectNode, TypeAlias 

26 

27 

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 

34 

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") 

37 

38 def on_attribute_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 

39 self.records.append("on_attribute_node") 

40 

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") 

43 

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") 

46 

47 def on_class_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 

48 self.records.append("on_class_node") 

49 

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") 

52 

53 def on_function_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 

54 self.records.append("on_function_node") 

55 

56 def on_instance(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: # noqa: D102,ARG002 

57 self.records.append("on_instance") 

58 

59 def on_members(self, *, node: ast.AST | ObjectNode, obj: Object, **kwargs: Any) -> None: # noqa: D102,ARG002 

60 self.records.append("on_members") 

61 

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") 

64 

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") 

67 

68 def on_module_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 

69 self.records.append("on_module_node") 

70 

71 def on_node(self, *, node: ast.AST | ObjectNode, **kwargs: Any) -> None: # noqa: D102,ARG002 

72 self.records.append("on_node") 

73 

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") 

76 

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") 

79 

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") 

82 

83 

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. 

110 

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} 

122 

123 

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) 

157 

158 

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) 

196 

197 

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 

204 

205 def on_alias(self, *, alias: Alias, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

206 self.records.append("on_alias") 

207 

208 def on_attribute(self, *, attr: Attribute, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

209 self.records.append("on_attribute") 

210 

211 def on_class(self, *, cls: Class, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

212 self.records.append("on_class") 

213 

214 def on_function(self, *, func: Function, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

215 self.records.append("on_function") 

216 

217 def on_module(self, *, mod: Module, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

218 self.records.append("on_module") 

219 

220 def on_object(self, *, obj: Object, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

221 self.records.append("on_object") 

222 

223 def on_package(self, *, pkg: Module, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

224 self.records.append("on_package") 

225 

226 def on_type_alias(self, *, type_alias: TypeAlias, loader: GriffeLoader, **kwargs: Any) -> None: # noqa: ARG002,D102 

227 self.records.append("on_type_alias") 

228 

229 

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) 

259 

260 

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)