diff --git a/tests/unit/test_complexity.py b/tests/unit/test_complexity.py new file mode 100644 index 0000000..f1c7796 --- /dev/null +++ b/tests/unit/test_complexity.py @@ -0,0 +1,270 @@ +"""Unit tests for complexity analysis module.""" + +import pytest +from codesnap.core.complexity import ( + calculate_cyclomatic_complexity, + calculate_nesting_depth, + count_lines, + rate_complexity, + analyze_file_complexity, + get_complexity_summary, + ComplexityMetrics, +) +from codesnap.core.parser import FunctionInfo + + +class TestCalculateCyclomaticComplexity: + """Tests for cyclomatic complexity calculation.""" + + def test_empty_content(self): + complexity, decisions = calculate_cyclomatic_complexity("") + assert complexity == 1 + assert decisions == 0 + + def test_simple_function(self): + content = "def test():\n pass" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity == 1 + + def test_if_statement(self): + content = "if x > 0:\n pass" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity >= 1 + + def test_multiple_if_statements(self): + content = """ +if x > 0: + pass +elif x < 0: + pass +else: + pass +""" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity >= 3 + + def test_for_loop(self): + content = "for i in range(10):\n pass" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity >= 1 + + def test_while_loop(self): + content = "while True:\n pass" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity >= 1 + + def test_try_except(self): + content = """ +try: + pass +except Exception: + pass +""" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity >= 1 + + def test_and_or_operators(self): + content = "if x > 0 and y > 0:\n pass" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity >= 2 + + def test_ternary_operator(self): + content = "x = 1 if cond else 2" + complexity, decisions = calculate_cyclomatic_complexity(content) + assert complexity >= 1 + + +class TestCalculateNestingDepth: + """Tests for nesting depth calculation.""" + + def test_flat_code(self): + depth = calculate_nesting_depth("x = 1\ny = 2") + assert depth >= 0 + + def test_single_brace_level(self): + depth = calculate_nesting_depth("if x: { y = 1 }") + assert depth >= 0 + + def test_nested_braces(self): + content = """ +if x: + if y: + if z: + pass +""" + depth = calculate_nesting_depth(content) + assert depth >= 0 + + def test_mixed_brackets(self): + content = """ +def test(): + data = [ + [1, 2], + {a: b} + ] +""" + depth = calculate_nesting_depth(content) + assert depth >= 1 + + def test_balanced_brackets(self): + content = "[](){}" + depth = calculate_nesting_depth(content) + assert depth >= 1 + + def test_unbalanced_close(self): + content = "x = 1]" + depth = calculate_nesting_depth(content) + assert depth >= 0 + + +class TestCountLines: + """Tests for line counting.""" + + def test_empty_content(self): + total, comments = count_lines("") + assert total >= 0 + assert comments >= 0 + + def test_single_line(self): + total, comments = count_lines("x = 1") + assert total >= 1 + assert comments >= 0 + + def test_python_comments(self): + content = "# This is a comment\nx = 1\n# Another comment" + total, comments = count_lines(content) + assert total >= 3 + assert comments >= 2 + + def test_python_docstring(self): + content = '"""This is a docstring"""' + total, comments = count_lines(content) + assert total >= 1 + + def test_multiline_python_comment(self): + content = """ +''' +Multiline +Comment +''' +x = 1 +""" + total, comments = count_lines(content) + assert total >= 5 + + def test_cpp_comments(self): + content = "// Single line comment\nx = 1;" + total, comments = count_lines(content) + assert total >= 2 + assert comments >= 1 + + def test_c_multiline_comment(self): + content = "/* Multi\n Line */\nx = 1;" + total, comments = count_lines(content) + assert total >= 3 + assert comments >= 1 + + +class TestRateComplexity: + """Tests for complexity rating.""" + + def test_low_complexity(self): + assert rate_complexity(1, 1) == "low" + assert rate_complexity(5, 2) == "low" + assert rate_complexity(9, 3) == "low" + + def test_medium_complexity(self): + assert rate_complexity(10, 3) == "medium" + assert rate_complexity(15, 4) == "medium" + assert rate_complexity(19, 5) == "medium" + + def test_high_complexity(self): + assert rate_complexity(20, 3) == "high" + assert rate_complexity(25, 6) == "high" + assert rate_complexity(50, 2) == "high" + + def test_high_nesting(self): + result = rate_complexity(5, 6) + assert result in ["low", "medium", "high"] + + +class TestAnalyzeFileComplexity: + """Tests for file complexity analysis.""" + + def test_empty_file(self): + metrics, func_complexities = analyze_file_complexity("", [], "python") + assert metrics.cyclomatic_complexity >= 1 + assert len(func_complexities) == 0 + + def test_simple_file(self): + content = "x = 1\ny = 2" + metrics, func_complexities = analyze_file_complexity(content, [], "python") + assert metrics.complexity_rating in ["low", "medium", "high"] + + def test_complex_file(self): + content = """ +def test(): + if x > 0: + if y > 0: + if z > 0: + pass +""" + func = FunctionInfo( + name="test", + node_type="function", + start_line=1, + end_line=6, + parameters=[], + ) + metrics, func_complexities = analyze_file_complexity(content, [func], "python") + assert metrics.complexity_rating in ["low", "medium", "high"] + assert len(func_complexities) >= 0 + + def test_suggestions_generated(self): + content = """ +def test(): + pass +""" * 25 + metrics, func_complexities = analyze_file_complexity(content, [], "python") + assert isinstance(metrics.suggestions, list) + + +class TestGetComplexitySummary: + """Tests for complexity summary generation.""" + + def test_empty_list(self): + summary = get_complexity_summary([]) + assert summary["total_files"] == 0 + assert summary["avg_complexity"] == 0 + + def test_single_file(self): + metrics = ComplexityMetrics( + cyclomatic_complexity=10, + nesting_depth=2, + lines_of_code=50, + ) + summary = get_complexity_summary([metrics]) + assert summary["total_files"] == 1 + assert summary["avg_complexity"] == 10 + + def test_multiple_files(self): + metrics_list = [ + ComplexityMetrics(cyclomatic_complexity=5), + ComplexityMetrics(cyclomatic_complexity=15), + ComplexityMetrics(cyclomatic_complexity=10), + ] + summary = get_complexity_summary(metrics_list) + assert summary["total_files"] == 3 + assert summary["avg_complexity"] == 10 + + def test_rating_distribution(self): + metrics_list = [ + ComplexityMetrics(cyclomatic_complexity=5), + ComplexityMetrics(cyclomatic_complexity=15), + ComplexityMetrics(cyclomatic_complexity=25), + ] + summary = get_complexity_summary(metrics_list) + assert summary["rating_distribution"]["low"] >= 0 + assert summary["rating_distribution"]["medium"] >= 0 + assert summary["rating_distribution"]["high"] >= 0 + assert summary["rating_distribution"]["low"] + summary["rating_distribution"]["medium"] + summary["rating_distribution"]["high"] == 3