Coverage for tests / test_npath / test_python.py: 100%

75 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-08 15:04 -0800

1"""Tests for NPath complexity (Python).""" 

2 

3from analyzers.python import analyze_source 

4 

5 

6def _npath(code: str) -> int: 

7 """Return NPath of the first function in code.""" 

8 result = analyze_source(code) 

9 assert result.functions, f"No functions found in:\n{code}" 

10 return result.functions[0].npath 

11 

12 

13def _npaths(code: str) -> dict[str, int]: 

14 """Return {name: npath} for all functions in code.""" 

15 result = analyze_source(code) 

16 return {f.name: f.npath for f in result.functions} 

17 

18 

19# --- Simple --- 

20 

21def test_simple_function(): 

22 assert _npath("def f(): pass") == 1 

23 

24 

25def test_empty_source(): 

26 result = analyze_source("") 

27 assert len(result.functions) == 0 

28 

29 

30# --- If statements --- 

31 

32def test_if_only(): 

33 code = """ 

34def f(x): 

35 if x: 

36 pass 

37""" 

38 assert _npath(code) == 2 # NP(if body)=1 + 1 (no else) 

39 

40 

41def test_if_else(): 

42 code = """ 

43def f(x): 

44 if x: 

45 pass 

46 else: 

47 pass 

48""" 

49 assert _npath(code) == 2 # NP(if body)=1 + NP(else body)=1 

50 

51 

52def test_if_elif_else(): 

53 code = """ 

54def f(x): 

55 if x > 0: 

56 pass 

57 elif x < 0: 

58 pass 

59 else: 

60 pass 

61""" 

62 assert _npath(code) == 3 # 1 + 1 + 1 

63 

64 

65def test_if_elif_no_else(): 

66 code = """ 

67def f(x): 

68 if x > 0: 

69 pass 

70 elif x < 0: 

71 pass 

72""" 

73 assert _npath(code) == 3 # 1 + 1 + 1 (implicit no-else path) 

74 

75 

76# --- Bool ops in conditions --- 

77 

78def test_bool_ops_in_if(): 

79 code = """ 

80def f(a, b): 

81 if a and b: 

82 pass 

83""" 

84 assert _npath(code) == 3 # 1 + 1 (no else) + 1 (one bool op) 

85 

86 

87def test_two_bool_ops(): 

88 code = """ 

89def f(a, b, c): 

90 if a and b or c: 

91 pass 

92""" 

93 assert _npath(code) == 4 # 1 + 1 (no else) + 2 (two bool ops) 

94 

95 

96# --- Loops --- 

97 

98def test_for_loop(): 

99 code = """ 

100def f(items): 

101 for x in items: 

102 pass 

103""" 

104 assert _npath(code) == 2 # NP(body)=1 + 1 

105 

106 

107def test_while_loop(): 

108 code = """ 

109def f(x): 

110 while x: 

111 pass 

112""" 

113 assert _npath(code) == 2 # NP(body)=1 + 1 

114 

115 

116def test_while_with_bool_cond(): 

117 code = """ 

118def f(a, b): 

119 while a and b: 

120 pass 

121""" 

122 assert _npath(code) == 3 # NP(body)=1 + 1 + 1 (bool op) 

123 

124 

125def test_for_else(): 

126 code = """ 

127def f(items): 

128 for x in items: 

129 pass 

130 else: 

131 pass 

132""" 

133 assert _npath(code) == 2 # NP(body)=1 + NP(else)=1 

134 

135 

136# --- Try/except --- 

137 

138def test_try_except(): 

139 code = """ 

140def f(): 

141 try: 

142 pass 

143 except: 

144 pass 

145""" 

146 assert _npath(code) == 2 # NP(try body)=1 + NP(except body)=1 

147 

148 

149# --- Ternary --- 

150 

151def test_ternary(): 

152 code = """ 

153def f(c, x, y): 

154 return x if c else y 

155""" 

156 assert _npath(code) == 2 # NP(x)=1 + NP(y)=1 

157 

158 

159# --- Sequential ifs (multiplicative) --- 

160 

161def test_sequential_ifs(): 

162 code = """ 

163def f(a, b): 

164 if a: 

165 pass 

166 if b: 

167 pass 

168""" 

169 assert _npath(code) == 4 # 2 * 2 

170 

171 

172# --- Nested if --- 

173 

174def test_nested_if(): 

175 code = """ 

176def f(a, b): 

177 if a: 

178 if b: 

179 pass 

180""" 

181 # Outer if: NP(inner if) + 1 (no else) 

182 # Inner if: 1 + 1 (no else) = 2 

183 # Outer: 2 + 1 = 3 

184 assert _npath(code) == 3 

185 

186 

187# --- Match --- 

188 

189def test_match_three_cases(): 

190 code = """ 

191def f(x): 

192 match x: 

193 case 1: 

194 pass 

195 case 2: 

196 pass 

197 case 3: 

198 pass 

199""" 

200 assert _npath(code) == 4 # 1+1+1 + 1 (no wildcard) 

201 

202 

203def test_match_with_wildcard(): 

204 code = """ 

205def f(x): 

206 match x: 

207 case 1: 

208 pass 

209 case _: 

210 pass 

211""" 

212 assert _npath(code) == 2 # 1+1, has wildcard 

213 

214 

215# --- Nested ternary --- 

216 

217def test_nested_ternary(): 

218 code = """ 

219def f(c1, c2, a, b, d): 

220 return a if c1 else (b if c2 else d) 

221""" 

222 # Outer: NP(a) + NP(inner) = 1 + (1+1) = 3 

223 assert _npath(code) == 3 

224 

225 

226# --- Nested function --- 

227 

228def test_nested_function(): 

229 code = """ 

230def outer(x): 

231 if x: 

232 pass 

233 def inner(y): 

234 if y: 

235 pass 

236""" 

237 npaths = _npaths(code) 

238 assert npaths["outer"] == 2 # inner counts as 1 

239 assert npaths["inner"] == 2 

240 

241 

242# --- With statement --- 

243 

244def test_with_statement(): 

245 code = """ 

246def f(): 

247 with open("x") as fp: 

248 if True: 

249 pass 

250""" 

251 assert _npath(code) == 2 # with body contains if -> npath 2 

252 

253 

254# --- Lambda with ternary --- 

255 

256def test_lambda_ternary(): 

257 code = """ 

258def f(): 

259 g = lambda x: x if x > 0 else -x 

260""" 

261 # The expression_statement contains a ternary inside the lambda 

262 # lambda body is recursed: ternary -> 2 

263 assert _npath(code) == 2