comparison vendor/symfony/phpunit-bridge/DeprecationErrorHandler.php @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children a9cd425dd02b
comparison
equal deleted inserted replaced
-1:000000000000 0:c75dbcec494b
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Bridge\PhpUnit;
13
14 /**
15 * Catch deprecation notices and print a summary report at the end of the test suite.
16 *
17 * @author Nicolas Grekas <p@tchwork.com>
18 */
19 class DeprecationErrorHandler
20 {
21 const MODE_WEAK = 'weak';
22 const MODE_WEAK_VENDORS = 'weak_vendors';
23 const MODE_DISABLED = 'disabled';
24
25 private static $isRegistered = false;
26
27 /**
28 * Registers and configures the deprecation handler.
29 *
30 * The following reporting modes are supported:
31 * - use "weak" to hide the deprecation report but keep a global count;
32 * - use "weak_vendors" to act as "weak" but only for vendors;
33 * - use "/some-regexp/" to stop the test suite whenever a deprecation
34 * message matches the given regular expression;
35 * - use a number to define the upper bound of allowed deprecations,
36 * making the test suite fail whenever more notices are trigerred.
37 *
38 * @param int|string|false $mode The reporting mode, defaults to not allowing any deprecations
39 */
40 public static function register($mode = 0)
41 {
42 if (self::$isRegistered) {
43 return;
44 }
45
46 $UtilPrefix = class_exists('PHPUnit_Util_ErrorHandler') ? 'PHPUnit_Util_' : 'PHPUnit\Util\\';
47
48 $getMode = function () use ($mode) {
49 static $memoizedMode = false;
50
51 if (false !== $memoizedMode) {
52 return $memoizedMode;
53 }
54 if (false === $mode) {
55 $mode = getenv('SYMFONY_DEPRECATIONS_HELPER');
56 }
57 if (DeprecationErrorHandler::MODE_WEAK !== $mode && DeprecationErrorHandler::MODE_WEAK_VENDORS !== $mode && (!isset($mode[0]) || '/' !== $mode[0])) {
58 $mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0;
59 }
60
61 return $memoizedMode = $mode;
62 };
63
64 $inVendors = function ($path) {
65 /** @var string[] absolute paths to vendor directories */
66 static $vendors;
67 if (null === $vendors) {
68 foreach (get_declared_classes() as $class) {
69 if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
70 $r = new \ReflectionClass($class);
71 $v = dirname(dirname($r->getFileName()));
72 if (file_exists($v.'/composer/installed.json')) {
73 $vendors[] = $v;
74 }
75 }
76 }
77 }
78 $realPath = realpath($path);
79 if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) {
80 return true;
81 }
82 foreach ($vendors as $vendor) {
83 if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, strlen($vendor), 1), '/'.DIRECTORY_SEPARATOR)) {
84 return true;
85 }
86 }
87
88 return false;
89 };
90
91 $deprecations = array(
92 'unsilencedCount' => 0,
93 'remainingCount' => 0,
94 'legacyCount' => 0,
95 'otherCount' => 0,
96 'remaining vendorCount' => 0,
97 'unsilenced' => array(),
98 'remaining' => array(),
99 'legacy' => array(),
100 'other' => array(),
101 'remaining vendor' => array(),
102 );
103 $deprecationHandler = function ($type, $msg, $file, $line, $context = array()) use (&$deprecations, $getMode, $UtilPrefix, $inVendors) {
104 $mode = $getMode();
105 if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || DeprecationErrorHandler::MODE_DISABLED === $mode) {
106 $ErrorHandler = $UtilPrefix.'ErrorHandler';
107
108 return $ErrorHandler::handleError($type, $msg, $file, $line, $context);
109 }
110
111 $trace = debug_backtrace(true);
112 $group = 'other';
113 $isVendor = DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $inVendors($file);
114
115 $i = count($trace);
116 while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) {
117 // No-op
118 }
119
120 if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) {
121 if (isset($trace[$i]['class']) && 0 === strpos($trace[$i]['class'], 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor')) {
122 $parsedMsg = unserialize($msg);
123 $msg = $parsedMsg['deprecation'];
124 $class = $parsedMsg['class'];
125 $method = $parsedMsg['method'];
126 // If the deprecation has been triggered via
127 // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
128 // then we need to use the serialized information to determine
129 // if the error has been triggered from vendor code.
130 $isVendor = DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && isset($parsedMsg['triggering_file']) && $inVendors($parsedMsg['triggering_file']);
131 } else {
132 $class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class'];
133 $method = $trace[$i]['function'];
134 }
135
136 $Test = $UtilPrefix.'Test';
137
138 if (0 !== error_reporting()) {
139 $group = 'unsilenced';
140 } elseif (0 === strpos($method, 'testLegacy')
141 || 0 === strpos($method, 'provideLegacy')
142 || 0 === strpos($method, 'getLegacy')
143 || strpos($class, '\Legacy')
144 || in_array('legacy', $Test::getGroups($class, $method), true)
145 ) {
146 $group = 'legacy';
147 } elseif ($isVendor) {
148 $group = 'remaining vendor';
149 } else {
150 $group = 'remaining';
151 }
152
153 if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $msg)) {
154 $e = new \Exception($msg);
155 $r = new \ReflectionProperty($e, 'trace');
156 $r->setAccessible(true);
157 $r->setValue($e, array_slice($trace, 1, $i));
158
159 echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':';
160 echo "\n".$msg;
161 echo "\nStack trace:";
162 echo "\n".str_replace(' '.getcwd().DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString());
163 echo "\n";
164
165 exit(1);
166 }
167 if ('legacy' !== $group && DeprecationErrorHandler::MODE_WEAK !== $mode) {
168 $ref = &$deprecations[$group][$msg]['count'];
169 ++$ref;
170 $ref = &$deprecations[$group][$msg][$class.'::'.$method];
171 ++$ref;
172 }
173 } elseif (DeprecationErrorHandler::MODE_WEAK !== $mode) {
174 $ref = &$deprecations[$group][$msg]['count'];
175 ++$ref;
176 }
177 ++$deprecations[$group.'Count'];
178 };
179 $oldErrorHandler = set_error_handler($deprecationHandler);
180
181 if (null !== $oldErrorHandler) {
182 restore_error_handler();
183 if (array($UtilPrefix.'ErrorHandler', 'handleError') === $oldErrorHandler) {
184 restore_error_handler();
185 self::register($mode);
186 }
187 } else {
188 self::$isRegistered = true;
189 if (self::hasColorSupport()) {
190 $colorize = function ($str, $red) {
191 $color = $red ? '41;37' : '43;30';
192
193 return "\x1B[{$color}m{$str}\x1B[0m";
194 };
195 } else {
196 $colorize = function ($str) { return $str; };
197 }
198 register_shutdown_function(function () use ($getMode, &$deprecations, $deprecationHandler, $colorize) {
199 $mode = $getMode();
200 if (isset($mode[0]) && '/' === $mode[0]) {
201 return;
202 }
203 $currErrorHandler = set_error_handler('var_dump');
204 restore_error_handler();
205
206 if (DeprecationErrorHandler::MODE_WEAK === $mode) {
207 $colorize = function ($str) { return $str; };
208 }
209 if ($currErrorHandler !== $deprecationHandler) {
210 echo "\n", $colorize('THE ERROR HANDLER HAS CHANGED!', true), "\n";
211 }
212
213 $cmp = function ($a, $b) {
214 return $b['count'] - $a['count'];
215 };
216
217 $groups = array('unsilenced', 'remaining');
218 if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) {
219 $groups[] = 'remaining vendor';
220 }
221 array_push($groups, 'legacy', 'other');
222
223 $displayDeprecations = function ($deprecations) use ($colorize, $cmp, $groups) {
224 foreach ($groups as $group) {
225 if ($deprecations[$group.'Count']) {
226 echo "\n", $colorize(
227 sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']),
228 'legacy' !== $group && 'remaining vendor' !== $group
229 ), "\n";
230
231 uasort($deprecations[$group], $cmp);
232
233 foreach ($deprecations[$group] as $msg => $notices) {
234 echo "\n ", $notices['count'], 'x: ', $msg, "\n";
235
236 arsort($notices);
237
238 foreach ($notices as $method => $count) {
239 if ('count' !== $method) {
240 echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n";
241 }
242 }
243 }
244 }
245 }
246 if (!empty($notices)) {
247 echo "\n";
248 }
249 };
250
251 $displayDeprecations($deprecations);
252
253 // store failing status
254 $isFailing = DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount'];
255
256 // reset deprecations array
257 foreach ($deprecations as $group => $arrayOrInt) {
258 $deprecations[$group] = is_int($arrayOrInt) ? 0 : array();
259 }
260
261 register_shutdown_function(function () use (&$deprecations, $isFailing, $displayDeprecations, $mode) {
262 foreach ($deprecations as $group => $arrayOrInt) {
263 if (0 < (is_int($arrayOrInt) ? $arrayOrInt : count($arrayOrInt))) {
264 echo "Shutdown-time deprecations:\n";
265 break;
266 }
267 }
268 $displayDeprecations($deprecations);
269 if ($isFailing || DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) {
270 exit(1);
271 }
272 });
273 });
274 }
275 }
276
277 public static function collectDeprecations($outputFile)
278 {
279 $deprecations = array();
280 $previousErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) use (&$deprecations, &$previousErrorHandler) {
281 if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) {
282 if ($previousErrorHandler) {
283 return $previousErrorHandler($type, $msg, $file, $line, $context);
284 }
285 static $autoload = true;
286
287 $ErrorHandler = class_exists('PHPUnit_Util_ErrorHandler', $autoload) ? 'PHPUnit_Util_ErrorHandler' : 'PHPUnit\Util\ErrorHandler';
288 $autoload = false;
289
290 return $ErrorHandler::handleError($type, $msg, $file, $line, $context);
291 }
292 $deprecations[] = array(error_reporting(), $msg, $file);
293 });
294
295 register_shutdown_function(function () use ($outputFile, &$deprecations) {
296 file_put_contents($outputFile, serialize($deprecations));
297 });
298 }
299
300 /**
301 * Returns true if STDOUT is defined and supports colorization.
302 *
303 * Reference: Composer\XdebugHandler\Process::supportsColor
304 * https://github.com/composer/xdebug-handler
305 *
306 * @return bool
307 */
308 private static function hasColorSupport()
309 {
310 if (!defined('STDOUT')) {
311 return false;
312 }
313
314 if (DIRECTORY_SEPARATOR === '\\') {
315 return (function_exists('sapi_windows_vt100_support')
316 && sapi_windows_vt100_support(STDOUT))
317 || false !== getenv('ANSICON')
318 || 'ON' === getenv('ConEmuANSI')
319 || 'xterm' === getenv('TERM');
320 }
321
322 if (function_exists('stream_isatty')) {
323 return stream_isatty(STDOUT);
324 }
325
326 if (function_exists('posix_isatty')) {
327 return posix_isatty(STDOUT);
328 }
329
330 $stat = fstat(STDOUT);
331 // Check if formatted mode is S_IFCHR
332 return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
333 }
334 }