F
FANG
your tests pass. do they bite?
repository root
all · 2026-10-29T04:28:21+00:00
23 tests · 0.22s suite

73.3% mutation score

Scope: all files · 403 mutants generated · 23 tests collected

73.3%
B · decent
killed
44
mutants your tests noticed
survived
16
behaviour changes left unchecked
mutants
60
of 403 generated
run time
5.7s
0.22s baseline

Showing 16 mutants. Press / to search, Esc to reset.

fang/mutators.py:144 _MutationFactory._add return survived
- return False+ return None

`return False` -> `return None`

Assert the return value; calling the function is not enough.

0.28s
fang/mutators.py:149 _MutationFactory._add return survived
- return False+ return None

`return False` -> `return None`

Assert the return value; calling the function is not enough.

0.26s
fang/mutators.py:156 _MutationFactory._add constant survived
- compile(candidate, self.sf.path, "exec", dont_inherit=True)+ compile(candidate, self.sf.path, "exec", dont_inherit=False)

`True` -> `False`

Assert against a different value so this constant is observable.

0.25s
fang/mutators.py:158 _MutationFactory._add constant survived
- return False+ return True

`False` -> `True`

Assert against a different value so this constant is observable.

0.25s
fang/mutators.py:268 _MutationFactory.visit_Assign constant survived
- if isinstance(target, ast.Name) and target.id.startswith("__") and target.id.endswith("__"):+ if isinstance(target, ast.Name) and target.id.startswith("__") and target.id.endswith(""):

string '__' -> ""

Assert against a different value so this constant is observable.

0.27s
fang/mutators.py:276 _MutationFactory.visit_AnnAssign statement survived
- self.visit(node.value)+ pass

remove `self.visit(node.value)`

Assert the side effect of this call (state, output, or log).

0.28s
fang/mutators.py:283 _MutationFactory._is_main_guard constant survived
- return False+ return True

`False` -> `True`

Assert against a different value so this constant is observable.

0.25s
fang/mutators.py:320 _MutationFactory._consider_condition constant survived
- "negate condition `%s`" % truncate(source, 64),+ "negate condition `%s`" % truncate(source, 65),

`64` -> `65`

Assert against a different value so this constant is observable.

0.27s
fang/mutators.py:355 _MutationFactory.visit_BoolOp arithmetic survived
- found = self._token_between(node.values[index], node.values[index + 1], _BOOL_PATTERN)+ found = self._token_between(node.values[index], node.values[index - 1], _BOOL_PATTERN)

`+` -> `-` in `index + 1`

Assert the exact numeric result, not just its type.

0.28s
fang/mutators.py:415 _MutationFactory.visit_UnaryOp constant survived
- "remove negation: `%s` -> `%s`" % (truncate(self.sf.segment(node), 48), truncate(operand, 48)),+ "remove negation: `%s` -> `%s`" % (truncate(self.sf.segment(node), 48), truncate(operand, 49)),

`48` -> `49`

Assert against a different value so this constant is observable.

0.28s
fang/mutators.py:426 _MutationFactory.visit_UnaryOp constant survived
- "remove `not`: `%s`" % truncate(operand, 64),+ "remove `not`: `%s`" % truncate(operand, 65),

`64` -> `65`

Assert against a different value so this constant is observable.

0.28s
fang/mutators.py:428 _MutationFactory.visit_UnaryOp statement survived
- self.generic_visit(node)+ pass

remove `self.generic_visit(node)`

Assert the side effect of this call (state, output, or log).

0.30s
fang/mutators.py:438 _MutationFactory.visit_Return constant survived
- source = truncate(self.sf.text[start:end], 64)+ source = truncate(self.sf.text[start:end], 65)

`64` -> `65`

Assert against a different value so this constant is observable.

0.27s
fang/mutators.py:453 _MutationFactory.visit_Constant boolean survived
- if value is None or value is Ellipsis:+ if value is None and value is Ellipsis:

`or` -> `and` in `value is None or value is Ellipsis`

Cover a case where only one operand is true.

0.27s
fang/mutators.py:459 _MutationFactory.visit_Constant comparison survived
- new_value = 1 if value == 0 else (0 if value == 1 else value + 1)+ new_value = 1 if value == 0 else (0 if value != 1 else value + 1)

`==` -> `!=` in `value == 1`

Add a test at the boundary where this comparison flips.

