Coverage for tests/test_extensions.py: 100.00%

62 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-11 13:44 +0200

1"""Tests for the `extensions` module.""" 

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 Extension, load_extensions, temporary_visited_module 

12 

13if TYPE_CHECKING: 

14 import ast 

15 

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

17 

18 

19class ExtensionTest(Extension): # noqa: D101 

20 def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107 

21 super().__init__() 

22 self.records: list[str] = [] 

23 self.args = args 

24 self.kwargs = kwargs 

25 

26 def on_attribute_instance(self, *, node: ast.AST | ObjectNode, attr: Attribute, **kwargs: Any) -> None: # noqa: D102,ARG002 

27 self.records.append("on_attribute_instance") 

28 

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

30 self.records.append("on_attribute_node") 

31 

32 def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: # noqa: D102,ARG002 

33 self.records.append("on_class_instance") 

34 

35 def on_class_members(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: # noqa: D102,ARG002 

36 self.records.append("on_class_members") 

37 

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

39 self.records.append("on_class_node") 

40 

41 def on_function_instance(self, *, node: ast.AST | ObjectNode, func: Function, **kwargs: Any) -> None: # noqa: D102,ARG002 

42 self.records.append("on_function_instance") 

43 

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

45 self.records.append("on_function_node") 

46 

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

48 self.records.append("on_instance") 

49 

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

51 self.records.append("on_members") 

52 

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

54 self.records.append("on_module_instance") 

55 

56 def on_module_members(self, *, node: ast.AST | ObjectNode, mod: Module, **kwargs: Any) -> None: # noqa: D102,ARG002 

57 self.records.append("on_module_members") 

58 

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

60 self.records.append("on_module_node") 

61 

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

63 self.records.append("on_node") 

64 

65 def on_type_alias_instance(self, *, node: ast.AST | ObjectNode, type_alias: TypeAlias, **kwargs: Any) -> None: # noqa: D102,ARG002 

66 self.records.append("on_type_alias_instance") 

67 

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

69 self.records.append("on_type_alias_node") 

70 

71 

72@pytest.mark.parametrize( 

73 "extension", 

74 [ 

75 # With module path. 

76 "tests.test_extensions", 

77 {"tests.test_extensions": {"option": 0}}, 

78 # With extension path. 

79 "tests.test_extensions.ExtensionTest", 

80 {"tests.test_extensions.ExtensionTest": {"option": 0}}, 

81 # With filepath. 

82 "tests/test_extensions.py", 

83 {"tests/test_extensions.py": {"option": 0}}, 

84 # With filepath and extension name. 

85 "tests/test_extensions.py:ExtensionTest", 

86 {"tests/test_extensions.py:ExtensionTest": {"option": 0}}, 

87 # With instance. 

88 ExtensionTest(option=0), 

89 # With class. 

90 ExtensionTest, 

91 # With absolute paths (esp. important to test for Windows). 

92 Path("tests/test_extensions.py").absolute().as_posix(), 

93 Path("tests/test_extensions.py:ExtensionTest").absolute().as_posix(), 

94 ], 

95) 

96def test_loading_extensions(extension: str | dict[str, dict[str, Any]] | Extension | type[Extension]) -> None: 

97 """Test the extensions loading mechanisms. 

98 

99 Parameters: 

100 extension: Extension specification (parametrized). 

101 """ 

102 extensions = load_extensions(extension) 

103 loaded: ExtensionTest = extensions._extensions[0] # type: ignore[assignment] 

104 # We cannot use isinstance here, 

105 # because loading from a filepath drops the parent `tests` package, 

106 # resulting in a different object than the present ExtensionTest. 

107 assert loaded.__class__.__name__ == "ExtensionTest" 

108 if isinstance(extension, (dict, ExtensionTest)): 

109 assert loaded.kwargs == {"option": 0} 

110 

111 

112# YORE: EOL 3.11: Remove block. 

113def test_extension_events_without_type_aliases() -> None: 

114 """Test events triggering.""" 

115 extension = ExtensionTest() 

116 with temporary_visited_module( 

117 """ 

118 attr = 0 

119 def func(): ... 

120 class Class: 

121 cattr = 1 

122 def method(self): ... 

123 """, 

124 extensions=load_extensions(extension), 

125 ): 

126 pass 

127 events = [ 

128 "on_attribute_instance", 

129 "on_attribute_node", 

130 "on_class_instance", 

131 "on_class_members", 

132 "on_class_node", 

133 "on_function_instance", 

134 "on_function_node", 

135 "on_instance", 

136 "on_members", 

137 "on_module_instance", 

138 "on_module_members", 

139 "on_module_node", 

140 "on_node", 

141 ] 

142 assert set(events) == set(extension.records) 

143 

144 

145# YORE: EOL 3.11: Remove line. 

146@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 type aliases") 

147def test_extension_events() -> None: 

148 """Test events triggering.""" 

149 extension = ExtensionTest() 

150 with temporary_visited_module( 

151 """ 

152 attr = 0 

153 def func(): ... 

154 class Class: 

155 cattr = 1 

156 def method(self): ... 

157 type TypeAlias = list[int] 

158 """, 

159 extensions=load_extensions(extension), 

160 ): 

161 pass 

162 events = [ 

163 "on_attribute_instance", 

164 "on_attribute_node", 

165 "on_class_instance", 

166 "on_class_members", 

167 "on_class_node", 

168 "on_function_instance", 

169 "on_function_node", 

170 "on_instance", 

171 "on_members", 

172 "on_module_instance", 

173 "on_module_members", 

174 "on_module_node", 

175 "on_node", 

176 "on_type_alias_instance", 

177 "on_type_alias_node", 

178 ] 

179 assert set(events) == set(extension.records)