8000 [PhpUnitBridge] Use serialized trace when using serialized deprecation · symfony/symfony@c10ace7 · GitHub
[go: up one dir, main page]

Skip to content

Commit c10ace7

8000
Browse files
committed
[PhpUnitBridge] Use serialized trace when using serialized deprecation
1 parent 75461d0 commit c10ace7

File tree

4 files changed

+147
-27
lines changed

4 files changed

+147
-27
lines changed

src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,19 @@ public static function collectDeprecations($outputFile)
108108
return $ErrorHandler::handleError($type, $msg, $file, $line, $context);
109109
}
110110

111-
$deprecations[] = [error_reporting(), $msg, $file];
111+
$trace = debug_backtrace();
112+
$filesStack = [];
113+
foreach ($trace as $line) {
114+
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
115+
continue;
116+
}
117+
118+
if (isset($line['file'])) {
119+
$filesStack[] = $line['file'];
120+
}
121+
}
122+
123+
$deprecations[] = [error_reporting(), $msg, $file, $filesStack];
112124
});
113125

114126
register_shutdown_function(function () use ($outputFile, &$deprecations) {

src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class Deprecation
4646
/** @var string[] absolute paths to vendor directories */
4747
private static $vendors;
4848

49+
private $originalFilesStack;
50+
4951
/**
5052
* @param string $message
5153
* @param string $file
@@ -66,6 +68,7 @@ public function __construct($message, array $trace, $file)
6668
$this->message = $parsedMsg['deprecation'];
6769
$this->originClass = $parsedMsg['class'];
6870
$this->originMethod = $parsedMsg['method'];
71+
$this->originalFilesStack = $parsedMsg['files_stack'];
6972
// If the deprecation has been triggered via
7073
// \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
7174
// then we need to use the serialized information to determine
@@ -159,6 +162,24 @@ public function isLegacy($utilPrefix)
159162
|| \in_array('legacy', $test::getGroups($class, $method), true);
160163
}
161164

165+
private function getOriginalFilesStack(): array
166+
{
167+
if (null === $this->originalFilesStack) {
168+
$this->originalFilesStack = [];
169+
foreach ($this->trace as $line) {
170+
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
171+
continue;
172+
}
173+
if (!isset($line['file'])) {
174+
continue;
175+
}
176+
$this->originalFilesStack[] = $line['file'];
177+
}
178+
}
179+
180+
return $this->originalFilesStack;
181+
}
182+
162183
/**
163184
* Tells whether both the calling package and the called package are vendor
164185
* packages.
@@ -168,14 +189,8 @@ public function isLegacy($utilPrefix)
168189
public function isIndirect()
169190
{
170191
$erroringFile = $erroringPackage = null;
171-
foreach ($this->trace as $line) {
172-
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
173-
continue;
174-
}
175-
if (!isset($line['file'])) {
176-
continue;
177-
}
178-
$file = $line['file'];
192+
193+
foreach ($this->getOriginalFilesStack() as $file) {
179194
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
180195
continue;
181196
}

src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ class SymfonyTestsListenerTrait
3232
private static $globallyEnabled = false;
3333
private $state = -1;
3434
private $skippedFile = false;
35-
private $wasSkipped = array();
36-
private $isSkipped = array();
37-
private $expectedDeprecations = array();
38-
private $gatheredDeprecations = array();
35+
private $wasSkipped = [];
36+
private $isSkipped = [];
37+
private $expectedDeprecations = [];
38+
private $gatheredDeprecations = [];
3939
private $previousErrorHandler;
4040
private $testsWithWarnings;
4141
private $reportUselessTests;
@@ -45,7 +45,7 @@ class SymfonyTestsListenerTrait
4545
/**
4646
* @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive)
4747
*/
48-
public function __construct(array $mockedNamespaces = array())
48+
public function __construct(array $mockedNamespaces = [])
4949
{
5050
if (class_exists('PHPUnit_Util_Blacklist')) {
5151
\PHPUnit_Util_Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait'] = 2;
@@ -57,7 +57,7 @@ public function __construct(array $mockedNamespaces = array())
5757

5858
foreach ($mockedNamespaces as $type => $namespaces) {
5959
if (!\is_array($namespaces)) {
60-
$namespaces = array($namespaces);
60+
$namespaces = [$namespaces];
6161
}
6262
if ('time-sensitive' === $type) {
6363
foreach ($namespaces as $ns) {
@@ -114,7 +114,7 @@ public function startTestSuite($suite)
114114
$Test = 'PHPUnit\Util\Test';
115115
}
116116
$suiteName = $suite->getName();
117-
$this->testsWithWarnings = array();
117+
$this->testsWithWarnings = [];
118118

119119
foreach ($suite->tests() as $test) {
120120
if (!($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) {
@@ -145,11 +145,11 @@ public function startTestSuite($suite)
145145

146146
if (!$this->wasSkipped = require $this->skippedFile) {
147147
echo "All tests already ran successfully.\n";
148-
$suite->setTests(array());
148+
$suite->setTests([]);
149149
}
150150
}
151151
}
152-
$testSuites = array($suite);
152+
$testSuites = [$suite];
153153
for ($i = 0; isset($testSuites[$i]); ++$i) {
154154
foreach ($testSuites[$i]->tests() as $test) {
155155
if ($test instanceof \PHPUnit_Framework_TestSuite || $test instanceof TestSuite) {
@@ -168,7 +168,7 @@ public function startTestSuite($suite)
168168
}
169169
}
170170
} elseif (2 === $this->state) {
171-
$skipped = array();
171+
$skipped = [];
172172
foreach ($suite->tests() as $test) {
173173
if (!($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)
174174
|| isset($this->wasSkipped[$suiteName]['*'])
@@ -240,7 +240,7 @@ public function startTest($test)
240240
$test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
241241

242242
$this->expectedDeprecations = $annotations['method']['expectedDeprecation'];
243-
$this->previousErrorHandler = set_error_handler(array($this, 'handleError'));
243+
$this->previousErrorHandler = set_error_handler([$this, 'handleError']);
244244
}
245245
}
246246
}
@@ -281,8 +281,14 @@ public function endTest($test, $time)
281281
$deprecations = file_get_contents($this->runsInSeparateProcess);
282282
unlink($this->runsInSeparateProcess);
283283
putenv('SYMFONY_DEPRECATIONS_SERIALIZE');
284-
foreach ($deprecations ? unserialize($deprecations) : array() as $deprecation) {
285-
$error = serialize(array('deprecation' => $deprecation[1], 'class' => $className, 'method' => $test->getName(false), 'triggering_file' => isset($deprecation[2]) ? $deprecation[2] : null));
284+
foreach ($deprecations ? unserialize($deprecations) : [] as $deprecation) {
285+
$error = serialize([
286+
'deprecation' => $deprecation[1],
287+
'class' => $className,
288+
'method' => $test->getName(false),
289+
'triggering_file' => isset($deprecation[2]) ? $deprecation[2] : null,
290+
'files_stack' => $deprecation[3],
291+
]);
286292
if ($deprecation[0]) {
287293
@trigger_error($error, E_USER_DEPRECATED);
288294
} else {
@@ -293,13 +299,13 @@ public function endTest($test, $time)
293299
}
294300

295301
if ($this->expectedDeprecations) {
296-
if (!\in_array($test->getStatus(), array($BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE), true)) {
302+
if (!\in_array($test->getStatus(), [$BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE], true)) {
297303
$test->addToAssertionCount(\count($this->expectedDeprecations));
298304
}
299305

300306
restore_error_handler();
301307

302-
if (!$errored && !\in_array($test->getStatus(), array($BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE, $BaseTestRunner::STATUS_FAILURE, $BaseTestRunner::STATUS_ERROR), true)) {
308+
if (!$errored && !\in_array($test->getStatus(), [$BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE, $BaseTestRunner::STATUS_FAILURE, $BaseTestRunner::STATUS_ERROR], true)) {
303309
try {
304310
$prefix = "@expectedDeprecation:\n";
305311
$test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", $this->expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", $this->gatheredDeprecations)."\n");
@@ -310,20 +316,20 @@ public function endTest($test, $time)
310316
}
311317
}
312318

313-
$this->expectedDeprecations = $this->gatheredDeprecations = array();
319+
$this->expectedDeprecations = $this->gatheredDeprecations = [];
314320
$this->previousErrorHandler = null;
315321
}
316322
if (!$this->runsInSeparateProcess && -2 < $this->state && ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) {
317323
if (\in_array('time-sensitive', $groups, true)) {
318324
ClockMock::withClockMock(false);
319325
}
320326
if (\in_array('dns-sensitive', $groups, true)) {
321-
DnsMock::withMockedHosts(array());
327+
DnsMock::withMockedHosts([]);
322328
}
323329
}
324330
}
325331

326-
public function handleError($type, $msg, $file, $line, $context = array())
332+
public function handleError($type, $msg, $file, $line, $context = [])
327333
{
328334
if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) {
329335
$h = $this->previousErrorHandler;

src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler;
1313

14+
use Composer\Autoload\ClassLoader;
1415
use PHPUnit\Framework\TestCase;
1516
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation;
17+
use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5;
1618

1719
class DeprecationTest extends TestCase
1820
{
@@ -21,6 +23,21 @@ public static function setUpBeforeClass(): void
2123
// Use Deprecation class of the repository. Without that, Deprecation
2224
// class from vendors that is not up to date during PR is used
2325
require_once __DIR__.'/../../DeprecationErrorHandler/Deprecation.php';
26+
27+
$vendorDir = self::getVendorDir();
28+
29+
mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true);
30+
mkdir($vendorDir.'/myfakevendor/myfakepackage2');
31+
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php');
32+
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php');
33+
touch($vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php');
34+
}
35+
36+
private static function getVendorDir(): string
37+
{
38+
$reflection = new \ReflectionClass(ClassLoader::class);
39+
40+
return \dirname($reflection->getFileName(), 2);
2441
}
2542

2643
public function testItCanDetermineTheClassWhereTheDeprecationHappened()
@@ -107,6 +124,58 @@ public function testIsSelf(bool $expectedIsSelf, string $message, string $traceC
107124
$this->assertEquals($expectedIsSelf, $deprecation->isSelf());
108125
}
109126

127+
public function providerIsIndirectUsesRightTrace(): array
128+
{
129+
$vendorDir = self::getVendorDir();
130+
131+
return [
132+
'no_file_in_stack' => [false, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]],
133+
'files_in_stack_from_various_packages' => [
134+
true,
135+
'',
136+
[
137+
['function' => 'myfunc1', 'file' => $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php'],
138+
['function' => 'myfunc2', 'file' => $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php'],
139+
],
140+
],
141+
'serialized_stack_files_from_same_package' => [
142+
false,
143+
serialize([
144+
'deprecation' => 'My deprecation message',
145+
'class' => 'MyClass',
146+
'method' => 'myMethod',
147+
'files_stack' => [
148+
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php',
149+
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
150+
],
151+
]),
152+
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
153+
],
154+
'serialized_stack_files_from_various_packages' => [
155+
true,
156+
serialize([
157+
'deprecation' => 'My deprecation message',
158+
'class' => 'MyClass',
159+
'method' => 'myMethod',
160+
'files_stack' => [
161+
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php',
162+
$vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
163+
],
164+
]),
165+
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
166+
],
167+
];
168+
}
169+
170+
/**
171+
* @dataProvider providerIsIndirectUsesRightTrace
172+
*/
173+
public function testIsIndirectUsesRightTrace(bool $expectedIsIndirect, string $message, array $trace): void
174+
{
175+
$deprecation = new Deprecation($message, $trace, '');
176+
$this->assertEquals($expectedIsIndirect, $deprecation->isIndirect());
177+
}
178+
110179
/**
111180
* This method is here to simulate the extra level from the piece of code
112181
* triggering an error to the error handler.
@@ -115,4 +184,22 @@ public function debugBacktrace(): array
115184
{
116185
return debug_backtrace();
117186
}
187+
188+ AAC5
private static function removeDir($dir): void
189+
{
190+
$files = glob($dir.'/*');
191+
foreach ($files as $file) {
192+
if (is_file($file)) {
193+
unlink($file);
194+
} else {
195+
self::removeDir($file);
196+
}
197+
}
198+
rmdir($dir);
199+
}
200+
201+
public static function tearDownAfterClass(): void
202+
{
203+
self::removeDir(self::getVendorDir().'/myfakevendor');
204+
}
118205
}

0 commit comments

Comments
 (0)
0