[PhpUnitBridge] Add the ability to expect a deprecation inside a test

This commit is contained in:
Thomas Calvet 2020-01-03 12:27:29 +01:00 committed by Nicolas Grekas
parent 9eb7cb1b7b
commit a3a928050d
4 changed files with 139 additions and 22 deletions

View File

@ -6,6 +6,7 @@ CHANGELOG
* ignore verbosity settings when the build fails because of deprecations
* added per-group verbosity
* added `ExpectDeprecationTrait` to be able to define an expected deprecation from inside a test
5.0.0
-----

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PhpUnit;
use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait;
trait ExpectDeprecationTrait
{
/**
* @param string $message
*
* @return void
*/
protected function expectDeprecation($message)
{
if (!SymfonyTestsListenerTrait::$previousErrorHandler) {
SymfonyTestsListenerTrait::$previousErrorHandler = set_error_handler([SymfonyTestsListenerTrait::class, 'handleError']);
}
SymfonyTestsListenerTrait::$expectedDeprecations[] = $message;
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\PhpUnit\Legacy;
use Doctrine\Common\Annotations\AnnotationRegistry;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\RiskyTestError;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\BaseTestRunner;
@ -20,6 +21,7 @@ use PHPUnit\Util\Blacklist;
use PHPUnit\Util\Test;
use Symfony\Bridge\PhpUnit\ClockMock;
use Symfony\Bridge\PhpUnit\DnsMock;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
use Symfony\Component\ErrorHandler\DebugClassLoader;
@ -32,16 +34,16 @@ use Symfony\Component\ErrorHandler\DebugClassLoader;
*/
class SymfonyTestsListenerTrait
{
public static $expectedDeprecations = [];
public static $previousErrorHandler;
private static $gatheredDeprecations = [];
private static $globallyEnabled = false;
private $state = -1;
private $skippedFile = false;
private $wasSkipped = [];
private $isSkipped = [];
private $expectedDeprecations = [];
private $gatheredDeprecations = [];
private $previousErrorHandler;
private $error;
private $runsInSeparateProcess = false;
private $checkNumAssertions = false;
/**
* @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive)
@ -220,15 +222,17 @@ class SymfonyTestsListenerTrait
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)) {
$this->error = new AssertionFailedError('Only tests with the `@group legacy` annotation can have `@expectedDeprecation`.');
if (isset($annotations['method']['expectedDeprecation']) || $this->checkNumAssertions = \in_array(ExpectDeprecationTrait::class, class_uses($test), true)) {
if (isset($annotations['method']['expectedDeprecation'])) {
self::$expectedDeprecations = $annotations['method']['expectedDeprecation'];
self::$previousErrorHandler = set_error_handler([self::class, 'handleError']);
}
if ($this->checkNumAssertions) {
$this->checkNumAssertions = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything() && !$test->doesNotPerformAssertions();
}
$test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
$this->expectedDeprecations = $annotations['method']['expectedDeprecation'];
$this->previousErrorHandler = set_error_handler([$this, 'handleError']);
}
}
}
@ -242,9 +246,12 @@ class SymfonyTestsListenerTrait
$className = \get_class($test);
$groups = Test::getGroups($className, $test->getName(false));
if ($errored = null !== $this->error) {
$test->getTestResultObject()->addError($test, $this->error, 0);
$this->error = null;
if ($this->checkNumAssertions) {
if (!self::$expectedDeprecations && !$test->getNumAssertions()) {
$test->getTestResultObject()->addFailure($test, new RiskyTestError('This test did not perform any assertions'), $time);
}
$this->checkNumAssertions = false;
}
if ($this->runsInSeparateProcess) {
@ -263,24 +270,26 @@ class SymfonyTestsListenerTrait
$this->runsInSeparateProcess = false;
}
if ($this->expectedDeprecations) {
if (self::$expectedDeprecations) {
if (!\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true)) {
$test->addToAssertionCount(\count($this->expectedDeprecations));
$test->addToAssertionCount(\count(self::$expectedDeprecations));
}
restore_error_handler();
if (!$errored && !\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR], true)) {
if (!\in_array('legacy', $groups, true)) {
$test->getTestResultObject()->addError($test, new AssertionFailedError('Only tests with the `@group legacy` annotation can expect a deprecation.'), 0);
} elseif (!\in_array($test->getStatus(), [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");
$test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", self::$expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", self::$gatheredDeprecations)."\n");
} catch (AssertionFailedError $e) {
$test->getTestResultObject()->addFailure($test, $e, $time);
}
}
$this->expectedDeprecations = $this->gatheredDeprecations = [];
$this->previousErrorHandler = null;
self::$expectedDeprecations = self::$gatheredDeprecations = [];
self::$previousErrorHandler = null;
}
if (!$this->runsInSeparateProcess && -2 < $this->state && ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) {
if (\in_array('time-sensitive', $groups, true)) {
@ -292,10 +301,10 @@ class SymfonyTestsListenerTrait
}
}
public function handleError($type, $msg, $file, $line, $context = [])
public static function handleError($type, $msg, $file, $line, $context = [])
{
if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) {
$h = $this->previousErrorHandler;
$h = self::$previousErrorHandler;
return $h ? $h($type, $msg, $file, $line, $context) : false;
}
@ -308,7 +317,7 @@ class SymfonyTestsListenerTrait
if (error_reporting()) {
$msg = 'Unsilenced deprecation: '.$msg;
}
$this->gatheredDeprecations[] = $msg;
self::$gatheredDeprecations[] = $msg;
return null;
}

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PhpUnit\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
final class ExpectDeprecationTraitTest extends TestCase
{
use ExpectDeprecationTrait;
/**
* Do not remove this test in the next major version.
*
* @group legacy
*/
public function testOne()
{
$this->expectDeprecation('foo');
@trigger_error('foo', E_USER_DEPRECATED);
}
/**
* Do not remove this test in the next major version.
*
* @group legacy
*/
public function testMany()
{
$this->expectDeprecation('foo');
$this->expectDeprecation('bar');
@trigger_error('foo', E_USER_DEPRECATED);
@trigger_error('bar', E_USER_DEPRECATED);
}
/**
* Do not remove this test in the next major version.
*
* @group legacy
*
* @expectedDeprecation foo
*/
public function testOneWithAnnotation()
{
$this->expectDeprecation('bar');
@trigger_error('foo', E_USER_DEPRECATED);
@trigger_error('bar', E_USER_DEPRECATED);
}
/**
* Do not remove this test in the next major version.
*
* @group legacy
*
* @expectedDeprecation foo
* @expectedDeprecation bar
*/
public function testManyWithAnnotation()
{
$this->expectDeprecation('ccc');
$this->expectDeprecation('fcy');
@trigger_error('foo', E_USER_DEPRECATED);
@trigger_error('bar', E_USER_DEPRECATED);
@trigger_error('ccc', E_USER_DEPRECATED);
@trigger_error('fcy', E_USER_DEPRECATED);
}
}