Summary
For any non-PHPUnit test framework adapter, Factory::create() unconditionally
calls TestFrameworkConfigLocator::locate() and passes the result into the
adapter factory. If the project has no <framework>.{xml,yml,xml.dist,...} file,
locate() throws FileOrDirectoryNotFound and mutation testing aborts before
the adapter is ever built — even for adapters that don't consume a config file
at all.
This forces users of such adapters to commit a dummy placeholder config file
purely to satisfy the lookup.
Environment
- infection/infection: 0.32.6
- Adapter: testo/bridge-infection 0.1.8 (any third-party adapter that ignores
$testFrameworkConfigPath reproduces this)
- PHP: 8.2
Steps to reproduce
- Use a third-party adapter whose factory does not read the config path
(e.g. testo/bridge-infection; its TestoAdapter never receives or uses
$testFrameworkConfigPath).
- Do not add any
testo.{xml,yml,xml.dist,yml.dist,dist.xml,dist.yml} file.
- Run Infection.
Expected
The adapter is built and mutation testing runs, since the adapter does not
depend on a framework config file.
Actual
Infection throws FileOrDirectoryNotFound from
TestFrameworkConfigLocator::locate() and never reaches the adapter factory.
src/TestFramework/Factory.php, in the third-party adapter branch:
Root cause
if ($adapterName === $factory::getAdapterName()) {
$configuration = $this->infectionConfig;
return $factory::create(
$this->testFrameworkFinder->find($factory::getExecutableName()),
$this->tmpDir,
$this->configLocator->locate($factory::getAdapterName()), // <-- throws
null,
$this->jUnitFilePath,
$this->projectDir,
$configuration->source->directories,
$skipCoverage,
);
}
locate() is mandatory here, but the resolved path is optional from the
adapter's perspective — the TestFrameworkAdapterFactory::create() contract
accepts the path but does not require the file to exist or to be meaningful.
Possible solution
Tolerate a missing config for third-party adapters and pass an empty string
(the signature requires a non-nullable string, so null is not an option):
use Infection\FileSystem\Locator\FileOrDirectoryNotFound;
if ($adapterName === $factory::getAdapterName()) {
$configuration = $this->infectionConfig;
try {
$configPath = $this->configLocator->locate($factory::getAdapterName());
} catch (FileOrDirectoryNotFound) {
// Third-party adapters may not consume a framework config file at
// all; tolerate its absence instead of aborting.
$configPath = '';
}
return $factory::create(
$this->testFrameworkFinder->find($factory::getExecutableName()),
$this->tmpDir,
$configPath,
null,
$this->jUnitFilePath,
$this->projectDir,
$configuration->source->directories,
$skipCoverage,
);
}
The PHPUnit branch is unaffected and should keep requiring its config, so this
change is scoped to third-party adapters only.
(You may well prefer a different design — e.g. letting the adapter factory
declare whether it needs a config file, or exposing an "optional config" flag
on the adapter interface. The snippet above is just the minimal change that
resolves it for us.)
Current workaround
I commit an empty placeholder testo.xml.dist (<testo/>) whose sole purpose
is to satisfy the locator. It works, but it's a confusing artifact for anyone
reading the repo, since nothing actually reads it.
Summary
For any non-PHPUnit test framework adapter,
Factory::create()unconditionallycalls
TestFrameworkConfigLocator::locate()and passes the result into theadapter factory. If the project has no
<framework>.{xml,yml,xml.dist,...}file,locate()throwsFileOrDirectoryNotFoundand mutation testing aborts beforethe adapter is ever built — even for adapters that don't consume a config file
at all.
This forces users of such adapters to commit a dummy placeholder config file
purely to satisfy the lookup.
Environment
$testFrameworkConfigPathreproduces this)Steps to reproduce
(e.g.
testo/bridge-infection; itsTestoAdapternever receives or uses$testFrameworkConfigPath).testo.{xml,yml,xml.dist,yml.dist,dist.xml,dist.yml}file.Expected
The adapter is built and mutation testing runs, since the adapter does not
depend on a framework config file.
Actual
Infection throws
FileOrDirectoryNotFoundfromTestFrameworkConfigLocator::locate()and never reaches the adapter factory.src/TestFramework/Factory.php, in the third-party adapter branch:Root cause
locate()is mandatory here, but the resolved path is optional from theadapter's perspective — the
TestFrameworkAdapterFactory::create()contractaccepts the path but does not require the file to exist or to be meaningful.
Possible solution
Tolerate a missing config for third-party adapters and pass an empty string
(the signature requires a non-nullable
string, sonullis not an option):The PHPUnit branch is unaffected and should keep requiring its config, so this
change is scoped to third-party adapters only.
(You may well prefer a different design — e.g. letting the adapter factory
declare whether it needs a config file, or exposing an "optional config" flag
on the adapter interface. The snippet above is just the minimal change that
resolves it for us.)
Current workaround
I commit an empty placeholder
testo.xml.dist(<testo/>) whose sole purposeis to satisfy the locator. It works, but it's a confusing artifact for anyone
reading the repo, since nothing actually reads it.