Operators Module¶
The operators module provides the mutation operator system for pytest-gremlins. Operators identify AST patterns and generate mutated variants (gremlins).
Overview¶
Mutation operators are the core of mutation testing. Each operator:
- Identifies specific AST node patterns it can mutate
- Generates one or more mutated variants of matching nodes
- Provides human-readable descriptions for reports
Module Exports¶
from pytest_gremlins.operators import (
GremlinOperator, # Protocol for all operators
OperatorRegistry, # Central operator registration
ArithmeticOperator, # +, -, *, /, //, %, **
BooleanOperator, # and/or, True/False, not
BoundaryOperator, # Off-by-one (value +/- 1)
ComparisonOperator, # <, <=, >, >=, ==, !=
ReturnOperator, # return value mutations
)
Protocol¶
GremlinOperator¶
All mutation operators must implement this protocol.
GremlinOperator
¶
Bases: Protocol
Protocol for all mutation operators.
A GremlinOperator identifies specific AST patterns and generates mutated variants (gremlins) of those patterns.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique identifier for this operator (e.g., 'comparison', 'arithmetic'). |
description |
str
|
Human-readable description for reports. |
can_mutate
¶
Return True if this operator can mutate the given AST node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this operator can generate mutations for this node. |
Source code in src/pytest_gremlins/operators/protocol.py
mutate
¶
Return all mutated variants of this node.
Each returned AST node represents one gremlin (mutation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to mutate. |
required |
Returns:
| Type | Description |
|---|---|
list[AST]
|
List of mutated AST nodes, one for each possible mutation. |
Source code in src/pytest_gremlins/operators/protocol.py
| Python | |
|---|---|
Protocol Methods¶
| Method | Returns | Description |
|---|---|---|
name |
str |
Unique identifier (e.g., 'comparison') |
description |
str |
Human-readable description |
can_mutate(node) |
bool |
Whether operator can mutate this node |
mutate(node) |
list[AST] |
List of mutated AST variants |
Implementing a Custom Operator¶
import ast
import copy
class CustomOperator:
"""Example custom mutation operator."""
@property
def name(self) -> str:
return 'custom'
@property
def description(self) -> str:
return 'Custom mutations for demonstration'
def can_mutate(self, node: ast.AST) -> bool:
# Return True for nodes this operator handles
return isinstance(node, ast.Constant) and node.value == 42
def mutate(self, node: ast.AST) -> list[ast.AST]:
# Return list of mutated variants
if not isinstance(node, ast.Constant):
return []
mutated = copy.deepcopy(node)
mutated.value = 0
return [mutated]
Registry¶
OperatorRegistry¶
Central registry for managing mutation operators.
OperatorRegistry
¶
Central registry for gremlin operators.
This class manages the registration and retrieval of mutation operators. Operators are registered by their name and can be retrieved individually or as a group.
Example
from pytest_gremlins.operators import ComparisonOperator registry = OperatorRegistry() registry.register(ComparisonOperator) 'comparison' in registry.available() True
Source code in src/pytest_gremlins/operators/registry.py
register
¶
Register an operator class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator_class
|
type[GremlinOperator]
|
The operator class to register. |
required |
name
|
str | None
|
Optional name to register under. If not provided, uses the operator's name property. |
None
|
Source code in src/pytest_gremlins/operators/registry.py
register_decorator
¶
Decorator to register an operator class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str | None
|
Optional name to register under. |
None
|
Returns:
| Type | Description |
|---|---|
Callable[[type[GremlinOperator]], type[GremlinOperator]]
|
Decorator function that registers the class. |
Example
registry = OperatorRegistry() @registry.register_decorator('comparison') ... class ComparisonOperator: ... ...
Source code in src/pytest_gremlins/operators/registry.py
get
¶
Get a single operator by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The registered name of the operator. |
required |
Returns:
| Type | Description |
|---|---|
GremlinOperator
|
An instance of the requested operator. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no operator is registered with the given name. |
Source code in src/pytest_gremlins/operators/registry.py
get_all
¶
Get operator instances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
list[str] | None
|
If provided, only return these operators (in order). If None, return all registered operators. |
None
|
Returns:
| Type | Description |
|---|---|
list[GremlinOperator]
|
List of operator instances. |
Source code in src/pytest_gremlins/operators/registry.py
available
¶
List all registered operator names.
Returns:
| Type | Description |
|---|---|
list[str]
|
List of registered operator names. |
Registry Methods¶
| Method | Description |
|---|---|
register(cls, name=None) |
Register an operator class |
register_decorator(name=None) |
Decorator for registration |
get(name) |
Get single operator by name |
get_all(enabled=None) |
Get list of operator instances |
available() |
List registered operator names |
Usage Examples¶
from pytest_gremlins.operators import OperatorRegistry, ComparisonOperator
# Create a registry
registry = OperatorRegistry()
# Register operators
registry.register(ComparisonOperator)
registry.register(CustomOperator, name='custom')
# List available operators
print(registry.available()) # ['comparison', 'custom']
# Get specific operator
op = registry.get('comparison')
# Get all operators
all_ops = registry.get_all()
# Get subset of operators
subset = registry.get_all(enabled=['comparison', 'arithmetic'])
Using the Decorator¶
registry = OperatorRegistry()
@registry.register_decorator('my_operator')
class MyOperator:
@property
def name(self) -> str:
return 'my_operator'
# ...
Built-in Operators¶
pytest-gremlins includes five built-in operators covering common mutation patterns.
ComparisonOperator¶
Mutates comparison operators to catch boundary and off-by-one bugs.
ComparisonOperator
¶
Mutate comparison operators.
Generates mutations for comparison operators, swapping them with related operators to catch off-by-one and boundary condition bugs.
Mutations
- < -> <=, >
- <= -> <, >
-
-> >=, <
-
= -> >, <
- == -> !=
- != -> ==
can_mutate
¶
Return True if this operator can mutate the given AST node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this is a Compare node with supported operators. |
Source code in src/pytest_gremlins/operators/comparison.py
mutate
¶
Return all mutated variants of this node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to mutate. |
required |
Returns:
| Type | Description |
|---|---|
list[AST]
|
List of mutated AST nodes, one for each possible mutation. |
Source code in src/pytest_gremlins/operators/comparison.py
get_symbol
¶
Get the symbol for a comparison operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
op
|
cmpop
|
A comparison operator AST node. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The symbol string (e.g., '<', '<=', '=='), or '?' if unknown. |
Source code in src/pytest_gremlins/operators/comparison.py
Mutations¶
| Original | Mutations |
|---|---|
< |
<=, > |
<= |
<, > |
> |
>=, < |
>= |
>, < |
== |
!= |
!= |
== |
Example¶
# Original code
if age >= 18:
return "adult"
# Mutations generated:
# 1. if age > 18: (>= to >)
# 2. if age < 18: (>= to <)
ArithmeticOperator¶
Mutates arithmetic operators to catch calculation errors.
ArithmeticOperator
¶
Mutate arithmetic operators.
Generates mutations for arithmetic operators, swapping them with related operators to catch calculation errors.
Mutations
-
- -> -
-
- -> +
-
- -> /
- / -> *
- // -> /
- % -> //
- ** -> *
can_mutate
¶
Return True if this operator can mutate the given AST node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this is a BinOp node with a supported arithmetic operator. |
Source code in src/pytest_gremlins/operators/arithmetic.py
mutate
¶
Return all mutated variants of this node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to mutate. |
required |
Returns:
| Type | Description |
|---|---|
list[AST]
|
List of mutated AST nodes, one for each possible mutation. |
Source code in src/pytest_gremlins/operators/arithmetic.py
get_symbol
¶
Get the symbol for an arithmetic operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
op
|
operator
|
An arithmetic operator AST node. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The symbol string (e.g., '+', '-', '*'), or '?' if unknown. |
Source code in src/pytest_gremlins/operators/arithmetic.py
Mutations¶
| Original | Mutations |
|---|---|
+ |
- |
- |
+ |
* |
/ |
/ |
* |
// |
/ |
% |
// |
** |
* |
Example¶
# Original code
total = price * quantity
# Mutation generated:
# total = price / quantity (* to /)
BooleanOperator¶
Mutates boolean logic to catch logic errors.
BooleanOperator
¶
Mutate boolean operators and values.
Generates mutations for boolean logic to catch logic errors.
Mutations
- and -> or
- or -> and
- not x -> x
- True -> False
- False -> True
can_mutate
¶
Return True if this operator can mutate the given AST node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this is a boolean operation or value we can mutate. |
Source code in src/pytest_gremlins/operators/boolean.py
mutate
¶
Return all mutated variants of this node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to mutate. |
required |
Returns:
| Type | Description |
|---|---|
list[AST]
|
List of mutated AST nodes, one for each possible mutation. |
Source code in src/pytest_gremlins/operators/boolean.py
Mutations¶
| Original | Mutations |
|---|---|
and |
or |
or |
and |
not x |
x |
True |
False |
False |
True |
Example¶
# Original code
if is_admin and is_active:
grant_access()
# Mutation generated:
# if is_admin or is_active: (and to or)
BoundaryOperator¶
Mutates integer constants in comparisons by ± 1 to catch off-by-one errors.
BoundaryOperator
¶
Mutate boundary conditions in comparisons.
Generates mutations for integer constants in comparisons by shifting them by ± 1 to catch off-by-one errors.
Mutations
- x >= 18 -> x >= 17, x >= 19
- x > 0 -> x > -1, x > 1
can_mutate
¶
Return True if this operator can mutate the given AST node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this is a comparison with an integer constant. |
Source code in src/pytest_gremlins/operators/boundary.py
mutate
¶
Return all mutated variants of this node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to mutate. |
required |
Returns:
| Type | Description |
|---|---|
list[AST]
|
List of mutated AST nodes, one for each boundary shift. |
Source code in src/pytest_gremlins/operators/boundary.py
Mutations¶
For each integer constant in a comparison:
| Original | Mutations |
|---|---|
n |
n - 1, n + 1 |
Example¶
# Original code
if age >= 18:
return "adult"
# Mutations generated:
# 1. if age >= 17: (18 to 17)
# 2. if age >= 19: (18 to 19)
Note
BoundaryOperator only targets integer constants within comparison expressions.
Boolean values (True/False) are excluded.
ReturnOperator¶
Mutates return statements to verify tests check return values.
ReturnOperator
¶
Mutate return statements.
Generates mutations for return statements to verify that tests actually check return values.
Mutations
- return x -> return None
- return True -> return False
- return False -> return True
can_mutate
¶
Return True if this operator can mutate the given AST node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this is a return statement with a non-None value. |
Source code in src/pytest_gremlins/operators/return_value.py
mutate
¶
Return all mutated variants of this node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node
|
AST
|
The AST node to mutate. |
required |
Returns:
| Type | Description |
|---|---|
list[AST]
|
List of mutated AST nodes. |
Source code in src/pytest_gremlins/operators/return_value.py
Mutations¶
| Original | Mutations |
|---|---|
return x |
return None |
return True |
return False |
return False |
return True |
Example¶
# Original code
def is_valid(data):
return True
# Mutations generated:
# 1. return None (value to None)
# 2. return False (True to False)
Operator Selection¶
Via CLI¶
# Use all operators (default)
pytest --gremlins
# Use specific operators
pytest --gremlins --gremlin-operators=comparison,boundary
# Use single operator
pytest --gremlins --gremlin-operators=arithmetic
Via pyproject.toml¶
Programmatically¶
from pytest_gremlins.instrumentation.transformer import get_default_registry
# Get the default registry with all 5 operators
registry = get_default_registry()
# Get specific operators
ops = registry.get_all(enabled=['comparison', 'boundary'])
Operator Statistics¶
The default operators generate mutations as follows:
| Operator | AST Nodes | Mutations per Node |
|---|---|---|
| comparison | Compare |
1-2 per operator |
| arithmetic | BinOp |
1 |
| boolean | BoolOp, UnaryOp, Constant |
1 |
| boundary | Compare with int constants |
2 per constant |
| return | Return |
1-2 |
A typical function with 10 lines might generate 5-15 gremlins depending on the code patterns present.