Plugin Module¶
The plugin module provides the pytest integration for pytest-gremlins. It implements pytest hooks that enable mutation testing as part of the test lifecycle.
Overview¶
The plugin handles:
- Command-line options - Adding
--gremlinsand related flags - Configuration - Loading settings from pyproject.toml and merging with CLI
- Source discovery - Finding Python files to mutate
- Instrumentation - Transforming source code with embedded mutations
- Test execution - Running tests against each gremlin
- Result reporting - Displaying mutation scores and survivors
Command-Line Options¶
| Option | Default | Description |
|---|---|---|
--gremlins |
False |
Enable mutation testing |
--gremlin-operators |
All | Comma-separated list of operators to use |
--gremlin-report |
console |
Report format: console, html, json (repeatable) |
--gremlin-targets |
None (auto-discovered) | Comma-separated source directories/files |
--gremlin-exclude |
None | Glob patterns to exclude from mutation (repeatable) |
--gremlin-cache |
False |
Enable incremental analysis cache |
--gremlin-clear-cache |
False |
Clear cache before running |
--gremlin-parallel |
False |
Enable parallel execution |
--gremlin-workers |
CPU count | Number of parallel workers |
--gremlin-batch |
False |
Enable batch execution mode |
--gremlin-batch-size |
10 |
Gremlins per batch |
--gremlins-html-dir |
None | Output directory for HTML reports |
--strict-pardons |
False |
Treat pardoned gremlins as CI failures (exit non-zero if any exist) |
--gremlin-audit-pardons |
False |
Audit pardon pragma usage |
--gremlin-max-pardons-pct |
None | Maximum percentage of pardoned gremlins |
--max-pardons |
None | Maximum absolute number of pardoned gremlins |
Usage Examples¶
Basic Usage¶
# Enable mutation testing
pytest --gremlins
# Target specific directory
pytest --gremlins --gremlin-targets=mypackage/
# Generate HTML report
pytest --gremlins --gremlin-report=html
With Caching¶
# Enable incremental caching (faster subsequent runs)
pytest --gremlins --gremlin-cache
# Clear cache and start fresh
pytest --gremlins --gremlin-cache --gremlin-clear-cache
Parallel Execution¶
# Run with parallel workers (auto-detects CPU count)
pytest --gremlins --gremlin-parallel
# Specify worker count
pytest --gremlins --gremlin-parallel --gremlin-workers=8
# Use batch mode for reduced subprocess overhead
pytest --gremlins --gremlin-batch --gremlin-batch-size=20
Selective Operators¶
# Use only comparison and boundary operators
pytest --gremlins --gremlin-operators=comparison,boundary
# Use only arithmetic operator
pytest --gremlins --gremlin-operators=arithmetic
Configuration¶
Configuration can be specified in pyproject.toml under [tool.pytest-gremlins]:
[tool.pytest-gremlins]
# Mutation operators to enable
operators = ["comparison", "arithmetic", "boolean"]
# Source paths to mutate
paths = ["src/mypackage"]
# Glob patterns to exclude from mutation
exclude = ["**/migrations/**"]
# Number of parallel workers ("auto" or an integer)
workers = "auto"
# Enable incremental analysis cache
cache = true
# Report formats
report = ["html", "json"]
# Gremlins per batch in batch mode
batch_size = 20
# Pardon budget (percentage and absolute cap)
max-pardons-pct = 5.0
max_pardons = 10
Configuration Precedence¶
- CLI arguments (highest priority)
- pyproject.toml
[tool.pytest-gremlins]section - Built-in defaults (lowest priority)
GremlinConfig¶
GremlinConfig
dataclass
¶
GremlinConfig(operators=None, paths=None, exclude=None, workers=None, cache=None, report=None, batch_size=None, max_pardons_pct=None, max_pardons=None)
Configuration for pytest-gremlins.
All fields are optional and default to None, meaning the plugin will use CLI defaults or built-in defaults.
Attributes:
| Name | Type | Description |
|---|---|---|
operators |
list[str] | None
|
List of mutation operator names to enable. |
paths |
list[str] | None
|
List of paths to scan for source files to mutate. |
exclude |
list[str] | None
|
List of glob patterns for files to exclude from mutation. |
workers |
int | str | None
|
Number of parallel workers, "auto" for CPU count, or None. |
cache |
bool | None
|
Whether to enable incremental analysis cache. |
report |
list[str] | None
|
List of report formats (e.g. ["html", "json"]). |
batch_size |
int | None
|
Number of gremlins per batch in batch mode. |
Configuration Functions¶
load_config
¶
Load configuration from pyproject.toml.
Reads the [tool.pytest-gremlins] section from pyproject.toml in the given directory. Returns default configuration if the file or section does not exist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rootdir
|
Path
|
Directory containing pyproject.toml. |
required |
Returns:
| Type | Description |
|---|---|
GremlinConfig
|
GremlinConfig with values from pyproject.toml or defaults. |
Source code in src/pytest_gremlins/config.py
| Python | |
|---|---|
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
merge_configs
¶
merge_configs(file_config, cli_operators=None, cli_targets=None, cli_exclude=None, cli_workers=None, cli_cache=None, cli_report=None, cli_batch_size=None, cli_max_pardons_pct=None, cli_max_pardons=None)
Merge CLI arguments with file configuration.
CLI arguments take precedence over pyproject.toml configuration. Empty strings are treated as not provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_config
|
GremlinConfig
|
Configuration loaded from pyproject.toml. |
required |
cli_operators
|
str | None
|
Comma-separated operator names from CLI (--gremlin-operators). |
None
|
cli_targets
|
str | None
|
Comma-separated target paths from CLI (--gremlin-targets). |
None
|
cli_exclude
|
list[str] | None
|
Glob patterns from CLI (--gremlin-exclude, repeatable). |
None
|
cli_workers
|
int | None
|
Worker count from CLI (--gremlin-workers), already resolved to int. |
None
|
cli_cache
|
bool | None
|
Cache flag from CLI (--gremlin-cache). |
None
|
cli_report
|
list[str] | None
|
Report format list from CLI (--gremlin-report). |
None
|
cli_batch_size
|
int | None
|
Batch size from CLI (--gremlin-batch-size). |
None
|
cli_max_pardons_pct
|
float | None
|
Max pardoned % from CLI (--gremlin-max-pardons-pct). |
None
|
cli_max_pardons
|
int | None
|
Max absolute pardon count from CLI (--max-pardons). |
None
|
Returns:
| Type | Description |
|---|---|
GremlinConfig
|
GremlinConfig with CLI values overriding file config where provided. |
Source code in src/pytest_gremlins/config.py
GremlinSession¶
The GremlinSession dataclass maintains state throughout a mutation testing run.
GremlinSession
dataclass
¶
GremlinSession(enabled=False, operators=list(), report_formats=(lambda: ['console'])(), gremlins=list(), results=list(), source_files=dict(), test_files=list(), target_paths=list(), instrumented_dir=None, coverage_collector=None, test_selector=None, prioritized_selector=None, test_node_ids=dict(), total_tests=0, cache_enabled=False, cache=None, source_hashes=dict(), test_hashes=dict(), cache_hits=0, cache_misses=0, parallel_enabled=False, parallel_workers=None, batch_enabled=False, batch_size=10, xdist_item_ids=None, xdist_active=False, xdist_workers=None, coverage_mode=PRIVATE, private_coverage=None, gremlins_tmpdir=None, exclude_patterns=list(), strict_pardons=False, audit_pardons=False, max_pardons_pct=None, max_pardons=None, no_coverage_filter=False, test_name_to_node_ids=dict(), explain_gremlin_id=None)
Session state for mutation testing.
Attributes:
| Name | Type | Description |
|---|---|---|
enabled |
bool
|
Whether mutation testing is enabled. |
operators |
list[GremlinOperator]
|
List of operators to use for mutation. |
report_formats |
list[str]
|
List of report formats (console, html, json). |
gremlins |
list[Gremlin]
|
All gremlins found in the source code. |
results |
list[GremlinResult]
|
Results from testing each gremlin. |
source_files |
dict[str, str]
|
Mapping of file paths to their source code. |
test_files |
list[Path]
|
List of test file paths that were collected. |
instrumented_dir |
Path | None
|
Temporary directory containing instrumented source files. |
coverage_collector |
CoverageCollector | None
|
Collects coverage data per-test. |
test_selector |
TestSelector | None
|
Selects tests based on coverage data. |
prioritized_selector |
PrioritizedSelector | None
|
Selects tests ordered by specificity (most specific first). |
test_node_ids |
dict[str, str]
|
Maps test names to their pytest node IDs. |
total_tests |
int
|
Total number of tests collected. |
cache_enabled |
bool
|
Whether incremental caching is enabled. |
cache |
IncrementalCache | None
|
The incremental cache instance (if caching is enabled). |
source_hashes |
dict[str, str]
|
Content hashes for source files. |
test_hashes |
dict[str, str]
|
Content hashes for test files. |
cache_hits |
int
|
Number of cache hits in this session. |
cache_misses |
int
|
Number of cache misses in this session. |
parallel_enabled |
bool
|
Whether parallel execution is enabled. |
parallel_workers |
int | None
|
Number of parallel workers (None = CPU count). |
batch_enabled |
bool
|
Whether batch execution mode is enabled. |
batch_size |
int
|
Number of gremlins per batch in batch mode. |
xdist_item_ids |
list[str] | None
|
Test node IDs captured from the first xdist worker after
collection finishes. |
coverage_mode |
CoverageMode
|
Whether to reuse pytest-cov's coverage (PIGGYBACK) or manage an inline coverage instance (PRIVATE). |
private_coverage |
Coverage | None
|
The inline |
gremlins_tmpdir |
str | None
|
Path (as a string) to the shared temporary directory
where xdist workers write their per-worker coverage data files in
PRIVATE mode. |
exclude_patterns |
list[str]
|
Glob patterns from |
test_name_to_node_ids |
dict[str, list[str]]
|
Reverse index mapping bare function names to their full pytest node IDs. Built at session setup for O(1) lookup when resolving coverage contexts. |
Session Attributes¶
| Attribute | Type | Description |
|---|---|---|
enabled |
bool |
Whether mutation testing is active |
operators |
list[GremlinOperator] |
Active mutation operators |
report_formats |
list[str] |
Output formats (console/html/json) |
gremlins |
list[Gremlin] |
All discovered gremlins |
results |
list[GremlinResult] |
Test results for each gremlin |
source_files |
dict[str, str] |
Map of file paths to source code |
test_files |
list[Path] |
Collected test file paths |
target_paths |
list[Path] |
Source paths to mutate |
instrumented_dir |
Path \| None |
Temp directory with instrumented code |
coverage_collector |
CoverageCollector \| None |
Coverage data collector |
test_selector |
TestSelector \| None |
Coverage-based test selector |
prioritized_selector |
PrioritizedSelector \| None |
Priority-ordered selector |
test_node_ids |
dict[str, str] |
Map of test names to pytest node IDs |
total_tests |
int |
Total number of collected tests |
cache_enabled |
bool |
Whether caching is active |
cache |
IncrementalCache \| None |
The cache instance |
source_hashes |
dict[str, str] |
Content hashes for source files |
test_hashes |
dict[str, str] |
Content hashes for test files |
cache_hits |
int |
Number of cache hits |
cache_misses |
int |
Number of cache misses |
parallel_enabled |
bool |
Whether parallel mode is active |
parallel_workers |
int \| None |
Number of workers (None = auto) |
batch_enabled |
bool |
Whether batch mode is active |
batch_size |
int |
Gremlins per batch |
xdist_item_ids |
list[str] \| None |
Test IDs captured from xdist workers |
xdist_active |
bool |
Whether xdist is active |
xdist_workers |
int \| None |
Number of xdist workers |
coverage_mode |
CoverageMode |
PIGGYBACK (reuse pytest-cov) or PRIVATE |
private_coverage |
coverage.Coverage \| None |
Inline coverage instance (PRIVATE mode) |
gremlins_tmpdir |
str \| None |
Shared temp dir for xdist worker coverage data |
exclude_patterns |
list[str] |
Glob patterns to skip during source discovery |
strict_pardons |
bool |
Treat pardoned gremlins as CI failures (exit non-zero if any exist) |
audit_pardons |
bool |
Whether to audit pardon pragma usage |
max_pardons_pct |
float \| None |
Maximum percentage of pardoned gremlins |
max_pardons |
int \| None |
Maximum absolute pardon count |
pytest Hooks¶
The plugin implements these pytest hooks:
pytest_addoption¶
Adds command-line options for mutation testing configuration.
def pytest_addoption(parser: pytest.Parser) -> None:
"""Add command-line options for pytest-gremlins."""
pytest_configure¶
Initializes the gremlin session based on command-line options and pyproject.toml.
When xdist is available, also registers pytest_configure_node and
pytest_xdist_node_collection_finished hooks.
def pytest_configure(config: pytest.Config) -> None:
"""Configure pytest-gremlins based on command-line options."""
pytest_sessionstart¶
Sets up inline coverage collection in PRIVATE mode (when --cov is not active).
def pytest_sessionstart(session: pytest.Session) -> None:
"""Start inline coverage collection if needed."""
pytest_runtestloop¶
Hookimpl wrapper that saves and stops coverage data after the test loop completes.
def pytest_runtestloop(session: pytest.Session) -> Generator[None, None, None]:
"""Wrap the test loop to capture coverage data."""
pytest_collection_finish¶
After test collection completes, discovers source files and generates gremlins.
def pytest_collection_finish(session: pytest.Session) -> None:
"""After test collection, discover source files and generate gremlins."""
pytest_configure_node (xdist only)¶
Passes gremlin temp directory path to xdist workers via node.workerinput.
def pytest_configure_node(node: _XdistWorkerNode) -> None:
"""Pass gremlins config to xdist worker nodes."""
pytest_xdist_node_collection_finished (xdist only)¶
Captures test node IDs from the first xdist worker's collection.
def pytest_xdist_node_collection_finished(node: object, ids: list[str]) -> None:
"""Capture collected test IDs from xdist workers."""
pytest_sessionfinish¶
After all tests run, executes mutation testing against each gremlin.
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
"""After all tests run, execute mutation testing."""
pytest_terminal_summary¶
Adds mutation testing results to pytest's terminal output.
def pytest_terminal_summary(
terminalreporter: pytest.TerminalReporter,
exitstatus: int,
config: pytest.Config,
) -> None:
"""Add mutation testing results to terminal output."""
pytest_unconfigure¶
Cleans up temporary files and closes resources.
Environment Variables¶
| Variable | Description |
|---|---|
ACTIVE_GREMLIN |
Set by plugin to indicate which gremlin is active during test execution |
PYTEST_GREMLINS_SOURCES_FILE |
Path to JSON file containing instrumented source code |
Internal Functions¶
These functions are internal to the plugin but documented for understanding the implementation.
Source Discovery¶
def _discover_source_files(
session: pytest.Session,
gremlin_session: GremlinSession,
) -> dict[str, str]:
"""Discover Python source files to mutate."""
Test Selection¶
def _select_tests_for_gremlin_prioritized(
gremlin: Gremlin,
gremlin_session: GremlinSession,
) -> list[str]:
"""Select tests for a gremlin, ordered by specificity."""
Mutation Testing Execution¶
def _run_mutation_testing(
session: pytest.Session,
gremlin_session: GremlinSession,
) -> list[GremlinResult]:
"""Run mutation testing for all gremlins (sequential mode)."""
def _run_parallel_mutation_testing(
session: pytest.Session,
gremlin_session: GremlinSession,
) -> list[GremlinResult]:
"""Run mutation testing in parallel across multiple workers."""
def _run_batch_mutation_testing(
session: pytest.Session,
gremlin_session: GremlinSession,
) -> list[GremlinResult]:
"""Run mutation testing using batch execution."""