bug #33820 [PhpUnitBridge] Fix some errors when using serialized deprecations (l-vo)

This PR was submitted for the 4.3 branch but it was squashed and merged into the 4.4 branch instead.

Discussion
----------

[PhpUnitBridge] Fix some errors when using serialized deprecations

| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | n/a
| License       | MIT
| Doc PR        | n/a

This PR attempts to fix conflicts that arose in #31478

Creating as a draft for now as I think having separate test methods no longer make sense (`isSelf()` and `isIndirect()` have been replaced with `getType()`). @l-vo please review and confirm I did not loose anything valuable from your original contribution.

Commits
-------

056d59824f [PhpUnitBridge] Fix some errors when using serialized deprecations
This commit is contained in:
Nicolas Grekas 2020-02-04 15:16:59 +01:00
commit 84d32aca73
3 changed files with 180 additions and 10 deletions

View File

@ -104,7 +104,19 @@ class DeprecationErrorHandler
return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context);
}
$deprecations[] = [error_reporting(), $msg, $file];
$trace = debug_backtrace();
$filesStack = [];
foreach ($trace as $line) {
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
continue;
}
if (isset($line['file'])) {
$filesStack[] = $line['file'];
}
}
$deprecations[] = [error_reporting(), $msg, $file, $filesStack];
return null;
});

View File

@ -44,6 +44,8 @@ class Deprecation
*/
private static $internalPaths = [];
private $originalFilesStack;
/**
* @param string $message
* @param string $file
@ -64,6 +66,7 @@ class Deprecation
$this->message = $parsedMsg['deprecation'];
$this->originClass = $parsedMsg['class'];
$this->originMethod = $parsedMsg['method'];
$this->originalFilesStack = $parsedMsg['files_stack'];
// If the deprecation has been triggered via
// \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
// then we need to use the serialized information to determine
@ -162,6 +165,24 @@ class Deprecation
return false !== strpos($this->triggeringFile, \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR.'phpunit'.\DIRECTORY_SEPARATOR);
}
private function getOriginalFilesStack(): array
{
if (null === $this->originalFilesStack) {
$this->originalFilesStack = [];
foreach ($this->trace as $line) {
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
continue;
}
if (!isset($line['file'])) {
continue;
}
$this->originalFilesStack[] = $line['file'];
}
}
return $this->originalFilesStack;
}
/**
* Tells whether both the calling package and the called package are vendor
* packages.
@ -178,14 +199,8 @@ class Deprecation
return self::TYPE_UNDETERMINED;
}
$erroringFile = $erroringPackage = null;
foreach ($this->trace as $line) {
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
continue;
}
if (!isset($line['file'])) {
continue;
}
$file = $line['file'];
foreach ($this->getOriginalFilesStack() as $file) {
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
continue;
}

View File

@ -11,12 +11,32 @@
namespace Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler;
use Composer\Autoload\ClassLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation;
use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5;
class DeprecationTest extends TestCase
{
public static function setUpBeforeClass(): void
{
$vendorDir = self::getVendorDir();
mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true);
mkdir($vendorDir.'/myfakevendor/myfakepackage2');
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php');
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php');
touch($vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php');
}
private static function getVendorDir(): string
{
$reflection = new \ReflectionClass(ClassLoader::class);
return \dirname($reflection->getFileName(), 2);
}
public function testItCanDetermineTheClassWhereTheDeprecationHappened()
{
$deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__);
@ -118,12 +138,135 @@ class DeprecationTest extends TestCase
$this->assertTrue($deprecation->isMuted());
}
public function providerGetTypeDetectsSelf(): array
{
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$v = \dirname(\dirname($r->getFileName()));
if (file_exists($v.'/composer/installed.json')) {
$loader = require $v.'/autoload.php';
$reflection = new \ReflectionClass($loader);
$prop = $reflection->getProperty('prefixDirsPsr4');
$prop->setAccessible(true);
$currentValue = $prop->getValue($loader);
$currentValue['Symfony\\Bridge\\PhpUnit\\'] = [realpath(__DIR__.'/../..')];
$prop->setValue($loader, $currentValue);
}
}
}
return [
'not_from_vendors_file' => [Deprecation::TYPE_SELF, '', 'MyClass1', ''],
'nonexistent_file' => [Deprecation::TYPE_UNDETERMINED, '', 'MyClass1', 'dummy_vendor_path'],
'serialized_trace_with_nonexistent_triggering_file' => [
Deprecation::TYPE_UNDETERMINED,
serialize([
'class' => '',
'method' => '',
'deprecation' => '',
'triggering_file' => 'dummy_vendor_path',
'files_stack' => [],
]),
SymfonyTestsListenerForV5::class,
'',
],
];
}
/**
* @dataProvider providerGetTypeDetectsSelf
*/
public function testGetTypeDetectsSelf(string $expectedType, string $message, string $traceClass, string $file): void
{
$trace = [
['class' => 'MyClass1', 'function' => 'myMethod'],
['class' => $traceClass, 'function' => 'myMethod'],
];
$deprecation = new Deprecation($message, $trace, $file);
$this->assertEquals($expectedType, $deprecation->getType());
}
public function providerGetTypeUsesRightTrace(): array
{
$vendorDir = self::getVendorDir();
return [
'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]],
'files_in_stack_from_various_packages' => [
Deprecation::TYPE_INDIRECT,
'',
[
['function' => 'myfunc1', 'file' => $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php'],
['function' => 'myfunc2', 'file' => $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php'],
],
],
'serialized_stack_files_from_same_package' => [
Deprecation::TYPE_DIRECT,
serialize([
'deprecation' => 'My deprecation message',
'class' => 'MyClass',
'method' => 'myMethod',
'files_stack' => [
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php',
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
],
]),
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
],
'serialized_stack_files_from_various_packages' => [
Deprecation::TYPE_INDIRECT,
serialize([
'deprecation' => 'My deprecation message',
'class' => 'MyClass',
'method' => 'myMethod',
'files_stack' => [
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php',
$vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
],
]),
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
],
];
}
/**
* @dataProvider providerGetTypeUsesRightTrace
*/
public function testGetTypeUsesRightTrace(string $expectedType, string $message, array $trace): void
{
$deprecation = new Deprecation(
$message,
$trace,
self::getVendorDir().'/myfakevendor/myfakepackage2/MyFakeFile.php'
);
$this->assertEquals($expectedType, $deprecation->getType());
}
/**
* This method is here to simulate the extra level from the piece of code
* triggering an error to the error handler
* triggering an error to the error handler.
*/
public function debugBacktrace(): array
{
return debug_backtrace();
}
private static function removeDir($dir): void
{
$files = glob($dir.'/*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
} else {
self::removeDir($file);
}
}
rmdir($dir);
}
public static function tearDownAfterClass(): void
{
self::removeDir(self::getVendorDir().'/myfakevendor');
}
}