bug #31730 [PhpUnitBridge] More accurate grouping (greg0ire)
This PR was squashed before being merged into the 4.3 branch (closes #31730).
Discussion
----------
[PhpUnitBridge] More accurate grouping
| Q | A
| ------------- | ---
| Branch? | 4.3
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | n/a
| License | MIT
| Doc PR | n/a
Sometimes, you cannot easily know if code was written by a vendor or
directly in the application, for instance if the code comes from a file
in the cache. In that case, it is better not to classify the deprecation
as direct or indirect.
@jmsche please test this on your application when you can, I think you might be having that issue.
Commits
-------
d9f0ba386a
[PhpUnitBridge] More accurate grouping
This commit is contained in:
commit
64b68d4922
@ -139,10 +139,13 @@ class DeprecationErrorHandler
|
||||
$group = 'unsilenced';
|
||||
} elseif ($deprecation->isLegacy(self::$utilPrefix)) {
|
||||
$group = 'legacy';
|
||||
} elseif (!$deprecation->isSelf()) {
|
||||
$group = $deprecation->isIndirect() ? 'remaining indirect' : 'remaining direct';
|
||||
} else {
|
||||
$group = 'remaining self';
|
||||
$group = [
|
||||
Deprecation::TYPE_SELF => 'remaining self',
|
||||
Deprecation::TYPE_DIRECT => 'remaining direct',
|
||||
Deprecation::TYPE_INDIRECT => 'remaining indirect',
|
||||
Deprecation::TYPE_UNDETERMINED => 'other',
|
||||
][$deprecation->getType()];
|
||||
}
|
||||
|
||||
if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) {
|
||||
|
@ -18,6 +18,15 @@ use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor;
|
||||
*/
|
||||
class Deprecation
|
||||
{
|
||||
private const PATH_TYPE_VENDOR = 'path_type_vendor';
|
||||
private const PATH_TYPE_SELF = 'path_type_internal';
|
||||
private const PATH_TYPE_UNDETERMINED = 'path_type_undetermined';
|
||||
|
||||
public const TYPE_SELF = 'type_self';
|
||||
public const TYPE_DIRECT = 'type_direct';
|
||||
public const TYPE_INDIRECT = 'type_indirect';
|
||||
public const TYPE_UNDETERMINED = 'type_undetermined';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -39,13 +48,21 @@ class Deprecation
|
||||
private $originMethod;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @var string one of the PATH_TYPE_* constants
|
||||
*/
|
||||
private $self;
|
||||
private $triggeringFilePathType;
|
||||
|
||||
/** @var string[] absolute paths to vendor directories */
|
||||
private static $vendors;
|
||||
|
||||
/**
|
||||
* @var string[] absolute paths to source or tests of the project. This
|
||||
* excludes cache directories, because it is based on
|
||||
* autoloading rules and cache systems typically do not use
|
||||
* those.
|
||||
*/
|
||||
private static $internalPaths;
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param string $file
|
||||
@ -59,7 +76,7 @@ class Deprecation
|
||||
// No-op
|
||||
}
|
||||
$line = $trace[$i];
|
||||
$this->self = !$this->pathOriginatesFromVendor($file);
|
||||
$this->trigerringFilePathType = $this->getPathType($file);
|
||||
if (isset($line['object']) || isset($line['class'])) {
|
||||
if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) {
|
||||
$parsedMsg = unserialize($this->message);
|
||||
@ -70,8 +87,9 @@ class Deprecation
|
||||
// \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
|
||||
// then we need to use the serialized information to determine
|
||||
// if the error has been triggered from vendor code.
|
||||
$this->self = isset($parsedMsg['triggering_file'])
|
||||
&& $this->pathOriginatesFromVendor($parsedMsg['triggering_file']);
|
||||
if (isset($parsedMsg['triggering_file'])) {
|
||||
$this->trigerringFilePathType = $this->getPathType($parsedMsg['triggering_file']);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -101,14 +119,6 @@ class Deprecation
|
||||
return isset($this->originClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSelf()
|
||||
{
|
||||
return $this->self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@ -163,10 +173,16 @@ class Deprecation
|
||||
* Tells whether both the calling package and the called package are vendor
|
||||
* packages.
|
||||
*
|
||||
* @return bool
|
||||
* @return string
|
||||
*/
|
||||
public function isIndirect()
|
||||
public function getType()
|
||||
{
|
||||
if (self::PATH_TYPE_SELF === $this->trigerringFilePathType) {
|
||||
return self::TYPE_SELF;
|
||||
}
|
||||
if (self::PATH_TYPE_UNDETERMINED === $this->trigerringFilePathType) {
|
||||
return self::TYPE_UNDETERMINED;
|
||||
}
|
||||
$erroringFile = $erroringPackage = null;
|
||||
foreach ($this->trace as $line) {
|
||||
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
|
||||
@ -179,13 +195,16 @@ class Deprecation
|
||||
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
|
||||
continue;
|
||||
}
|
||||
if (!$this->pathOriginatesFromVendor($file)) {
|
||||
return false;
|
||||
if (self::PATH_TYPE_SELF === $this->getPathType($file)) {
|
||||
return self::TYPE_DIRECT;
|
||||
}
|
||||
if (self::PATH_TYPE_UNDETERMINED === $this->getPathType($file)) {
|
||||
return self::TYPE_UNDETERMINED;
|
||||
}
|
||||
if (null !== $erroringFile && null !== $erroringPackage) {
|
||||
$package = $this->getPackage($file);
|
||||
if ('composer' !== $package && $package !== $erroringPackage) {
|
||||
return true;
|
||||
return self::TYPE_INDIRECT;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -193,11 +212,11 @@ class Deprecation
|
||||
$erroringPackage = $this->getPackage($file);
|
||||
}
|
||||
|
||||
return false;
|
||||
return self::TYPE_DIRECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* pathOriginatesFromVendor() should always be called prior to calling this method.
|
||||
* getPathType() should always be called prior to calling this method.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
@ -237,6 +256,15 @@ class Deprecation
|
||||
$v = \dirname(\dirname($r->getFileName()));
|
||||
if (file_exists($v.'/composer/installed.json')) {
|
||||
self::$vendors[] = $v;
|
||||
$loader = require $v.'/autoload.php';
|
||||
$paths = self::getSourcePathsFromPrefixes(array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4()));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($paths as $path) {
|
||||
foreach (self::$vendors as $vendor) {
|
||||
if (0 !== strpos($path, $vendor)) {
|
||||
self::$internalPaths[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,24 +273,41 @@ class Deprecation
|
||||
return self::$vendors;
|
||||
}
|
||||
|
||||
private static function getSourcePathsFromPrefixes(array $prefixesByNamespace)
|
||||
{
|
||||
foreach ($prefixesByNamespace as $prefixes) {
|
||||
foreach ($prefixes as $prefix) {
|
||||
if (false !== realpath($prefix)) {
|
||||
yield realpath($prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool
|
||||
* @return string
|
||||
*/
|
||||
private function pathOriginatesFromVendor($path)
|
||||
private function getPathType($path)
|
||||
{
|
||||
$realPath = realpath($path);
|
||||
if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) {
|
||||
return true;
|
||||
return self::PATH_TYPE_UNDETERMINED;
|
||||
}
|
||||
foreach (self::getVendors() as $vendor) {
|
||||
if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
|
||||
return true;
|
||||
return self::PATH_TYPE_VENDOR;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
foreach (self::$internalPaths as $internalPath) {
|
||||
if (0 === strpos($realPath, $internalPath)) {
|
||||
return self::PATH_TYPE_SELF;
|
||||
}
|
||||
}
|
||||
|
||||
return self::PATH_TYPE_UNDETERMINED;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -281,19 +326,4 @@ class Deprecation
|
||||
"\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()).
|
||||
"\n";
|
||||
}
|
||||
|
||||
private function getPackageFromLine(array $line)
|
||||
{
|
||||
if (!isset($line['file'])) {
|
||||
return 'internal function';
|
||||
}
|
||||
if (!$this->pathOriginatesFromVendor($line['file'])) {
|
||||
return 'source code';
|
||||
}
|
||||
try {
|
||||
return $this->getPackage($line['file']);
|
||||
} catch (\RuntimeException $e) {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class DeprecationTest extends TestCase
|
||||
public function testItCanTellWhetherItIsInternal()
|
||||
{
|
||||
$deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__);
|
||||
$this->assertTrue($deprecation->isSelf());
|
||||
$this->assertSame(Deprecation::TYPE_SELF, $deprecation->getType());
|
||||
}
|
||||
|
||||
public function testLegacyTestMethodIsDetectedAsSuch()
|
||||
@ -46,7 +46,7 @@ class DeprecationTest extends TestCase
|
||||
public function testItRulesOutFilesOutsideVendorsAsIndirect()
|
||||
{
|
||||
$deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__);
|
||||
$this->assertFalse($deprecation->isIndirect());
|
||||
$this->assertNotSame(Deprecation::TYPE_INDIRECT, $deprecation->getType());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,15 +73,13 @@ Unsilenced deprecation notices (3)
|
||||
1x: unsilenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Remaining self deprecation notices (1)
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (2)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
1x: silenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
I get precedence over any exit statements inside the deprecation error handler.
|
||||
|
@ -1,3 +1,5 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitFake::getLoader();
|
||||
|
@ -1,5 +1,22 @@
|
||||
<?php
|
||||
|
||||
class ComposerLoaderFake
|
||||
{
|
||||
public function getPrefixes()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class ComposerAutoloaderInitFake
|
||||
{
|
||||
public static function getLoader()
|
||||
{
|
||||
return new ComposerLoaderFake();
|
||||
}
|
||||
}
|
||||
|
@ -61,14 +61,12 @@ Unsilenced deprecation notices (3)
|
||||
1x: unsilenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Remaining self deprecation notices (1)
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (2)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
1x: silenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
|
@ -73,17 +73,15 @@ Unsilenced deprecation notices (3)
|
||||
1x: unsilenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Remaining self deprecation notices (1)
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (2)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
1x: silenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
Shutdown-time deprecations:
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
@ -61,14 +61,13 @@ Unsilenced deprecation notices (3)
|
||||
1x: unsilenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Remaining self deprecation notices (1)
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (2)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
1x: silenced bar deprecation
|
||||
1x in FooTestCase::testNonLegacyBar
|
||||
|
||||
Legacy deprecation notices (1)
|
||||
|
||||
Other deprecation notices (1)
|
||||
|
||||
1x: root deprecation
|
||||
|
||||
|
Reference in New Issue
Block a user