Skip to content

fix(codecov,junit): attribute inherited tests to the concrete class - #231

Merged
roxblnfk merged 1 commit into
1.xfrom
fix/junit-abstract-test-classname
Jun 21, 2026
Merged

fix(codecov,junit): attribute inherited tests to the concrete class#231
roxblnfk merged 1 commit into
1.xfrom
fix/junit-abstract-test-classname

Conversation

@roxblnfk

Copy link
Copy Markdown
Member

What was changed

A #[Test] method declared on an abstract base class and run through a concrete subclass was identified by its declaring (abstract) class in two reports:

  • coverage XML — <covered by="Abstract\Base::method">
  • JUnit XML — <testcase classname="Abstract\Base">

…while the surrounding JUnit <testsuite> is named after the concrete subclass.

Both CoverageTestInterceptor::buildMethodId() and JUnitWriter::classnameFor() now use the concrete (runtime) case class (caseInfo->definition->reflection) instead of ReflectionMethod::getDeclaringClass(), with a fallback to the declaring class when no case reflection is available (free functions are unaffected).

Added regression tests + stubs (abstract base + concrete child) in both modules.

See commit history for details.

Why?

Infection joins coverage to a test file by matching the covered class against //testsuite[@name="FQN"] in the JUnit report. Because coverage named the abstract base but the JUnit suite named the concrete subclass, the lookup never resolved and Infection threw TestFileNameNotFoundException for every mutant covered only by an inherited test — breaking mutation runs on any codebase that uses an abstract test class with final subclasses.

Reproduced end-to-end (abstract AbstractCalculatorTest + final IntCalculatorTest) and verified against Infection's own JUnitTestFileDataProvider lookup:

  • Before: TestFileNameNotFoundException — no <testsuite name="...AbstractCalculatorTest">.
  • After: resolves via //testsuite[@name="...IntCalculatorTest"]IntCalculatorTest.php.

Bonus: coverage no longer collapses several subclasses into a single Abstract::method entry — each concrete subclass is attributed distinctly.

Checklist

  • Closes #
  • Tested
    • Tested manually
    • Unit tests added
  • Documentation

A #[Test] method declared on an abstract base and run through a subclass
was identified by its declaring (abstract) class in two reports:
- coverage XML `<covered by="Abstract::method">`
- JUnit `<testcase classname="Abstract">`

while the JUnit `<testsuite>` is named after the concrete subclass.
Infection joins coverage to a test file by matching the covered class
against `//testsuite[@name="FQN"]`, so the abstract name never resolved
and it threw TestFileNameNotFoundException for every mutant covered only
by an inherited test.

Use the concrete (runtime) case class instead of getDeclaringClass() in
both CoverageTestInterceptor::buildMethodId() and JUnitWriter::classnameFor().
This also stops coverage from collapsing several subclasses into one
Abstract::method entry.
@roxblnfk
roxblnfk requested a review from a team as a code owner June 21, 2026 13:30
@roxblnfk
roxblnfk merged commit dd29e63 into 1.x Jun 21, 2026
20 checks passed
@roxblnfk
roxblnfk deleted the fix/junit-abstract-test-classname branch June 21, 2026 13:49
roxblnfk added a commit that referenced this pull request Jun 21, 2026
A #[Test] method declared on an abstract base and run through a subclass
was identified by its declaring (abstract) class in two reports:
- coverage XML `<covered by="Abstract::method">`
- JUnit `<testcase classname="Abstract">`

while the JUnit `<testsuite>` is named after the concrete subclass.
Infection joins coverage to a test file by matching the covered class
against `//testsuite[@name="FQN"]`, so the abstract name never resolved
and it threw TestFileNameNotFoundException for every mutant covered only
by an inherited test.

Use the concrete (runtime) case class instead of getDeclaringClass() in
both CoverageTestInterceptor::buildMethodId() and JUnitWriter::classnameFor().
This also stops coverage from collapsing several subclasses into one
Abstract::method entry.
@roxblnfk roxblnfk mentioned this pull request Jun 21, 2026
roxblnfk added a commit that referenced this pull request Jun 24, 2026
A #[Test] method inherited from an abstract base reported its declaring (abstract) class in the testStarted locationHint, while the enclosing testSuite is named after the concrete subclass. TeamCity then filed the inherited test under the abstract class instead of the subclass that actually runs it.

testLocationHint() now anchors on the concrete case class (name and file) from the case reflection, falling back to the method's declaring class only when no case reflection is available. Mirrors the JUnit/coverage fix in #231.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant