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

97 statements  

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

1"""Tests for Python cognitive complexity.""" 

2 

3from analyzers.python import analyze_source 

4 

5 

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

7 """Helper: return total complexity 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].complexity 

11 

12 

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

14 """Helper: return {name: complexity} for all functions in code.""" 

15 result = analyze_source(code) 

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

17 

18 

19# --- Basic control flow --- 

20 

21 

22def test_simple_function(): 

23 assert _score("def f(): pass") == 0 

24 

25 

26def test_single_if(): 

27 code = """ 

28def f(x): 

29 if x: # +1 

30 pass 

31""" 

32 assert _score(code) == 1 

33 

34 

35def test_if_else(): 

36 code = """ 

37def f(x): 

38 if x: # +1 

39 pass 

40 else: # +1 

41 pass 

42""" 

43 assert _score(code) == 2 

44 

45 

46def test_if_elif_else(): 

47 code = """ 

48def f(x): 

49 if x > 0: # +1 

50 pass 

51 elif x < 0: # +1 

52 pass 

53 else: # +1 

54 pass 

55""" 

56 assert _score(code) == 3 

57 

58 

59def test_for_loop(): 

60 code = """ 

61def f(items): 

62 for x in items: # +1 

63 pass 

64""" 

65 assert _score(code) == 1 

66 

67 

68def test_while_loop(): 

69 code = """ 

70def f(x): 

71 while x > 0: # +1 

72 x -= 1 

73""" 

74 assert _score(code) == 1 

75 

76 

77# --- Nesting --- 

78 

79 

80def test_nested_if(): 

81 code = """ 

82def f(a, b): 

83 if a: # +1 (nesting 0) 

84 if b: # +1 + 1 (nesting 1) 

85 pass 

86""" 

87 assert _score(code) == 3 

88 

89 

90def test_deeply_nested(): 

91 code = """ 

92def f(a, b, c): 

93 if a: # +1 (nesting 0) 

94 for x in b: # +1 + 1 (nesting 1) 

95 if c: # +1 + 2 (nesting 2) 

96 pass 

97""" 

98 assert _score(code) == 6 

99 

100 

101def test_if_else_nesting(): 

102 """else increases nesting for its body but gets no nesting bonus.""" 

103 code = """ 

104def f(a, b): 

105 if a: # +1 (nesting 0) 

106 pass 

107 else: # +1 (no nesting bonus) 

108 if b: # +1 + 1 (nesting 1, inside else body) 

109 pass 

110""" 

111 assert _score(code) == 4 

112 

113 

114# --- Try/Except --- 

115 

116 

117def test_try_except(): 

118 """try has no increment; except gets +1.""" 

119 code = """ 

120def f(): 

121 try: 

122 pass 

123 except ValueError: # +1 

124 pass 

125""" 

126 assert _score(code) == 1 

127 

128 

129def test_try_except_nested(): 

130 code = """ 

131def f(x): 

132 if x: # +1 (nesting 0) 

133 try: 

134 pass 

135 except ValueError: # +1 + 1 (nesting 1) 

136 pass 

137""" 

138 assert _score(code) == 3 

139 

140 

141# --- Boolean operators --- 

142 

143 

144def test_single_and(): 

145 code = """ 

146def f(a, b): 

147 if a and b: # +1 (if) + 1 (boolean) 

148 pass 

149""" 

150 assert _score(code) == 2 

151 

152 

153def test_chained_same_op(): 

154 code = """ 

155def f(a, b, c): 

156 if a and b and c: # +1 (if) + 1 (boolean chain, same op) 

157 pass 

158""" 

159 assert _score(code) == 2 

160 

161 

162def test_mixed_boolean(): 

163 code = """ 

164def f(a, b, c): 

165 if a and b or c: # +1 (if) + 2 (and->or switch) 

166 pass 

167""" 

168 assert _score(code) == 3 

169 

170 

171def test_complex_boolean(): 

172 code = """ 

173def f(a, b, c, d): 

174 if a or b or c and d: # +1 (if) + 2 (or sequence, then switch to and) 

175 pass 

176""" 

177 assert _score(code) == 3 

178 

179 

180# --- Ternary / conditional expression --- 

181 

182 

183def test_ternary(): 

184 code = """ 

