Skip to content

Third-party test framework adapters are required to have a config file, even when they never read one #372

Description

@dragomano

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

  1. 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).
  2. Do not add any testo.{xml,yml,xml.dist,yml.dist,dist.xml,dist.yml} file.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    blockedBlocked by an external dependency; deferred until it is resolved

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions