Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Bridge\PhpUnit; Chris@0: Chris@0: /** Chris@0: * Catch deprecation notices and print a summary report at the end of the test suite. Chris@0: * Chris@0: * @author Nicolas Grekas Chris@0: */ Chris@0: class DeprecationErrorHandler Chris@0: { Chris@0: const MODE_WEAK = 'weak'; Chris@0: const MODE_DISABLED = 'disabled'; Chris@0: Chris@0: private static $isRegistered = false; Chris@0: Chris@0: /** Chris@0: * Registers and configures the deprecation handler. Chris@0: * Chris@0: * The following reporting modes are supported: Chris@0: * - use "weak" to hide the deprecation report but keep a global count; Chris@0: * - use "/some-regexp/" to stop the test suite whenever a deprecation Chris@0: * message matches the given regular expression; Chris@0: * - use a number to define the upper bound of allowed deprecations, Chris@0: * making the test suite fail whenever more notices are trigerred. Chris@0: * Chris@0: * @param int|string|false $mode The reporting mode, defaults to not allowing any deprecations Chris@0: */ Chris@0: public static function register($mode = 0) Chris@0: { Chris@0: if (self::$isRegistered) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $getMode = function () use ($mode) { Chris@0: static $memoizedMode = false; Chris@0: Chris@0: if (false !== $memoizedMode) { Chris@0: return $memoizedMode; Chris@0: } Chris@0: if (false === $mode) { Chris@0: $mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); Chris@0: } Chris@0: if (DeprecationErrorHandler::MODE_WEAK !== $mode && (!isset($mode[0]) || '/' !== $mode[0])) { Chris@0: $mode = preg_match('/^[1-9][0-9]*$/', $mode) ? (int) $mode : 0; Chris@0: } Chris@0: Chris@0: return $memoizedMode = $mode; Chris@0: }; Chris@0: Chris@0: $deprecations = array( Chris@0: 'unsilencedCount' => 0, Chris@0: 'remainingCount' => 0, Chris@0: 'legacyCount' => 0, Chris@0: 'otherCount' => 0, Chris@0: 'unsilenced' => array(), Chris@0: 'remaining' => array(), Chris@0: 'legacy' => array(), Chris@0: 'other' => array(), Chris@0: ); Chris@0: $deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $getMode) { Chris@0: $mode = $getMode(); Chris@0: if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || DeprecationErrorHandler::MODE_DISABLED === $mode) { Chris@0: return \PHPUnit_Util_ErrorHandler::handleError($type, $msg, $file, $line, $context); Chris@0: } Chris@0: Chris@0: $trace = debug_backtrace(true); Chris@0: $group = 'other'; Chris@0: Chris@0: $i = count($trace); Chris@0: while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_')))) { Chris@0: // No-op Chris@0: } Chris@0: Chris@0: if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { Chris@0: $class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class']; Chris@0: $method = $trace[$i]['function']; Chris@0: Chris@0: if (0 !== error_reporting()) { Chris@0: $group = 'unsilenced'; Chris@0: } elseif (0 === strpos($method, 'testLegacy') Chris@0: || 0 === strpos($method, 'provideLegacy') Chris@0: || 0 === strpos($method, 'getLegacy') Chris@0: || strpos($class, '\Legacy') Chris@0: || in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) Chris@0: ) { Chris@0: $group = 'legacy'; Chris@0: } else { Chris@0: $group = 'remaining'; Chris@0: } Chris@0: Chris@0: if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $msg)) { Chris@0: $e = new \Exception($msg); Chris@0: $r = new \ReflectionProperty($e, 'trace'); Chris@0: $r->setAccessible(true); Chris@0: $r->setValue($e, array_slice($trace, 1, $i)); Chris@0: Chris@0: echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; Chris@0: echo "\n".$msg; Chris@0: echo "\nStack trace:"; Chris@0: echo "\n".str_replace(' '.getcwd().DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); Chris@0: echo "\n"; Chris@0: Chris@0: exit(1); Chris@0: } Chris@0: if ('legacy' !== $group && DeprecationErrorHandler::MODE_WEAK !== $mode) { Chris@0: $ref = &$deprecations[$group][$msg]['count']; Chris@0: ++$ref; Chris@0: $ref = &$deprecations[$group][$msg][$class.'::'.$method]; Chris@0: ++$ref; Chris@0: } Chris@0: } elseif (DeprecationErrorHandler::MODE_WEAK !== $mode) { Chris@0: $ref = &$deprecations[$group][$msg]['count']; Chris@0: ++$ref; Chris@0: } Chris@0: ++$deprecations[$group.'Count']; Chris@0: }; Chris@0: $oldErrorHandler = set_error_handler($deprecationHandler); Chris@0: Chris@0: if (null !== $oldErrorHandler) { Chris@0: restore_error_handler(); Chris@0: if (array('PHPUnit_Util_ErrorHandler', 'handleError') === $oldErrorHandler) { Chris@0: restore_error_handler(); Chris@0: self::register($mode); Chris@0: } Chris@0: } else { Chris@0: self::$isRegistered = true; Chris@0: if (self::hasColorSupport()) { Chris@0: $colorize = function ($str, $red) { Chris@0: $color = $red ? '41;37' : '43;30'; Chris@0: Chris@0: return "\x1B[{$color}m{$str}\x1B[0m"; Chris@0: }; Chris@0: } else { Chris@0: $colorize = function ($str) { return $str; }; Chris@0: } Chris@0: register_shutdown_function(function () use ($getMode, &$deprecations, $deprecationHandler, $colorize) { Chris@0: $mode = $getMode(); Chris@0: if (isset($mode[0]) && '/' === $mode[0]) { Chris@0: return; Chris@0: } Chris@0: $currErrorHandler = set_error_handler('var_dump'); Chris@0: restore_error_handler(); Chris@0: Chris@0: if (DeprecationErrorHandler::MODE_WEAK === $mode) { Chris@0: $colorize = function ($str) { return $str; }; Chris@0: } Chris@0: if ($currErrorHandler !== $deprecationHandler) { Chris@0: echo "\n", $colorize('THE ERROR HANDLER HAS CHANGED!', true), "\n"; Chris@0: } Chris@0: Chris@0: $cmp = function ($a, $b) { Chris@0: return $b['count'] - $a['count']; Chris@0: }; Chris@0: Chris@0: foreach (array('unsilenced', 'remaining', 'legacy', 'other') as $group) { Chris@0: if ($deprecations[$group.'Count']) { Chris@0: echo "\n", $colorize(sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), 'legacy' !== $group), "\n"; Chris@0: Chris@0: uasort($deprecations[$group], $cmp); Chris@0: Chris@0: foreach ($deprecations[$group] as $msg => $notices) { Chris@0: echo "\n", rtrim($msg, '.'), ': ', $notices['count'], "x\n"; Chris@0: Chris@0: arsort($notices); Chris@0: Chris@0: foreach ($notices as $method => $count) { Chris@0: if ('count' !== $method) { Chris@0: echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: if (!empty($notices)) { Chris@0: echo "\n"; Chris@0: } Chris@0: Chris@0: if (DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { Chris@0: exit(1); Chris@0: } Chris@0: }); Chris@0: } Chris@0: } Chris@0: Chris@0: private static function hasColorSupport() Chris@0: { Chris@0: if ('\\' === DIRECTORY_SEPARATOR) { Chris@0: return Chris@0: '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD Chris@0: || false !== getenv('ANSICON') Chris@0: || 'ON' === getenv('ConEmuANSI') Chris@0: || 'xterm' === getenv('TERM'); Chris@0: } Chris@0: Chris@0: return defined('STDOUT') && function_exists('posix_isatty') && @posix_isatty(STDOUT); Chris@0: } Chris@0: }