diff --git a/.travis.yml b/.travis.yml index c00e525ce1..4321d9f320 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,4 +100,5 @@ script: - if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --ansi; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY"$REPORT"; fi - if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --ansi --prefer-lowest --prefer-stable; $PHPUNIT --exclude-group tty,benchmark,intl-data'"$REPORT"; fi # Test the PhpUnit bridge using the original phpunit script - - if [[ $deps = low ]]; then (cd src/Symfony/Bridge/PhpUnit && phpenv global 5.3 && php --version && composer update && phpunit); fi + - if [[ $deps = low ]]; then (cd src/Symfony/Bridge/PhpUnit && wget https://phar.phpunit.de/phpunit-4.8.phar); fi + - if [[ $deps = low ]]; then (cd src/Symfony/Bridge/PhpUnit && phpenv global 5.3 && php --version && composer update && php phpunit-4.8.phar); fi diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 831a26adaa..ff5d94c217 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -41,6 +41,8 @@ class DeprecationErrorHandler return; } + $UtilPrefix = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_' : 'PHPUnit\Util\\'; + $getMode = function () use ($mode) { static $memoizedMode = false; @@ -67,23 +69,26 @@ class DeprecationErrorHandler 'legacy' => array(), 'other' => array(), ); - $deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $getMode) { + $deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $getMode, $UtilPrefix) { $mode = $getMode(); if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || DeprecationErrorHandler::MODE_DISABLED === $mode) { - return \PHPUnit_Util_ErrorHandler::handleError($type, $msg, $file, $line, $context); + $ErrorHandler = $UtilPrefix.'ErrorHandler'; + + return $ErrorHandler::handleError($type, $msg, $file, $line, $context); } $trace = debug_backtrace(true); $group = 'other'; $i = count($trace); - while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_')))) { + while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) { // No-op } if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { $class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class']; $method = $trace[$i]['function']; + $Test = $UtilPrefix.'Test'; if (0 !== error_reporting()) { $group = 'unsilenced'; @@ -91,7 +96,7 @@ class DeprecationErrorHandler || 0 === strpos($method, 'provideLegacy') || 0 === strpos($method, 'getLegacy') || strpos($class, '\Legacy') - || in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) + || in_array('legacy', $Test::getGroups($class, $method), true) ) { $group = 'legacy'; } else { @@ -128,7 +133,7 @@ class DeprecationErrorHandler if (null !== $oldErrorHandler) { restore_error_handler(); - if (array('PHPUnit_Util_ErrorHandler', 'handleError') === $oldErrorHandler) { + if (array($UtilPrefix.'ErrorHandler', 'handleError') === $oldErrorHandler) { restore_error_handler(); self::register($mode); } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/Command.php b/src/Symfony/Bridge/PhpUnit/Legacy/Command.php new file mode 100644 index 0000000000..0aec8ab67f --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/Command.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +/** + * {@inheritdoc} + * + * @internal + */ +class Command extends \PHPUnit_TextUI_Command +{ + /** + * {@inheritdoc} + */ + protected function createRunner() + { + return new TestRunner($this->arguments['loader']); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php new file mode 100644 index 0000000000..a4e90941cb --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use Symfony\Bridge\PhpUnit\SymfonyTestsListenerTrait; + +/** + * Collects and replays skipped tests. + * + * @author Nicolas Grekas + * + * @internal + */ +class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener +{ + use SymfonyTestsListenerTrait; + + public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) + { + return $this->doStartTestSuite($suite); + } + + public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) + { + return $this->doAddSkippedTest($test, $e, $time); + } + + public function startTest(\PHPUnit_Framework_Test $test) + { + return $this->doStartTest($test); + } + + public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) + { + return $this->doAddWarning($test, $e, $time); + } + + public function endTest(\PHPUnit_Framework_Test $test, $time) + { + return $this->doEndTest($test, $time); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php new file mode 100644 index 0000000000..f3c6bb2b19 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +/** + * {@inheritdoc} + * + * @internal + */ +class TestRunner extends \PHPUnit_TextUI_TestRunner +{ + /** + * {@inheritdoc} + */ + protected function handleConfiguration(array &$arguments) + { + $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); + $arguments['listeners'][] = new SymfonyTestsListener(); + + return parent::handleConfiguration($arguments); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php index 47c360ac45..9dd1bdc8b0 100644 --- a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php +++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php @@ -11,234 +11,50 @@ namespace Symfony\Bridge\PhpUnit; -use Doctrine\Common\Annotations\AnnotationRegistry; +use PHPUnit\Framework\BaseTestListener; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; + +if (class_exists('PHPUnit_Framework_BaseTestListener')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListener', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); + + return; +} /** * Collects and replays skipped tests. * * @author Nicolas Grekas + * + * @final */ -class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener +class SymfonyTestsListener extends BaseTestListener { - private static $globallyEnabled = false; - private $state = -1; - private $skippedFile = false; - private $wasSkipped = array(); - private $isSkipped = array(); - private $expectedDeprecations = array(); - private $gatheredDeprecations = array(); - private $previousErrorHandler; - private $testsWithWarnings; + use SymfonyTestsListenerTrait; - /** - * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive) - */ - public function __construct(array $mockedNamespaces = array()) + public function startTestSuite(TestSuite $suite) { - \PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\DeprecationErrorHandler'] = 1; - \PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\SymfonyTestsListener'] = 1; - - $warn = false; - foreach ($mockedNamespaces as $type => $namespaces) { - if (!is_array($namespaces)) { - $namespaces = array($namespaces); - } - if (is_int($type)) { - // @deprecated BC with v2.8 to v3.0 - $type = 'time-sensitive'; - $warn = true; - } - if ('time-sensitive' === $type) { - foreach ($namespaces as $ns) { - ClockMock::register($ns.'\DummyClass'); - } - } - if ('dns-sensitive' === $type) { - foreach ($namespaces as $ns) { - DnsMock::register($ns.'\DummyClass'); - } - } - } - if (self::$globallyEnabled) { - $this->state = -2; - } else { - self::$globallyEnabled = true; - if ($warn) { - echo "Clock-mocked namespaces for SymfonyTestsListener need to be nested in a \"time-sensitive\" key. This will be enforced in Symfony 4.0.\n"; - } - } + return $this->doStartTestSuite($suite); } - public function __destruct() + public function addSkippedTest(Test $test, \Exception $e, $time) { - if (0 < $this->state) { - file_put_contents($this->skippedFile, 'isSkipped, true).';'); - } + return $this->doAddSkippedTest($test, $e, $time); } - public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) + public function startTest(Test $test) { - $suiteName = $suite->getName(); - $this->testsWithWarnings = array(); - - if (-1 === $this->state) { - echo "Testing $suiteName\n"; - $this->state = 0; - - if (!class_exists('Doctrine\Common\Annotations\AnnotationRegistry', false) && class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { - AnnotationRegistry::registerLoader('class_exists'); - } - - if ($this->skippedFile = getenv('SYMFONY_PHPUNIT_SKIPPED_TESTS')) { - $this->state = 1; - - if (file_exists($this->skippedFile)) { - $this->state = 2; - - if (!$this->wasSkipped = require $this->skippedFile) { - echo "All tests already ran successfully.\n"; - $suite->setTests(array()); - } - } - } - $testSuites = array($suite); - for ($i = 0; isset($testSuites[$i]); ++$i) { - foreach ($testSuites[$i]->tests() as $test) { - if ($test instanceof \PHPUnit_Framework_TestSuite) { - if (!class_exists($test->getName(), false)) { - $testSuites[] = $test; - continue; - } - $groups = \PHPUnit_Util_Test::getGroups($test->getName()); - if (in_array('time-sensitive', $groups, true)) { - ClockMock::register($test->getName()); - } - if (in_array('dns-sensitive', $groups, true)) { - DnsMock::register($test->getName()); - } - } - } - } - } elseif (2 === $this->state) { - $skipped = array(); - foreach ($suite->tests() as $test) { - if (!$test instanceof \PHPUnit_Framework_TestCase - || isset($this->wasSkipped[$suiteName]['*']) - || isset($this->wasSkipped[$suiteName][$test->getName()])) { - $skipped[] = $test; - } - } - $suite->setTests($skipped); - } + return $this->doStartTest($test); } - public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) + public function addWarning(Test $test, Warning $e, $time) { - if (0 < $this->state) { - if ($test instanceof \PHPUnit_Framework_TestCase) { - $class = get_class($test); - $method = $test->getName(); - } else { - $class = $test->getName(); - $method = '*'; - } - - $this->isSkipped[$class][$method] = 1; - } + return $this->doAddWarning($test, $e, $time); } - public function startTest(\PHPUnit_Framework_Test $test) + public function endTest(Test $test, $time) { - if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { - $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName(false)); - - if (in_array('time-sensitive', $groups, true)) { - ClockMock::register(get_class($test)); - ClockMock::withClockMock(true); - } - if (in_array('dns-sensitive', $groups, true)) { - DnsMock::register(get_class($test)); - } - - $annotations = \PHPUnit_Util_Test::parseTestMethodAnnotations(get_class($test), $test->getName(false)); - - if (isset($annotations['class']['expectedDeprecation'])) { - $test->getTestResultObject()->addError($test, new \PHPUnit_Framework_AssertionFailedError('`@expectedDeprecation` annotations are not allowed at the class level.'), 0); - } - if (isset($annotations['method']['expectedDeprecation'])) { - if (!in_array('legacy', $groups, true)) { - $test->getTestResultObject()->addError($test, new \PHPUnit_Framework_AssertionFailedError('Only tests with the `@group legacy` annotation can have `@expectedDeprecation`.'), 0); - } - $this->expectedDeprecations = $annotations['method']['expectedDeprecation']; - $this->previousErrorHandler = set_error_handler(array($this, 'handleError')); - } - } - } - - public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) - { - if ($test instanceof \PHPUnit_Framework_TestCase) { - $this->testsWithWarnings[$test->getName()] = true; - } - } - - public function endTest(\PHPUnit_Framework_Test $test, $time) - { - $className = get_class($test); - $classGroups = \PHPUnit_Util_Test::getGroups($className); - $groups = \PHPUnit_Util_Test::getGroups($className, $test->getName(false)); - - if ($this->expectedDeprecations) { - restore_error_handler(); - - if (!in_array($test->getStatus(), array(\PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED, \PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE, \PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE, \PHPUnit_Runner_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 (\PHPUnit_Framework_AssertionFailedError $e) { - $test->getTestResultObject()->addFailure($test, $e, $time); - } - } - - $this->expectedDeprecations = $this->gatheredDeprecations = array(); - $this->previousErrorHandler = null; - } - if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { - if (in_array('time-sensitive', $groups, true)) { - ClockMock::withClockMock(false); - } - if (in_array('dns-sensitive', $groups, true)) { - DnsMock::withMockedHosts(array()); - } - } - - if ($test instanceof \PHPUnit_Framework_TestCase && 0 === strpos($test->getName(), 'testLegacy') && !isset($this->testsWithWarnings[$test->getName()]) && !in_array('legacy', $groups, true)) { - $result = $test->getTestResultObject(); - - if (method_exists($result, 'addWarning')) { - $result->addWarning($test, new \PHPUnit_Framework_Warning('Using the "testLegacy" prefix to mark tests as legacy is deprecated since version 3.3 and will be removed in 4.0. Use the "@group legacy" notation instead to add the test to the legacy group.'), $time); - } - } - - if ($test instanceof \PHPUnit_Framework_TestCase && strpos($className, '\Legacy') && !isset($this->testsWithWarnings[$test->getName()]) && !in_array('legacy', $classGroups, true)) { - $result = $test->getTestResultObject(); - - if (method_exists($result, 'addWarning')) { - $result->addWarning($test, new \PHPUnit_Framework_Warning('Using the "Legacy" prefix to mark all tests of a class as legacy is deprecated since version 3.3 and will be removed in 4.0. Use the "@group legacy" notation instead to add the test to the legacy group.'), $time); - } - } - } - - public function handleError($type, $msg, $file, $line, $context) - { - if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { - $h = $this->previousErrorHandler; - - return $h ? $h($type, $msg, $file, $line, $context) : false; - } - if (error_reporting()) { - $msg = 'Unsilenced deprecation: '.$msg; - } - $this->gatheredDeprecations[] = $msg; + return $this->doEndTest($test, $time); } } diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListenerTrait.php new file mode 100644 index 0000000000..f91c2eac70 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListenerTrait.php @@ -0,0 +1,283 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit; + +use Doctrine\Common\Annotations\AnnotationRegistry; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Util\Blacklist; +use PHPUnit\Util\Test; + +/** + * Collects and replays skipped tests. + * + * @author Nicolas Grekas + * + * @internal + */ +trait SymfonyTestsListenerTrait +{ + private static $globallyEnabled = false; + private $state = -1; + private $skippedFile = false; + private $wasSkipped = array(); + private $isSkipped = array(); + private $expectedDeprecations = array(); + private $gatheredDeprecations = array(); + private $previousErrorHandler; + private $testsWithWarnings; + + /** + * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive) + */ + public function __construct(array $mockedNamespaces = array()) + { + if (class_exists('PHPUnit_Util_Blacklist')) { + \PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\DeprecationErrorHandler'] = 1; + \PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\SymfonyTestsListener'] = 1; + \PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\SymfonyTestsListenerTrait'] = 1; + \PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListener'] = 1; + } else { + Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\DeprecationErrorHandler'] = 1; + Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\SymfonyTestsListener'] = 1; + Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\SymfonyTestsListenerTrait'] = 1; + Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListener'] = 1; + } + + $warn = false; + foreach ($mockedNamespaces as $type => $namespaces) { + if (!is_array($namespaces)) { + $namespaces = array($namespaces); + } + if (is_int($type)) { + // @deprecated BC with v2.8 to v3.0 + $type = 'time-sensitive'; + $warn = true; + } + if ('time-sensitive' === $type) { + foreach ($namespaces as $ns) { + ClockMock::register($ns.'\DummyClass'); + } + } + if ('dns-sensitive' === $type) { + foreach ($namespaces as $ns) { + DnsMock::register($ns.'\DummyClass'); + } + } + } + if (self::$globallyEnabled) { + $this->state = -2; + } else { + self::$globallyEnabled = true; + if ($warn) { + echo "Clock-mocked namespaces for SymfonyTestsListener need to be nested in a \"time-sensitive\" key. This will be enforced in Symfony 4.0.\n"; + } + } + } + + public function __destruct() + { + if (0 < $this->state) { + file_put_contents($this->skippedFile, 'isSkipped, true).';'); + } + } + + private function doStartTestSuite($suite) + { + if (class_exists('PHPUnit_Util_Blacklist', false)) { + $Test = 'PHPUnit_Util_Test'; + } else { + $Test = 'PHPUnit\Util\Test'; + } + $suiteName = $suite->getName(); + $this->testsWithWarnings = array(); + + if (-1 === $this->state) { + echo "Testing $suiteName\n"; + $this->state = 0; + + if (!class_exists('Doctrine\Common\Annotations\AnnotationRegistry', false) && class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { + AnnotationRegistry::registerLoader('class_exists'); + } + + if ($this->skippedFile = getenv('SYMFONY_PHPUNIT_SKIPPED_TESTS')) { + $this->state = 1; + + if (file_exists($this->skippedFile)) { + $this->state = 2; + + if (!$this->wasSkipped = require $this->skippedFile) { + echo "All tests already ran successfully.\n"; + $suite->setTests(array()); + } + } + } + $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 (!class_exists($test->getName(), false)) { + $testSuites[] = $test; + continue; + } + $groups = $Test::getGroups($test->getName()); + if (in_array('time-sensitive', $groups, true)) { + ClockMock::register($test->getName()); + } + if (in_array('dns-sensitive', $groups, true)) { + DnsMock::register($test->getName()); + } + } + } + } + } elseif (2 === $this->state) { + $skipped = array(); + foreach ($suite->tests() as $test) { + if (!($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase) + || isset($this->wasSkipped[$suiteName]['*']) + || isset($this->wasSkipped[$suiteName][$test->getName()])) { + $skipped[] = $test; + } + } + $suite->setTests($skipped); + } + } + + private function doAddSkippedTest($test, \Exception $e, $time) + { + if (0 < $this->state) { + if ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase) { + $class = get_class($test); + $method = $test->getName(); + } else { + $class = $test->getName(); + $method = '*'; + } + + $this->isSkipped[$class][$method] = 1; + } + } + + private function doStartTest($test) + { + if (-2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) { + 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)); + + if (in_array('time-sensitive', $groups, true)) { + ClockMock::register(get_class($test)); + ClockMock::withClockMock(true); + } + if (in_array('dns-sensitive', $groups, true)) { + DnsMock::register(get_class($test)); + } + + $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); + } + if (isset($annotations['method']['expectedDeprecation'])) { + if (!in_array('legacy', $groups, true)) { + $test->getTestResultObject()->addError($test, new $AssertionFailedError('Only tests with the `@group legacy` annotation can have `@expectedDeprecation`.'), 0); + } + $this->expectedDeprecations = $annotations['method']['expectedDeprecation']; + $this->previousErrorHandler = set_error_handler(array($this, 'handleError')); + } + } + } + + private function doAddWarning($test, $e, $time) + { + if ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase) { + $this->testsWithWarnings[$test->getName()] = true; + } + } + + private function doEndTest($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)); + + if ($this->expectedDeprecations) { + restore_error_handler(); + + if (!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); + } + } + + $this->expectedDeprecations = $this->gatheredDeprecations = array(); + $this->previousErrorHandler = null; + } + if (-2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) { + if (in_array('time-sensitive', $groups, true)) { + ClockMock::withClockMock(false); + } + if (in_array('dns-sensitive', $groups, true)) { + DnsMock::withMockedHosts(array()); + } + } + + if (($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase) && 0 === strpos($test->getName(), 'testLegacy') && !isset($this->testsWithWarnings[$test->getName()]) && !in_array('legacy', $groups, true)) { + $result = $test->getTestResultObject(); + + if (method_exists($result, 'addWarning')) { + $result->addWarning($test, new $Warning('Using the "testLegacy" prefix to mark tests as legacy is deprecated since version 3.3 and will be removed in 4.0. Use the "@group legacy" notation instead to add the test to the legacy group.'), $time); + } + } + + if (($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase) && strpos($className, '\Legacy') && !isset($this->testsWithWarnings[$test->getName()]) && !in_array('legacy', $classGroups, true)) { + $result = $test->getTestResultObject(); + + if (method_exists($result, 'addWarning')) { + $result->addWarning($test, new $Warning('Using the "Legacy" prefix to mark all tests of a class as legacy is deprecated since version 3.3 and will be removed in 4.0. Use the "@group legacy" notation instead to add the test to the legacy group.'), $time); + } + } + } + + public function handleError($type, $msg, $file, $line, $context) + { + if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { + $h = $this->previousErrorHandler; + + return $h ? $h($type, $msg, $file, $line, $context) : false; + } + if (error_reporting()) { + $msg = 'Unsilenced deprecation: '.$msg; + } + $this->gatheredDeprecations[] = $msg; + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt index fac5c53ae7..9f8524f525 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt @@ -18,13 +18,18 @@ require_once __DIR__.'/../../bootstrap.php'; @trigger_error('root deprecation', E_USER_DEPRECATED); -class PHPUnit_Util_Test +eval(<<<'EOPHP' +namespace PHPUnit\Util; + +class Test { public static function getGroups() { return array(); } } +EOPHP +); class FooTestCase { diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php index 034d07a30f..a178ac7e89 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php @@ -11,9 +11,10 @@ namespace Symfony\Bridge\PhpUnit\Tests; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\DnsMock; -class DnsMockTest extends \PHPUnit_Framework_TestCase +class DnsMockTest extends TestCase { protected function tearDown() { diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php index 203fd16414..77b617fdcc 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php @@ -11,10 +11,20 @@ namespace Symfony\Bridge\PhpUnit\TextUI; +use PHPUnit\TextUI\Command as BaseCommand; + +if (class_exists('PHPUnit_TextUI_Command')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\Command', 'Symfony\Bridge\PhpUnit\TextUI\Command'); + + return; +} + /** * {@inheritdoc} + * + * @internal */ -class Command extends \PHPUnit_TextUI_Command +class Command extends BaseCommand { /** * {@inheritdoc} diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php index 94602bb3d6..2d3259759b 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php @@ -11,12 +11,21 @@ namespace Symfony\Bridge\PhpUnit\TextUI; +use PHPUnit\TextUI\TestRunner as BaseRunner; use Symfony\Bridge\PhpUnit\SymfonyTestsListener; +if (class_exists('PHPUnit_TextUI_Command')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunner', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); + + return; +} + /** * {@inheritdoc} + * + * @internal */ -class TestRunner extends \PHPUnit_TextUI_TestRunner +class TestRunner extends BaseRunner { /** * {@inheritdoc} diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index 5e2ed0ca85..f2ceffb1b5 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -13,7 +13,7 @@ use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; // Detect if we're loaded by an actual run of phpunit -if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists('PHPUnit_TextUI_Command', false)) { +if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists('PHPUnit_TextUI_Command', false) && !class_exists('PHPUnit\TextUI\Command', false)) { return; } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index bbc99f10cd..847bb691d3 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -25,7 +25,7 @@ "ext-zip": "Zip support is required when using bin/simple-phpunit" }, "conflict": { - "phpunit/phpunit": ">=6.0" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" }, "autoload": { "files": [ "bootstrap.php" ],