8000 [PhpunitBridge] Fix deprecation type detection · symfony/symfony@223e847 · GitHub
[go: up one dir, main page]

Skip to content

Commit 223e847

Browse files
committed
[PhpunitBridge] Fix deprecation type detection
When using several vendor directories
1 parent 77418e7 commit 223e847

File tree

6 files changed

+266
-57
lines changed

6 files changed

+266
-57
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,10 @@ private static function getVendors()
264264
if (file_exists($v.'/composer/installed.json')) {
265265
self::$vendors[] = $v;
266266
$loader = require $v.'/autoload.php';
267-
$paths = self::getSourcePathsFromPrefixes(array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4()));
267+
self::addSourcePathsFromPrefixes(
268+
array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4()),
269+
$paths
270+
);
268271
}
269272
}
270273
}
@@ -280,12 +283,12 @@ private static function getVendors()
280283
return self::$vendors;
281284
}
282285

283-
private static function getSourcePathsFromPrefixes(array $prefixesByNamespace)
286+
private static function addSourcePathsFromPrefixes(array $prefixesByNamespace, array &$paths)
284287
{
285288
foreach ($prefixesByNamespace as $prefixes) {
286289
foreach ($prefixes as $prefix) {
287290
if (false !== realpath($prefix)) {
288-
yield realpath($prefix);
291+
$paths[] = realpath($prefix);
289292
}
290293
}
291294
}

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

Lines changed: 73 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,48 @@
1111

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

14+
use App\Services\AppService;
1415
use PHPUnit\Framework\TestCase;
1516
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
1617
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation;
1718
use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5;
1819
use Symfony\Bridge\PhpUnit\SetUpTearDownTrait;
20+
use Symfony\Bridge\PhpUnit\Tests\TestUtils\FakeVendor;
21+
use Symfony\Component\Filesystem\Filesystem;
1922

2023
class DeprecationTest extends TestCase
2124
{
2225
use SetUpTearDownTrait;
2326

24-
private static $vendorDir;
25-
private static $prefixDirsPsr4;
27+
/**
28+
* @var FakeVendor
29+
*/
30+
private static $fakeVendor;
2631

2732
private static function getVendorDir()
2833
{
29-
if (null !== self::$vendorDir) {
30-
return self::$vendorDir;
34+
if (null !== self::$fakeVendor) {
35+
return self::$fakeVendor->getRootDir();
3136
}
3237

33-
foreach (get_declared_classes() as $class) {
34-
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
35-
$r = new \ReflectionClass($class);
36-
$vendorDir = \dirname(\dirname($r->getFileName()));
37-
if (file_exists($vendorDir.'/composer/installed.json') && @mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true)) {
38-
break;
39-
}
40-
}
41-
}
38+
self::$fakeVendor = FakeVendor::create()
39+
->addPackage(null, 'myfakevendor', 'myfakepackage1', 'Fake\\Package1\\')
40+
->addPackage(null, 'myfakevendor', 'myfakepackage2', 'Fake\\Package2\\')
41+
->generate();
42+
$rootDir = self::$fakeVendor->getRootDir();
4243

43-
self::$vendorDir = $vendorDir;
44-
mkdir($vendorDir.'/myfakevendor/myfakepackage2');
45-
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php');
46-
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php');
47-
touch($vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php');
44+
$fs = new Filesystem();
45+
$fs->mkdir([
46+
$rootDir.'/myfakevendor/myfakepackage1',
47+
$rootDir.'/myfakevendor/myfakepackage2',
48+
]);
49+
$fs->touch([
50+
$rootDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php',
51+
$rootDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
52+
$rootDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
53+
]);
4854

49-
return self::$vendorDir;
55+
return $rootDir;
5056
}
5157

5258
public function testItCanDetermineTheClassWhereTheDeprecationHappened()
@@ -231,6 +237,9 @@ public function providerGetTypeUsesRightTrace()
231237
*/
232238
public function testGetTypeUsesRightTrace(string $expectedType, string $message, array $trace)
233239
{
240+
$this->resetStaticVendors();
241+
require_once self::getVendorDir().'/autoload.php';
242+
234243
$deprecation = new Deprecation(
235244
$message,
236245
$trace,
@@ -239,54 +248,64 @@ public function testGetTypeUsesRightTrace(string $expectedType, string $message,
239248
$this->assertSame($expectedType, $deprecation->getType());
240249
}
241250

242-
/**
243-
* This method is here to simulate the extra level from the piece of code
244-
* triggering an error to the error handler.
245-
*/
246-
public function debugBacktrace()
251+
private function resetStaticVendors()
247252
{
248-
return debug_backtrace();
253+
$reflectionClass = new \ReflectionClass(Deprecation::class);
254+
$vendorsProp = $reflectionClass->getProperty('vendors');
255+
$vendorsProp->setAccessible(true);
256+
$vendorsProp->setValue(null);
249257
}
250258

251-
private static function removeDir($dir)
259+
public function testWithMultipleVendorDirs()
252260
{
253-
$files = glob($dir.'/*');
254-
foreach ($files as $file) {
255-
if (is_file($file)) {
256-
unlink($file);
257-
} else {
258-
self::removeDir($file);
261+
$this->resetStaticVendors();
262+
263+
$changed = null;
264+
try {
265+
$sut = new DeprecationErrorHandler();
266+
$changed = set_error_handler([$sut, 'handleError']);
267+
268+
$fakeVendor1 = FakeVendor::create()
269+
->addPsr4Dir(__DIR__.'/../Fixtures/fake_app', 'App\\Services\\')
270+
->addPackage(__DIR__.'/fake_vendor/acme/lib', 'acme', 'lib', 'acme\\lib\\')
271+
->generate();
272+
273+
require $fakeVendor1->getRootDir().'/autoload.php';
274+
275+
$fakeVendor2 = FakeVendor::create()
276+
->addPackage(__DIR__.'/fake_vendor/foo/lib', 'foo', 'lib', 'foo\\lib\\')
277+
->generate();
278+
279+
require $fakeVendor2->getRootDir().'/autoload.php';
280+
281+
(new AppService())->directDeprecations();
282+
283+
ob_start();
284+
$sut->shutdown();
285+
$deprecations = ob_get_clean();
286+
$this->assertStringContainsString('Remaining direct deprecation notices (2)', $deprecations);
287+
} finally {
288+
$fakeVendor1->clear();
289+
$fakeVendor2->clear();
290+
if ($changed) {
291+
restore_error_handler();
259292
}
260293
}
261-
rmdir($dir);
262294
}
263295

264-
private static function doSetupBeforeClass()
296+
/**
297+
* This method is here to simulate the extra level from the piece of code
298+
* triggering an error to the error handler.
299+
*/
300+
public function debugBacktrace()
265301
{
266-
foreach (get_declared_classes() as $class) {
267-
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
268-
$r = new \ReflectionClass($class);
269-
$v = \dirname(\dirname($r->getFileName()));
270-
if (file_exists($v.'/composer/installed.json')) {
271-
$loader = require $v.'/autoload.php';
272-
$reflection = new \ReflectionClass($loader);
273-
$prop = $reflection->getProperty('prefixDirsPsr4');
274-
$prop->setAccessible(true);
275-
$currentValue = $prop->getValue($loader);
276-
self::$prefixDirsPsr4[] = [$prop, $loader, $currentValue];
277-
$currentValue['Symfony\\Bridge\\PhpUnit\\'] = [realpath(__DIR__.'/../..')];
278-
$prop->setValue($loader, $currentValue);
279-
}
280-
}
281-
}
302+
return debug_backtrace();
282303
}
283304

284305
private static function doTearDownAfterClass()
285306
{
286-
foreach (self::$prefixDirsPsr4 as [$prop, $loader, $prefixDirsPsr4]) {
287-
$prop->setValue($loader, $prefixDirsPsr4);
307+
if (self::$fakeVendor) {
308+
self::$fakeVendor->clear();
288309
}
289-
290-
self::removeDir(self::getVendorDir().'/myfakevendor');
291310
}
292311
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace foo\lib;
4+
5+
class SomeOtherService
6+
{
7+
public function deprecatedApi()
8+
{
9+
@trigger_error(
10+
__FUNCTION__.' from foo is deprecated! You should stop relying on it!',
11+
E_USER_DEPRECATED
12+
);
13+
}
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use acme\lib\SomeService;
6+
use foo\lib\SomeOtherService;
7+
8+
final class AppService
9+
{
10+
public function directDeprecations()
11+
{
12+
$service1 = new SomeService();
13+
$service1->deprecatedApi();
14+
15+
$service2 = new SomeOtherService();
16+
$service2->deprecatedApi();
17+
}
18+
}
19+
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PhpUnit\Tests\TestUtils;
4+
5+
use Symfony\Component\Filesystem\Filesystem;
6+
7+
/**
8+
* Allow to create fake composer vendor directories for tests.
9+
*
10+
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
11+
*/
12+
final class FakeVendor
13+
{
14+
/**
15+
* Root dir path where packages are stored.
16+
*
17+
* @var string
18+
*/
19+
private $rootDir;
20+
21+
/**
22+
* @var Filesystem
23+
*/
24+
private $fs;
25+
26+
/**
27+
* @var array
28+
*/
29+
private $packagesData = [];
30+
31+
/**
32+
* @var array
33+
*/
34+
private $psr4Prefixes = [];
35+
36+
private function __construct()
37+
{
38+
$this->fs = new Filesystem();
39+
}
40+
41+
public static function create()
42+
{
43+
return new self();
44+
}
45+
46+
public function addPackage($packagePath, $vendorName, $packageName, $namespace)
47+
{
48+
$this->packagesData[] = \func_get_args();
49+
50+
return $this;
51+
}
52+
53+
public function addPsr4Dir($path, $namespace)
54+
{
55+
$this->psr4Prefixes[$namespace] = [$path.'/'];
56+
57+
return $this;
58+
}
59+
60+
public function generate()
61+
{
62+
$uniqid = uniqid(time());
63+
$this->rootDir = sys_get_temp_dir().'/fake_vendor_'.$uniqid;
64+
65+
foreach ($this->packagesData as list($packagePath, $vendorName, $packageName, $namespace)) {
66+
$copyTarget = $this->rootDir.'/'.$vendorName.'/'.$packageName;
67+
if ($packagePath) {
68+
$this->fs->mirror($packagePath, $copyTarget);
69+
}
70+
71+
$this->psr4Prefixes[$namespace] = [$copyTarget.'/'];
72+
}
73+
74+
$this->fs->dumpFile($this->rootDir.'/composer/installed.json', json_encode([
75+
'just here' => 'for the detection',
76+
]));
77+
78+
$psr4PrefixesExported = var_export($this->psr4Prefixes, true);
79+
$content = <<<PHP
80+
<?php
81+
82+
class ComposerLoaderFake$uniqid
83+
{
84+
public function getPrefixes()
85+
{
86+
return [];
87+
}
88+
89+
public function getPrefixesPsr4()
90+
{
91+
return $psr4PrefixesExported;
92+
}
93+
94+
public function loadClass(\$className)
95+
{
96+
foreach (\$this->getPrefixesPsr4() as \$prefix => \$baseDirs) {
97+
if (strpos(\$className, \$prefix) !== 0) {
98+
continue;
99+
}
100+
101+
foreach (\$baseDirs as \$baseDir) {
102+
\$file = str_replace([\$prefix, '\\\'], [\$baseDir, '/'], \$className.'.php');
103+
if (file_exists(\$file)) {
104+
require \$file;
105+
}
106+
}
107+
}
108+
}
109+
}
110+
111+
class ComposerAutoloaderInitFake$uniqid
112+
{
113+
private static \$loader;
114+
115+
public static function getLoader()
116+
{
117+
if (null === self::\$loader) {
118+
self::\$loader = new ComposerLoaderFake$uniqid();
119+
120+
spl_autoload_register([self::\$loader, 'loadClass']);
121+
}
122+
123+
return self::\$loader;
124+
}
125+
}
126+
PHP;
127+
128+
$this->fs->dumpFile($this->rootDir.'/composer/autoload_real.php', $content);
129+
$content = <<<PHP
130+
<?php
131+
require_once __DIR__.'/composer/autoload_real.php';
132+
133+
return ComposerAutoloaderInitFake$uniqid::getloader();
134+
PHP;
135+
$this->fs->dumpFile($this->rootDir.'/autoload.php', $content);
136+
137+
return $this;
138+
}
139+
140+
public function clear()
141+
{
142+
$this->fs->remove($this->rootDir);
143+
144+
return $this;
145+
}
146+
147+
public function getRootDir()
148+
{
149+
return $this->rootDir;
150+
}
151+
}

0 commit comments

Comments
 (0)
0