feature #21539 Introduce weak vendors mode (greg0ire)

This PR was merged into the 3.3-dev branch.

Discussion
----------

Introduce weak vendors mode

Deprecations coming from the vendors are segregated from other
deprecations. A new mode is introduced, in which deprecations coming
from the vendors are not taken into account when deciding to exit with
an error code.

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/pull/7453

<!--
- Bug fixes must be submitted against the lowest branch where they apply
  (lowest branches are regularly merged to upper ones so they get the fixes too).
- Features and deprecations must be submitted against the master branch.
- Please fill in this template according to the PR you're about to submit.
- Replace this comment by a description of what your PR is solving.
-->

Sample output :

# Weak vendor mode

## With both vendors and non-vendor errors

```

WARNINGS!
Tests: 1068, Assertions: 2714, Warnings: 6, Skipped: 15.

Remaining deprecation notices (1)

some error: 1x
    1x in SonataAdminBundleTest::testBuild from Sonata\AdminBundle\Tests

Remaining vendor deprecation notices (4)

Legacy deprecation notices (48)
```

Exit code is 1

## After fixing non-vendor errors

```
WARNINGS!
Tests: 1068, Assertions: 2714, Warnings: 6, Skipped: 15.

Remaining vendor deprecation notices (4)

Legacy deprecation notices (48)
```

Exit code is 0

# TODO

- [x] fix colorization issues (vendor deprecation notices are always in red)
- [x] make the vendor detection more robust
- [x] make the vendor dir configurable
- [x] ~figure out how to run tests and~ add more of them
    - [x] test on non-vendor file
    - [x] test on vendor file
- [x] do not change the output of other modes

Commits
-------

61fd043dd5 Introduce weak vendors mode
This commit is contained in:
Fabien Potencier 2017-02-27 17:37:33 -08:00
commit 43bff2279b
7 changed files with 196 additions and 6 deletions

View File

@ -19,6 +19,7 @@ namespace Symfony\Bridge\PhpUnit;
class DeprecationErrorHandler
{
const MODE_WEAK = 'weak';
const MODE_WEAK_VENDORS = 'weak_vendors';
const MODE_DISABLED = 'disabled';
private static $isRegistered = false;
@ -28,6 +29,7 @@ class DeprecationErrorHandler
*
* The following reporting modes are supported:
* - use "weak" to hide the deprecation report but keep a global count;
* - use "weak_vendors" to act as "weak" but only for vendors;
* - use "/some-regexp/" to stop the test suite whenever a deprecation
* message matches the given regular expression;
* - use a number to define the upper bound of allowed deprecations,
@ -52,13 +54,37 @@ class DeprecationErrorHandler
if (false === $mode) {
$mode = getenv('SYMFONY_DEPRECATIONS_HELPER');
}
if (DeprecationErrorHandler::MODE_WEAK !== $mode && (!isset($mode[0]) || '/' !== $mode[0])) {
if (DeprecationErrorHandler::MODE_WEAK !== $mode && DeprecationErrorHandler::MODE_WEAK_VENDORS !== $mode && (!isset($mode[0]) || '/' !== $mode[0])) {
$mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0;
}
return $memoizedMode = $mode;
};
$inVendors = function ($path) {
/** @var string[] absolute paths to vendor directories */
static $vendors;
if (null === $vendors) {
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')) {
$vendors[] = $v;
}
}
}
}
$path = realpath($path) ?: $path;
foreach ($vendors as $vendor) {
if (0 === strpos($path, $vendor) && false !== strpbrk(substr($path, strlen($vendor), 1), '/'.DIRECTORY_SEPARATOR)) {
return true;
}
}
return false;
};
$deprecations = array(
'unsilencedCount' => 0,
'remainingCount' => 0,
@ -69,7 +95,13 @@ class DeprecationErrorHandler
'legacy' => array(),
'other' => array(),
);
$deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $getMode, $UtilPrefix) {
if (self::MODE_WEAK_VENDORS === $mode) {
$deprecations += array(
'remaining vendorCount' => 0,
'remaining vendor' => array(),
);
}
$deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $getMode, $UtilPrefix, $inVendors) {
$mode = $getMode();
if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || DeprecationErrorHandler::MODE_DISABLED === $mode) {
$ErrorHandler = $UtilPrefix.'ErrorHandler';
@ -80,6 +112,8 @@ class DeprecationErrorHandler
$trace = debug_backtrace(true);
$group = 'other';
$isWeak = DeprecationErrorHandler::MODE_WEAK === $mode || (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor = $inVendors($file));
$i = count($trace);
while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) {
// No-op
@ -99,6 +133,8 @@ class DeprecationErrorHandler
|| in_array('legacy', $Test::getGroups($class, $method), true)
) {
$group = 'legacy';
} elseif (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor) {
$group = 'remaining vendor';
} else {
$group = 'remaining';
}
@ -117,13 +153,13 @@ class DeprecationErrorHandler
exit(1);
}
if ('legacy' !== $group && DeprecationErrorHandler::MODE_WEAK !== $mode) {
if ('legacy' !== $group && !$isWeak) {
$ref = &$deprecations[$group][$msg]['count'];
++$ref;
$ref = &$deprecations[$group][$msg][$class.'::'.$method];
++$ref;
}
} elseif (DeprecationErrorHandler::MODE_WEAK !== $mode) {
} elseif (!$isWeak) {
$ref = &$deprecations[$group][$msg]['count'];
++$ref;
}
@ -167,9 +203,18 @@ class DeprecationErrorHandler
return $b['count'] - $a['count'];
};
foreach (array('unsilenced', 'remaining', 'legacy', 'other') as $group) {
$groups = array('unsilenced', 'remaining');
if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) {
$groups[] = 'remaining vendor';
}
array_push($groups, 'legacy', 'other');
foreach ($groups as $group) {
if ($deprecations[$group.'Count']) {
echo "\n", $colorize(sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), 'legacy' !== $group), "\n";
echo "\n", $colorize(
sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']),
'legacy' !== $group && 'remaining vendor' !== $group
), "\n";
uasort($deprecations[$group], $cmp);

View File

@ -0,0 +1,35 @@
<?php
eval(<<<'EOPHP'
namespace PHPUnit\Util;
class Test
{
public static function getGroups()
{
return array();
}
}
EOPHP
);
@trigger_error('root deprecation', E_USER_DEPRECATED);
class FooTestCase
{
public function testLegacyFoo()
{
@trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED);
}
public function testNonLegacyBar()
{
@trigger_error('silenced bar deprecation', E_USER_DEPRECATED);
trigger_error('unsilenced bar deprecation', E_USER_DEPRECATED);
}
}
$foo = new FooTestCase();
$foo->testLegacyFoo();
$foo->testNonLegacyBar();

