sebastianbergmann/phpunit · error · PHPUnit\TextUI\RuntimeException

Cannot read from %s

Error message

Cannot read from %s

What it means

When --test-file-list is used, TestSuiteBuilder::build() first checks is_file() on the list file itself; if the path is not an existing regular file (missing, or a directory), it throws RuntimeException('Cannot read from ...'). The list file must be a plain text file containing one test file path per line.

Source

Thrown at src/TextUI/Configuration/TestSuiteBuilder.php:86

        if ($configuration->hasCliArguments() || $configuration->hasTestFilesFile()) {
            $arguments = [];

            if ($configuration->hasCliArguments()) {
                foreach ($configuration->cliArguments() as $cliArgument) {
                    $argument = realpath($cliArgument);

                    if ($argument === false) {
                        throw new TestFileNotFoundException($cliArgument);
                    }

                    $arguments[] = $argument;
                }
            }

            if ($configuration->hasTestFilesFile()) {
                if (!is_file($configuration->testFilesFile())) {
                    throw new RuntimeException('Cannot read from ' . $configuration->testFilesFile());
                }

                $directory = dirname($configuration->testFilesFile()) . DIRECTORY_SEPARATOR;

                $fileLines = file($configuration->testFilesFile());

                // @codeCoverageIgnoreStart
                if ($fileLines === false) {
                    throw new RuntimeException('Cannot read from ' . $configuration->testFilesFile());
                }
                // @codeCoverageIgnoreEnd

                foreach ($fileLines as $file) {
                    $file     = trim($file);
                    $argument = realpath($file);

                    if ($argument === false) {
                        $argument = realpath($directory . $file);

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Point --test-file-list at an existing text file with one test path per line
  2. Make the generating step fail loudly and verify the file exists before invoking phpunit: `test -s changed-tests.txt`
  3. Use an absolute path in CI to avoid cwd surprises

Example fix

# before (directory passed by mistake)
vendor/bin/phpunit --test-file-list=tests/

# after
git diff --name-only main -- 'tests/*Test.php' > changed-tests.txt
vendor/bin/phpunit --test-file-list=changed-tests.txt
Defensive patterns

Strategy: validation

Validate before calling

$listFile = 'changed-tests.txt';

if (!is_file($listFile) || !is_readable($listFile)) {
    fwrite(STDERR, "--test-file-list target missing or unreadable: {$listFile}" . PHP_EOL);
    exit(2);
}

// shell: test -s "$list" || { echo "list generation failed"; exit 2; }

Try / catch

try {
    (new \PHPUnit\TextUI\Configuration\TestSuiteBuilder)->build($configuration);
} catch (\RuntimeException $e) {
    if (str_starts_with($e->getMessage(), 'Cannot read from ')) {
        // the --test-file-list path is not a readable regular file; fix the path/generation step
    }
}

Prevention

When it happens

Trigger: `vendor/bin/phpunit --test-file-list=tests/` (passing a directory instead of a file); a typo'd path; the list file was supposed to be generated by a previous pipeline step that failed silently; empty CI variable expanding to a bare or wrong path.

Common situations: Assuming the option accepts a directory; CI artifacts (the list) not uploaded between jobs; path relative to a different cwd.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/ceb47bab6565474b. Report an issue: GitHub.