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