View File

@ -0,0 +1,3 @@
<?php
require_once __DIR__.'/composer/autoload_real.php';

View File

@ -0,0 +1,5 @@
<?php
class ComposerAutoloaderInitFake
{
}

View File

@ -0,0 +1 @@
{"just here": "for the detection"}

View File

@ -0,0 +1,74 @@
--TEST--
Test DeprecationErrorHandler in weak vendors mode on a non vendor file
--FILE--
<?php
putenv('SYMFONY_DEPRECATIONS_HELPER=weak_vendors');
putenv('ANSICON');
putenv('ConEmuANSI');
putenv('TERM');
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
}
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
require PHPUNIT_COMPOSER_INSTALL;
require_once __DIR__.'/../../bootstrap.php';
@trigger_error('root deprecation', E_USER_DEPRECATED);
eval(<<<'EOPHP'
namespace PHPUnit\Util;
class Test
{
public static function getGroups()
{
return array();
}
}
EOPHP
);
class FooTestCase
{
public function testLegacyFoo()
{
@trigger_error('silenced foo deprecation', E_USER_DEPRECATED);
trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED);
trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED);
}
public function testNonLegacyBar()
{
@trigger_error('silenced bar deprecation', E_USER_DEPRECATED);
trigger_error('unsilenced bar deprecation', E_USER_DEPRECATED);
}
}
$foo = new FooTestCase();
$foo->testLegacyFoo();
$foo->testNonLegacyBar();
?>
--EXPECTF--
Unsilenced deprecation notices (3)
unsilenced foo deprecation: 2x
2x in FooTestCase::testLegacyFoo
unsilenced bar deprecation: 1x
1x in FooTestCase::testNonLegacyBar
Remaining deprecation notices (1)
silenced bar deprecation: 1x
1x in FooTestCase::testNonLegacyBar
Legacy deprecation notices (1)
Other deprecation notices (1)
root deprecation: 1x

View File

@ -0,0 +1,27 @@
--TEST--
Test DeprecationErrorHandler in weak vendors mode on vendor file
--FILE--
<?php
putenv('SYMFONY_DEPRECATIONS_HELPER=weak_vendors');
putenv('ANSICON');
putenv('ConEmuANSI');
putenv('TERM');
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
}
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
require PHPUNIT_COMPOSER_INSTALL;
require_once __DIR__.'/../../bootstrap.php';
require __DIR__.'/fake_vendor/autoload.php';
require __DIR__.'/fake_vendor/acme/lib/deprecation_riddled.php';
--EXPECTF--
Unsilenced deprecation notices (2)
Remaining vendor deprecation notices (1)
Legacy deprecation notices (1)
Other deprecation notices (1)