fix(codecov,junit): attribute inherited tests to the concrete class - #231
Merged
Conversation
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
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.
Merged
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:<covered by="Abstract\Base::method"><testcase classname="Abstract\Base">…while the surrounding JUnit
<testsuite>is named after the concrete subclass.Both
CoverageTestInterceptor::buildMethodId()andJUnitWriter::classnameFor()now use the concrete (runtime) case class (caseInfo->definition->reflection) instead ofReflectionMethod::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 threwTestFileNameNotFoundExceptionfor 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+ finalIntCalculatorTest) and verified against Infection's ownJUnitTestFileDataProviderlookup:TestFileNameNotFoundException— no<testsuite name="...AbstractCalculatorTest">.//testsuite[@name="...IntCalculatorTest"]→IntCalculatorTest.php.Bonus: coverage no longer collapses several subclasses into a single
Abstract::methodentry — each concrete subclass is attributed distinctly.Checklist