Skip to content

Commit 9bf6a62

Browse files
[PhpUnitBridge] new bridge for testing with PHPUnit
0 parents  commit 9bf6a62

File tree

7 files changed

+325
-0
lines changed

7 files changed

+325
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml

DeprecationErrorHandler.php

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 <[email protected]>
18+
*/
19+
class DeprecationErrorHandler
20+
{
21+
private static $isRegistered = false;
22+
23+
public static function register($strict = false)
24+
{
25+
if (self::$isRegistered) {
26+
return;
27+
}
28+
$deprecations = array(
29+
'remainingCount' => 0,
30+
'legacyCount' => 0,
31+
'otherCount' => 0,
32+
'remaining' => array(),
33+
'legacy' => array(),
34+
'other' => array(),
35+
);
36+
$deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $strict) {
37+
if (E_USER_DEPRECATED !== $type) {
38+
return \PHPUnit_Util_ErrorHandler::handleError($type, $msg, $file, $line, $context);
39+
}
40+
41+
$trace = debug_backtrace(PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT : true);
42+
43+
$i = count($trace);
44+
while (isset($trace[--$i]['class']) && ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_'))) {
45+
// No-op
46+
}
47+
48+
if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) {
49+
$class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class'];
50+
$method = $trace[$i]['function'];
51+
52+
$group = 0 === strpos($method, 'testLegacy') || 0 === strpos($method, 'provideLegacy') || 0 === strpos($method, 'getLegacy') || strpos($class, '\Legacy') || in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) ? 'legacy' : 'remaining';
53+
54+
if ('legacy' === $group && 0 === (error_reporting() & E_USER_DEPRECATED)) {
55+
$ref =& $deprecations[$group]['Silenced']['count'];
56+
++$ref;
57+
} else {
58+
$ref =& $deprecations[$group][$msg]['count'];
59+
++$ref;
60+
$ref =& $deprecations[$group][$msg][$class.'::'.$method];
61+
++$ref;
62+
}
63+
} else {
64+
$group = 'other';
65+
$ref =& $deprecations[$group][$msg]['count'];
66+
++$ref;
67+
}
68+
++$deprecations[$group.'Count'];
69+
unset($trace, $ref);
70+
71+
if ('legacy' !== $group) {
72+
try {
73+
$e = $strict ? error_reporting(-1) : error_reporting();
74+
$result = \PHPUnit_Util_ErrorHandler::handleError($type, $msg, $file, $line, $context);
75+
error_reporting($e);
76+
} catch (\Exception $x) {
77+
error_reporting($e);
78+
79+
throw $x;
80+
}
81+
82+
return $result;
83+
}
84+
};
85+
$oldErrorHandler = set_error_handler($deprecationHandler);
86+
87+
if (null !== $oldErrorHandler) {
88+
restore_error_handler();
89+
if (array('PHPUnit_Util_ErrorHandler', 'handleError') === $oldErrorHandler) {
90+
restore_error_handler();
91+
self::register();
92+
}
93+
} else {
94+
self::$isRegistered = true;
95+
if (self::hasColorSupport()) {
96+
$colorize = function ($str, $red) {
97+
$color = $red ? '41;37' : '43;30';
98+
99+
return "\x1B[{$color}m{$str}\x1B[0m";
100+
};
101+
} else {
102+
$colorize = function ($str) {return $str;};
103+
}
104+
register_shutdown_function(function () use (&$deprecations, $deprecationHandler, $colorize) {
105+
$currErrorHandler = set_error_handler('var_dump');
106+
restore_error_handler();
107+
108+
if ($currErrorHandler !== $deprecationHandler) {
109+
echo "\n", $colorize('THE ERROR HANDLER HAS CHANGED!', true), "\n";
110+
}
111+
112+
$cmp = function ($a, $b) {
113+
return $b['count'] - $a['count'];
114+
};
115+
116+
foreach (array('remaining', 'legacy', 'other') as $group) {
117+
if ($deprecations[$group]) {
118+
echo "\n", $colorize(sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), 'legacy' !== $group), "\n";
119+
120+
uasort($deprecations[$group], $cmp);
121+
122+
foreach ($deprecations[$group] as $msg => $notices) {
123+
echo "\n", $msg, ': ', $notices['count'], "x\n";
124+
125+
arsort($notices);
126+
127+
foreach ($notices as $method => $count) {
128+
if ('count' !== $method) {
129+
echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n";
130+
}
131+
}
132+
}
133+
}
134+
}
135+
if (!empty($notices)) {
136+
echo "\n";
137+
}
138+
});
139+
}
140+
}
141+
142+
private static function hasColorSupport()
143+
{
144+
if ('\\' === DIRECTORY_SEPARATOR) {
145+
return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
146+
}
147+
148+
return defined('STDOUT') && function_exists('posix_isatty') && @posix_isatty(STDOUT);
149+
}
150+
}

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2014-2015 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
PHPUnit Bridge
2+
==============
3+
4+
Provides utilities for PHPUnit, especially user deprecation notices management.
5+
6+
It comes with the following features:
7+
8+
* disable the garbage collector;
9+
* auto-register `class_exists` to load Doctrine annotations;
10+
* print a user deprecation notices summary at the end of the test suite.
11+
12+
Handling user deprecation notices is sensitive to the SYMFONY_DEPRECATIONS_HELPER
13+
environment variable. This env var configures 3 behaviors depending on its value:
14+
15+
* when set to `strict`, all but legacy-tagged deprecation notices will make tests
16+
fail. This is the recommended mode for best forward compatibility.
17+
* `weak` on the contrary will make tests ignore all deprecation notices.
18+
This is the recommended mode for legacy projects that must use deprecated
19+
interfaces for backward compatibility reasons.
20+
* any other value will respect the current error reporting level.
21+
22+
All three modes will display a summary of deprecation notices at the end of the
23+
test suite, split in two groups:
24+
25+
* **Legacy** deprecation notices denote tests that explicitly test some legacy
26+
interfaces. In all 3 modes, deprecation notices triggered in a legacy-tagged
27+
test do never make a test fail. There are four ways to mark a test as legacy:
28+
- make its class start with the `Legacy` prefix;
29+
- make its method start with `testLegacy`;
30+
- make its data provider start with `provideLegacy` or `getLegacy`;
31+
- add the `@group legacy` annotation to its class or method.
32+
* **Remaining/Other** deprecation notices are all other (non-legacy)
33+
notices, grouped by message, test class and method.
34+
35+
Usage
36+
-----
37+
38+
Add this bridge to the `require-dev` section of your composer.json file
39+
(not in `require`) with e.g.
40+
`composer require --dev "symfony/phpunit-bridge"`.
41+
42+
When running `phpunit`, you will see a summary of deprecation notices at the end
43+
of the test suite.
44+
45+
Deprecation notices in the **Remaining/Other** section need some thought.
46+
You have to decide either to:
47+
48+
* update your code to not use deprecated interfaces anymore, thus gaining better
49+
forward compatibility;
50+
* or move them to the **Legacy** section (by using one of the above way).
51+
52+
After reviewing them, you should silence deprecations in the **Legacy** section
53+
if you think they are triggered by tests dedicated to testing deprecated
54+
interfaces. To do so, add the following line at the beginning of your legacy
55+
test case or in the `setUp()` method of your legacy test class:
56+
`$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);`
57+
58+
Last but not least, you should then configure your C.I. to the reporting mode
59+
that is appropriated to your project by setting SYMFONY_DEPRECATIONS_HELPER to
60+
`strict`, `weak` or empty. It is recommended to start with `weak` mode, upgrade
61+
your code as described above, then when the *Remaining/Other* sections are empty,
62+
move to `strict` to keep forward compatibility on the long run.

