Skip to content

Coverage Tools

provide.testkit.quality.coverage

Coverage analysis integration for provide-testkit.

Provides pytest fixtures and utilities for tracking code coverage during tests. Integrates with the coverage.py library for comprehensive coverage analysis.

Features: - Automatic coverage tracking during test runs - Coverage reporting in multiple formats - Integration with quality gates - Artifact management for CI/CD

Usage

Basic coverage tracking

def test_with_coverage(coverage_tracker): result = coverage_tracker.start() # ... run tests coverage_tracker.stop() assert coverage_tracker.get_coverage() > 90

Session-wide coverage

def test_example(session_coverage): # Coverage automatically tracked across all tests pass

Classes

CoverageFixture

CoverageFixture(
    config: dict[str, Any] | None = None,
    artifact_dir: Path | None = None,
)

Bases: BaseQualityFixture

Pytest fixture for coverage tracking integration.

Initialize coverage fixture.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Coverage configuration

None
artifact_dir Path | None

Directory for artifacts

None
Source code in provide/testkit/quality/coverage/fixture.py
def __init__(self, config: dict[str, Any] | None = None, artifact_dir: Path | None = None) -> None:
    """Initialize coverage fixture.

    Args:
        config: Coverage configuration
        artifact_dir: Directory for artifacts
    """
    super().__init__(config, artifact_dir)
    self.tracker: CoverageTracker | None = None
Functions
setup
setup() -> None

Setup coverage tracking.

Source code in provide/testkit/quality/coverage/fixture.py
def setup(self) -> None:
    """Setup coverage tracking."""
    if not COVERAGE_AVAILABLE:
        pytest.skip("Coverage.py not available")

    try:
        self.tracker = CoverageTracker(self.config)
    except Exception as e:
        pytest.skip(f"Failed to initialize coverage: {e}")
teardown
teardown() -> None

Stop coverage and generate reports.

Source code in provide/testkit/quality/coverage/fixture.py
def teardown(self) -> None:
    """Stop coverage and generate reports."""
    if self.tracker and self.tracker.is_running:
        self.tracker.stop()
start_tracking
start_tracking() -> None

Start coverage tracking.

Source code in provide/testkit/quality/coverage/fixture.py
def start_tracking(self) -> None:
    """Start coverage tracking."""
    self.ensure_setup()
    if self.tracker:
        self.tracker.start()
stop_tracking
stop_tracking() -> None

Stop coverage tracking.

Source code in provide/testkit/quality/coverage/fixture.py
def stop_tracking(self) -> None:
    """Stop coverage tracking."""
    if self.tracker:
        self.tracker.stop()
get_coverage
get_coverage() -> float

Get current coverage percentage.

Source code in provide/testkit/quality/coverage/fixture.py
def get_coverage(self) -> float:
    """Get current coverage percentage."""
    if not self.tracker:
        return 0.0
    return self.tracker.get_coverage()
generate_report
generate_report(format: str = 'terminal') -> str

Generate coverage report.

Source code in provide/testkit/quality/coverage/fixture.py
def generate_report(self, format: str = "terminal") -> str:
    """Generate coverage report."""
    if not self.tracker:
        return "No coverage data"
    return self.tracker.generate_report(format)

CoverageReporter

CoverageReporter(config: dict[str, Any] | None = None)

Specialized reporter for coverage results.

Initialize coverage reporter.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Reporter configuration

