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

1"""Tests for unused function detection.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6import tempfile 

7 

8from call_graph import build_call_graph, find_unused_functions 

9 

10 

11def test_entry_point_unused(): 

12 code = """ 

13def calling(): 

14 called() 

15 

16def called(): 

17 pass 

18""" 

19 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: 

20 f.write(code) 

21 f.flush() 

22 

23 graph = build_call_graph([Path(f.name)]) 

24 unused = find_unused_functions(graph) 

25 

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 

31 

32 Path(f.name).unlink() 

33 

34 

35def test_mutual_cycle_both_used(): 

36 code = """ 

37def foo(): 

38 return bar() 

39 

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() 

46 

47 graph = build_call_graph([Path(f.name)]) 

48 unused = find_unused_functions(graph) 

49 

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 

53 

54 Path(f.name).unlink() 

55 

56 

57def test_private_filtered(): 

58 code = """ 

59def _helper(): 

60 pass 

61 

62def main(): 

63 _helper() 

64""" 

65 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: 

66 f.write(code) 

67 f.flush() 

68 

69 graph = build_call_graph([Path(f.name)]) 

70 unused = find_unused_functions(graph) 

71 

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 

79 

80 Path(f.name).unlink() 

81 

82 

83def test_chained_usage(): 

84 code = """ 

85def a(): 

86 b() 

87 

88def b(): 

89 c() 

90 

91def c(): 

92 pass 

93""" 

94 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: 

95 f.write(code) 

96 f.flush() 

97 

98 graph = build_call_graph([Path(f.name)]) 

99 unused = find_unused_functions(graph) 

100 

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 

107 

108 Path(f.name).unlink() 

109 

110 

111def test_unused_function_detection(): 

112 code = """ 

113def a(): 

114 pass 

115 

116def b(): 

117 pass 

118""" 

119 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: 

120 f.write(code) 

121 f.flush() 

122 

123 graph = build_call_graph([Path(f.name)]) 

124 unused = find_unused_functions(graph) 

125 

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 

131 

132 Path(f.name).unlink() 

133 

134 

135def test_caller_is_unused(): 

136 code = """ 

137def foo(): 

138 return bar() 

139 

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() 

146 

147 graph = build_call_graph([Path(f.name)]) 

148 unused = find_unused_functions(graph) 

149 

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 

155 

156 Path(f.name).unlink()