feature #32940 [PhpUnitBridge] Add polyfill for PhpUnit namespace (jderusse)

This PR was merged into the 4.4 branch.

Discussion
----------

[PhpUnitBridge] Add polyfill for PhpUnit namespace

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? |no
| Tests pass?   | yes
| Fixed tickets | #32844
| License       | MIT
| Doc PR        | NA

This PR provides PhpUnit >= 6 Namespaces class.

This will simplify tests that have to be compatible with multiple version of PHPUnit

```diff
- if (class_exists('PHPUnit_Foo')) {
-   PHPUnit_Foo::bar():
- } else {
-   \PHPUnit\Foo::bar();
- }
+ \PHPUnit\Foo::bar();
```

WIP  => waiting for #32941 to be green

Commits
-------

b7520f738d Add polyfill for PhpUnit namespace
This commit is contained in:
Nicolas Grekas 2019-08-05 15:08:36 +02:00
commit 0c95aad721
12 changed files with 123 additions and 73 deletions

View File

@ -6,6 +6,7 @@ CHANGELOG
* made the bridge act as a polyfill for newest PHPUnit features
* added `SetUpTearDownTrait` to allow working around the `void` return-type added by PHPUnit 8
* added namespace aliases for PHPUnit < 6
4.3.0
-----

View File

@ -11,7 +11,7 @@
namespace Symfony\Bridge\PhpUnit;
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) {
class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV5', 'Symfony\Bridge\PhpUnit\CoverageListener');
} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) {
class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV6', 'Symfony\Bridge\PhpUnit\CoverageListener');

View File

@ -11,6 +11,7 @@
namespace Symfony\Bridge\PhpUnit;
use PHPUnit\Util\ErrorHandler;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Configuration;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation;
@ -48,7 +49,6 @@ class DeprecationErrorHandler
];
private static $isRegistered = false;
private static $utilPrefix;
/**
* Registers and configures the deprecation handler.
@ -72,15 +72,13 @@ class DeprecationErrorHandler
return;
}
self::$utilPrefix = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_' : 'PHPUnit\Util\\';
$handler = new self();
$oldErrorHandler = set_error_handler([$handler, 'handleError']);
if (null !== $oldErrorHandler) {
restore_error_handler();
if ([self::$utilPrefix.'ErrorHandler', 'handleError'] === $oldErrorHandler) {
if ([ErrorHandler::class, 'handleError'] === $oldErrorHandler) {
restore_error_handler();
self::register($mode);
}
@ -100,12 +98,7 @@ class DeprecationErrorHandler
return $previousErrorHandler($type, $msg, $file, $line, $context);
}
static $autoload = true;
$ErrorHandler = class_exists('PHPUnit_Util_ErrorHandler', $autoload) ? 'PHPUnit_Util_ErrorHandler' : 'PHPUnit\Util\ErrorHandler';
$autoload = false;
return $ErrorHandler::handleError($type, $msg, $file, $line, $context);
return ErrorHandler::handleError($type, $msg, $file, $line, $context);
}
$deprecations[] = [error_reporting(), $msg, $file];
@ -122,9 +115,7 @@ class DeprecationErrorHandler
public function handleError($type, $msg, $file, $line, $context = [])
{
if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || !$this->getConfiguration()->isEnabled()) {
$ErrorHandler = self::$utilPrefix.'ErrorHandler';
return $ErrorHandler::handleError($type, $msg, $file, $line, $context);
return ErrorHandler::handleError($type, $msg, $file, $line, $context);
}
$deprecation = new Deprecation($msg, debug_backtrace(), $file);
@ -140,7 +131,7 @@ class DeprecationErrorHandler
if (0 !== error_reporting()) {
$group = 'unsilenced';
} elseif ($deprecation->isLegacy(self::$utilPrefix)) {
} elseif ($deprecation->isLegacy()) {
$group = 'legacy';
} else {
$group = [

View File

@ -11,6 +11,7 @@
namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
use PHPUnit\Util\Test;
use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor;
/**
@ -156,9 +157,8 @@ class Deprecation
*
* @return bool
*/
public function isLegacy($utilPrefix)
public function isLegacy()
{
$test = $utilPrefix.'Test';
$class = $this->originatingClass();
$method = $this->originatingMethod();
@ -166,7 +166,7 @@ class Deprecation
|| 0 === strpos($method, 'provideLegacy')
|| 0 === strpos($method, 'getLegacy')
|| strpos($class, '\Legacy')
|| \in_array('legacy', $test::getGroups($class, $method), true);
|| \in_array('legacy', Test::getGroups($class, $method), true);
}
/**

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\PhpUnit\Legacy;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Test;
/**
* PHP 5.3 compatible trait-like shared implementation.
@ -65,12 +66,7 @@ class CoverageListenerTrait
return;
}
$testClass = \PHPUnit\Util\Test::class;
if (!class_exists($testClass, false)) {
$testClass = \PHPUnit_Util_Test::class;
}
$r = new \ReflectionProperty($testClass, 'annotationCache');
$r = new \ReflectionProperty(Test::class, 'annotationCache');
$r->setAccessible(true);
$cache = $r->getValue();
@ -79,7 +75,7 @@ class CoverageListenerTrait
'covers' => \is_array($sutFqcn) ? $sutFqcn : array($sutFqcn),
),
));
$r->setValue($testClass, $cache);
$r->setValue(Test::class, $cache);
}
private function findSutFqcn($test)

View File

@ -66,7 +66,7 @@ trait PolyfillTestCaseTrait
*/
public function expectException($exception)
{
$property = new \ReflectionProperty(class_exists('PHPUnit_Framework_TestCase') ? 'PHPUnit_Framework_TestCase' : TestCase::class, 'expectedException');
$property = new \ReflectionProperty(TestCase::class, 'expectedException');
$property->setAccessible(true);
$property->setValue($this, $exception);
}
@ -78,7 +78,7 @@ trait PolyfillTestCaseTrait
*/
public function expectExceptionCode($code)
{
$property = new \ReflectionProperty(class_exists('PHPUnit_Framework_TestCase') ? 'PHPUnit_Framework_TestCase' : TestCase::class, 'expectedExceptionCode');
$property = new \ReflectionProperty(TestCase::class, 'expectedExceptionCode');
$property->setAccessible(true);
$property->setValue($this, $code);
}
@ -90,7 +90,7 @@ trait PolyfillTestCaseTrait
*/
public function expectExceptionMessage($message)
{
$property = new \ReflectionProperty(class_exists('PHPUnit_Framework_TestCase') ? 'PHPUnit_Framework_TestCase' : TestCase::class, 'expectedExceptionMessage');
$property = new \ReflectionProperty(TestCase::class, 'expectedExceptionMessage');
$property->setAccessible(true);
$property->setValue($this, $message);
}
@ -102,7 +102,7 @@ trait PolyfillTestCaseTrait
*/
public function expectExceptionMessageRegExp($messageRegExp)
{
$property = new \ReflectionProperty(class_exists('PHPUnit_Framework_TestCase') ? 'PHPUnit_Framework_TestCase' : TestCase::class, 'expectedExceptionMessageRegExp');
$property = new \ReflectionProperty(TestCase::class, 'expectedExceptionMessageRegExp');
$property->setAccessible(true);
$property->setValue($this, $messageRegExp);
}

