diff --git a/phpunit.xsd b/phpunit.xsd
index cca0a3b412d..f9b3b2f174b 100644
--- a/phpunit.xsd
+++ b/phpunit.xsd
@@ -258,6 +258,7 @@
+
diff --git a/src/Framework/TestResult.php b/src/Framework/TestResult.php
index 4a5b1634ed0..5fcd1883340 100644
--- a/src/Framework/TestResult.php
+++ b/src/Framework/TestResult.php
@@ -139,6 +139,11 @@ class TestResult implements Countable
*/
protected $beStrictAboutResourceUsageDuringSmallTests = false;
+ /**
+ * @var int
+ */
+ private $defaultTimeLimit = 0;
+
/**
* @var bool
*/
@@ -639,8 +644,8 @@ public function run(Test $test): void
try {
if (!$test instanceof WarningTestCase &&
- $test->getSize() != \PHPUnit\Util\Test::UNKNOWN &&
$this->enforceTimeLimit &&
+ ($this->defaultTimeLimit || $test->getSize() != \PHPUnit\Util\Test::UNKNOWN) &&
\extension_loaded('pcntl') && \class_exists(Invoker::class)) {
switch ($test->getSize()) {
case \PHPUnit\Util\Test::SMALL:
@@ -656,6 +661,11 @@ public function run(Test $test): void
case \PHPUnit\Util\Test::LARGE:
$_timeout = $this->timeoutForLargeTests;
+ break;
+
+ case \PHPUnit\Util\Test::UNKNOWN:
+ $_timeout = $this->defaultTimeLimit;
+
break;
}
@@ -1061,6 +1071,14 @@ public function wasSuccessful(): bool
return empty($this->errors) && empty($this->failures) && empty($this->warnings);
}
+ /**
+ * Sets the default timeout for tests
+ */
+ public function setDefaultTimeLimit(int $timeout): void
+ {
+ $this->defaultTimeLimit = $timeout;
+ }
+
/**
* Sets the timeout for small tests.
*/
diff --git a/src/TextUI/Command.php b/src/TextUI/Command.php
index 24c96c5bc96..7a95420672d 100644
--- a/src/TextUI/Command.php
+++ b/src/TextUI/Command.php
@@ -84,6 +84,7 @@ class Command
'disallow-test-output' => null,
'disallow-resource-usage' => null,
'disallow-todo-tests' => null,
+ 'default-time-limit=' => null,
'enforce-time-limit' => null,
'exclude-group=' => null,
'filter=' => null,
@@ -692,6 +693,11 @@ protected function handleArguments(array $argv): void
break;
+ case '--default-time-limit':
+ $this->arguments['defaultTimeLimit'] = (int) $option[1];
+
+ break;
+
case '--enforce-time-limit':
$this->arguments['enforceTimeLimit'] = true;
@@ -1120,6 +1126,7 @@ protected function showHelp(): void
--disallow-test-output Be strict about output during tests
--disallow-resource-usage Be strict about resource usage during small tests
--enforce-time-limit Enforce time limit based on test size
+ --default-time-limit= Timeout in seconds for tests without @small, @medium or @large
--disallow-todo-tests Disallow @todo-annotated tests
--process-isolation Run each test in a separate PHP process
diff --git a/src/TextUI/TestRunner.php b/src/TextUI/TestRunner.php
index 98a6276de37..a054725ad32 100644
--- a/src/TextUI/TestRunner.php
+++ b/src/TextUI/TestRunner.php
@@ -54,6 +54,7 @@
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport;
use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Environment\Runtime;
+use SebastianBergmann\Invoker\Invoker;
/**
* A TestRunner for the Command Line Interface (CLI)
@@ -579,7 +580,18 @@ public function doRun(Test $suite, array $arguments = [], bool $exit = true): Te
$result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']);
$result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']);
$result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']);
+
+ if ($arguments['enforceTimeLimit'] === true) {
+ if (!\class_exists(Invoker::class)) {
+ $this->writeMessage('Error', 'Package phpunit/php-invoker is required for enforcing time limits');
+ }
+
+ if (!\extension_loaded('pcntl') || \strpos(\ini_get('disable_functions'), 'pcntl') !== false) {
+ $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits');
+ }
+ }
$result->enforceTimeLimit($arguments['enforceTimeLimit']);
+ $result->setDefaultTimeLimit($arguments['defaultTimeLimit']);
$result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']);
$result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']);
$result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']);
@@ -942,6 +954,10 @@ protected function handleConfiguration(array &$arguments): void
$arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput'];
}
+ if (isset($phpunitConfiguration['defaultTimeLimit']) && !isset($arguments['defaultTimeLimit'])) {
+ $arguments['defaultTimeLimit'] = $phpunitConfiguration['defaultTimeLimit'];
+ }
+
if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) {
$arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit'];
}
@@ -1179,6 +1195,7 @@ protected function handleConfiguration(array &$arguments): void
$arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30;
$arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false;
$arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false;
+ $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0;
$arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false;
$arguments['excludeGroups'] = $arguments['excludeGroups'] ?? [];
$arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false;
diff --git a/src/Util/Configuration.php b/src/Util/Configuration.php
index ad70571c888..0a184303936 100644
--- a/src/Util/Configuration.php
+++ b/src/Util/Configuration.php
@@ -59,6 +59,7 @@
* beStrictAboutResourceUsageDuringSmallTests="false"
* beStrictAboutTestsThatDoNotTestAnything="false"
* beStrictAboutTodoAnnotatedTests="false"
+ * defaultTimeLimit="0"
* enforceTimeLimit="false"
* ignoreDeprecatedCodeUnitsFromCodeCoverage="false"
* timeoutForSmallTests="1"
@@ -855,6 +856,13 @@ public function getPHPUnitConfiguration(): array
);
}
+ if ($root->hasAttribute('defaultTimeLimit')) {
+ $result['defaultTimeLimit'] = $this->getInteger(
+ (string) $root->getAttribute('defaultTimeLimit'),
+ 1
+ );
+ }
+
if ($root->hasAttribute('enforceTimeLimit')) {
$result['enforceTimeLimit'] = $this->getBoolean(
(string) $root->getAttribute('enforceTimeLimit'),
diff --git a/tests/Regression/GitHub/2085/Issue2085Test.php b/tests/Regression/GitHub/2085/Issue2085Test.php
new file mode 100644
index 00000000000..c97efe2621d
--- /dev/null
+++ b/tests/Regression/GitHub/2085/Issue2085Test.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+use PHPUnit\Framework\TestCase;
+
+class Issue2085Test extends TestCase
+{
+ public function testShouldAbortSlowTestByEnforcingTimeLimit(): void
+ {
+ $this->assertTrue(true);
+ \sleep(1.2);
+ $this->assertTrue(true);
+ }
+}
diff --git a/tests/Regression/GitHub/2085/configuration_enforce_time_limit_options.xml b/tests/Regression/GitHub/2085/configuration_enforce_time_limit_options.xml
new file mode 100644
index 00000000000..609ca4a505d
--- /dev/null
+++ b/tests/Regression/GitHub/2085/configuration_enforce_time_limit_options.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/tests/Regression/GitHub/2085/enforce-time-limit-options-via-config-without-invoker.phpt b/tests/Regression/GitHub/2085/enforce-time-limit-options-via-config-without-invoker.phpt
new file mode 100644
index 00000000000..5dc14d8e71d
--- /dev/null
+++ b/tests/Regression/GitHub/2085/enforce-time-limit-options-via-config-without-invoker.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Test XML config enforceTimeLimit, defaultTimeLimit without php-invoker, with pcntl
+--SKIPIF--
+ Timeout in seconds for tests without @small, @medium or @large
--disallow-todo-tests Disallow @todo-annotated tests
--process-isolation Run each test in a separate PHP process
diff --git a/tests/end-to-end/help2.phpt b/tests/end-to-end/help2.phpt
index 7e9b5d06fe9..05c9688f226 100644
--- a/tests/end-to-end/help2.phpt
+++ b/tests/end-to-end/help2.phpt
@@ -57,6 +57,7 @@ Test Execution Options:
--disallow-test-output Be strict about output during tests
--disallow-resource-usage Be strict about resource usage during small tests
--enforce-time-limit Enforce time limit based on test size
+ --default-time-limit= Timeout in seconds for tests without @small, @medium or @large
--disallow-todo-tests Disallow @todo-annotated tests
--process-isolation Run each test in a separate PHP process
diff --git a/tests/unit/Util/ConfigurationTest.php b/tests/unit/Util/ConfigurationTest.php
index 92bffa2d5e8..95d8652ebff 100644
--- a/tests/unit/Util/ConfigurationTest.php
+++ b/tests/unit/Util/ConfigurationTest.php
@@ -471,6 +471,7 @@ public function testPHPUnitConfigurationIsReadCorrectly(): void
'reportUselessTests' => false,
'strictCoverage' => false,
'disallowTestOutput' => false,
+ 'defaultTimeLimit' => 123,
'enforceTimeLimit' => false,
'extensionsDirectory' => '/tmp',
'printerClass' => 'PHPUnit\TextUI\ResultPrinter',