Merge branch '4.x' into remove-language-option

This commit is contained in:
nuno maduro
2025-07-14 10:37:08 +01:00
committed by GitHub
22 changed files with 470 additions and 60 deletions

View File

@ -27,6 +27,7 @@ jobs:
php-version: 8.3
tools: composer:v2
coverage: none
extensions: sockets
- name: Install Dependencies
run: composer update --prefer-stable --no-interaction --no-progress --ansi

View File

@ -13,7 +13,7 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
symfony: ['7.1']
symfony: ['7.3']
php: ['8.3', '8.4']
dependency_version: [prefer-lowest, prefer-stable]
@ -29,6 +29,7 @@ jobs:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
extensions: sockets
- name: Setup Problem Matches
run: |

View File

@ -18,18 +18,19 @@
],
"require": {
"php": "^8.3.0",
"brianium/paratest": "^7.10.2",
"nunomaduro/collision": "^8.8.1",
"brianium/paratest": "^7.10.3",
"nunomaduro/collision": "^8.8.2",
"nunomaduro/termwind": "^2.3.1",
"pestphp/pest-plugin": "^4.0.0",
"pestphp/pest-plugin-arch": "^4.0.0",
"pestphp/pest-plugin-mutate": "^4.0.0",
"pestphp/pest-plugin-profanity": "^4.0.0",
"phpunit/phpunit": "^12.2.1"
"phpunit/phpunit": "^12.2.6",
"symfony/process": "^7.3.0"
},
"conflict": {
"filp/whoops": "<2.16.0",
"phpunit/phpunit": ">12.2.1",
"filp/whoops": "<2.18.3",
"phpunit/phpunit": ">12.2.6",
"sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0"
},
@ -55,8 +56,8 @@
},
"require-dev": {
"pestphp/pest-dev-tools": "^4.0.0",
"pestphp/pest-plugin-type-coverage": "^4.0.0",
"symfony/process": "^7.3.0"
"pestphp/pest-plugin-browser": "^4.0.0",
"pestphp/pest-plugin-type-coverage": "^4.0.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
@ -72,9 +73,9 @@
],
"scripts": {
"refacto": "rector",
"lint": "pint",
"lint": "pint --parallel",
"test:refacto": "rector --dry-run",
"test:lint": "pint --test",
"test:lint": "pint --parallel --test",
"test:profanity": "php bin/pest --profanity --compact",
"test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug",
"test:type:coverage": "php -d memory_limit=-1 bin/pest --type-coverage --min=100",
@ -113,6 +114,7 @@
"Pest\\Plugins\\Snapshot",
"Pest\\Plugins\\Verbose",
"Pest\\Plugins\\Version",
"Pest\\Plugins\\Shard",
"Pest\\Plugins\\Parallel"
]
},

98
overrides/Report/PHP.php Normal file
View File