None
Source code in provide/testkit/quality/coverage/reporter.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize coverage reporter.

    Args:
        config: Reporter configuration
    """
    self.config = config or {}
Functions
format_terminal_report
format_terminal_report(result: QualityResult) -> str

Format coverage result for terminal output.

Parameters:

Name Type Description Default
result QualityResult

Coverage result to format

required

Returns:

Type Description
str

Formatted terminal report

Source code in provide/testkit/quality/coverage/reporter.py
def format_terminal_report(self, result: QualityResult) -> str:
    """Format coverage result for terminal output.

    Args:
        result: Coverage result to format

    Returns:
        Formatted terminal report
    """
    lines = [
        f"Coverage Report - {result.tool}",
        "=" * 40,
    ]

    if result.score is not None:
        lines.append(f"Coverage: {result.score}%")

    details = result.details
    if "total_statements" in details:
        lines.extend(
            [
                f"Total Statements: {details.get('total_statements', 0)}",
                f"Missing Statements: {details.get('missing_statements', 0)}",
            ]
        )

    if "branch_coverage" in details and details["branch_coverage"] is not None:
        lines.append(f"Branch Coverage: {details['branch_coverage']}%")

    if "threshold" in details:
        lines.append(f"Threshold: {details['threshold']}%")

    if result.execution_time:
        lines.append(f"Execution Time: {result.execution_time:.2f}s")

    return "\n".join(lines)
format_json_report
format_json_report(result: QualityResult) -> dict[str, Any]

Format coverage result as JSON data.

Parameters:

Name Type Description Default
result QualityResult

Coverage result to format

required

Returns:

Type Description
dict[str, Any]

JSON-serializable report data

Source code in provide/testkit/quality/coverage/reporter.py
def format_json_report(self, result: QualityResult) -> dict[str, Any]:
    """Format coverage result as JSON data.

    Args:
        result: Coverage result to format

    Returns:
        JSON-serializable report data
    """
    return {
        "tool": result.tool,
        "passed": result.passed,
        "score": result.score,
        "details": result.details,
        "execution_time": result.execution_time,
        "artifacts": [str(p) for p in result.artifacts],
    }
format_html_summary
format_html_summary(result: QualityResult) -> str

Format coverage result as HTML summary.

Parameters:

Name Type Description Default
result QualityResult

Coverage result to format

required

Returns:

Type Description
str

HTML summary

Source code in provide/testkit/quality/coverage/reporter.py
def format_html_summary(self, result: QualityResult) -> str:
    """Format coverage result as HTML summary.

    Args:
        result: Coverage result to format

    Returns:
        HTML summary
    """
    status_color = "green" if result.passed else "red"
    status_text = "PASSED" if result.passed else "FAILED"

    html_parts = [
        '<div class="coverage-summary">',
        "<h3>Coverage Report</h3>",
        f'<p><span style="color: {status_color}">Status: {status_text}</span></p>',
    ]

    if result.score is not None:
        html_parts.append(f"<p>Coverage: <strong>{result.score}%</strong></p>")

    details = result.details
    if "total_statements" in details:
        html_parts.extend(
            [
                f"<p>Total Statements: {details.get('total_statements', 0)}</p>",
                f"<p>Missing Statements: {details.get('missing_statements', 0)}</p>",
            ]
        )

    html_parts.append("</div>")
    return "\n".join(html_parts)
generate_dashboard_data
generate_dashboard_data(
    result: QualityResult,
) -> dict[str, Any]

Generate data for coverage dashboard.

Parameters:

Name Type Description Default
result QualityResult

Coverage result

required

Returns:

Type Description
dict[str, Any]

Dashboard data structure

Source code in provide/testkit/quality/coverage/reporter.py
def generate_dashboard_data(self, result: QualityResult) -> dict[str, Any]:
    """Generate data for coverage dashboard.

    Args:
        result: Coverage result

    Returns:
        Dashboard data structure
    """
    dashboard_data: dict[str, Any] = {
        "title": "Code Coverage",
        "status": "passed" if result.passed else "failed",
        "primary_metric": {
            "label": "Coverage",
            "value": result.score,
            "unit": "%",
            "threshold": result.details.get("threshold", 0),
        },
        "secondary_metrics": [],
    }

    details = result.details
    secondary_metrics = dashboard_data["secondary_metrics"]
    if "total_statements" in details and isinstance(secondary_metrics, list):
        secondary_metrics.extend(
            [
                {"label": "Total Statements", "value": details.get("total_statements", 0)},
                {"label": "Missing Statements", "value": details.get("missing_statements", 0)},
            ]
        )

    if (
        "branch_coverage" in details
        and details["branch_coverage"] is not None
        and isinstance(secondary_metrics, list)
    ):
        secondary_metrics.append(
            {"label": "Branch Coverage", "value": details["branch_coverage"], "unit": "%"}
        )

    return dashboard_data

CoverageTracker

CoverageTracker(config: dict[str, Any] | None = None)

Wrapper for coverage.py library with testkit integration.

Provides high-level interface for coverage tracking with automatic artifact management and integration with the quality framework.

Initialize coverage tracker.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Coverage configuration options

None
Source code in provide/testkit/quality/coverage/tracker.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize coverage tracker.

    Args:
        config: Coverage configuration options
    """
    if not COVERAGE_AVAILABLE:
        raise QualityToolError(
            "Coverage.py not available. Install with: pip install coverage", tool="coverage"
        )

    self.config = config or {}
    self.coverage: Coverage | None = None
    self.is_running = False
    self.artifact_dir: Path | None = None
