diff --git a/ChangeLog-7.1.md b/ChangeLog-7.1.md index 2682cae76bb..07853de2361 100644 --- a/ChangeLog-7.1.md +++ b/ChangeLog-7.1.md @@ -4,6 +4,10 @@ All notable changes of the PHPUnit 7.1 release series are documented in this fil ## [7.1.0] - 2018-04-06 +### Added + +* Implemented [#3002](https://github.com/sebastianbergmann/phpunit/issues/3002): Support for test runner extensions + ### Changed * `PHPUnit\Framework\Assert` is no longer searched for test methods diff --git a/phpunit.xsd b/phpunit.xsd index 3d54573c50d..81690e13cd1 100644 --- a/phpunit.xsd +++ b/phpunit.xsd @@ -50,6 +50,11 @@ + + + + + @@ -248,6 +253,7 @@ + diff --git a/src/Runner/Hook/AfterLastTestHook.php b/src/Runner/Hook/AfterLastTestHook.php new file mode 100644 index 00000000000..77034d30b05 --- /dev/null +++ b/src/Runner/Hook/AfterLastTestHook.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PHPUnit\Runner; + +interface AfterLastTestHook extends Hook +{ + public function executeAfterLastTest(): void; +} diff --git a/src/Runner/Hook/BeforeFirstTestHook.php b/src/Runner/Hook/BeforeFirstTestHook.php new file mode 100644 index 00000000000..d7f8e3d4bc6 --- /dev/null +++ b/src/Runner/Hook/BeforeFirstTestHook.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PHPUnit\Runner; + +interface BeforeFirstTestHook extends Hook +{ + public function executeBeforeFirstTest(): void; +} diff --git a/src/Runner/Hook/Hook.php b/src/Runner/Hook/Hook.php new file mode 100644 index 00000000000..680ce7c5db2 --- /dev/null +++ b/src/Runner/Hook/Hook.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PHPUnit\Runner; + +interface Hook +{ +} diff --git a/src/TextUI/TestRunner.php b/src/TextUI/TestRunner.php index cb49778a7da..8233ba84e6a 100644 --- a/src/TextUI/TestRunner.php +++ b/src/TextUI/TestRunner.php @@ -18,11 +18,14 @@ use PHPUnit\Framework\TestListener; use PHPUnit\Framework\TestResult; use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\AfterLastTestHook; use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Runner\BeforeFirstTestHook; use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator; use PHPUnit\Runner\Filter\Factory; use PHPUnit\Runner\Filter\IncludeGroupFilterIterator; use PHPUnit\Runner\Filter\NameFilterIterator; +use PHPUnit\Runner\Hook; use PHPUnit\Runner\StandardTestSuiteLoader; use PHPUnit\Runner\TestSuiteLoader; use PHPUnit\Runner\Version; @@ -86,6 +89,11 @@ class TestRunner extends BaseTestRunner */ private $messagePrinted = false; + /** + * @var Hook[] + */ + private $extensions = []; + /** * @param TestSuiteLoader $loader * @param CodeCoverageFilter $filter @@ -505,8 +513,20 @@ public function doRun(Test $suite, array $arguments = [], $exit = true): TestRes $suite->setRunTestInSeparateProcess($arguments['processIsolation']); } + foreach ($this->extensions as $extension) { + if ($extension instanceof BeforeFirstTestHook) { + $extension->executeBeforeFirstTest(); + } + } + $suite->run($result); + foreach ($this->extensions as $extension) { + if ($extension instanceof AfterLastTestHook) { + $extension->executeAfterLastTest(); + } + } + $result->flushListeners(); if ($this->printer instanceof ResultPrinter) { @@ -892,6 +912,34 @@ protected function handleConfiguration(array &$arguments): void $arguments['excludeGroups'] = \array_diff($groupConfiguration['exclude'], $groupCliArgs); } + foreach ($arguments['configuration']->getExtensionConfiguration() as $extension) { + if (!\class_exists($extension['class'], false) && $extension['file'] !== '') { + require_once $extension['file']; + } + + if (!\class_exists($extension['class'])) { + throw new Exception( + \sprintf( + 'Class "%s" does not exist', + $extension['class'] + ) + ); + } + + $extensionClass = new ReflectionClass($extension['class']); + + if (!$extensionClass->implementsInterface(Hook::class)) { + throw new Exception( + \sprintf( + 'Class "%s" does not implement a PHPUnit\Runner\Hook interface', + $extension['class'] + ) + ); + } + + $this->extensions[] = $extensionClass->newInstance(); + } + foreach ($arguments['configuration']->getListenerConfiguration() as $listener) { if (!\class_exists($listener['class'], false) && $listener['file'] !== '') { diff --git a/src/Util/Configuration.php b/src/Util/Configuration.php index c1dcdd6433d..b4b741997de 100644 --- a/src/Util/Configuration.php +++ b/src/Util/Configuration.php @@ -231,6 +231,31 @@ public function getFilename(): string return $this->filename; } + public function getExtensionConfiguration(): array + { + $result = []; + + foreach ($this->xpath->query('extensions/extension') as $extension) { + /** @var DOMElement $extension */ + $class = (string) $extension->getAttribute('class'); + $file = ''; + + if ($extension->getAttribute('file')) { + $file = $this->toAbsolutePath( + (string) $extension->getAttribute('file'), + true + ); + } + + $result[] = [ + 'class' => $class, + 'file' => $file + ]; + } + + return $result; + } + /** * Returns the configuration for SUT filtering. * diff --git a/tests/TextUI/_files/Extension.php b/tests/TextUI/_files/Extension.php new file mode 100644 index 00000000000..1407739cfb6 --- /dev/null +++ b/tests/TextUI/_files/Extension.php @@ -0,0 +1,18 @@ + + + + + + diff --git a/tests/TextUI/hooks.phpt b/tests/TextUI/hooks.phpt new file mode 100644 index 00000000000..ef88193ac59 --- /dev/null +++ b/tests/TextUI/hooks.phpt @@ -0,0 +1,16 @@ +--TEST-- +phpunit --configuration _files/hooks.xml BankAccountTest ../_files/BankAccountTest.php +--FILE-- +