@ -0,0 +1,98 @@
<?php
/*
* BSD 3-Clause License
*
* Copyright (c) 2001-2023, Sebastian Bergmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeCoverage\Report;
use const PHP_EOL;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;
use function dirname;
use function serialize;
use function str_contains;
final class PHP
{
public function process(CodeCoverage $coverage, ?string $target = null): string
{
$coverage->clearCache();
$buffer = "<?php return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'".PHP_EOL.serialize($coverage).PHP_EOL.'END_OF_COVERAGE_SERIALIZATION'.PHP_EOL.');';
if ($target !== null) {
if (! str_contains($target, '://')) {
Filesystem::createDirectory(dirname($target));
}
if (! is_writable(dirname($target))) {
throw new WriteOperationFailedException($target);
}
$fp = @fopen($target, 'wb');
if (! $fp) {
throw new WriteOperationFailedException($target);
}
$chunkSize = 1024 * 1024 * 8;
$offset = 0;
$total = strlen($buffer);
while ($offset < $total) {
$written = @fwrite($fp, substr($buffer, $offset, $chunkSize));
if ($written === false) {
fclose($fp);
throw new WriteOperationFailedException($target);
}
$offset += $written;
}
fclose($fp);
}
return $buffer;
}
}

View File

@ -0,0 +1,17 @@
<div class="mx-2 mb-1">
<p>
<span>Using the <span class="text-yellow font-bold">visit()</span> function requires the Pest Plugin Browser to be installed.</span>
<span class="ml-1 text-yellow font-bold">Run:</span>
</p>
<div>
<span class="text-gray mr-1">- </span>
<span>composer require pestphp/pest-plugin-browser:^4.0 --dev</span>
</div>
<div>
<span class="text-gray mr-1">- </span>
<span>npx playwright install</span>
</div>
</div>

View File

@ -15,17 +15,18 @@ final class BootOverrides implements Bootstrapper
/**
* The list of files to be overridden.
*
* @var array<string, string>
* @var array<int, string>
*/
public const array FILES = [
'53c246e5f416a39817ac81124cdd64ea8403038d01d7a202e1ffa486fbdf3fa7' => 'Runner/Filter/NameFilterIterator.php',
'77ffb7647b583bd82e37962c6fbdc4b04d3344d8a2c1ed103e625ed1ff7cb5c2' => 'Runner/ResultCache/DefaultResultCache.php',
'd0e81317889ad88c707db4b08a94cadee4c9010d05ff0a759f04e71af5efed89' => 'Runner/TestSuiteLoader.php',
'3bb609b0d3bf6dee8df8d6cd62a3c8ece823c4bb941eaaae39e3cb267171b9d2' => 'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
'8abdad6413329c6fe0d7d44a8b9926e390af32c0b3123f3720bb9c5bbc6fbb7e' => 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php',
'b4250fc3ffad5954624cb5e682fd940b874e8d3422fa1ee298bd7225e1aa5fc2' => 'TextUI/TestSuiteFilterProcessor.php',
'8cfcb4999af79463eca51a42058e502ea4ddc776cba5677bf2f8eb6093e21a5c' => 'Event/Value/ThrowableBuilder.php',
'86cd9bcaa53cdd59c5b13e58f30064a015c549501e7629d93b96893d4dee1eb1' => 'Logging/JUnit/JunitXmlLogger.php',
'Runner/Filter/NameFilterIterator.php',
'Runner/ResultCache/DefaultResultCache.php',
'Runner/TestSuiteLoader.php',
'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php',
'TextUI/TestSuiteFilterProcessor.php',
'Event/Value/ThrowableBuilder.php',
'Logging/JUnit/JunitXmlLogger.php',
'Report/PHP.php',
];
/**

View File

@ -438,15 +438,7 @@ trait Testable
return;
}
if (count($this->__snapshotChanges) === 1) {
$this->markTestIncomplete($this->__snapshotChanges[0]);
return;
}
$messages = implode(PHP_EOL, array_map(static fn (string $message): string => '- $message', $this->__snapshotChanges));
$this->markTestIncomplete($messages);
$this->markTestIncomplete(implode('. ', $this->__snapshotChanges));
}
/**

View File

@ -102,6 +102,14 @@ final readonly class Configuration
return Configuration\Project::getInstance();
}
/**
* Gets the browser configuration.
*/
public function browser(): Browser\Configuration
{
return new Browser\Configuration;
}
/**
* Proxies calls to the uses method.
*

View File

@ -2,11 +2,14 @@
declare(strict_types=1);
use Pest\Browser\Api\ArrayablePendingAwaitablePage;
use Pest\Browser\Api\PendingAwaitablePage;
use Pest\Concerns\Expectable;
use Pest\Configuration;
use Pest\Exceptions\AfterAllWithinDescribe;
use Pest\Exceptions\BeforeAllWithinDescribe;
use Pest\Expectation;
use Pest\Installers\PluginBrowser;
use Pest\Mutate\Contracts\MutationTestRunner;
use Pest\Mutate\Repositories\ConfigurationRepository;
use Pest\PendingCalls\AfterEachCall;
@ -278,3 +281,51 @@ if (! function_exists('mutates')) {
}
}
}
if (! function_exists('fixture')) {
/**
* Returns the absolute path to a fixture file.
*/
function fixture(string $file): string
{
$file = implode(DIRECTORY_SEPARATOR, [
TestSuite::getInstance()->rootPath,
TestSuite::getInstance()->testPath,
'Fixtures',
str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file),
]);
$fileRealPath = realpath($file);
if ($fileRealPath === false) {
throw new InvalidArgumentException(
'The fixture file ['.$file.'] does not exist.',
);
}
return $fileRealPath;
}
}
if (! function_exists('visit')) {
/**
* Browse to the given URL.
*
* @template TUrl of array<int, string>|string
*
* @param TUrl $url
* @param array<string, mixed> $options
* @return (TUrl is array<int, string> ? ArrayablePendingAwaitablePage : PendingAwaitablePage)
*/
function visit(array|string $url, array $options = []): ArrayablePendingAwaitablePage|PendingAwaitablePage
{
if (! class_exists(\Pest\Browser\Configuration::class)) {
PluginBrowser::install();
exit(0);
}
// @phpstan-ignore-next-line
return test()->visit($url, $options);
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pest\Installers;
use Pest\Support\View;
final readonly class PluginBrowser
{
public static function install(): void
{
View::render('installers/plugin-browser');
}
}

View File

@ -178,10 +178,9 @@ final class TestCall // @phpstan-ignore-line
}
/**
* Runs the current test multiple times with
* each item of the given `iterable`.
* Runs the current test multiple times with each item of the given `iterable`.
*
* @param array<\Closure|iterable<int|string, mixed>|string> $data
* @param Closure|iterable<array-key, mixed>|string $data
*/
public function with(Closure|iterable|string ...$data): self
{

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string
{
return '4.0.0-alpha.2';
return '4.0.0-alpha.5';
}
function testDirectory(string $file = ''): string

View File

@ -5,7 +5,10 @@ declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\Terminable;
use Pest\Factories\Attribute;
use Pest\Factories\TestCaseMethodFactory;
use Pest\PendingCalls\TestCall;
use PHPUnit\Framework\Attributes\Group;
/**
* @internal
@ -23,28 +26,19 @@ final class Only implements Terminable
.DIRECTORY_SEPARATOR
.'.temp';
/**
* {@inheritDoc}
*/
public function terminate(): void
{
if (Parallel::isWorker()) {
return;
}
$lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock';
if (file_exists($lockFile)) {
unlink($lockFile);
}
}
/**
* Creates the lock file.
*/
public static function enable(TestCall $testCall, string $group = '__pest_only'): void
public static function enable(TestCall|TestCaseMethodFactory $testCall, string $group = '__pest_only'): void
{
$testCall->group($group);
if ($testCall instanceof TestCall) {
$testCall->group($group);
} else {
$testCall->attributes[] = new Attribute(
Group::class,
[$group],
);
}
if (Environment::name() === Environment::CI || Parallel::isWorker()) {
return;
@ -88,4 +82,20 @@ final class Only implements Terminable
return file_get_contents($lockFile) ?: '__pest_only'; // @phpstan-ignore-line
}
/**
* {@inheritDoc}
*/
public function terminate(): void
{
if (Parallel::isWorker()) {
return;
}
$lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock';
if (file_exists($lockFile)) {
unlink($lockFile);
}
}
}

177
src/Plugins/Shard.php Normal file
View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\AddsOutput;
use Pest\Contracts\Plugins\HandlesArguments;
use Pest\Exceptions\InvalidOption;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
/**
* @internal
*/
final class Shard implements AddsOutput, HandlesArguments
{
use Concerns\HandleArguments;
private const string SHARD_OPTION = 'shard';
/**
* The shard index and total number of shards.
*
* @var array{
* index: int,
* total: int,
* testsRan: int,
* testsCount: int
* }|null
*/
private static ?array $shard = null;
/**
* Creates a new Plugin instance.
*/
public function __construct(
private readonly OutputInterface $output,
) {
//
}
/**
* {@inheritDoc}
*/
public function handleArguments(array $arguments): array
{
if (! $this->hasArgument('--shard', $arguments)) {
return $arguments;
}
// @phpstan-ignore-next-line
$input = new ArgvInput($arguments);
['index' => $index, 'total' => $total] = self::getShard($input);
$arguments = $this->popArgument("--shard=$index/$total", $this->popArgument('--shard', $this->popArgument(
"$index/$total",
$arguments,
)));
/** @phpstan-ignore-next-line */
$tests = $this->allTests($arguments);
$testsToRun = (array_chunk($tests, max(1, (int) ceil(count($tests) / $total))))[$index - 1] ?? [];
self::$shard = [
'index' => $index,
'total' => $total,
'testsRan' => count($testsToRun),
'testsCount' => count($tests),
];
return [...$arguments, '--filter', $this->buildFilterArgument($testsToRun)];
}
/**
* Returns all tests that the test suite would run.
*
* @param list<string> $arguments
* @return list<string>
*/
private function allTests(array $arguments): array
{
$output = (new Process([
'php',
...$this->removeParallelArguments($arguments),
'--list-tests',
]))->mustRun()->getOutput();
preg_match_all('/ - (?:P\\\\)?(Tests\\\\[^:]+)::/', $output, $matches);
return array_values(array_unique($matches[1]));
}
/**
* @param array<int, string> $arguments
* @return array<int, string>
*/
private function removeParallelArguments(array $arguments): array
{
return array_filter($arguments, fn (string $argument): bool => ! in_array($argument, ['--parallel', '-p'], strict: true));
}
/**
* Builds the filter argument for the given tests to run.
*/
private function buildFilterArgument(mixed $testsToRun): string
{
return addslashes(implode('|', $testsToRun));
}
/**
* Adds output after the Test Suite execution.
*/
public function addOutput(int $exitCode): int
{
if (self::$shard === null) {
return $exitCode;
}
[
'index' => $index,
'total' => $total,
'testsRan' => $testsRan,
'testsCount' => $testsCount,
] = self::$shard;
$this->output->writeln(sprintf(
' <fg=gray>Shard:</> <fg=default>%d of %d</> — %d file%s ran, out of %d.',
$index,
$total,
$testsRan,
$testsRan === 1 ? '' : 's',
$testsCount,
));
return $exitCode;
}
/**
* Returns the shard information.
*
* @return array{index: int, total: int}
*/
public static function getShard(InputInterface $input): array
{
if ($input->hasParameterOption('--'.self::SHARD_OPTION)) {
$shard = $input->getParameterOption('--'.self::SHARD_OPTION);
} else {
$shard = null;
}
if (! is_string($shard) || ! preg_match('/^\d+\/\d+$/', $shard)) {
throw new InvalidOption('The [--shard] option must be in the format "index/total".');
}
[$index, $total] = explode('/', $shard);
if (! is_numeric($index) || ! is_numeric($total)) {
throw new InvalidOption('The [--shard] option must be in the format "index/total".');
}
if ($index <= 0 || $total <= 0 || $index > $total) {
throw new InvalidOption('The [--shard] option index must be a non-negative integer less than the total number of shards.');
}
$index = (int) $index;
$total = (int) $total;
return [
'index' => $index,
'total' => $total,
];
}
}

View File

@ -40,7 +40,7 @@ final class Result
*/
public static function exitCode(Configuration $configuration, TestResult $result): int
{
if ($result->wasSuccessfulIgnoringPhpunitWarnings()) {
if ($result->wasSuccessful()) {
if ($configuration->failOnWarning()) {
$warnings = $result->numberOfTestsWithTestTriggeredPhpunitWarningEvents()
+ count($result->warnings())
@ -60,7 +60,7 @@ final class Result
return self::FAILURE_EXIT;
}
if ($result->wasSuccessfulIgnoringPhpunitWarnings()) {
if ($result->wasSuccessful()) {
if ($configuration->failOnRisky() && $result->hasTestConsideredRiskyEvents()) {
$returnCode = self::FAILURE_EXIT;
}

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Pest\Support;
use Pest\Exceptions\ShouldNotHappen;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\Directory;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\Environment\Runtime;
@ -88,10 +87,20 @@ final class Coverage
throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath));
}
/** @var CodeCoverage $codeCoverage */
$codeCoverage = require $reportPath;
$handle = fopen($reportPath, 'r');
$code = '';
while (is_resource($handle) && ! feof($handle)) {
$code .= fread($handle, 8192);
}
if (is_resource($handle)) {
fclose($handle);
}
unlink($reportPath);
$codeCoverage = eval(substr($code, 5));
$totalCoverage = $codeCoverage->getReport()->percentageOfExecutedLines();
/** @var Directory<File|Directory> $report */

View File

@ -1,5 +1,5 @@
Pest Testing Framework 4.0.0-alpha.2.
Pest Testing Framework 4.0.0-alpha.5.
USAGE: pest <file> [options]
@ -53,7 +53,7 @@
--disallow-test-output ................. Be strict about output during tests
--enforce-time-limit ................. Enforce time limit based on test size
--default-time-limit [sec] Timeout in seconds for tests that have no declared size
--dont-report-useless-tests .. Do not report tests that do not test anything
--do-not-report-useless-tests Do not report tests that do not test anything
--stop-on-defect ... Stop after first error, failure, warning, or risky test
--stop-on-error ..................................... Stop after first error
--stop-on-failure ................................. Stop after first failure
@ -69,10 +69,21 @@
--fail-on-deprecation Signal failure using shell exit code when a deprecation was triggered
--fail-on-phpunit-deprecation Signal failure using shell exit code when a PHPUnit deprecation was triggered
--fail-on-phpunit-notice Signal failure using shell exit code when a PHPUnit notice was triggered
--fail-on-phpunit-warning Signal failure using shell exit code when a PHPUnit warning was triggered
--fail-on-notice Signal failure using shell exit code when a notice was triggered
--fail-on-skipped Signal failure using shell exit code when a test was skipped
--fail-on-incomplete Signal failure using shell exit code when a test was marked incomplete
--fail-on-all-issues Signal failure using shell exit code when an issue is triggered
--do-not-fail-on-empty-test-suite Do not signal failure using shell exit code when no tests were run
--do-not-fail-on-warning Do not signal failure using shell exit code when a warning was triggered
--do-not-fail-on-risky Do not signal failure using shell exit code when a test was considered risky
--do-not-fail-on-deprecation Do not signal failure using shell exit code when a deprecation was triggered
--do-not-fail-on-phpunit-deprecation Do not signal failure using shell exit code when a PHPUnit deprecation was triggered
--do-not-fail-on-phpunit-notice Do not signal failure using shell exit code when a PHPUnit notice was triggered
--do-not-fail-on-phpunit-warning Do not signal failure using shell exit code when a PHPUnit warning was triggered
--do-not-fail-on-notice Do not signal failure using shell exit code when a notice was triggered
--do-not-fail-on-skipped Do not signal failure using shell exit code when a test was skipped
--do-not-fail-on-incomplete Do not signal failure using shell exit code when a test was marked incomplete
--cache-result ............................ Write test results to cache file
--do-not-cache-result .............. Do not write test results to cache file
--order-by [order] Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size
@ -106,6 +117,7 @@
LOGGING OPTIONS:
--log-junit [file] .......... Write test results in JUnit XML format to file
--log-otr [file] Write test results in Open Test Reporting XML format to file
--include-git-information Include Git information in Open Test Reporting XML logfile
--log-teamcity [file] ........ Write test results in TeamCity format to file
--testdox-html [file] .. Write test results in TestDox format (HTML) to file
--testdox-text [file] Write test results in TestDox format (plain text) to file

View File

@ -1,3 +1,3 @@
Pest Testing Framework 4.0.0-alpha.2.
Pest Testing Framework 4.0.0-alpha.5.

View File

@ -1040,6 +1040,10 @@
✓ it may fail
✓ it may fail with the given message
PASS Tests\Features\Fixture
✓ it may return a file path
✓ it may throw an exception if the file does not exist
WARN Tests\Features\Helpers
✓ it can set/get properties on $this
! it gets null if property do not exist → Undefined property Tests\Features\Helpers::$wqdwqdqw
@ -1708,4 +1712,4 @@
WARN Tests\Visual\Version
- visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1144 passed (2736 assertions)
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1146 passed (2739 assertions)

View File

@ -37,6 +37,7 @@ arch('dependencies')
'Termwind',
'ParaTest',
'Pest\Arch',
'Pest\Browser',
'Pest\Mutate\Contracts\Configuration',
'Pest\Mutate\Decorators\TestCallDecorator',
'Pest\Mutate\Repositories\ConfigurationRepository',

View File

@ -0,0 +1,12 @@
<?php
it('may return a file path', function () {
$file = fixture('phpunit-in-isolation.xml');
expect($file)->toBeString()
->toBeFile();
});
it('may throw an exception if the file does not exist', function () {
fixture('file-that-does-not-exist.php');
})->throws(InvalidArgumentException::class);

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) {
expect($run('--exclude-group=integration'))
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1134 passed (2712 assertions)')
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1136 passed (2715 assertions)')
->toContain('Parallel: 3 processes');
})->skipOnWindows();