Skip to content

Commit

Permalink
Initial work on #3002
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Feb 21, 2018
1 parent cc976dc commit dfd2b13
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 0 deletions.
4 changes: 4 additions & 0 deletions ChangeLog-7.1.md
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions phpunit.xsd
Expand Up @@ -50,6 +50,11 @@
<xs:element name="group" type="xs:string" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="extensionsType">
<xs:sequence>
<xs:element name="extension" type="objectType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="listenersType">
<xs:sequence>
<xs:element name="listener" type="objectType" maxOccurs="unbounded"/>
Expand Down Expand Up @@ -248,6 +253,7 @@
<xs:element name="testdoxGroups" type="groupsType" minOccurs="0"/>
<xs:element name="filter" type="filtersType" minOccurs="0"/>
<xs:element name="logging" type="loggersType" minOccurs="0"/>
<xs:element name="extensions" type="extensionsType" minOccurs="0"/>
<xs:element name="listeners" type="listenersType" minOccurs="0"/>
<xs:element name="php" type="phpType" minOccurs="0"/>
</xs:all>
Expand Down
16 changes: 16 additions & 0 deletions src/Runner/Hook/AfterLastTestHook.php
@@ -0,0 +1,16 @@
<?php
/*
* 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 PHPUnit\Runner;

interface AfterLastTestHook extends Hook
{
public function executeAfterLastTest(): void;
}
16 changes: 16 additions & 0 deletions src/Runner/Hook/BeforeFirstTestHook.php
@@ -0,0 +1,16 @@
<?php
/*
* 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 PHPUnit\Runner;

interface BeforeFirstTestHook extends Hook
{
public function executeBeforeFirstTest(): void;
}
15 changes: 15 additions & 0 deletions src/Runner/Hook/Hook.php
@@ -0,0 +1,15 @@
<?php
/*
* 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 PHPUnit\Runner;

interface Hook
{
}
48 changes: 48 additions & 0 deletions src/TextUI/TestRunner.php
Expand Up @@ -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;
Expand Down Expand Up @@ -86,6 +89,11 @@ class TestRunner extends BaseTestRunner
*/
private $messagePrinted = false;

/**
* @var Hook[]
*/
private $extensions = [];

/**
* @param TestSuiteLoader $loader
* @param CodeCoverageFilter $filter
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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'] !== '') {
Expand Down
25 changes: 25 additions & 0 deletions src/Util/Configuration.php
Expand Up @@ -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.
*
Expand Down
18 changes: 18 additions & 0 deletions tests/TextUI/_files/Extension.php
@@ -0,0 +1,18 @@
<?php
namespace PHPUnit\Test;

use PHPUnit\Runner\AfterLastTestHook;
use PHPUnit\Runner\BeforeFirstTestHook;

final class Extension implements BeforeFirstTestHook, AfterLastTestHook
{
public function executeAfterLastTest(): void
{
print __METHOD__ . PHP_EOL;
}

public function executeBeforeFirstTest(): void
{
print __METHOD__ . PHP_EOL;
}
}
11 changes: 11 additions & 0 deletions tests/TextUI/_files/NullPrinter.php
@@ -0,0 +1,11 @@
<?php
namespace PHPUnit\Test;

use PHPUnit\Util\Printer;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestListenerDefaultImplementation;

final class NullPrinter extends Printer implements TestListener
{
use TestListenerDefaultImplementation;
}
8 changes: 8 additions & 0 deletions tests/TextUI/_files/hooks.xml
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.1/phpunit.xsd"
printerClass="PHPUnit\Test\NullPrinter">
<extensions>
<extension class="PHPUnit\Test\Extension"/>
</extensions>
</phpunit>
16 changes: 16 additions & 0 deletions tests/TextUI/hooks.phpt
@@ -0,0 +1,16 @@
--TEST--
phpunit --configuration _files/hooks.xml BankAccountTest ../_files/BankAccountTest.php
--FILE--
<?php
$_SERVER['argv'][1] = '--configuration';
$_SERVER['argv'][2] = __DIR__ . '/_files/hooks.xml';
$_SERVER['argv'][3] = 'BankAccountTest';
$_SERVER['argv'][4] = __DIR__ . '/../_files/BankAccountTest.php';

require __DIR__ . '/../bootstrap.php';
PHPUnit\TextUI\Command::main();
--EXPECTF--
PHPUnit %s by Sebastian Bergmann and contributors.

PHPUnit\Test\Extension::executeBeforeFirstTest
PHPUnit\Test\Extension::executeAfterLastTest

0 comments on commit dfd2b13

Please sign in to comment.