Skip to content

Commit b95ce02

Browse files
committed
Introduce weak_lagging_vendors mode
In this mode, failures coming from vendors that call other vendors in a deprecated way are not taken into account when deciding to make the build fail. They also appear in a separate group.
1 parent ef12afd commit b95ce02

File tree

5 files changed

+175
-7
lines changed

5 files changed

+175
-7
lines changed

src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class DeprecationErrorHandler
2020
{
2121
const MODE_WEAK = 'weak';
2222
const MODE_WEAK_VENDORS = 'weak_vendors';
23+
const MODE_WEAK_LAGGING_VENDORS = 'weak_lagging_vendors';
2324
const MODE_DISABLED = 'disabled';
2425

2526
private static $isRegistered = false;
@@ -30,6 +31,8 @@ class DeprecationErrorHandler
3031
* The following reporting modes are supported:
3132
* - use "weak" to hide the deprecation report but keep a global count;
3233
* - use "weak_vendors" to act as "weak" but only for vendors;
34+
* - use "weak_lagging_vendors" to act as "weak" but only for vendors that
35+
* failed to keep up with their upstream dependencies deprecations;
3336
* - use "/some-regexp/" to stop the test suite whenever a deprecation
3437
* message matches the given regular expression;
3538
* - use a number to define the upper bound of allowed deprecations,
@@ -54,7 +57,11 @@ public static function register($mode = 0)
5457
if (false === $mode) {
5558
$mode = getenv('SYMFONY_DEPRECATIONS_HELPER');
5659
}
57-
if (DeprecationErrorHandler::MODE_WEAK !== $mode && DeprecationErrorHandler::MODE_WEAK_VENDORS !== $mode && (!isset($mode[0]) || '/' !== $mode[0])) {
60+
if (!in_array($mode, array(
61+
DeprecationErrorHandler::MODE_WEAK,
62+
DeprecationErrorHandler::MODE_WEAK_VENDORS,
63+
DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS,
64+
), true) && (!isset($mode[0]) || '/' !== $mode[0])) {
5865
$mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0;
5966
}
6067

@@ -66,11 +73,13 @@ public static function register($mode = 0)
6673
'remainingCount' => 0,
6774
'legacyCount' => 0,
6875
'otherCount' => 0,
76+
'lagging vendorCount' => 0,
6977
'remaining vendorCount' => 0,
7078
'unsilenced' => array(),
7179
'remaining' => array(),
7280
'legacy' => array(),
7381
'other' => array(),
82+
'lagging vendor' => array(),
7483
'remaining vendor' => array(),
7584
);
7685
$deprecationHandler = function ($type, $msg, $file, $line, $context = array()) use (&$deprecations, $getMode, $UtilPrefix) {
@@ -84,8 +93,9 @@ public static function register($mode = 0)
8493
$trace = debug_backtrace(true);
8594
$group = 'other';
8695
$isVendor = false;
87-
$isWeak = DeprecationErrorHandler::MODE_WEAK === $mode || (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor = self::inVendors($file));
88-
96+
$isWeak = DeprecationErrorHandler::MODE_WEAK === $mode ||
97+
(DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor = self::inVendors($file)) ||
98+
(DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS === $mode && $isLaggingVendor = self::isLaggingVendor($trace));
8999
$i = count($trace);
90100
while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) {
91101
// No-op
@@ -101,7 +111,9 @@ public static function register($mode = 0)
101111
// \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
102112
// then we need to use the serialized information to determine
103113
// if the error has been triggered from vendor code.
104-
$isWeak = DeprecationErrorHandler::MODE_WEAK === $mode || (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor = isset($parsedMsg['triggering_file']) && self::inVendors($parsedMsg['triggering_file']));
114+
$isWeak = DeprecationErrorHandler::MODE_WEAK === $mode ||
115+
(DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor = isset($parsedMsg['triggering_file']) && self::inVendors($parsedMsg['triggering_file'])) ||
116+
(DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS === $mode); // not enough information to make the right call, so let's be lenient
105117
} else {
106118
$class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class'];
107119
$method = $trace[$i]['function'];
@@ -118,6 +130,8 @@ public static function register($mode = 0)
118130
|| in_array('legacy', $Test::getGroups($class, $method), true)
119131
) {
120132
$group = 'legacy';
133+
} elseif (DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS === $mode && $isLaggingVendor) {
134+
$group = 'lagging vendor';
121135
} elseif (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor) {
122136
$group = 'remaining vendor';
123137
} else {
@@ -192,13 +206,16 @@ public static function register($mode = 0)
192206
if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) {
193207
$groups[] = 'remaining vendor';
194208
}
209+
if (DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS === $mode) {
210+
$groups[] = 'lagging vendor';
211+
}
195212
array_push($groups, 'legacy', 'other');
196213

197214
foreach ($groups as $group) {
198215
if ($deprecations[$group.'Count']) {
199216
echo "\n", $colorize(
200217
sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']),
201-
'legacy' !== $group && 'remaining vendor' !== $group
218+
!in_array($group, array('legacy', 'remaining vendor', 'lagging vendor'), true)
202219
), "\n";
203220

204221
uasort($deprecations[$group], $cmp);
@@ -227,11 +244,58 @@ public static function register($mode = 0)
227244
}
228245
}
229246

230-
private static function inVendors(string $path): bool
247+
private static function isLaggingVendor(array $trace): bool
248+
{
249+
$erroringFile = $erroringPackage = null;
250+
foreach ($trace as $line) {
251+
if (!isset($line['file'])) {
252+
continue;
253+
}
254+
$file = $line['file'];
255+
if ('-' === $file) {
256+
continue;
257+
}
258+
if (!self::inVendors($file)) {
259+
return false;
260+
}
261+
if (null !== $erroringFile && null !== $erroringPackage) {
262+
if (self::getPackage($file) !== $erroringPackage) {
263+
return true;
264+
}
265+
} else {
266+
$erroringFile = $file;
267+
$erroringPackage = self::getPackage($file);
268+
}
269+
}
270+
271+
return false;
272+
}
273+
274+
/**
275+
* inVendors() should always be called prior to calling this method.
276+
*/
277+
private static function getPackage(string $path): string
278+
{
279+
$path = realpath($path) ?: $path;
280+
foreach (self::getVendors() as $vendorRoot) {
281+
if (0 === strpos($path, $vendorRoot)) {
282+
$relativePath = substr($path, strlen($vendorRoot) + 1);
283+
$vendor = strstr($relativePath, DIRECTORY_SEPARATOR, true);
284+
285+
return $vendor.'/'.strstr(substr($relativePath, strlen($vendor) + 1), DIRECTORY_SEPARATOR, true);
286+
}
287+
}
288+
289+
throw new \RuntimeException('No vendors found');
290+
}
291+
292+
private static function getVendors(): array
231293
{
232294
/** @var string[] absolute paths to vendor directories */
233295
static $vendors;
296+
234297
if (null === $vendors) {
298+
$vendors = array();
235299
foreach (get_declared_classes() as $class) {
236300
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
237301
$r = new \ReflectionClass($class);
@@ -242,11 +306,17 @@ private static function inVendors(string $path): bool
242306
}
243307
}
244308
}
309+
310+
return $vendors;
311+
}
312+
313+
private static function inVendors(string $path): bool
314+
{
245315
$realPath = realpath($path);
246316
if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) {
247317
return true;
248318
}
249-
foreach ($vendors as $vendor) {
319+
foreach (self::getVendors() as $vendor) {
250320
if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, strlen($vendor), 1), '/'.DIRECTORY_SEPARATOR)) {
251321
return true;
252322
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
/* We have not caught up on the deprecations yet and still call the other lib
3+
in a deprecated way. */
4+
5+
include __DIR__.'/../lib/SomeService.php';
6+
$defraculator = new \acme\lib\SomeService();
7+
$defraculator->deprecatedApi();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace acme\lib;
4+
5+
class SomeService
6+
{
7+
public function deprecatedApi()
8+
{
9+
@trigger_error(
10+
__FUNCTION__.' is deprecated! You should stop relying on it!',
11+
E_USER_DEPRECATED
12+
);
13+
}
14+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Test DeprecationErrorHandler in weak vendors mode when calling deprecated api
3+
--FILE--
4+
<?php
5+
6+
putenv('SYMFONY_DEPRECATIONS_HELPER=weak_lagging_vendors');
7+
putenv('ANSICON');
8+
putenv('ConEmuANSI');
9+
putenv('TERM');
10+
11+
$vendor = __DIR__;
12+
while (!file_exists($vendor.'/vendor')) {
13+
$vendor = dirname($vendor);
14+
}
15+
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
16+
require PHPUNIT_COMPOSER_INSTALL;
17+
require_once __DIR__.'/../../bootstrap.php';
18+
eval(<<<'EOPHP'
19+
namespace PHPUnit\Util;
20+
21+
class Test
22+
{
23+
public static function getGroups()
24+
{
25+
return array();
26+
}
27+
}
28+
EOPHP
29+
);
30+
require __DIR__.'/fake_vendor/autoload.php';
31+
require __DIR__.'/fake_vendor/acme/lib/SomeService.php';
32+
$defraculator = new \Acme\Lib\SomeService();
33+
$defraculator->deprecatedApi();
34+
35+
36+
?>
37+
--EXPECTF--
38+
Remaining deprecation notices (1)
39+
40+
deprecatedApi is deprecated! You should stop relying on it!: 1x
41+
1x in SomeService::deprecatedApi from acme\lib
42+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Test DeprecationErrorHandler in weak vendors mode on vendor file
3+
--FILE--
4+
<?php
5+
6+
putenv('SYMFONY_DEPRECATIONS_HELPER=weak_lagging_vendors');
7+
putenv('ANSICON');
8+
putenv('ConEmuANSI');
9+
putenv('TERM');
10+
11+
$vendor = __DIR__;
12+
while (!file_exists($vendor.'/vendor')) {
13+
$vendor = dirname($vendor);
14+
}
15+
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
16+
require PHPUNIT_COMPOSER_INSTALL;
17+
require_once __DIR__.'/../../bootstrap.php';
18+
eval(<<<'EOPHP'
19+
namespace PHPUnit\Util;
20+
21+
class Test
22+
{
23+
public static function getGroups()
24+
{
25+
return array();
26+
}
27+
}
28+
EOPHP
29+
);
30+
require __DIR__.'/fake_vendor/autoload.php';
31+
require __DIR__.'/fake_vendor/acme/lagging-lib/lagging_file.php';
32+
33+
?>
34+
--EXPECTF--
35+
Lagging vendor deprecation notices (1)

0 commit comments

Comments
 (0)