View File

@ -15,7 +15,9 @@ use Doctrine\Common\Annotations\AnnotationRegistry;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\BaseTestRunner;
use PHPUnit\Util\Blacklist;
use PHPUnit\Util\Test;
use Symfony\Bridge\PhpUnit\ClockMock;
use Symfony\Bridge\PhpUnit\DnsMock;
use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
@ -48,11 +50,7 @@ class SymfonyTestsListenerTrait
*/
public function __construct(array $mockedNamespaces = array())
{
if (class_exists('PHPUnit_Util_Blacklist')) {
\PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait'] = 2;
} else {
Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait'] = 2;
}
Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait'] = 2;
$enableDebugClassLoader = class_exists(DebugClassLoader::class) || class_exists(LegacyDebugClassLoader::class);
@ -113,11 +111,6 @@ class SymfonyTestsListenerTrait
public function startTestSuite($suite)
{
if (class_exists('PHPUnit_Util_Blacklist', false)) {
$Test = 'PHPUnit_Util_Test';
} else {
$Test = 'PHPUnit\Util\Test';
}
$suiteName = $suite->getName();
$this->testsWithWarnings = array();
@ -125,7 +118,7 @@ class SymfonyTestsListenerTrait
if (!($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) {
continue;
}
if (null === $Test::getPreserveGlobalStateSettings(\get_class($test), $test->getName(false))) {
if (null === Test::getPreserveGlobalStateSettings(\get_class($test), $test->getName(false))) {
$test->setPreserveGlobalState(false);
}
}
@ -157,12 +150,12 @@ class SymfonyTestsListenerTrait
$testSuites = array($suite);
for ($i = 0; isset($testSuites[$i]); ++$i) {
foreach ($testSuites[$i]->tests() as $test) {
if ($test instanceof \PHPUnit_Framework_TestSuite || $test instanceof TestSuite) {
if ($test instanceof TestSuite) {
if (!class_exists($test->getName(), false)) {
$testSuites[] = $test;
continue;
}
$groups = $Test::getGroups($test->getName());
$groups = Test::getGroups($test->getName());
if (\in_array('time-sensitive', $groups, true)) {
ClockMock::register($test->getName());
}
@ -213,14 +206,7 @@ class SymfonyTestsListenerTrait
putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$this->runsInSeparateProcess);
}
if (class_exists('PHPUnit_Util_Blacklist', false)) {
$Test = 'PHPUnit_Util_Test';
$AssertionFailedError = 'PHPUnit_Framework_AssertionFailedError';
} else {
$Test = 'PHPUnit\Util\Test';
$AssertionFailedError = 'PHPUnit\Framework\AssertionFailedError';
}
$groups = $Test::getGroups(\get_class($test), $test->getName(false));
$groups = Test::getGroups(\get_class($test), $test->getName(false));
if (!$this->runsInSeparateProcess) {
if (\in_array('time-sensitive', $groups, true)) {
@ -232,14 +218,14 @@ class SymfonyTestsListenerTrait
}
}
$annotations = $Test::parseTestMethodAnnotations(\get_class($test), $test->getName(false));
$annotations = Test::parseTestMethodAnnotations(\get_class($test), $test->getName(false));
if (isset($annotations['class']['expectedDeprecation'])) {
$test->getTestResultObject()->addError($test, new $AssertionFailedError('`@expectedDeprecation` annotations are not allowed at the class level.'), 0);
$test->getTestResultObject()->addError($test, new AssertionFailedError('`@expectedDeprecation` annotations are not allowed at the class level.'), 0);
}
if (isset($annotations['method']['expectedDeprecation'])) {
if (!\in_array('legacy', $groups, true)) {
$this->error = new $AssertionFailedError('Only tests with the `@group legacy` annotation can have `@expectedDeprecation`.');
$this->error = new AssertionFailedError('Only tests with the `@group legacy` annotation can have `@expectedDeprecation`.');
}
$test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
@ -259,18 +245,8 @@ class SymfonyTestsListenerTrait
public function endTest($test, $time)
{
if (class_exists('PHPUnit_Util_Blacklist', false)) {
$Test = 'PHPUnit_Util_Test';
$BaseTestRunner = 'PHPUnit_Runner_BaseTestRunner';
$Warning = 'PHPUnit_Framework_Warning';
} else {
$Test = 'PHPUnit\Util\Test';
$BaseTestRunner = 'PHPUnit\Runner\BaseTestRunner';
$Warning = 'PHPUnit\Framework\Warning';
}
$className = \get_class($test);
$classGroups = $Test::getGroups($className);
$groups = $Test::getGroups($className, $test->getName(false));
$groups = Test::getGroups($className, $test->getName(false));
if (null !== $this->reportUselessTests) {
$test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything($this->reportUselessTests);
@ -299,20 +275,18 @@ class SymfonyTestsListenerTrait
}
if ($this->expectedDeprecations) {
if (!\in_array($test->getStatus(), array($BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE), true)) {
if (!\in_array($test->getStatus(), array(BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE), true)) {
$test->addToAssertionCount(\count($this->expectedDeprecations));
}
restore_error_handler();
if (!$errored && !\in_array($test->getStatus(), array($BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE, $BaseTestRunner::STATUS_FAILURE, $BaseTestRunner::STATUS_ERROR), true)) {
if (!$errored && !\in_array($test->getStatus(), array(BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR), true)) {
try {
$prefix = "@expectedDeprecation:\n";
$test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", $this->expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", $this->gatheredDeprecations)."\n");
} catch (AssertionFailedError $e) {
$test->getTestResultObject()->addFailure($test, $e, $time);
} catch (\PHPUnit_Framework_AssertionFailedError $e) {
$test->getTestResultObject()->addFailure($test, $e, $time);
}
}

View File

@ -11,7 +11,7 @@
namespace Symfony\Bridge\PhpUnit;
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) {
class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) {
class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');

View File

@ -14,7 +14,7 @@ require __DIR__.'/../src/FooCov.php';
require __DIR__.'/../../../../Legacy/CoverageListenerTrait.php';
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) {
require_once __DIR__.'/../../../../Legacy/CoverageListenerForV5.php';
} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) {
require_once __DIR__.'/../../../../Legacy/CoverageListenerForV6.php';

View File

@ -24,8 +24,7 @@ class ProcessIsolationTest extends TestCase
public function testCallingOtherErrorHandler()
{
$class = class_exists('PHPUnit\Framework\Exception') ? 'PHPUnit\Framework\Exception' : 'PHPUnit_Framework_Exception';
$this->expectException($class);
$this->expectException(\class_exists('PHPUnit_Framework_Exception') ? 'PHPUnit_Framework_Exception' : 'PHPUnit\Framework\Exception');
$this->expectExceptionMessage('Test that PHPUnit\'s error handler fires.');
trigger_error('Test that PHPUnit\'s error handler fires.', E_USER_WARNING);

View File

@ -11,7 +11,7 @@
namespace Symfony\Bridge\PhpUnit\TextUI;
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) {
class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV5', 'Symfony\Bridge\PhpUnit\TextUI\Command');
} else {
class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV6', 'Symfony\Bridge\PhpUnit\TextUI\Command');

View File

@ -12,6 +12,95 @@
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
$classes = [
'PHPUnit_Framework_Assert', // override PhpUnit's ForwardCompat child class
'PHPUnit_Framework_AssertionFailedError', // override PhpUnit's ForwardCompat child class
'PHPUnit_Framework_BaseTestListener', // override PhpUnit's ForwardCompat child class
'PHPUnit_Framework_Constraint',
'PHPUnit_Framework_Constraint_And',
'PHPUnit_Framework_Constraint_ArrayHasKey',
'PHPUnit_Framework_Constraint_ArraySubset',
'PHPUnit_Framework_Constraint_Attribute',
'PHPUnit_Framework_Constraint_Callback',
'PHPUnit_Framework_Constraint_ClassHasAttribute',
'PHPUnit_Framework_Constraint_ClassHasStaticAttribute',
'PHPUnit_Framework_Constraint_Composite',
'PHPUnit_Framework_Constraint_Count',
'PHPUnit_Framework_Constraint_Exception',
'PHPUnit_Framework_Constraint_ExceptionCode',
'PHPUnit_Framework_Constraint_ExceptionMessage',
'PHPUnit_Framework_Constraint_ExceptionMessageRegExp',
'PHPUnit_Framework_Constraint_FileExists',
'PHPUnit_Framework_Constraint_GreaterThan',
'PHPUnit_Framework_Constraint_IsAnything',
'PHPUnit_Framework_Constraint_IsEmpty',
'PHPUnit_Framework_Constraint_IsEqual',
'PHPUnit_Framework_Constraint_IsFalse',
'PHPUnit_Framework_Constraint_IsIdentical',
'PHPUnit_Framework_Constraint_IsInstanceOf',
'PHPUnit_Framework_Constraint_IsJson',
'PHPUnit_Framework_Constraint_IsNull',
'PHPUnit_Framework_Constraint_IsTrue',
'PHPUnit_Framework_Constraint_IsType',
'PHPUnit_Framework_Constraint_JsonMatches',
'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider',
'PHPUnit_Framework_Constraint_LessThan',
'PHPUnit_Framework_Constraint_Not',
'PHPUnit_Framework_Constraint_ObjectHasAttribute',
'PHPUnit_Framework_Constraint_Or',
'PHPUnit_Framework_Constraint_PCREMatch',
'PHPUnit_Framework_Constraint_SameSize',
'PHPUnit_Framework_Constraint_StringContains',
'PHPUnit_Framework_Constraint_StringEndsWith',
'PHPUnit_Framework_Constraint_StringMatches',
'PHPUnit_Framework_Constraint_StringStartsWith',
'PHPUnit_Framework_Constraint_TraversableContains',
'PHPUnit_Framework_Constraint_TraversableContainsOnly',
'PHPUnit_Framework_Constraint_Xor',
'PHPUnit_Framework_Error',
'PHPUnit_Framework_Error_Deprecated',
'PHPUnit_Framework_Error_Notice',
'PHPUnit_Framework_Error_Warning',
'PHPUnit_Framework_Exception',
'PHPUnit_Framework_ExpectationFailedException',
'PHPUnit_Framework_MockObject_MockObject',
'PHPUnit_Framework_IncompleteTest',
'PHPUnit_Framework_IncompleteTestCase',
'PHPUnit_Framework_IncompleteTestError',
'PHPUnit_Framework_RiskyTest',
'PHPUnit_Framework_RiskyTestError',
'PHPUnit_Framework_SkippedTest',
'PHPUnit_Framework_SkippedTestCase',
'PHPUnit_Framework_SkippedTestError',
'PHPUnit_Framework_SkippedTestSuiteError',
'PHPUnit_Framework_SyntheticError',
'PHPUnit_Framework_Test',
'PHPUnit_Framework_TestCase', // override PhpUnit's ForwardCompat child class
'PHPUnit_Framework_TestFailure',
'PHPUnit_Framework_TestListener',
'PHPUnit_Framework_TestResult',
'PHPUnit_Framework_TestSuite', // override PhpUnit's ForwardCompat child class
'PHPUnit_Runner_BaseTestRunner',
'PHPUnit_Runner_Version',
'PHPUnit_Util_Blacklist',
'PHPUnit_Util_ErrorHandler',
'PHPUnit_Util_Test',
'PHPUnit_Util_XML',
];
foreach ($classes as $class) {
class_alias($class, '\\'.strtr($class, '_', '\\'), true);
}
}
// Detect if we need to serialize deprecations to a file.
if ($file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) {
DeprecationErrorHandler::collectDeprecations($file);