diff --git a/composer.json b/composer.json
index b18f188..41db2e8 100644
--- a/composer.json
+++ b/composer.json
@@ -26,6 +26,7 @@
"require": {
"php": "^8.3",
"composer-plugin-api": "^2.0",
+ "brianium/paratest": "^7.17",
"composer/composer": "^2.9",
"dg/bypass-finals": "^1.9",
"ergebnis/composer-normalize": "^2.50",
diff --git a/src/Command/TestsCommand.php b/src/Command/TestsCommand.php
index fc1706c..36e5711 100644
--- a/src/Command/TestsCommand.php
+++ b/src/Command/TestsCommand.php
@@ -103,6 +103,12 @@ protected function configure(): void
mode: InputOption::VALUE_OPTIONAL,
description: 'Filter which tests to run based on a pattern.',
)
+ ->addOption(
+ name: 'parallel',
+ shortcut: 'p',
+ mode: InputOption::VALUE_OPTIONAL,
+ description: 'Run tests in parallel using ParaTest. Optional number of workers.',
+ )
->addOption(
name: 'min-coverage',
mode: InputOption::VALUE_REQUIRED,
@@ -123,8 +129,6 @@ protected function configure(): void
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
- $output->writeln('Running PHPUnit tests...');
-
try {
$minimumCoverage = $this->resolveMinimumCoverage($input);
} catch (InvalidArgumentException $invalidArgumentException) {
@@ -134,13 +138,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
$arguments = [
- $this->getAbsolutePath('vendor/bin/phpunit'),
'--configuration=' . parent::getConfigFile(self::CONFIG),
'--bootstrap=' . $this->resolvePath($input, 'bootstrap'),
- '--display-deprecations',
- '--display-phpunit-deprecations',
- '--display-incomplete',
- '--display-skipped',
];
if (! $input->getOption('no-cache')) {
@@ -149,11 +148,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$coverageReportPath = $this->configureCoverageArguments($input, $arguments, null !== $minimumCoverage);
- if ($input->getOption('filter')) {
+ if (null !== $input->getOption('filter')) {
$arguments[] = '--filter=' . $input->getOption('filter');
}
- $command = new Process([...$arguments, $input->getArgument('path')]);
+ $parallel = null !== $input->getOption('parallel');
+
+ $command = $this->getAbsolutePath(\sprintf('vendor/bin/%s', $parallel ? 'paratest' : 'phpunit'));
+
+ if (! $parallel) {
+ $arguments[] = '--display-deprecations';
+ $arguments[] = '--display-phpunit-deprecations';
+ $arguments[] = '--display-incomplete';
+ $arguments[] = '--display-skipped';
+ } else {
+ $arguments[] = '--processes=' . ($input->getOption('parallel') ?: 'auto');
+ }
+
+ $output->writeln('Running tests using ' . ($parallel ? 'ParaTest' : 'PHPUnit') . '...');
+
+ $command = new Process([$command, ...$arguments, $input->getArgument('path')]);
$result = parent::runProcess($command, $output);
diff --git a/tests/GitAttributes/CandidateProviderTest.php b/tests/GitAttributes/CandidateProviderTest.php
index 84f840a..ec63f61 100644
--- a/tests/GitAttributes/CandidateProviderTest.php
+++ b/tests/GitAttributes/CandidateProviderTest.php
@@ -26,7 +26,7 @@
#[CoversClass(CandidateProvider::class)]
final class CandidateProviderTest extends TestCase
{
- private readonly CandidateProvider $provider;
+ private CandidateProvider $provider;
/**
* @return void
diff --git a/tests/GitAttributes/ExistenceCheckerTest.php b/tests/GitAttributes/ExistenceCheckerTest.php
index f07bb68..eeb1621 100644
--- a/tests/GitAttributes/ExistenceCheckerTest.php
+++ b/tests/GitAttributes/ExistenceCheckerTest.php
@@ -34,9 +34,9 @@ final class ExistenceCheckerTest extends TestCase
/**
* @property ObjectProphecy $filesystem
*/
- private readonly ObjectProphecy $filesystem;
+ private ObjectProphecy $filesystem;
- private readonly ExistenceChecker $checker;
+ private ExistenceChecker $checker;
/**
* @return void
diff --git a/tests/GitIgnore/ReaderTest.php b/tests/GitIgnore/ReaderTest.php
index f61d669..8ddd42a 100644
--- a/tests/GitIgnore/ReaderTest.php
+++ b/tests/GitIgnore/ReaderTest.php
@@ -33,7 +33,7 @@
#[UsesClass(GitIgnore::class)]
final class ReaderTest extends TestCase
{
- private readonly Reader $reader;
+ private Reader $reader;
/**
* @return void