0.28s
fang/mutators.py:500 _short_repr constant survived
- if len(text) > 32:+ if len(text) > 33:

`32` -> `33`

Assert against a different value so this constant is observable.

0.26s
fang/mutators.py:18 module constant killed
- "statement": "statement removal",+ "": "statement removal",

string 'statement' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_multiline_statement_removal 0.26s
fang/mutators.py:25 module constant killed
- ast.NotEq: "!=",+ ast.NotEq: "",

string '!=' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_comparison_replacement 0.28s
fang/mutators.py:29 module constant killed
- ast.GtE: ">=",+ ast.GtE: "",

string '>=' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_comparison_operator 0.26s
fang/mutators.py:31 module constant killed
- ast.IsNot: "is not",+ ast.IsNot: "",

string 'is not' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_comparison_operator 0.27s
fang/mutators.py:39 module constant killed
- "<": "<=",+ "": "<=",

string '<' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_comparison_replacement 0.28s
fang/mutators.py:43 module constant killed
- "is": "is not",+ "is": "",

string 'is not' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_comparison_operator 0.28s
fang/mutators.py:44 module constant killed
- "is not": "is",+ "": "is",

string 'is not' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_comparison_operator 0.25s
fang/mutators.py:50 module constant killed
- ast.Add: ("+", "-"),+ ast.Add: ("+", ""),

string '-' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_arithmetic_operator 0.25s
fang/mutators.py:59 module constant killed
- ast.Sub: ("-=", "+="),+ ast.Sub: ("", "+="),

string '-=' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_arithmetic_operator 0.27s
fang/mutators.py:61 module constant killed
- ast.Div: ("/=", "*="),+ ast.Div: ("", "*="),

string '/=' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_arithmetic_operator 0.25s
fang/mutators.py:61 module constant killed
- ast.Div: ("/=", "*="),+ ast.Div: ("/=", ""),

string '*=' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_every_arithmetic_operator 0.27s
fang/mutators.py:66 module constant killed
- r"\bnot\s+in\b|\bis\s+not\b|==|!=|<=|>=|<|>|\bin\b|\bis\b"+ ""

string '\\bnot\\s+in\\b|\\bis\\s+not... -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_mutated_line 0.25s
fang/mutators.py:71 module constant killed
- "comparison": "Add a test at the boundary where this comparison flips.",+ "comparison": "",

string 'Add a test at the boundary w... -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_descriptions_and_hints_are_present 0.27s
fang/mutators.py:75 module constant killed
- "return": "Assert the return value; calling the function is not enough.",+ "": "Assert the return value; calling the function is not enough.",

string 'return' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_descriptions_and_hints_are_present 0.25s
fang/mutators.py:112 _MutationFactory._collect_docstrings boolean killed
- if isinstance(node, holders) and node.body:+ if isinstance(node, holders) or node.body:

`and` -> `or` in `isinstance(node, holders) and node.body`

Cover a case where only one operand is true.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.20s
fang/mutators.py:115 _MutationFactory._collect_docstrings conditional killed
- isinstance(first, ast.Expr)
                    and isinstance(first.value, ast.Constant)
                    and isinstance(first.value.value, str)+ not (isinstance(first, ast.Expr)
                    and isinstance(first.value, ast.Constant)
                    and isinstance(first.value.value, str))

negate condition `isinstance(first, ast.Expr) and isinstance(first.value, ast.C...`

Test the branch this condition guards, not only the happy path.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.22s
fang/mutators.py:122 _MutationFactory._context_name constant killed
- return ".".join(self.context)+ return "".join(self.context)

string '.' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_context_is_dotted 0.25s
fang/mutators.py:125 _MutationFactory._in_scope comparison killed
- if self.only_lines is None:+ if self.only_lines is not None:

`is` -> `is not` in `self.only_lines is None`

Add a test at the boundary where this comparison flips.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.20s
fang/mutators.py:129 _MutationFactory._in_scope return killed
- return any(line in self.only_lines for line in range(first, last + 1))+ return None

`return any(line in self.only_lines for line in range(first, l...` -> `return None`

Assert the return value; calling the function is not enough.

killed by tests.test_mutators.TestMutators.test_only_lines_filters 0.28s
fang/mutators.py:169 _MutationFactory._add conditional killed
- if line == end_line:+ if not (line == end_line):

negate condition `line == end_line`

Test the branch this condition guards, not only the happy path.