Functions
analyze
analyze(path: Path, **kwargs: Any) -> QualityResult

Run coverage analysis on the given path.

Parameters:

Name Type Description Default
path Path

Path to analyze

required
**kwargs Any

Additional options including artifact_dir

{}

Returns:

Type Description
QualityResult

QualityResult with coverage data

Source code in provide/testkit/quality/coverage/tracker.py
def analyze(self, path: Path, **kwargs: Any) -> QualityResult:
    """Run coverage analysis on the given path.

    Args:
        path: Path to analyze
        **kwargs: Additional options including artifact_dir

    Returns:
        QualityResult with coverage data
    """
    self.artifact_dir = kwargs.get("artifact_dir", Path(".coverage"))
    start_time = time.time()

    try:
        # If coverage is already running, get current data
        if self.is_running and self.coverage:
            self.stop()

        # Start fresh coverage analysis
        self.start()

        # For analysis mode, we need to combine existing coverage data
        # This is typically used when coverage was collected during test runs
        self._load_existing_data()

        # Generate report
        result = self._create_result()
        result.execution_time = time.time() - start_time

        # Generate artifacts
        self._generate_artifacts(result)

        return result

    except Exception as e:
        return QualityResult(
            tool="coverage",
            passed=False,
            details={"error": str(e), "error_type": type(e).__name__},
            execution_time=time.time() - start_time,
        )
start
start() -> None

Start coverage tracking.

Source code in provide/testkit/quality/coverage/tracker.py
def start(self) -> None:
    """Start coverage tracking."""
    if self.is_running:
        return

    coverage_config = self._build_coverage_config()
    self.coverage = Coverage(**coverage_config)
    self.coverage.start()
    self.is_running = True
stop
stop() -> None

Stop coverage tracking and save data.

Source code in provide/testkit/quality/coverage/tracker.py
def stop(self) -> None:
    """Stop coverage tracking and save data."""
    if not self.is_running or not self.coverage:
        return

    self.coverage.stop()
    self.coverage.save()
    self.is_running = False
get_coverage
get_coverage() -> float

Get current coverage percentage.

Returns:

Type Description
float

Coverage percentage (0-100)

Source code in provide/testkit/quality/coverage/tracker.py
def get_coverage(self) -> float:
    """Get current coverage percentage.

    Returns:
        Coverage percentage (0-100)
    """
    if not self.coverage:
        return 0.0

    try:
        # Get coverage data
        total = self.coverage.report(file=None, show_missing=False)
        return round(total, 2)
    except Exception:
        return 0.0
generate_report
generate_report(format: str = 'terminal') -> str

Generate coverage report.

Parameters:

Name Type Description Default
format str

Report format (terminal, html, xml, json)

'terminal'

Returns:

Type Description
str

Report content (for terminal/json) or path (for html/xml)

Source code in provide/testkit/quality/coverage/tracker.py
def generate_report(self, format: str = "terminal") -> str:
    """Generate coverage report.

    Args:
        format: Report format (terminal, html, xml, json)

    Returns:
        Report content (for terminal/json) or path (for html/xml)
    """
    if not self.coverage:
        return "No coverage data available"

    if format == "terminal":
        return self._generate_terminal_report()
    elif format == "html" and self.artifact_dir:
        html_dir = self.artifact_dir / "htmlcov"
        self.coverage.html_report(directory=str(html_dir))
        return str(html_dir / "index.html")
    elif format == "xml" and self.artifact_dir:
        xml_file = self.artifact_dir / "coverage.xml"
        self.coverage.xml_report(outfile=str(xml_file))
        return str(xml_file)
    elif format == "json" and self.artifact_dir:
        json_file = self.artifact_dir / "coverage.json"
        self.coverage.json_report(outfile=str(json_file))
        return json_file.read_text()
    else:
        return f"Unsupported format: {format}"
