Coverage for tests/test_inheritance.py: 100.00%
62 statements
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-12 01:34 +0200
1"""Tests for class inheritance."""
3from __future__ import annotations
5from typing import TYPE_CHECKING, Callable
7import pytest
9from griffe import ModulesCollection, temporary_inspected_module, temporary_visited_module
11if TYPE_CHECKING:
12 from griffe import Class
15def _mro_paths(cls: Class) -> list[str]:
16 return [base.path for base in cls.mro()]
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.
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): ...
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
41 class C(B):
42 attr_from_c = 2
43 def method_from_c(self): ...
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()
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]
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"]
63 assert classa in classb.resolved_bases
64 assert classb in classc.resolved_bases
65 assert classc in classd.resolved_bases
67 classd_mro = classd.mro()
68 assert classa in classd_mro
69 assert classb in classd_mro
70 assert classc in classd_mro
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
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
83 assert "attr_from_b" in classd.all_members
86@pytest.mark.parametrize("agent", [temporary_visited_module, temporary_inspected_module])
87def test_nested_class_inheritance(agent: Callable) -> None:
88 """Test nested class inheritance.
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
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
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 ]
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.
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]
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.
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])
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
177 with temporary_inspected_module(code) as module:
178 assert _mro_paths(module["A"]) == [] # not supported either