Coverage for tests/test_extensions.py: 100.00%

50 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-16 15:54 +0200

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

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from typing import TYPE_CHECKING, Any 

7 

8import pytest 

9 

10from griffe import Extension, load_extensions, temporary_visited_module 

11 

12if TYPE_CHECKING: 

13 import ast 

14 

15 from griffe import Attribute, Class, Function, Module, Object, ObjectNode 

16 

17 

18class ExtensionTest(Extension): # noqa: D101 

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

20 super().__init__() 

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

22 self.args = args 

23 self.kwargs = kwargs 

24 

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

26 self.records.append("on_attribute_instance") 

27 

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

29 self.records.append("on_attribute_node") 

30 

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

32 self.records.append("on_class_instance") 

33 

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

35 self.records.append("on_class_members") 

36 

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

38 self.records.append("on_class_node") 

39 

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

41 self.records.append("on_function_instance") 

42 

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

44 self.records.append("on_function_node") 

45 

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

47 self.records.append("on_instance") 

48 

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

50 self.records.append("on_members") 

51 

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

53 self.records.append("on_module_instance") 

54 

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

56 self.records.append("on_module_members") 

57 

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

59 self.records.append("on_module_node") 

60 

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

62 self.records.append("on_node") 

63 

64 

65@pytest.mark.parametrize( 

66 "extension", 

67 [ 

68 # with module path 

69 "tests.test_extensions", 

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

71 # with extension path 

72 "tests.test_extensions.ExtensionTest", 

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

74 # with filepath 

75 "tests/test_extensions.py", 

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

77 # with filepath and extension name 

78 "tests/test_extensions.py:ExtensionTest", 

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

80 # with instance 

81 ExtensionTest(option=0), 

82 # with class 

83 ExtensionTest, 

84 # with absolute paths (esp. important to test for Windows) 

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

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

87 ], 

88) 

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

90 """Test the extensions loading mechanisms. 

91 

92 Parameters: 

93 extension: Extension specification (parametrized). 

94 """ 

95 extensions = load_extensions(extension) 

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

97 # We cannot use isinstance here, 

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

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

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

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

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

103 

104 

105def test_extension_events() -> None: 

106 """Test events triggering.""" 

107 extension = ExtensionTest() 

108 with temporary_visited_module( 

109 """ 

110 attr = 0 

111 def func(): ... 

112 class Class: 

113 cattr = 1 

114 def method(self): ... 

115 """, 

116 extensions=load_extensions(extension), 

117 ): 

118 pass 

119 events = [ 

120 "on_attribute_instance", 

121 "on_attribute_node", 

122 "on_class_instance", 

123 "on_class_members", 

124 "on_class_node", 

125 "on_function_instance", 

126 "on_function_node", 

127 "on_instance", 

128 "on_members", 

129 "on_module_instance", 

130 "on_module_members", 

131 "on_module_node", 

132 "on_node", 

133 ] 

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