Skip to content

Mutation testing: count uncovered code (--with-uncovered) - #168

Merged
rasuvaeff merged 3 commits into
masterfrom
test/mutation-with-uncovered
Nov 3, 2026
Merged

rasuvaeff merged 3 commits into
masterfrom
test/mutation-with-uncovered

Conversation

@rasuvaeff

@rasuvaeff rasuvaeff commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

What

composer mutation now runs infection --threads=max --with-uncovered (template command, Composer\Config::disableProcessTimeout, testFrameworkOptions: --suite=Unit, signature-default ignore regex, PublicVisibility/ProtectedVisibility off). minMsi stays at 92.

Numbers (PHP 8.4, pcov, zend.assertions=-1, --with-uncovered)

before this PR (flag on) after
mutants 4016 4016
killed + errored + timed out 3377 / 4021 = 84% 3767 / 4016 = 93.8% (94.1% without the 14 ignored)
Escaped 240 173
Not Covered 398 62
Ignored 6 14
Timed Out 26 1-3
Covered Code MSI 93% 95%

(The flag-less figure for the same command was 3093 mutants / 94.1% Covered Code MSI.)

What changed

  • src/ (behaviour-neutral, same text and same results):
    • failure messages assembled from several literals are built into a local $format/$reason before use. With the literals as call arguments the compiler folds them and the operand mutants sit on a line with no opcode, so they were Not Covered whatever the tests did;
    • six one-line signatures with a defaulted parameter are split one parameter per line, so the template's ignore pattern applies;
    • Expectation::describeGuardAgainst() and TypeRenderer::resolveParent() hold an operand in a local for the same reason.
  • Tests:
    • ExceptionMessageTest: every factory's message, word for word (61 cases; builds inside the test body, because a data provider executes nothing under coverage);
    • BypassFinalsTest, FinalReasonTest: Understudy::bypassFinals(), BypassUnavailable and the four explanations for a standing final run in-process, with the file:// wrapper installed and removed inside each test (the same global FileWrapperTest has always toggled). testo.php is untouched, so understudy#148 does not apply;
    • direct cases for default rendering (DefaultSourceRenderingTest, UnreproducibleDefaultTest), the parent keyword (ParentKeywordTest), intersection overlap, wire() edge types, TypeDefaultResolver type strings, DefaultFactories ancestry, the by-reference snapshot cap, Expectation::describeAgainst(), Outcome, tail matchers;
    • #[Covers] for the Codegen classes on the tests that compile doubles (Infection maps mutants to tests by #[Covers] only).

What cannot be killed, and why

  • about 20 Not Covered mutants sit inside \assert(), which CI compiles out;
  • defensive branches no input reaches (Arg::reason(null), renderConstantDefault() without a declaring class, foreignFileWrapper() without a source file, file_get_contents() === false after is_file());
  • equivalents, listed in AGENTS.md (DoubleState literal-key index, FinalStripper fast paths and loop bounds, ?-> on values never null, cache keys).

Stability

Three local runs and two CI runs of Coverage & Mutation are recorded in a comment below.

Unrelated to mutations

The commit "Apply rector 2.8.4 ExplicitNullableObjectInstanceRector" is dependency drift, not part of the mutation work: Rector 2.8.4 appeared after the last green master and turned the Psalm job's composer rector step red. The rewrite is === null / !== null to instanceof on ?ReflectionClass, ?ReflectionMethod, ?ReflectionType and ReflectionClass|false|null values in TypeRenderer, Wire and TargetUnifier; behaviour is the same.

The commit "Make the message expectations independent of the checkout's line endings" fixes the Windows job: the nowdoc expectations in ExceptionMessageTest become CRLF on a converting checkout while the messages carry LF.

…re messages

Run Infection with --with-uncovered (template command, signature-default and
visibility mutators as in the template). Overall MSI with the flag is about
94% on PHP 8.4 against the unchanged gate of 92.

Tests added: exact wording of every failure-message factory, in-process
coverage of bypassFinals(), BypassUnavailable and the explanations for a final
class that is still standing, and direct cases for default rendering, the
parent keyword, intersection overlap, wire() edge types, TypeDefaultResolver
type strings, DefaultFactories ancestry and the by-reference snapshot cap.
Codegen classes are named in #[Covers] by the tests that compile doubles.

src/ changes are behaviour-neutral: messages assembled from several literals
are built into a local before use, and six one-line signatures with a default
are split one parameter per line.
@rasuvaeff rasuvaeff added the enhancement New feature or request label Nov 3, 2026
@rasuvaeff rasuvaeff self-assigned this Nov 3, 2026
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: af70a088-377f-42a1-8904-f19aeb618084
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@rasuvaeff
rasuvaeff merged commit 79ab359 into master Nov 3, 2026
25 checks passed
@rasuvaeff
rasuvaeff deleted the test/mutation-with-uncovered branch November 3, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant