Initial upload: Add repohealth-cli project with CI/CD workflow
This commit is contained in:
270
tests/test_analyzers.py
Normal file
270
tests/test_analyzers.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user