Source code for curate_ipsum.parsers

"""
Unified mutation testing parser interface.

This module provides a single entry point for parsing mutation testing output
from multiple frameworks (Stryker, mutmut, etc.) with automatic framework
detection.

Usage:
    from curate_ipsum.parsers import parse_mutation_output

    # Auto-detect framework
    result = parse_mutation_output(working_directory="/path/to/project")

    # Explicit framework
    result = parse_mutation_output(
        working_directory="/path/to/project",
        tool="mutmut"
    )
"""

from __future__ import annotations

import logging

from curate_ipsum.models import FileMutationStats
from curate_ipsum.parsers.cosmic_ray_parser import parse_cosmic_ray_output
from curate_ipsum.parsers.detection import (
    FrameworkDetection,
    MutationFramework,
    ProjectLanguage,
    detect_available_frameworks,
    detect_language,
    recommend_framework,
)
from curate_ipsum.parsers.mutmut_parser import parse_mutmut_output
from curate_ipsum.parsers.poodle_parser import parse_poodle_output
from curate_ipsum.parsers.stryker_parser import parse_stryker_output
from curate_ipsum.parsers.universalmutator_parser import parse_universalmutator_output

LOG = logging.getLogger("parsers")

__all__ = [
    # Main interface
    "parse_mutation_output",
    "UnsupportedFrameworkError",
    # Detection
    "MutationFramework",
    "FrameworkDetection",
    "ProjectLanguage",
    "detect_language",
    "detect_available_frameworks",
    "recommend_framework",
    # Individual parsers (for direct use)
    "parse_stryker_output",
    "parse_mutmut_output",
    "parse_cosmic_ray_output",
    "parse_poodle_output",
    "parse_universalmutator_output",
]


[docs] class UnsupportedFrameworkError(Exception): """Raised when a mutation framework is not supported.""" pass
[docs] def parse_mutation_output( working_directory: str, tool: str | None = None, report_path: str | None = None, ) -> tuple[int, int, int, int, float, list[FileMutationStats]]: """ Parse mutation testing output, auto-detecting framework if not specified. This is the main entry point for all mutation testing parsers. It routes to the appropriate parser based on the tool parameter or auto-detection. Args: working_directory: Project directory containing mutation output tool: Optional framework name ("stryker", "mutmut", etc.) If None, auto-detects based on available output files report_path: Optional path to report file or cache (interpretation depends on framework) Returns: Tuple of (total_mutants, killed, survived, no_coverage, score, by_file) - total_mutants: Total number of mutants processed - killed: Number of mutants killed by tests - survived: Number of mutants that survived - no_coverage: Number of mutants with no test coverage - score: Mutation score as killed/(killed+survived) - by_file: Per-file breakdown as FileMutationStats list Raises: UnsupportedFrameworkError: If framework is not supported FileNotFoundError: If report/cache not found Examples: # Auto-detect framework >>> total, killed, survived, no_cov, score, by_file = parse_mutation_output("./") # Explicit Stryker >>> parse_mutation_output("./", tool="stryker") # Explicit mutmut with custom cache path >>> parse_mutation_output("./", tool="mutmut", report_path="./.mutmut-cache") """ # Auto-detect if not specified if tool is None: detection = recommend_framework(working_directory) tool = detection.framework.value LOG.info( "Auto-detected framework: %s (confidence=%.2f, reason=%s)", tool, detection.confidence, detection.evidence, ) if detection.framework == MutationFramework.UNKNOWN: raise UnsupportedFrameworkError( f"Could not auto-detect mutation framework. {detection.evidence}. " f"Supported frameworks: stryker, mutmut, cosmic-ray, poodle, universalmutator" ) tool_lower = tool.lower().replace("-", "_").replace(" ", "_") # Route to appropriate parser if tool_lower in ("stryker", "stryker_js"): return parse_stryker_output(report_path, working_directory) elif tool_lower in ("mutmut", "mut_mut"): return parse_mutmut_output(working_directory, report_path) elif tool_lower in ("cosmic_ray", "cosmicray", "cosmic"): return parse_cosmic_ray_output(working_directory, report_path) elif tool_lower in ("poodle", "poodle_test"): return parse_poodle_output(working_directory, report_path) elif tool_lower in ("universalmutator", "universal_mutator", "um"): return parse_universalmutator_output(working_directory, report_path) elif tool_lower in ("mutpy", "mut_py"): raise UnsupportedFrameworkError( "mutpy parser not yet implemented. " "Supported frameworks: stryker, mutmut, cosmic-ray, poodle, universalmutator" ) else: raise UnsupportedFrameworkError( f"Unknown mutation framework: {tool}. " f"Supported frameworks: stryker, mutmut, cosmic-ray, poodle, universalmutator" )
def get_detected_tool(working_directory: str) -> str | None: """ Get the name of the auto-detected tool without parsing. Useful when you need to know which tool was detected before running the full parse. Args: working_directory: Project directory Returns: Tool name string, or None if no tool detected """ detection = recommend_framework(working_directory) if detection.framework == MutationFramework.UNKNOWN: return None return detection.framework.value