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
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""Tests for Python cognitive complexity."""
3from analyzers.python import analyze_source
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
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}
19# --- Basic control flow ---
22def test_simple_function():
23 assert _score("def f(): pass") == 0
26def test_single_if():
27 code = """
28def f(x):
29 if x: # +1
30 pass
31"""
32 assert _score(code) == 1
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
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
59def test_for_loop():
60 code = """
61def f(items):
62 for x in items: # +1
63 pass
64"""
65 assert _score(code) == 1
68def test_while_loop():
69 code = """
70def f(x):
71 while x > 0: # +1
72 x -= 1
73"""
74 assert _score(code) == 1
77# --- Nesting ---
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
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
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
114# --- Try/Except ---
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
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
141# --- Boolean operators ---
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
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
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
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
180# --- Ternary / conditional expression ---
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
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
200# --- Recursion ---
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
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
222# --- Nested functions ---
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
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
254# --- Lambda ---
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
268# --- Comprehensive example ---
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
292# --- FileComplexity ---
295def test_file_total():
296 code = """
297def f(x):
298 if x: # +1
299 pass
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
311def test_function_lines():
312 code = """
313def first():
314 pass
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"
326# --- Edge cases ---
329def test_empty_function():
330 assert _score("def f(): pass") == 0
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