Skip to content

Commit

Permalink
bug #33820 [PhpUnitBridge] Fix some errors when using serialized depr…
Browse files Browse the repository at this point in the history
…ecations (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
-------

056d598 [PhpUnitBridge] Fix some errors when using serialized deprecations
  • Loading branch information
nicolas-grekas committed Feb 4, 2020
2 parents 3750988 + 056d598 commit 84d32ac
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 10 deletions.
14 changes: 13 additions & 1 deletion src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php
Expand Up @@ -104,7 +104,19 @@ public static function collectDeprecations($outputFile)
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;
});
Expand Down
31 changes: 23 additions & 8 deletions src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php
Expand Up @@ -44,6 +44,8 @@ class Deprecation
*/
private static $internalPaths = [];

private $originalFilesStack;

/**
* @param string $message
* @param string $file
Expand All @@ -64,6 +66,7 @@ public function __construct($message, array $trace, $file)
$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
Expand Down Expand Up @@ -162,6 +165,24 @@ public function isMuted()
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.
Expand All @@ -178,14 +199,8 @@ public function getType()
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;
}
Expand Down
Expand Up @@ -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__);
Expand Down Expand Up @@ -118,12 +138,135 @@ public function testItTakesMutesDeprecationFromPhpUnitFiles()
$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');
}
}

0 comments on commit 84d32ac

Please sign in to comment.