185def f(x): 

186 return x if x > 0 else -x # +1 (ternary, nesting 0) 

187""" 

188 assert _score(code) == 1 

189 

190 

191def test_ternary_nested(): 

192 code = """ 

193def f(x): 

194 if x: # +1 (nesting 0) 

195 return x if x > 0 else -x # +1 + 1 (ternary, nesting 1) 

196""" 

197 assert _score(code) == 3 

198 

199 

200# --- Recursion --- 

201 

202 

203def test_recursion(): 

204 code = """ 

205def factorial(n): 

206 if n <= 1: # +1 

207 return 1 

208 return n * factorial(n-1) # +1 (recursion) 

209""" 

210 assert _score(code) == 2 

211 

212 

213def test_no_false_recursion(): 

214 """Calling a different function shouldn't count as recursion.""" 

215 code = """ 

216def f(x): 

217 return g(x) 

218""" 

219 assert _score(code) == 0 

220 

221 

222# --- Nested functions --- 

223 

224 

225def test_nested_function_separate_scoring(): 

226 """Nested functions are scored independently; outer doesn't include inner.""" 

227 code = """ 

228def outer(x): 

229 if x: # +1 for outer 

230 pass 

231 def inner(y): 

232 if y: # +1 for inner 

233 pass 

234""" 

235 scores = _scores(code) 

236 assert scores["outer"] == 1 

237 assert scores["inner"] == 1 

238 

239 

240def test_nested_function_nesting_reset(): 

241 """Nested function starts at nesting=0 regardless of outer nesting.""" 

242 code = """ 

243def outer(x): 

244 if x: # +1 for outer 

245 def inner(y): 

246 if y: # +1 for inner (nesting 0, not 1) 

247 pass 

248""" 

249 scores = _scores(code) 

250 assert scores["outer"] == 1 

251 assert scores["inner"] == 1 

252 

253 

254# --- Lambda --- 

255 

256 

257def test_lambda_nesting(): 

258 """Lambda increases nesting level.""" 

259 code = """ 

260def f(items): 

261 return list(map(lambda x: x if x > 0 else 0, items)) 

262""" 

263 # lambda increases nesting to 1 

264 # ternary inside lambda: +1 + 1 (nesting 1) = 2 

265 assert _score(code) == 2 

266 

267 

268# --- Comprehensive example --- 

269 

270 

271def test_comprehensive(): 

272 code = """ 

273def process(items, flag): 

274 if flag: # +1 (nesting 0) 

275 for item in items: # +1 +1 (nesting 1) 

276 if item > 0: # +1 +2 (nesting 2) 

277 pass 

278 elif item < 0: # +1 (no nesting bonus) 

279 pass 

280 else: # +1 (no nesting bonus) 

281 try: 

282 pass 

283 except Exception: # +1 +3 (nesting 3) 

284 pass 

285 else: # +1 (no nesting bonus) 

286 pass 

287""" 

288 # 1 + 2 + 3 + 1 + 1 + 4 + 1 = 13 

289 assert _score(code) == 13 

290 

291 

292# --- FileComplexity --- 

293 

294 

295def test_file_total(): 

296 code = """ 

297def f(x): 

298 if x: # +1 

299 pass 

300 

301def g(a, b): 

302 if a: # +1 

303 if b: # +1 +1 = 2 

304 pass 

305""" 

306 result = analyze_source(code) 

307 assert result.total == 4 

308 assert len(result.functions) == 2 

309 

310 

311def test_function_lines(): 

312 code = """ 

313def first(): 

314 pass 

315 

316def second(): 

317 pass 

318""" 

319 result = analyze_source(code) 

320 assert result.functions[0].line == 2 

321 assert result.functions[0].name == "first" 

322 assert result.functions[1].line == 5 

323 assert result.functions[1].name == "second" 

324 

325 

326# --- Edge cases --- 

327 

328 

329def test_empty_function(): 

330 assert _score("def f(): pass") == 0 

331 

332 

333def test_not_operator_no_increment(): 

334 """'not' is a unary operator, not a boolean sequence.""" 

335 code = """ 

336def f(x): 

337 if not x: # +1 (if only) 

338 pass 

339""" 

340 assert _score(code) == 1