bootstrap.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use Doctrine\Common\Annotations\AnnotationRegistry;
4+
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
5+
6+
if (!class_exists('PHPUnit_Util_ErrorHandler')) {
7+
return;
8+
}
9+
10+
if (PHP_VERSION_ID >= 50400 && gc_enabled()) {
11+
// Disabling Zend Garbage Collection to prevent segfaults with PHP5.4+
12+
// https://bugs.php.net/bug.php?id=53976
13+
gc_disable();
14+
}
15+
16+
if (class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) {
17+
AnnotationRegistry::registerLoader('class_exists');
18+
}
19+
20+
switch (getenv('SYMFONY_DEPRECATIONS_HELPER')) {
21+
case 'strict':
22+
DeprecationErrorHandler::register(true);
23+
break;
24+
25+
case 'weak':
26+
error_reporting(error_reporting() & ~E_USER_DEPRECATED);
27+
// No break;
28+
default:
29+
DeprecationErrorHandler::register(false);
30+
break;
31+
}

composer.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "symfony/phpunit-bridge",
3+
"type": "symfony-bridge",
4+
"description": "Symfony PHPUnit Bridge",
5+
"keywords": [],
6+
"homepage": "http://symfony.com",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "Nicolas Grekas",
11+
"email": "[email protected]"
12+
},
13+
{
14+
"name": "Symfony Community",
15+
"homepage": "http://symfony.com/contributors"
16+
}
17+
],
18+
"require": {
19+
"php": ">=5.3.9"
20+
},
21+
"autoload": {
22+
"files": [ "bootstrap.php" ],
23+
"psr-0": { "Symfony\\Bridge\\PhpUnit\\": "" }
24+
},
25+
"target-dir": "Symfony/Bridge/PhpUnit",
26+
"minimum-stability": "dev",
27+
"extra": {
28+
"branch-alias": {
29+
"dev-master": "2.7-dev"
30+
}
31+
}
32+
}

phpunit.xml.dist

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
5+
backupGlobals="false"
6+
colors="true"
7+
bootstrap="vendor/autoload.php"
8+
>
9+
<php>
10+
<ini name="error_reporting" value="-1" />
11+
</php>
12+
13+
<testsuites>
14+
<testsuite name="Symfony PHPUnit Bridge Test Suite">
15+
<directory>./Tests/</directory>
16+
</testsuite>
17+
</testsuites>
18+
19+
<filter>
20+
<whitelist>
21+
<directory>./</directory>
22+
<exclude>
23+
<directory>./Tests</directory>
24+
<directory>./vendor</directory>
25+
</exclude>
26+
</whitelist>
27+
</filter>
28+
</phpunit>

0 commit comments

Comments
 (0)