Coverage for tests/test_inheritance.py: 100.00%

62 statements  

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

1"""Tests for class inheritance.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Callable 

6 

7import pytest 

8 

9from griffe import ModulesCollection, temporary_inspected_module, temporary_visited_module 

10 

11if TYPE_CHECKING: 

12 from griffe import Class 

13 

14 

15def _mro_paths(cls: Class) -> list[str]: 

16 return [base.path for base in cls.mro()] 

17 

18 

19@pytest.mark.parametrize("agent1", [temporary_visited_module, temporary_inspected_module]) 

20@pytest.mark.parametrize("agent2", [temporary_visited_module, temporary_inspected_module]) 

21def test_loading_inherited_members(agent1: Callable, agent2: Callable) -> None: 

22 """Test basic class inheritance. 

23 

24 Parameters: 

25 agent1: A parametrized agent to load a module. 

26 agent2: A parametrized agent to load a module. 

27 """ 

28 code1 = """ 

29 class A: 

30 attr_from_a = 0 

31 def method_from_a(self): ... 

32 

33 class B(A): 

34 attr_from_a = 1 

35 attr_from_b = 1 

36 def method_from_b(self): ... 

37 """ 

38 code2 = """ 

39 from module1 import B 

40 

41 class C(B): 

42 attr_from_c = 2 

43 def method_from_c(self): ... 

44 

45 class D(C): 

46 attr_from_a = 3 

47 attr_from_d = 3 

48 def method_from_d(self): ... 

49 """ 

50 inspection_options = {} 

51 collection = ModulesCollection() 

52 

53 with agent1(code1, module_name="module1", modules_collection=collection) as module1: 

54 if agent2 is temporary_inspected_module: 

55 inspection_options["import_paths"] = [module1.filepath.parent] 

56 

57 with agent2(code2, module_name="module2", modules_collection=collection, **inspection_options) as module2: 

58 classa = module1["A"] 

59 classb = module1["B"] 

60 classc = module2["C"] 

61 classd = module2["D"] 

62 

63 assert classa in classb.resolved_bases 

64 assert classb in classc.resolved_bases 

65 assert classc in classd.resolved_bases 

66 

67 classd_mro = classd.mro() 

68 assert classa in classd_mro 

69 assert classb in classd_mro 

70 assert classc in classd_mro 

71 

72 inherited_members = classd.inherited_members 

73 assert "attr_from_a" not in inherited_members # overwritten 

74 assert "attr_from_b" in inherited_members 

75 assert "attr_from_c" in inherited_members 

76 assert "attr_from_d" not in inherited_members # own-declared 

77 

78 assert "method_from_a" in inherited_members 

79 assert "method_from_b" in inherited_members 

80 assert "method_from_c" in inherited_members 

81 assert "method_from_d" not in inherited_members # own-declared 

82 

83 assert "attr_from_b" in classd.all_members 

84 

85 

86@pytest.mark.parametrize("agent", [temporary_visited_module, temporary_inspected_module]) 

87def test_nested_class_inheritance(agent: Callable) -> None: 

88 """Test nested class inheritance. 

89 

90 Parameters: 

91 agent: A parametrized agent to load a module. 

92 """ 

93 code = """ 

94 class A: 

95 class B: 

96 attr_from_b = 0 

97 

98 class C(A.B): 

99 attr_from_c = 1 

100 """ 

101 with agent(code) as module: 

102 assert "attr_from_b" in module["C"].inherited_members 

103 

104 code = """ 

105 class OuterA: 

106 class Inner: ... 

107 class OuterB(OuterA): 

108 class Inner(OuterA.Inner): ... 

109 class OuterC(OuterA): 

110 class Inner(OuterA.Inner): ... 

111 class OuterD(OuterC): 

112 class Inner(OuterC.Inner, OuterB.Inner): ... 

113 """ 

114 with temporary_visited_module(code) as module: 

115 assert _mro_paths(module["OuterD.Inner"]) == [ 

116 "module.OuterC.Inner", 

117 "module.OuterB.Inner", 

118 "module.OuterA.Inner", 

119 ] 

120 

121 

122@pytest.mark.parametrize( 

123 ("classes", "cls", "expected_mro"), 

124 [ 

125 (["A", "B(A)"], "B", ["A"]), 

126 (["A", "B(A)", "C(A)", "D(B, C)"], "D", ["B", "C", "A"]), 

127 (["A", "B(A)", "C(A)", "D(C, B)"], "D", ["C", "B", "A"]), 

128 (["A(Z)"], "A", []), 

129 (["A(str)"], "A", []), 

130 (["A", "B(A)", "C(B)", "D(C)"], "D", ["C", "B", "A"]), 

131 (["A", "B(A)", "C(B)", "D(C)", "E(A)", "F(B)", "G(F, E)", "H(G, D)"], "H", ["G", "F", "D", "C", "B", "E", "A"]), 

132 (["A", "B(A[T])", "C(B[T])"], "C", ["B", "A"]), 

133 ], 

134) 

135def test_computing_mro(classes: list[str], cls: str, expected_mro: list[str]) -> None: 

136 """Test computing MRO. 

137 

138 Parameters: 

139 classes: A list of classes inheriting from each other. 

140 cls: The class to compute the MRO of. 

141 expected_mro: The expected computed MRO. 

142 """ 

143 code = "class " + ": ...\nclass ".join(classes) + ": ..." 

144 with temporary_visited_module(code) as module: 

145 assert _mro_paths(module[cls]) == [f"module.{base}" for base in expected_mro] 

146 

147 

148@pytest.mark.parametrize( 

149 ("classes", "cls"), 

150 [ 

151 (["A", "B(A, A)"], "B"), 

152 (["A(D)", "B", "C(A, B)", "D(C)"], "D"), 

153 ], 

154) 

155def test_uncomputable_mro(classes: list[str], cls: str) -> None: 

156 """Test uncomputable MRO. 

157 

158 Parameters: 

159 classes: A list of classes inheriting from each other. 

160 cls: The class to compute the MRO of. 

161 """ 

162 code = "class " + ": ...\nclass ".join(classes) + ": ..." 

163 with temporary_visited_module(code) as module, pytest.raises(ValueError, match="Cannot compute C3 linearization"): 

164 _mro_paths(module[cls]) 

165 

166 

167def test_dynamic_base_classes() -> None: 

168 """Test dynamic base classes.""" 

169 code = """ 

170 from collections import namedtuple 

171 class A(namedtuple("B", "attrb")): 

172 attra = 0 

173 """ 

174 with temporary_visited_module(code) as module: 

175 assert _mro_paths(module["A"]) == [] # not supported 

176 

177 with temporary_inspected_module(code) as module: 

178 assert _mro_paths(module["A"]) == [] # not supported either