killed by tests.test_mutators.TestMutators.test_mutated_line 0.27s
fang/mutators.py:172 _MutationFactory._add arithmetic killed
- + replacement+ - replacement

`+` -> `-` in `source_line[: col - 1] + replacement`

Assert the exact numeric result, not just its type.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.22s
fang/mutators.py:227 _MutationFactory._find_text_between arithmetic killed
- return start + index, start + index + len(needle), needle+ return start - index, start + index + len(needle), needle

`+` -> `-` in `start + index`

Assert the exact numeric result, not just its type.

killed by tests.test_mutators.TestMutators.test_every_arithmetic_operator 0.28s
fang/mutators.py:281 _MutationFactory._is_main_guard constant killed
- return False+ return True

`False` -> `True`

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_mutated_line 0.25s
fang/mutators.py:285 _MutationFactory._is_main_guard comparison killed
- if isinstance(op, ast.Eq) and isinstance(comparator, ast.Constant) and comparator.value == "__main__":+ if isinstance(op, ast.Eq) and isinstance(comparator, ast.Constant) and comparator.value != "__main__":

`==` -> `!=` in `comparator.value == "__main__"`

Add a test at the boundary where this comparison flips.

killed by tests.test_mutators.TestMutators.test_skips_docstrings_main_guard_and_imports 0.28s
fang/mutators.py:286 _MutationFactory._is_main_guard constant killed
- return True+ return False

`True` -> `False`

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_skips_docstrings_main_guard_and_imports 0.28s
fang/mutators.py:320 _MutationFactory._consider_condition constant killed
- "negate condition `%s`" % truncate(source, 64),+ "" % truncate(source, 64),

string 'negate condition `%s`' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.22s
fang/mutators.py:330 _MutationFactory.visit_Compare constant killed
- found = self._token_between(operands[index], operands[index + 1], _COMPARE_PATTERN)+ found = self._token_between(operands[index], operands[index + 0], _COMPARE_PATTERN)

`1` -> `0`

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_mutated_line 0.27s
fang/mutators.py:337 _MutationFactory.visit_Compare comparison killed
- if replacement is None:+ if replacement is not None:

`is` -> `is not` in `replacement is None`

Add a test at the boundary where this comparison flips.

killed by tests.test_mutators.TestMutators.test_mutated_line 0.26s
fang/mutators.py:339 _MutationFactory.visit_Compare statement killed
- self._add(
                    "comparison",
                    start,
                    end,
                    replacement,
                    node,
                    "`%s` -> `%s` in `%s`"
                    % (token, replacement, truncate(self.sf.segment(node), 64)),
                )+ pass

remove `self._add( "comparison", start, end, replacement, node, "`%s`...`

Assert the side effect of this call (state, output, or log).

killed by tests.test_mutators.TestMutators.test_mutated_line 0.27s
fang/mutators.py:351 _MutationFactory.visit_BoolOp comparison killed
- if "boolean" in self.operators:+ if "boolean" not in self.operators:

`in` -> `not in` in `"boolean" in self.operators`

Add a test at the boundary where this comparison flips.

killed by tests.test_mutators.TestMutators.test_boolean_operators 0.25s
fang/mutators.py:353 _MutationFactory.visit_BoolOp constant killed
- replacement = "or" if token == "and" else "and"+ replacement = "" if token == "and" else "and"

string 'or' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_boolean_operators 0.28s
fang/mutators.py:361 _MutationFactory.visit_BoolOp statement killed
- self._add(
                    "boolean",
                    start,
                    end,
                    replacement,
                    node,
                    "`%s` -> `%s` in `%s`"
                    % (token, replacement, truncate(self.sf.segment(node), 64)),
                )+ pass

remove `self._add( "boolean", start, end, replacement, node, "`%s` ->...`

Assert the side effect of this call (state, output, or log).

killed by tests.test_mutators.TestMutators.test_boolean_operators 0.28s
fang/mutators.py:376 _MutationFactory.visit_BinOp conditional killed
- if found:+ if not (found):

negate condition `found`

Test the branch this condition guards, not only the happy path.

killed by tests.test_mutators.TestMutators.test_every_arithmetic_operator 0.27s
fang/mutators.py:387 _MutationFactory.visit_BinOp statement killed
- self.generic_visit(node)+ pass

remove `self.generic_visit(node)`

