[PhpUnitBridge] fix reporting deprecations when they come from DebugClassLoader
This commit is contained in:
parent
9197199731
commit
6a4312deac
1
src/Symfony/Bridge/PhpUnit/.gitignore
vendored
1
src/Symfony/Bridge/PhpUnit/.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
vendor/
|
vendor/
|
||||||
composer.lock
|
composer.lock
|
||||||
phpunit.xml
|
phpunit.xml
|
||||||
Tests/DeprecationErrorHandler/fake_vendor/symfony/error-handler/DebugClassLoader.php
|
|
||||||
|
@ -132,15 +132,17 @@ class DeprecationErrorHandler
|
|||||||
return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context);
|
return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
$deprecation = new Deprecation($msg, debug_backtrace(), $file);
|
$trace = debug_backtrace();
|
||||||
|
|
||||||
|
if (isset($trace[1]['function'], $trace[1]['args'][0]) && ('trigger_error' === $trace[1]['function'] || 'user_error' === $trace[1]['function'])) {
|
||||||
|
$msg = $trace[1]['args'][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$deprecation = new Deprecation($msg, $trace, $file);
|
||||||
if ($deprecation->isMuted()) {
|
if ($deprecation->isMuted()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$group = 'other';
|
|
||||||
|
|
||||||
if ($deprecation->originatesFromAnObject()) {
|
|
||||||
$class = $deprecation->originatingClass();
|
|
||||||
$method = $deprecation->originatingMethod();
|
|
||||||
$msg = $deprecation->getMessage();
|
$msg = $deprecation->getMessage();
|
||||||
|
|
||||||
if (error_reporting() & $type) {
|
if (error_reporting() & $type) {
|
||||||
@ -161,15 +163,17 @@ class DeprecationErrorHandler
|
|||||||
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('legacy' !== $group) {
|
if ('legacy' !== $group) {
|
||||||
$ref = &$this->deprecations[$group][$msg]['count'];
|
$ref = &$this->deprecations[$group][$msg]['count'];
|
||||||
++$ref;
|
++$ref;
|
||||||
|
|
||||||
|
if ($deprecation->originatesFromAnObject()) {
|
||||||
|
$class = $deprecation->originatingClass();
|
||||||
|
$method = $deprecation->originatingMethod();
|
||||||
$ref = &$this->deprecations[$group][$msg][$class.'::'.$method];
|
$ref = &$this->deprecations[$group][$msg][$class.'::'.$method];
|
||||||
++$ref;
|
++$ref;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$ref = &$this->deprecations[$group][$msg]['count'];
|
|
||||||
++$ref;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
++$this->deprecations[$group.'Count'];
|
++$this->deprecations[$group.'Count'];
|
||||||
|
@ -55,27 +55,48 @@ class Deprecation
|
|||||||
public function __construct($message, array $trace, $file)
|
public function __construct($message, array $trace, $file)
|
||||||
{
|
{
|
||||||
$this->trace = $trace;
|
$this->trace = $trace;
|
||||||
|
|
||||||
if ('trigger_error' === (isset($trace[1]['function']) ? $trace[1]['function'] : null)
|
|
||||||
&& (DebugClassLoader::class === ($class = (isset($trace[2]['class']) ? $trace[2]['class'] : null)) || LegacyDebugClassLoader::class === $class)
|
|
||||||
&& 'checkClass' === (isset($trace[2]['function']) ? $trace[2]['function'] : null)
|
|
||||||
&& null !== ($extraFile = (isset($trace[2]['args'][1]) ? $trace[2]['args'][1] : null))
|
|
||||||
&& '' !== $extraFile
|
|
||||||
&& false !== $extraFile = realpath($extraFile)
|
|
||||||
) {
|
|
||||||
$this->getOriginalFilesStack();
|
|
||||||
array_splice($this->originalFilesStack, 2, 1, $extraFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->message = $message;
|
$this->message = $message;
|
||||||
|
|
||||||
$i = \count($trace);
|
$i = \count($trace);
|
||||||
while (1 < $i && $this->lineShouldBeSkipped($trace[--$i])) {
|
while (1 < $i && $this->lineShouldBeSkipped($trace[--$i])) {
|
||||||
// No-op
|
// No-op
|
||||||
}
|
}
|
||||||
|
|
||||||
$line = $trace[$i];
|
$line = $trace[$i];
|
||||||
$this->triggeringFile = $file;
|
$this->triggeringFile = $file;
|
||||||
|
|
||||||
|
for ($j = 1; $j < $i; ++$j) {
|
||||||
|
if (!isset($trace[$j]['function'], $trace[1 + $j]['class'], $trace[1 + $j]['args'][0])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('trigger_error' === $trace[$j]['function'] && !isset($trace[$j]['class'])) {
|
||||||
|
if (\in_array($trace[1 + $j]['class'], [DebugClassLoader::class, LegacyDebugClassLoader::class], true)) {
|
||||||
|
$class = $trace[1 + $j]['args'][0];
|
||||||
|
$this->triggeringFile = isset($trace[1 + $j]['args'][1]) ? realpath($trace[1 + $j]['args'][1]) : (new \ReflectionClass($class))->getFileName();
|
||||||
|
$this->getOriginalFilesStack();
|
||||||
|
array_splice($this->originalFilesStack, 0, $j, [$this->triggeringFile]);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($line['object']) || isset($line['class'])) {
|
if (isset($line['object']) || isset($line['class'])) {
|
||||||
if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) {
|
if (!isset($line['class'], $trace[$i - 2]['function']) || 0 !== strpos($line['class'], SymfonyTestsListenerFor::class)) {
|
||||||
|
$this->originClass = isset($line['object']) ? \get_class($line['object']) : $line['class'];
|
||||||
|
$this->originMethod = $line['function'];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('trigger_error' !== $trace[$i - 2]['function'] || isset($trace[$i - 2]['class'])) {
|
||||||
|
$this->originClass = \get_class($line['args'][0]);
|
||||||
|
$this->originMethod = $line['args'][0]->getName();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
set_error_handler(function () {});
|
set_error_handler(function () {});
|
||||||
$parsedMsg = unserialize($this->message);
|
$parsedMsg = unserialize($this->message);
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
@ -92,11 +113,6 @@ class Deprecation
|
|||||||
if (isset($parsedMsg['triggering_file'])) {
|
if (isset($parsedMsg['triggering_file'])) {
|
||||||
$this->triggeringFile = $parsedMsg['triggering_file'];
|
$this->triggeringFile = $parsedMsg['triggering_file'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->originClass = isset($line['object']) ? \get_class($line['object']) : $line['class'];
|
|
||||||
$this->originMethod = $line['function'];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +146,9 @@ class Deprecation
|
|||||||
throw new \LogicException('Check with originatesFromAnObject() before calling this method.');
|
throw new \LogicException('Check with originatesFromAnObject() before calling this method.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->originClass;
|
$class = $this->originClass;
|
||||||
|
|
||||||
|
return false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,8 +176,7 @@ class Deprecation
|
|||||||
*/
|
*/
|
||||||
public function isLegacy()
|
public function isLegacy()
|
||||||
{
|
{
|
||||||
$class = $this->originatingClass();
|
if (!$this->originClass || (new \ReflectionClass($this->originClass))->isInternal()) {
|
||||||
if ((new \ReflectionClass($class))->isInternal()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,8 +185,8 @@ class Deprecation
|
|||||||
return 0 === strpos($method, 'testLegacy')
|
return 0 === strpos($method, 'testLegacy')
|
||||||
|| 0 === strpos($method, 'provideLegacy')
|
|| 0 === strpos($method, 'provideLegacy')
|
||||||
|| 0 === strpos($method, 'getLegacy')
|
|| 0 === strpos($method, 'getLegacy')
|
||||||
|| strpos($class, '\Legacy')
|
|| strpos($this->originClass, '\Legacy')
|
||||||
|| \in_array('legacy', Test::getGroups($class, $method), true);
|
|| \in_array('legacy', Test::getGroups($this->originClass, $method), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,11 +212,10 @@ class Deprecation
|
|||||||
*/
|
*/
|
||||||
public function getType()
|
public function getType()
|
||||||
{
|
{
|
||||||
$triggeringFilePathType = $this->getPathType($this->triggeringFile);
|
if (self::PATH_TYPE_SELF === $pathType = $this->getPathType($this->triggeringFile)) {
|
||||||
if (self::PATH_TYPE_SELF === $triggeringFilePathType) {
|
|
||||||
return self::TYPE_SELF;
|
return self::TYPE_SELF;
|
||||||
}
|
}
|
||||||
if (self::PATH_TYPE_UNDETERMINED === $triggeringFilePathType) {
|
if (self::PATH_TYPE_UNDETERMINED === $pathType) {
|
||||||
return self::TYPE_UNDETERMINED;
|
return self::TYPE_UNDETERMINED;
|
||||||
}
|
}
|
||||||
$erroringFile = $erroringPackage = null;
|
$erroringFile = $erroringPackage = null;
|
||||||
@ -208,10 +224,10 @@ class Deprecation
|
|||||||
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
|
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (self::PATH_TYPE_SELF === $this->getPathType($file)) {
|
if (self::PATH_TYPE_SELF === $pathType = $this->getPathType($file)) {
|
||||||
return self::TYPE_DIRECT;
|
return self::TYPE_DIRECT;
|
||||||
}
|
}
|
||||||
if (self::PATH_TYPE_UNDETERMINED === $this->getPathType($file)) {
|
if (self::PATH_TYPE_UNDETERMINED === $pathType) {
|
||||||
return self::TYPE_UNDETERMINED;
|
return self::TYPE_UNDETERMINED;
|
||||||
}
|
}
|
||||||
if (null !== $erroringFile && null !== $erroringPackage) {
|
if (null !== $erroringFile && null !== $erroringPackage) {
|
||||||
@ -233,7 +249,7 @@ class Deprecation
|
|||||||
if (null === $this->originalFilesStack) {
|
if (null === $this->originalFilesStack) {
|
||||||
$this->originalFilesStack = [];
|
$this->originalFilesStack = [];
|
||||||
foreach ($this->trace as $frame) {
|
foreach ($this->trace as $frame) {
|
||||||
if (!isset($frame['file']) || \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
|
if (!isset($frame['file'], $frame['function']) || (!isset($frame['class']) && \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,13 +275,10 @@ class Deprecation
|
|||||||
$relativePath = substr($path, \strlen($vendorRoot) + 1);
|
$relativePath = substr($path, \strlen($vendorRoot) + 1);
|
||||||
$vendor = strstr($relativePath, \DIRECTORY_SEPARATOR, true);
|
$vendor = strstr($relativePath, \DIRECTORY_SEPARATOR, true);
|
||||||
if (false === $vendor) {
|
if (false === $vendor) {
|
||||||
throw new \RuntimeException(sprintf('Could not find directory separator "%s" in path "%s".', \DIRECTORY_SEPARATOR, $relativePath));
|
return 'symfony';
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtrim($vendor.'/'.strstr(substr(
|
return rtrim($vendor.'/'.strstr(substr($relativePath, \strlen($vendor) + 1), \DIRECTORY_SEPARATOR, true), '/');
|
||||||
$relativePath,
|
|
||||||
\strlen($vendor) + 1
|
|
||||||
), \DIRECTORY_SEPARATOR, true), '/');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,6 +292,13 @@ class Deprecation
|
|||||||
{
|
{
|
||||||
if (null === self::$vendors) {
|
if (null === self::$vendors) {
|
||||||
self::$vendors = $paths = [];
|
self::$vendors = $paths = [];
|
||||||
|
self::$vendors[] = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Legacy';
|
||||||
|
if (class_exists(DebugClassLoader::class, false)) {
|
||||||
|
self::$vendors[] = \dirname((new \ReflectionClass(DebugClassLoader::class))->getFileName());
|
||||||
|
}
|
||||||
|
if (class_exists(LegacyDebugClassLoader::class, false)) {
|
||||||
|
self::$vendors[] = \dirname((new \ReflectionClass(LegacyDebugClassLoader::class))->getFileName());
|
||||||
|
}
|
||||||
foreach (get_declared_classes() as $class) {
|
foreach (get_declared_classes() as $class) {
|
||||||
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
|
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
|
||||||
$r = new \ReflectionClass($class);
|
$r = new \ReflectionClass($class);
|
||||||
@ -354,10 +374,9 @@ class Deprecation
|
|||||||
$reflection->setAccessible(true);
|
$reflection->setAccessible(true);
|
||||||
$reflection->setValue($exception, $this->trace);
|
$reflection->setValue($exception, $this->trace);
|
||||||
|
|
||||||
return 'deprecation triggered by '.$this->originatingClass().'::'.$this->originatingMethod().':'.
|
return ($this->originatesFromAnObject() ? 'deprecation triggered by '.$this->originatingClass().'::'.$this->originatingMethod().":\n" : '')
|
||||||
"\n".$this->message.
|
.$this->message."\n"
|
||||||
"\nStack trace:".
|
."Stack trace:\n"
|
||||||
"\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()).
|
.str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString())."\n";
|
||||||
"\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,11 @@ class DeprecationTest extends TestCase
|
|||||||
public function providerGetTypeUsesRightTrace()
|
public function providerGetTypeUsesRightTrace()
|
||||||
{
|
{
|
||||||
$vendorDir = self::getVendorDir();
|
$vendorDir = self::getVendorDir();
|
||||||
|
$fakeTrace = [
|
||||||
|
['function' => 'trigger_error'],
|
||||||
|
['class' => SymfonyTestsListenerTrait::class, 'function' => 'endTest'],
|
||||||
|
['class' => SymfonyTestsListenerForV5::class, 'function' => 'endTest'],
|
||||||
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]],
|
'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]],
|
||||||
@ -205,7 +210,7 @@ class DeprecationTest extends TestCase
|
|||||||
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
|
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
|
$fakeTrace,
|
||||||
],
|
],
|
||||||
'serialized_stack_files_from_various_packages' => [
|
'serialized_stack_files_from_various_packages' => [
|
||||||
Deprecation::TYPE_INDIRECT,
|
Deprecation::TYPE_INDIRECT,
|
||||||
@ -218,7 +223,7 @@ class DeprecationTest extends TestCase
|
|||||||
$vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
|
$vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
|
$fakeTrace,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -30,16 +30,6 @@ EOPHP
|
|||||||
);
|
);
|
||||||
require __DIR__.'/fake_vendor/autoload.php';
|
require __DIR__.'/fake_vendor/autoload.php';
|
||||||
|
|
||||||
// We need the real DebugClassLoader FQCN but in a vendor path.
|
|
||||||
if (!file_exists($errorHandlerRootDir = __DIR__.'/../../../../Component/ErrorHandler')) {
|
|
||||||
if (!file_exists($errorHandlerRootDir = __DIR__.'/../../vendor/symfony/error-handler')) {
|
|
||||||
die('Could not find the ErrorHandler component root directory.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents($fakeDebugClassLoadPath = __DIR__.'/fake_vendor/symfony/error-handler/DebugClassLoader.php', file_get_contents($errorHandlerRootDir.'/DebugClassLoader.php'));
|
|
||||||
require $fakeDebugClassLoadPath;
|
|
||||||
|
|
||||||
\Symfony\Component\ErrorHandler\DebugClassLoader::enable();
|
\Symfony\Component\ErrorHandler\DebugClassLoader::enable();
|
||||||
new \App\Services\BarService();
|
new \App\Services\BarService();
|
||||||
|
|
||||||
|
@ -29,13 +29,11 @@ Unsilenced deprecation notices (3)
|
|||||||
1x: unsilenced bar deprecation
|
1x: unsilenced bar deprecation
|
||||||
1x in FooTestCase::testNonLegacyBar
|
1x in FooTestCase::testNonLegacyBar
|
||||||
|
|
||||||
Remaining direct deprecation notices (1)
|
Remaining direct deprecation notices (2)
|
||||||
|
|
||||||
|
1x: root deprecation
|
||||||
|
|
||||||
1x: silenced bar deprecation
|
1x: silenced bar deprecation
|
||||||
1x in FooTestCase::testNonLegacyBar
|
1x in FooTestCase::testNonLegacyBar
|
||||||
|
|
||||||
Legacy deprecation notices (2)
|
Legacy deprecation notices (2)
|
||||||
|
|
||||||
Other deprecation notices (1)
|
|
||||||
|
|
||||||
1x: root deprecation
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Please update when phpunit needs to be reinstalled with fresh deps:
|
// Please update when phpunit needs to be reinstalled with fresh deps:
|
||||||
// Cache-Id: 2020-01-31 10:00 UTC
|
// Cache-Id: 2021-02-04 11:00 UTC
|
||||||
|
|
||||||
error_reporting(-1);
|
error_reporting(-1);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user