Coverage for tests / test_call_graph / test_unused.py: 100%
71 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""Tests for unused function detection."""
3from __future__ import annotations
5from pathlib import Path
6import tempfile
8from call_graph import build_call_graph, find_unused_functions
11def test_entry_point_unused():
12 code = """
13def calling():
14 called()
16def called():
17 pass
18"""
19 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
20 f.write(code)
21 f.flush()
23 graph = build_call_graph([Path(f.name)])
24 unused = find_unused_functions(graph)
26 # 'calling' is an entry point that's never called, so it's unused
27 # 'called' is called by 'calling', so it's not unused
28 unused_names = {u.name for u in unused}
29 assert "calling" in unused_names
30 assert "called" not in unused_names
32 Path(f.name).unlink()
35def test_mutual_cycle_both_used():
36 code = """
37def foo():
38 return bar()
40def bar():
41 return foo()
42"""
43 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
44 f.write(code)
45 f.flush()
47 graph = build_call_graph([Path(f.name)])
48 unused = find_unused_functions(graph)
50 # In a mutual call cycle, both functions are called (by each other)
51 # So neither should be detected as unused
52 assert len(unused) == 0
54 Path(f.name).unlink()
57def test_private_filtered():
58 code = """
59def _helper():
60 pass
62def main():
63 _helper()
64"""
65 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
66 f.write(code)
67 f.flush()
69 graph = build_call_graph([Path(f.name)])
70 unused = find_unused_functions(graph)
72 # 'main' is an entry point (never called), so it's unused
73 # '_helper' is called by main, but also filtered because it starts with '_'
74 # So only 'main' appears in unused
75 unused_names = {u.name for u in unused}
76 assert len(unused_names) == 1
77 assert "main" in unused_names
78 assert "_helper" not in unused_names
80 Path(f.name).unlink()
83def test_chained_usage():
84 code = """
85def a():
86 b()
88def b():
89 c()
91def c():
92 pass
93"""
94 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
95 f.write(code)
96 f.flush()
98 graph = build_call_graph([Path(f.name)])
99 unused = find_unused_functions(graph)
101 # 'a' is the entry point, not called by anything
102 # 'b' and 'c' are called by a and b respectively, so not unused
103 unused_names = {u.name for u in unused}
104 assert "a" in unused_names
105 assert "b" not in unused_names
106 assert "c" not in unused_names
108 Path(f.name).unlink()
111def test_unused_function_detection():
112 code = """
113def a():
114 pass
116def b():
117 pass
118"""
119 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
120 f.write(code)
121 f.flush()
123 graph = build_call_graph([Path(f.name)])
124 unused = find_unused_functions(graph)
126 # Neither function is called, so both are unused
127 unused_names = {u.name for u in unused}
128 assert len(unused_names) == 2
129 assert "a" in unused_names
130 assert "b" in unused_names
132 Path(f.name).unlink()
135def test_caller_is_unused():
136 code = """
137def foo():
138 return bar()
140def bar():
141 return 42
142"""
143 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
144 f.write(code)
145 f.flush()
147 graph = build_call_graph([Path(f.name)])
148 unused = find_unused_functions(graph)
150 # 'foo' is not called by anyone, so it's unused
151 # 'bar' IS called by 'foo', so it's not unused
152 unused_names = {u.name for u in unused}
153 assert "foo" in unused_names
154 assert "bar" not in unused_names
156 Path(f.name).unlink()