Assert the side effect of this call (state, output, or log).

killed by tests.test_mutators.TestMutators.test_every_arithmetic_operator 0.25s
fang/mutators.py:406 _MutationFactory.visit_UnaryOp boolean killed
- if isinstance(node.op, ast.USub) and "arithmetic" in self.operators:+ if isinstance(node.op, ast.USub) or "arithmetic" in self.operators:

`and` -> `or` in `isinstance(node.op, ast.USub) and "arithmetic" in self.operators`

Cover a case where only one operand is true.

killed by tests.test_mutators.TestMutators.test_boolean_operators 0.28s
fang/mutators.py:417 _MutationFactory.visit_UnaryOp constant killed
- elif isinstance(node.op, ast.Not) and "boolean" in self.operators:+ elif isinstance(node.op, ast.Not) and "" in self.operators:

string 'boolean' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_boolean_operators 0.26s
fang/mutators.py:433 _MutationFactory.visit_Return boolean killed
- if isinstance(node.value, ast.Constant) and node.value.value is None:+ if isinstance(node.value, ast.Constant) or node.value.value is None:

`and` -> `or` in `isinstance(node.value, ast.Constant) and node.value.value is ...`

Cover a case where only one operand is true.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.25s
fang/mutators.py:435 _MutationFactory.visit_Return boolean killed
- if self.context and self.context[-1] == "__init__":+ if self.context or self.context[-1] == "__init__":

`and` -> `or` in `self.context and self.context[-1] == "__init__"`

Cover a case where only one operand is true.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.25s
fang/mutators.py:440 _MutationFactory.visit_Return constant killed
- "return",+ "",

string 'return' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_operators_present 0.24s
fang/mutators.py:445 _MutationFactory.visit_Return constant killed
- "`%s` -> `return None`" % source,+ "" % source,

string '`%s` -> `return None`' -> ""

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_all_mutants_compile 0.24s
fang/mutators.py:450 _MutationFactory.visit_Constant conditional killed
- if "constant" not in self.operators or id(node) in self.docstrings:+ if not ("constant" not in self.operators or id(node) in self.docstrings):

negate condition `"constant" not in self.operators or id(node) in self.docstrings`

Test the branch this condition guards, not only the happy path.

killed by tests.test_mutators.TestMutators.test_empty_string_and_bytes_constants 0.25s
fang/mutators.py:453 _MutationFactory.visit_Constant conditional killed
- if value is None or value is Ellipsis:+ if not (value is None or value is Ellipsis):

negate condition `value is None or value is Ellipsis`

Test the branch this condition guards, not only the happy path.

killed by tests.test_mutators.TestMutators.test_empty_string_and_bytes_constants 0.27s
fang/mutators.py:463 _MutationFactory.visit_Constant constant killed
- new_value = 1.0 if value == 0.0 else value + 1.0+ new_value = 2.0 if value == 0.0 else value + 1.0

`1.0` -> `2.0`

Assert against a different value so this constant is observable.

killed by tests.test_mutators.TestMutators.test_empty_string_and_bytes_constants 0.26s
fang/mutators.py:484 _MutationFactory.visit_Expr conditional killed
- if name.lower() not in self.skip_calls:+ if not (name.lower() not in self.skip_calls):

negate condition `name.lower() not in self.skip_calls`

Test the branch this condition guards, not only the happy path.

killed by tests.test_mutators.TestMutators.test_multiline_statement_removal 0.23s

Files

filekilledsurvivedscore
fang/mutators.py441673%

Notes

sampled 60 of 403 mutants (seed 0); score is an estimate

How to read this report

Mutation score is the share of generated mutants your suite detected. A mutant is a small, syntactically valid change to your code: a flipped comparison, a changed constant, a removed return value. If the suite still passes, the mutant survived, which means no test observed that behaviour.

Survivors are not automatically bugs. Some mutants are equivalent (the change cannot alter observable behaviour, such as changing the rounding precision of a value that is already exact). Treat survivors as questions, not verdicts. The hint on each card suggests the kind of assertion that would settle it.

Fang only mutates the lines in scope, runs the suite with fail-fast, and treats a timeout or a crash caused by the mutant as detection. A green baseline is required; a red suite cannot distinguish a surviving mutant from a pre-existing failure.

Sampled 60 of 403 mutants. The score is an estimate; the 95% Wilson interval is 61.0% to 82.9%.