From c5549771d088de6ff30ac084b23b2926b09ea127 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 5 Feb 2026 17:14:09 +0000 Subject: [PATCH] Initial upload: Add repohealth-cli project with CI/CD workflow --- tests/test_analyzers.py | 270 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 tests/test_analyzers.py diff --git a/tests/test_analyzers.py b/tests/test_analyzers.py new file mode 100644 index 0000000..427bfe9 --- /dev/null +++ b/tests/test_analyzers.py @@ -0,0 +1,270 @@ +"""Tests for analyzer modules.""" + +import pytest +from pathlib import Path + +from repohealth.analyzers.bus_factor import BusFactorCalculator +from repohealth.analyzers.risk_analyzer import RiskAnalyzer +from repohealth.models.file_stats import FileAnalysis + + +class TestBusFactorCalculator: + """Tests for BusFactorCalculator.""" + + def setup_method(self): + """Set up test fixtures.""" + self.calculator = BusFactorCalculator() + + def test_calculate_gini_equal_distribution(self): + """Test Gini coefficient with equal distribution.""" + values = [10, 10, 10, 10] + gini = self.calculator.calculate_gini(values) + + assert gini == 0.0 + + def test_calculate_gini_unequal_distribution(self): + """Test Gini coefficient with unequal distribution.""" + values = [100, 0, 0, 0] + gini = self.calculator.calculate_gini(values) + + assert gini > 0.5 + assert gini <= 1.0 + + def test_calculate_gini_single_value(self): + """Test Gini coefficient with single value.""" + values = [100] + gini = self.calculator.calculate_gini(values) + + assert gini == 0.0 + + def test_calculate_gini_empty_list(self): + """Test Gini coefficient with empty list.""" + gini = self.calculator.calculate_gini([]) + + assert gini == 0.0 + + def test_calculate_file_bus_factor_single_author(self): + """Test bus factor with single author.""" + analysis = FileAnalysis( + path="test.py", + total_commits=10, + author_commits={"author@example.com": 10} + ) + + bus_factor = self.calculator.calculate_file_bus_factor(analysis) + + assert bus_factor == 1.0 + + def test_calculate_file_bus_factor_multiple_authors(self): + """Test bus factor with multiple authors.""" + analysis = FileAnalysis( + path="test.py", + total_commits=10, + author_commits={"a@x.com": 5, "b@x.com": 5} + ) + + bus_factor = self.calculator.calculate_file_bus_factor(analysis) + + assert bus_factor > 1.0 + + def test_calculate_file_bus_factor_no_commits(self): + """Test bus factor with no commits.""" + analysis = FileAnalysis( + path="test.py", + total_commits=0, + author_commits={} + ) + + bus_factor = self.calculator.calculate_file_bus_factor(analysis) + + assert bus_factor == 1.0 + + def test_calculate_repository_bus_factor(self): + """Test repository-level bus factor calculation.""" + files = [ + FileAnalysis( + path="file1.py", + total_commits=10, + author_commits={"a@x.com": 10} + ), + FileAnalysis( + path="file2.py", + total_commits=10, + author_commits={"a@x.com": 5, "b@x.com": 5} + ) + ] + + bus_factor = self.calculator.calculate_repository_bus_factor(files) + + assert bus_factor > 1.0 + + def test_assign_risk_levels(self): + """Test risk level assignment.""" + files = [ + FileAnalysis( + path="critical.py", + total_commits=10, + author_commits={"a@x.com": 10} + ), + FileAnalysis( + path="low_risk.py", + total_commits=10, + author_commits={"a@x.com": 3, "b@x.com": 3, "c@x.com": 4} + ) + ] + + assigned = self.calculator.assign_risk_levels(files) + + assert assigned[0].risk_level == "critical" + assert assigned[1].risk_level == "low" + + def test_calculate_repository_gini(self): + """Test repository-wide Gini coefficient.""" + files = [ + FileAnalysis( + path="file1.py", + total_commits=10, + author_commits={"a@x.com": 10} + ), + FileAnalysis( + path="file2.py", + total_commits=10, + author_commits={"b@x.com": 10} + ) + ] + + gini = self.calculator.calculate_repository_gini(files) + + assert gini > 0 + + +class TestRiskAnalyzer: + """Tests for RiskAnalyzer.""" + + def setup_method(self): + """Set up test fixtures.""" + self.analyzer = RiskAnalyzer() + + def test_identify_hotspots_critical(self): + """Test hotspot identification for critical files.""" + files = [ + FileAnalysis( + path="critical.py", + total_commits=10, + author_commits={"a@x.com": 9, "b@x.com": 1}, + bus_factor=1.1 + ), + FileAnalysis( + path="safe.py", + total_commits=10, + author_commits={"a@x.com": 4, "b@x.com": 6}, + bus_factor=2.0 + ) + ] + + hotspots = self.analyzer.identify_hotspots(files) + + assert len(hotspots) >= 1 + assert any(h.risk_level == "critical" for h in hotspots) + + def test_identify_hotspots_limit(self): + """Test hotspot limit parameter.""" + files = [ + FileAnalysis( + path=f"file{i}.py", + total_commits=10, + author_commits={"a@x.com": 9, "b@x.com": 1}, + bus_factor=1.1 + ) + for i in range(25) + ] + + hotspots = self.analyzer.identify_hotspots(files, limit=10) + + assert len(hotspots) == 10 + + def test_generate_suggestions(self): + """Test diversification suggestions generation.""" + files = [ + FileAnalysis( + path="file1.py", + total_commits=10, + author_commits={"a@x.com": 9, "b@x.com": 1} + ), + FileAnalysis( + path="file2.py", + total_commits=10, + author_commits={"a@x.com": 5, "b@x.com": 5} + ) + ] + + suggestions = self.analyzer.generate_suggestions(files) + + assert len(suggestions) > 0 + + def test_calculate_risk_summary(self): + """Test risk summary calculation.""" + files = [ + FileAnalysis( + path="f1.py", + total_commits=10, + author_commits={"a@x.com": 10}, + risk_level="critical" + ), + FileAnalysis( + path="f2.py", + total_commits=10, + author_commits={"a@x.com": 8, "b@x.com": 2}, + risk_level="high" + ), + FileAnalysis( + path="f3.py", + total_commits=10, + author_commits={"a@x.com": 4, "b@x.com": 6}, + risk_level="medium" + ) + ] + + summary = self.analyzer.calculate_risk_summary(files) + + assert summary["critical"] == 1 + assert summary["high"] == 1 + assert summary["medium"] == 1 + assert "overall_risk" in summary + + def test_calculate_risk_summary_empty(self): + """Test risk summary with empty files.""" + summary = self.analyzer.calculate_risk_summary([]) + + assert summary["overall_risk"] == "unknown" + + def test_analyze_module_risk(self): + """Test module-level risk analysis.""" + files = [ + FileAnalysis( + path="core/main.py", + total_commits=10, + author_commits={"a@x.com": 10}, + module="core", + risk_level="critical" + ), + FileAnalysis( + path="core/utils.py", + total_commits=10, + author_commits={"a@x.com": 10}, + module="core", + risk_level="critical" + ), + FileAnalysis( + path="tests/test.py", + total_commits=10, + author_commits={"a@x.com": 5, "b@x.com": 5}, + module="tests", + risk_level="medium" + ) + ] + + module_risk = self.analyzer.analyze_module_risk(files) + + assert "core" in module_risk + assert "tests" in module_risk