feature #39098 [PhpUnitBridge] Add log file option for deprecations (michaelKaefer)

This PR was merged into the 5.3-dev branch.

Discussion
----------

[PhpUnitBridge] Add log file option for deprecations

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #37679
| License       | MIT
| Doc PR        | symfony/symfony-docs#14558

Commits
-------

3ed22bea69 [PhpUnitBridge] Add log file option for deprecations
This commit is contained in:
Nicolas Grekas 2021-01-05 20:06:00 +01:00
commit 80bde1e27c
4 changed files with 110 additions and 19 deletions

View File

@ -7,6 +7,7 @@ CHANGELOG
* bumped the minimum PHP version to 7.1.3 * bumped the minimum PHP version to 7.1.3
* bumped the minimum PHPUnit version to 7.5 * bumped the minimum PHPUnit version to 7.5
* deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint. * deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint.
* added `logFile` option to write deprecations to a file instead of echoing them
5.1.0 5.1.0
----- -----

View File

@ -285,6 +285,8 @@ class DeprecationErrorHandler
* @param string[] $groups * @param string[] $groups
* @param Configuration $configuration * @param Configuration $configuration
* @param bool $isFailing * @param bool $isFailing
*
* @throws \InvalidArgumentException
*/ */
private function displayDeprecations($groups, $configuration, $isFailing) private function displayDeprecations($groups, $configuration, $isFailing)
{ {
@ -292,16 +294,26 @@ class DeprecationErrorHandler
return $b->count() - $a->count(); return $b->count() - $a->count();
}; };
if ($configuration->shouldWriteToLogFile()) {
if (false === $handle = @fopen($file = $configuration->getLogFile(), 'a')) {
throw new \InvalidArgumentException(sprintf('The configured log file "%s" is not writeable.', $file));
}
} else {
$handle = fopen('php://output', 'w');
}
foreach ($groups as $group) { foreach ($groups as $group) {
if ($this->deprecationGroups[$group]->count()) { if ($this->deprecationGroups[$group]->count()) {
echo "\n", self::colorize( $deprecationGroupMessage = sprintf(
sprintf( '%s deprecation notices (%d)',
'%s deprecation notices (%d)', \in_array($group, ['direct', 'indirect', 'self'], true) ? "Remaining $group" : ucfirst($group),
\in_array($group, ['direct', 'indirect', 'self'], true) ? "Remaining $group" : ucfirst($group), $this->deprecationGroups[$group]->count()
$this->deprecationGroups[$group]->count() );
), if ($configuration->shouldWriteToLogFile()) {
'legacy' !== $group && 'indirect' !== $group fwrite($handle, "\n$deprecationGroupMessage\n");
), "\n"; } else {
fwrite($handle, "\n".self::colorize($deprecationGroupMessage, 'legacy' !== $group && 'indirect' !== $group)."\n");
}
if ('legacy' !== $group && !$configuration->verboseOutput($group) && !$isFailing) { if ('legacy' !== $group && !$configuration->verboseOutput($group) && !$isFailing) {
continue; continue;
@ -310,14 +322,14 @@ class DeprecationErrorHandler
uasort($notices, $cmp); uasort($notices, $cmp);
foreach ($notices as $msg => $notice) { foreach ($notices as $msg => $notice) {
echo "\n ", $notice->count(), 'x: ', $msg, "\n"; fwrite($handle, sprintf("\n %sx: %s\n", $notice->count(), $msg));
$countsByCaller = $notice->getCountsByCaller(); $countsByCaller = $notice->getCountsByCaller();
arsort($countsByCaller); arsort($countsByCaller);
foreach ($countsByCaller as $method => $count) { foreach ($countsByCaller as $method => $count) {
if ('count' !== $method) { if ('count' !== $method) {
echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; fwrite($handle, sprintf(" %dx in %s\n", $count, preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method)));
} }
} }
} }
@ -325,7 +337,7 @@ class DeprecationErrorHandler
} }
if (!empty($notices)) { if (!empty($notices)) {
echo "\n"; fwrite($handle, "\n");
} }
} }

View File

@ -52,13 +52,19 @@ class Configuration
private $baselineDeprecations = []; private $baselineDeprecations = [];
/** /**
* @param int[] $thresholds A hash associating groups to thresholds * @var string|null
* @param string $regex Will be matched against messages, to decide whether to display a stack trace
* @param bool[] $verboseOutput Keyed by groups
* @param bool $generateBaseline Whether to generate or update the baseline file
* @param string $baselineFile The path to the baseline file
*/ */
private function __construct(array $thresholds = [], $regex = '', $verboseOutput = [], $generateBaseline = false, $baselineFile = '') private $logFile = null;
/**
* @param int[] $thresholds A hash associating groups to thresholds
* @param string $regex Will be matched against messages, to decide whether to display a stack trace
* @param bool[] $verboseOutput Keyed by groups
* @param bool $generateBaseline Whether to generate or update the baseline file
* @param string $baselineFile The path to the baseline file
* @param string|null $logFile The path to the log file
*/
private function __construct(array $thresholds = [], $regex = '', $verboseOutput = [], $generateBaseline = false, $baselineFile = '', $logFile = null)
{ {
$groups = ['total', 'indirect', 'direct', 'self']; $groups = ['total', 'indirect', 'direct', 'self'];
@ -119,6 +125,8 @@ class Configuration
throw new \InvalidArgumentException(sprintf('The baselineFile "%s" does not exist.', $this->baselineFile)); throw new \InvalidArgumentException(sprintf('The baselineFile "%s" does not exist.', $this->baselineFile));
} }
} }
$this->logFile = $logFile;
} }
/** /**
@ -238,6 +246,16 @@ class Configuration
return $this->verboseOutput[$group]; return $this->verboseOutput[$group];
} }
public function shouldWriteToLogFile()
{
return null !== $this->logFile;
}
public function getLogFile()
{
return $this->logFile;
}
/** /**
* @param string $serializedConfiguration an encoded string, for instance * @param string $serializedConfiguration an encoded string, for instance
* max[total]=1234&max[indirect]=42 * max[total]=1234&max[indirect]=42
@ -248,7 +266,7 @@ class Configuration
{ {
parse_str($serializedConfiguration, $normalizedConfiguration); parse_str($serializedConfiguration, $normalizedConfiguration);
foreach (array_keys($normalizedConfiguration) as $key) { foreach (array_keys($normalizedConfiguration) as $key) {
if (!\in_array($key, ['max', 'disabled', 'verbose', 'quiet', 'generateBaseline', 'baselineFile'], true)) { if (!\in_array($key, ['max', 'disabled', 'verbose', 'quiet', 'generateBaseline', 'baselineFile', 'logFile'], true)) {
throw new \InvalidArgumentException(sprintf('Unknown configuration option "%s".', $key)); throw new \InvalidArgumentException(sprintf('Unknown configuration option "%s".', $key));
} }
} }
@ -260,6 +278,7 @@ class Configuration
'quiet' => [], 'quiet' => [],
'generateBaseline' => false, 'generateBaseline' => false,
'baselineFile' => '', 'baselineFile' => '',
'logFile' => null,
]; ];
if ('' === $normalizedConfiguration['disabled'] || filter_var($normalizedConfiguration['disabled'], \FILTER_VALIDATE_BOOLEAN)) { if ('' === $normalizedConfiguration['disabled'] || filter_var($normalizedConfiguration['disabled'], \FILTER_VALIDATE_BOOLEAN)) {
@ -282,7 +301,8 @@ class Configuration
'', '',
$verboseOutput, $verboseOutput,
filter_var($normalizedConfiguration['generateBaseline'], \FILTER_VALIDATE_BOOLEAN), filter_var($normalizedConfiguration['generateBaseline'], \FILTER_VALIDATE_BOOLEAN),
$normalizedConfiguration['baselineFile'] $normalizedConfiguration['baselineFile'],
$normalizedConfiguration['logFile']
); );
} }

View File

@ -0,0 +1,58 @@
--TEST--
Test DeprecationErrorHandler with log file
--FILE--
<?php
$filename = tempnam(sys_get_temp_dir(), 'sf-').uniqid();
$k = 'SYMFONY_DEPRECATIONS_HELPER';
putenv($k.'='.$_SERVER[$k] = $_ENV[$k] = 'logFile='.$filename);
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';
class FooTestCase
{
public function testLegacyFoo()
{
trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED);
trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED);
}
public function testLegacyBar()
{
trigger_error('unsilenced bar deprecation', E_USER_DEPRECATED);
}
}
@trigger_error('root deprecation', E_USER_DEPRECATED);
$foo = new FooTestCase();
$foo->testLegacyFoo();
$foo->testLegacyBar();
register_shutdown_function(function () use ($filename) {
var_dump(file_get_contents($filename));
});
?>
--EXPECTF--
string(234) "
Unsilenced deprecation notices (3)
2x: unsilenced foo deprecation
2x in FooTestCase::testLegacyFoo
1x: unsilenced bar deprecation
1x in FooTestCase::testLegacyBar
Other deprecation notices (1)
1x: root deprecation
"