From 96ee43912e2a4bb41a43cdaf24dd1b997d98619e Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 17 Sep 2025 08:40:11 +0200 Subject: [PATCH 1/2] [mccabe] Add more functional tests to allow for easier refactor --- tests/functional/ext/mccabe/mccabe.py | 159 ++++++++++++++++++++----- tests/functional/ext/mccabe/mccabe.txt | 46 ++++--- 2 files changed, 158 insertions(+), 47 deletions(-) diff --git a/tests/functional/ext/mccabe/mccabe.py b/tests/functional/ext/mccabe/mccabe.py index b670b4a40f..9cb69735f0 100644 --- a/tests/functional/ext/mccabe/mccabe.py +++ b/tests/functional/ext/mccabe/mccabe.py @@ -1,24 +1,39 @@ # pylint: disable=invalid-name,unnecessary-pass,no-else-return,useless-else-on-loop # pylint: disable=undefined-variable,consider-using-sys-exit,unused-variable,too-many-return-statements -# pylint: disable=redefined-outer-name,using-constant-test,unused-argument +# pylint: disable=redefined-outer-name,using-constant-test,unused-argument,unnecessary-lambda-assignment # pylint: disable=broad-except, not-context-manager, no-method-argument, unspecified-encoding, broad-exception-raised """Checks use of "too-complex" check""" -def f1(): # [too-complex] +def just_a_pass(): # [too-complex] """McCabe rating: 1""" pass -def f2(n): # [too-complex] +def just_a_yield(): # [too-complex] + """McCabe rating: 1""" + yield from range(10) + + +def just_a_return(): # [too-complex] + """McCabe rating: 1""" + return 42 + + +def just_a_raise(): # [too-complex] + """McCabe rating: 1""" + raise ValueError("An error occurred") + + +def one_edge_multiple_operations(n): # [too-complex] """McCabe rating: 1""" k = n + 4 s = k + n return s -def f3(n): # [too-complex] +def if_elif_else(n): # [too-complex] """McCabe rating: 3""" if n > 3: return "bigger than three" @@ -28,13 +43,28 @@ def f3(n): # [too-complex] return "smaller than or equal to three" -def f4(): # [too-complex] +def if_with_conditionals(a, b, c): # [too-complex] + """McCabe rating: 2""" + if ( # pylint: disable=too-many-boolean-expressions + a + and b + or c + or (a and not b) + or (b and not c) + or (c and not a) + or (a and b and c) + ): + return True + return False + + +def for_loop(): # [too-complex] """McCabe rating: 2""" for i in range(10): print(i) -def f5(mylist): # [too-complex] +def for_loop_with_else(mylist): # [too-complex] """McCabe rating: 2""" for i in mylist: print(i) @@ -42,30 +72,54 @@ def f5(mylist): # [too-complex] print(None) -def f6(n): # [too-complex] +def recursive_if_else(n): # [too-complex] """McCabe rating: 2""" if n > 4: - return f(n - 1) + return recursive_if_else(n - 1) else: return n -def f7(): # [too-complex] +def for_loop_with_break(): # [too-complex] + """McCabe rating: 3""" + for i in range(10): + if i == 5: + break + + +def for_loop_with_continue(): # [too-complex] + """McCabe rating: 3""" + for i in range(10): + if i % 2 == 0: + continue + print(i) + + +def for_loop_with_continue_and_break(): # [too-complex] + """McCabe rating: 4""" + for i in range(10): + if i % 2 == 0: + continue + if i % 5 == 0: + break + + +def inner_functions(): # [too-complex] """McCabe rating: 3""" - def b(): + def inner_function(): # Known false negative ? """McCabe rating: 2""" - def c(): + def innermost_function(): # Known false negative ? """McCabe rating: 1""" pass - c() + innermost_function() - b() + inner_function() -def f8(): # [too-complex] +def try_with_multiple_except_and_else(): # [too-complex] """McCabe rating: 4""" try: print(1) @@ -77,7 +131,44 @@ def f8(): # [too-complex] print(4) -def f9(): # [too-complex] +def with_(fp): # [too-complex] + """McCabe rating: 1""" + with open(fp) as f: + content = f.read() + return content + + +def lambda_with_if(lst): # [too-complex] + """McCabe rating should be 4, but is 1 (known false negative ?) + + See counterpart 'comprehension_with_if' below.""" + f = lambda x: [x for x in lst if x % 2 == 0] or range(10) + return f(lst) + + +def comprehension_with_if(lst): # [too-complex] + """McCabe rating: should be 4 but is 1 (known false negative ?) + https://github.com/PyCQA/mccabe/issues/69 + """ + return [x for x in lst if x % 2 == 0] or range(10) + + +def comprehension_with_if_equivalent(lst): # [too-complex] + """McCabe rating: 4 + + See counterpart 'comprehension_with_if' above. + """ + xs = [] + for x in lst: + if x % 2 == 0: + xs.append(x) + if xs: + return xs + else: + return range(10) + + +def nested_ifs_elifs_elses(): # [too-complex] """McCabe rating: 9""" myint = 2 if myint > 5: @@ -103,7 +194,7 @@ def f9(): # [too-complex] myint = 4 -def f10(): # [too-complex] +def big_elif_chain_with_nested_ifs(): # [too-complex] """McCabe rating: 11""" myint = 2 if myint == 5: @@ -130,16 +221,16 @@ def f10(): # [too-complex] return myint -class MyClass1: +class ExampleComplexityClass: """Class of example to test mccabe""" - _name = "MyClass" # To force a tail.node=None + _name = "ExampleComplexityKlass" # To force a tail.node=None - def method1(): # [too-complex] + def just_a_pass(self): # [too-complex] """McCabe rating: 1""" pass - def method2(self, param1): # [too-complex, too-many-branches] + def highly_complex(self, param1): # [too-complex, too-many-branches] """McCabe rating: 15""" if not param1: pass @@ -148,9 +239,7 @@ def method2(self, param1): # [too-complex, too-many-branches] pass else: pass - pass - if param1: pass if param1: @@ -166,7 +255,6 @@ def method2(self, param1): # [too-complex, too-many-branches] if param1: for value in range(5): pass - pass for count in range(6): with open("myfile") as fp: @@ -195,7 +283,8 @@ def method2(self, param1): # [too-complex, too-many-branches] return param1 -for count in range(10): # [too-complex] +for count in range(10): # [too-complex] + # McCabe rating: 4 if count == 1: exit(0) elif count == 2: @@ -204,7 +293,7 @@ def method2(self, param1): # [too-complex, too-many-branches] exit(2) -def method3(self): # [too-complex] +def try_finally_with_nested_ifs(): # [too-complex] """McCabe rating: 3""" try: if True: @@ -215,23 +304,23 @@ def method3(self): # [too-complex] pass return True -def match_case_complexity(avg): # [too-complex] + +def match_case(avg): # [too-complex] """McCabe rating: 4 See https://github.com/astral-sh/ruff/issues/11421 """ # pylint: disable=bare-name-capture-pattern match avg: - case avg if avg < .3: + case avg if avg < 0.3: avg_grade = "F" - case avg if avg < .7: + case avg if avg < 0.7: avg_grade = "E+" case _: raise ValueError(f"Unexpected average: {avg}") return avg_grade - -def nested_match(data): # [too-complex] +def nested_match_case(data): # [too-complex] """McCabe rating: 8 Nested match statements.""" @@ -251,3 +340,13 @@ def nested_match(data): # [too-complex] return "Product with no price" case _: return "Unknown data type" + + +def yield_in_for_loop(a=None, b=None, c=None): # [too-complex] + """McCabe rating: 4""" + yield from a or () + for elt in b: + if elt is not None: + yield elt + if c is not None: + yield c diff --git a/tests/functional/ext/mccabe/mccabe.txt b/tests/functional/ext/mccabe/mccabe.txt index e38dfe94a2..a641576446 100644 --- a/tests/functional/ext/mccabe/mccabe.txt +++ b/tests/functional/ext/mccabe/mccabe.txt @@ -1,17 +1,29 @@ -too-complex:9:0:9:6:f1:'f1' is too complex. The McCabe rating is 1:HIGH -too-complex:14:0:14:6:f2:'f2' is too complex. The McCabe rating is 1:HIGH -too-complex:21:0:21:6:f3:'f3' is too complex. The McCabe rating is 3:HIGH -too-complex:31:0:31:6:f4:'f4' is too complex. The McCabe rating is 2:HIGH -too-complex:37:0:37:6:f5:'f5' is too complex. The McCabe rating is 2:HIGH -too-complex:45:0:45:6:f6:'f6' is too complex. The McCabe rating is 2:HIGH -too-complex:53:0:53:6:f7:'f7' is too complex. The McCabe rating is 3:HIGH -too-complex:68:0:68:6:f8:'f8' is too complex. The McCabe rating is 4:HIGH -too-complex:80:0:80:6:f9:'f9' is too complex. The McCabe rating is 9:HIGH -too-complex:106:0:106:7:f10:'f10' is too complex. The McCabe rating is 11:HIGH -too-complex:138:4:138:15:MyClass1.method1:'method1' is too complex. The McCabe rating is 1:HIGH -too-complex:142:4:142:15:MyClass1.method2:'method2' is too complex. The McCabe rating is 15:HIGH -too-many-branches:142:4:142:15:MyClass1.method2:Too many branches (19/12):UNDEFINED -too-complex:198:0:204:15::This 'for' is too complex. The McCabe rating is 4:HIGH -too-complex:207:0:207:11:method3:'method3' is too complex. The McCabe rating is 3:HIGH -too-complex:218:0:218:25:match_case_complexity:'match_case_complexity' is too complex. The McCabe rating is 4:HIGH -too-complex:234:0:234:16:nested_match:'nested_match' is too complex. The McCabe rating is 8:HIGH +too-complex:9:0:9:15:just_a_pass:'just_a_pass' is too complex. The McCabe rating is 1:HIGH +too-complex:14:0:14:16:just_a_yield:'just_a_yield' is too complex. The McCabe rating is 1:HIGH +too-complex:19:0:19:17:just_a_return:'just_a_return' is too complex. The McCabe rating is 1:HIGH +too-complex:24:0:24:16:just_a_raise:'just_a_raise' is too complex. The McCabe rating is 1:HIGH +too-complex:29:0:29:32:one_edge_multiple_operations:'one_edge_multiple_operations' is too complex. The McCabe rating is 1:HIGH +too-complex:36:0:36:16:if_elif_else:'if_elif_else' is too complex. The McCabe rating is 3:HIGH +too-complex:46:0:46:24:if_with_conditionals:'if_with_conditionals' is too complex. The McCabe rating is 2:HIGH +too-complex:61:0:61:12:for_loop:'for_loop' is too complex. The McCabe rating is 2:HIGH +too-complex:67:0:67:22:for_loop_with_else:'for_loop_with_else' is too complex. The McCabe rating is 2:HIGH +too-complex:75:0:75:21:recursive_if_else:'recursive_if_else' is too complex. The McCabe rating is 2:HIGH +too-complex:83:0:83:23:for_loop_with_break:'for_loop_with_break' is too complex. The McCabe rating is 3:HIGH +too-complex:90:0:90:26:for_loop_with_continue:'for_loop_with_continue' is too complex. The McCabe rating is 3:HIGH +too-complex:98:0:98:36:for_loop_with_continue_and_break:'for_loop_with_continue_and_break' is too complex. The McCabe rating is 4:HIGH +too-complex:107:0:107:19:inner_functions:'inner_functions' is too complex. The McCabe rating is 3:HIGH +too-complex:122:0:122:37:try_with_multiple_except_and_else:'try_with_multiple_except_and_else' is too complex. The McCabe rating is 4:HIGH +too-complex:134:0:134:9:with_:'with_' is too complex. The McCabe rating is 1:HIGH +too-complex:141:0:141:18:lambda_with_if:'lambda_with_if' is too complex. The McCabe rating is 1:HIGH +too-complex:149:0:149:25:comprehension_with_if:'comprehension_with_if' is too complex. The McCabe rating is 1:HIGH +too-complex:156:0:156:36:comprehension_with_if_equivalent:'comprehension_with_if_equivalent' is too complex. The McCabe rating is 4:HIGH +too-complex:171:0:171:26:nested_ifs_elifs_elses:'nested_ifs_elifs_elses' is too complex. The McCabe rating is 9:HIGH +too-complex:197:0:197:34:big_elif_chain_with_nested_ifs:'big_elif_chain_with_nested_ifs' is too complex. The McCabe rating is 11:HIGH +too-complex:229:4:229:19:ExampleComplexityClass.just_a_pass:'just_a_pass' is too complex. The McCabe rating is 1:HIGH +too-complex:233:4:233:22:ExampleComplexityClass.highly_complex:'highly_complex' is too complex. The McCabe rating is 15:HIGH +too-many-branches:233:4:233:22:ExampleComplexityClass.highly_complex:Too many branches (19/12):UNDEFINED +too-complex:286:0:293:15::This 'for' is too complex. The McCabe rating is 4:HIGH +too-complex:296:0:296:31:try_finally_with_nested_ifs:'try_finally_with_nested_ifs' is too complex. The McCabe rating is 3:HIGH +too-complex:308:0:308:14:match_case:'match_case' is too complex. The McCabe rating is 4:HIGH +too-complex:323:0:323:21:nested_match_case:'nested_match_case' is too complex. The McCabe rating is 8:HIGH +too-complex:345:0:345:21:yield_in_for_loop:'yield_in_for_loop' is too complex. The McCabe rating is 4:HIGH From bfe39331e8d66bf6a3feef751b158ddbf6c1fa1c Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 17 Sep 2025 13:04:58 +0200 Subject: [PATCH 2/2] [mccabe] Cover the behavior when the message is not raised --- tests/functional/ext/mccabe/not_too_complex.py | 14 ++++++++++++++ tests/functional/ext/mccabe/not_too_complex.rc | 4 ++++ tests/functional/ext/mccabe/not_too_complex.txt | 1 + 3 files changed, 19 insertions(+) create mode 100644 tests/functional/ext/mccabe/not_too_complex.py create mode 100644 tests/functional/ext/mccabe/not_too_complex.rc create mode 100644 tests/functional/ext/mccabe/not_too_complex.txt diff --git a/tests/functional/ext/mccabe/not_too_complex.py b/tests/functional/ext/mccabe/not_too_complex.py new file mode 100644 index 0000000000..b1744c3b5e --- /dev/null +++ b/tests/functional/ext/mccabe/not_too_complex.py @@ -0,0 +1,14 @@ +"""The other functional file has a max-complexity so low that it doesn't cover the + case when the code is not too complex.""" + + +def not_too_complex(): + """McCabe rating: 1""" + return True + + +def too_complex(condition): # [too-complex] + """McCabe rating: 2""" + if condition is True: + return True + return False diff --git a/tests/functional/ext/mccabe/not_too_complex.rc b/tests/functional/ext/mccabe/not_too_complex.rc new file mode 100644 index 0000000000..ae94bb6f17 --- /dev/null +++ b/tests/functional/ext/mccabe/not_too_complex.rc @@ -0,0 +1,4 @@ +[MAIN] +load-plugins=pylint.extensions.mccabe, + +max-complexity=1 diff --git a/tests/functional/ext/mccabe/not_too_complex.txt b/tests/functional/ext/mccabe/not_too_complex.txt new file mode 100644 index 0000000000..8f9ae3c6fc --- /dev/null +++ b/tests/functional/ext/mccabe/not_too_complex.txt @@ -0,0 +1 @@ +too-complex:10:0:10:15:too_complex:'too_complex' is too complex. The McCabe rating is 2:HIGH