report
report(
    result: QualityResult, format: str = "terminal"
) -> str

Generate report from QualityResult (implements QualityTool protocol).

Parameters:

Name Type Description Default
result QualityResult

Coverage result

required
format str

Report format

'terminal'

Returns:

Type Description
str

Formatted report

Source code in provide/testkit/quality/coverage/tracker.py
def report(self, result: QualityResult, format: str = "terminal") -> str:
    """Generate report from QualityResult (implements QualityTool protocol).

    Args:
        result: Coverage result
        format: Report format

    Returns:
        Formatted report
    """
    if format == "terminal":
        lines = [
            f"Coverage Report - {result.tool}",
            "=" * 40,
        ]

        if result.score is not None:
            lines.append(f"Coverage: {result.score}%")

        if "total_statements" in result.details:
            details = result.details
            lines.extend(
                [
                    f"Total Statements: {details.get('total_statements', 0)}",
                    f"Missing Statements: {details.get('missing_statements', 0)}",
                    f"Branch Coverage: {details.get('branch_coverage', 'N/A')}%",
                ]
            )

        return "\n".join(lines)

    return str(result.details)

Functions

coverage_tracker

coverage_tracker(
    request: FixtureRequest, tmp_path: Path
) -> Generator[CoverageFixture, None, None]

Pytest fixture for coverage tracking.

Provides a CoverageFixture instance that automatically starts and stops coverage tracking around individual tests.

Usage

def test_with_coverage(coverage_tracker): coverage_tracker.start_tracking() # ... test code coverage_tracker.stop_tracking() assert coverage_tracker.get_coverage() > 80

Source code in provide/testkit/quality/coverage/fixture.py
@pytest.fixture
def coverage_tracker(
    request: FixtureRequest,
    tmp_path: Path,
) -> Generator[CoverageFixture, None, None]:
    """Pytest fixture for coverage tracking.

    Provides a CoverageFixture instance that automatically starts and stops
    coverage tracking around individual tests.

    Usage:
        def test_with_coverage(coverage_tracker):
            coverage_tracker.start_tracking()
            # ... test code
            coverage_tracker.stop_tracking()
            assert coverage_tracker.get_coverage() > 80
    """
    # Get configuration from pytest request
    config = getattr(request, "param", {})

    # Create artifact directory for this test
    artifact_dir = tmp_path / "coverage"

    # Initialize fixture
    fixture = CoverageFixture(config=config, artifact_dir=artifact_dir)

    try:
        fixture.setup()
        yield fixture
    finally:
        fixture.teardown()

session_coverage

session_coverage(
    tmp_path_factory: TempPathFactory,
) -> Generator[CoverageFixture, None, None]

Session-wide coverage tracking.

Tracks coverage across all tests in the session. Useful for getting overall coverage metrics for the entire test suite.

Usage

def test_part_one(session_coverage): # Coverage tracked across all tests pass

def test_part_two(session_coverage): # Same coverage instance pass

Source code in provide/testkit/quality/coverage/fixture.py
@pytest.fixture(scope="session")
def session_coverage(
    tmp_path_factory: TempPathFactory,
) -> Generator[CoverageFixture, None, None]:
    """Session-wide coverage tracking.

    Tracks coverage across all tests in the session. Useful for getting
    overall coverage metrics for the entire test suite.

    Usage:
        def test_part_one(session_coverage):
            # Coverage tracked across all tests
            pass

        def test_part_two(session_coverage):
            # Same coverage instance
            pass
    """
    # Create session-wide artifact directory
    artifact_dir = tmp_path_factory.mktemp("session_coverage")

    # Initialize session fixture
    fixture = CoverageFixture(artifact_dir=artifact_dir)

    try:
        fixture.setup()
        fixture.start_tracking()
        yield fixture
    finally:
        fixture.stop_tracking()
        fixture.teardown()