Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e17206d

Browse filesBrowse files
lyrixxfabpot
authored andcommitted
[PhpUnitBridge] Added a CoverageListener to enhance the code coverage report
1 parent 701d41c commit e17206d
Copy full SHA for e17206d
Expand file treeCollapse file tree

15 files changed

+473
-0
lines changed

‎src/Symfony/Bridge/PhpUnit/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/PhpUnit/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
3.4.0
5+
-----
6+
7+
* added a `CoverageListener` to enhance the code coverage report
8+
49
3.3.0
510
-----
611

+44Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
use PHPUnit\Framework\BaseTestListener;
15+
use PHPUnit\Framework\Test;
16+
use Symfony\Bridge\PhpUnit\Legacy\CoverageListenerTrait;
17+
18+
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
19+
class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListener', 'Symfony\Bridge\PhpUnit\CoverageListener');
20+
// Using an early return instead of a else does not work when using the PHPUnit
21+
// phar due to some weird PHP behavior (the class gets defined without executing
22+
// the code before it and so the definition is not properly conditional)
23+
} else {
24+
/**
25+
* CoverageListener adds `@covers <className>` on each test suite when possible
26+
* to make the code coverage more accurate.
27+
*
28+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
29+
*/
30+
class CoverageListener extends BaseTestListener
31+
{
32+
private $trait;
33+
34+
public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false)
35+
{
36+
$this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound);
37+
}
38+
39+
public function startTest(Test $test)
40+
{
41+
$this->trait->startTest($test);
42+
}
43+
}
44+
}
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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\Legacy;
13+
14+
/**
15+
* CoverageListener adds `@covers <className>` on each test suite when possible
16+
* to make the code coverage more accurate.
17+
*
18+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
19+
*
20+
* @internal
21+
*/
22+
class CoverageListener extends \PHPUnit_Framework_BaseTestListener
23+
{
24+
private $trait;
25+
26+
public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false)
27+
{
28+
$this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound);
29+
}
30+
31+
public function startTest(\PHPUnit_Framework_Test $test)
32+
{
33+
$this->trait->startTest($test);
34+
}
35+
}
+113Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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\Legacy;
13+
14+
use PHPUnit\Framework\Test;
15+
use PHPUnit\Framework\Warning;
16+
17+
/**
18+
* PHP 5.3 compatible trait-like shared implementation.
19+
*
20+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
21+
*
22+
* @internal
23+
*/
24+
class CoverageListenerTrait
25+
{
26+
private $sutFqcnResolver;
27+
private $warningOnSutNotFound;
28+
private $warnings;
29+
30+
public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false)
31+
{
32+
$this->sutFqcnResolver = $sutFqcnResolver;
33+
$this->warningOnSutNotFound = $warningOnSutNotFound;
34+
$this->warnings = array();
35+
}
36+
37+
public function startTest($test)
38+
{
39+
$annotations = $test->getAnnotations();
40+
41+
$ignoredAnnotations = array('covers', 'coversDefaultClass', 'coversNothing');
42+
43+
foreach ($ignoredAnnotations as $annotation) {
44+
if (isset($annotations['class'][$annotation]) || isset($annotations['method'][$annotation])) {
45+
return;
46+
}
47+
}
48+
49+
$sutFqcn = $this->findSutFqcn($test);
50+
if (!$sutFqcn) {
51+
if ($this->warningOnSutNotFound) {
52+
$message = 'Could not find the tested class.';
53+
// addWarning does not exist on old PHPUnit version
54+
if (method_exists($test->getTestResultObject(), 'addWarning') && class_exists(Warning::class)) {
55+
$test->getTestResultObject()->addWarning($test, new Warning($message), 0);
56+
} else {
57+
$this->warnings[] = sprintf("%s::%s\n%s", get_class($test), $test->getName(), $message);
58+
}
59+
}
60+
61+
return;
62+
}
63+
64+
$testClass = \PHPUnit\Util\Test::class;
65+
if (!class_exists($testClass, false)) {
66+
$testClass = \PHPUnit_Util_Test::class;
67+
}
68+
69+
$r = new \ReflectionProperty($testClass, 'annotationCache');
70+
$r->setAccessible(true);
71+
72+
$cache = $r->getValue();
73+
$cache = array_replace_recursive($cache, array(
74+
get_class($test) => array(
75+
'covers' => array($sutFqcn),
76+
),
77+
));
78+
$r->setValue($testClass, $cache);
79+
}
80+
81+
private function findSutFqcn($test)
82+
{
83+
if ($this->sutFqcnResolver) {
84+
$resolver = $this->sutFqcnResolver;
85+
86+
return $resolver($test);
87+
}
88+
89+
$class = get_class($test);
90+
91+
$sutFqcn = str_replace('\\Tests\\', '\\', $class);
92+
$sutFqcn = preg_replace('{Test$}', '', $sutFqcn);
93+
94+
if (!class_exists($sutFqcn)) {
95+
return;
96+
}
97+
98+
return $sutFqcn;
99+
}
100+
101+
public function __destruct()
102+
{
103+
if (!$this->warnings) {
104+
return;
105+
}
106+
107+
echo "\n";
108+
109+
foreach ($this->warnings as $key => $warning) {
110+
echo sprintf("%d) %s\n", ++$key, $warning);
111+
}
112+
}
113+
}
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PhpUnit\Tests;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
class CoverageListenerTest extends TestCase
8+
{
9+
public function test()
10+
{
11+
if ("\n" !== PHP_EOL) {
12+
$this->markTestSkipped('This test cannot be run on Windows.');
13+
}
14+
15+
if (defined('HHVM_VERSION')) {
16+
$this->markTestSkipped('This test cannot be run on HHVM.');
17+
}
18+
19+
$dir = __DIR__.'/../Tests/Fixtures/coverage';
20+
$php = PHP_BINARY;
21+
$phpunit = $_SERVER['argv'][0];
22+
23+
exec("$php -d zend_extension=xdebug.so $phpunit -c $dir/phpunit-without-listener.xml.dist $dir/tests/ --coverage-text", $output);
24+
$output = implode("\n", $output);
25+
$this->assertContains('Foo', $output);
26+
27+
exec("$php -d zend_extension=xdebug.so $phpunit -c $dir/phpunit-with-listener.xml.dist $dir/tests/ --coverage-text", $output);
28+
$output = implode("\n", $output);
29+
$this->assertNotContains('Foo', $output);
30+
$this->assertContains("SutNotFoundTest::test\nCould not find the tested class.", $output);
31+
$this->assertNotContains("CoversTest::test\nCould not find the tested class.", $output);
32+
$this->assertNotContains("CoversDefaultClassTest::test\nCould not find the tested class.", $output);
33+
$this->assertNotContains("CoversNothingTest::test\nCould not find the tested class.", $output);
34+
}
35+
}
+32Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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="tests/bootstrap.php"
8+
failOnRisky="true"
9+
failOnWarning="true"
10+
>
11+
12+
<testsuites>
13+
<testsuite name="Fixtures/coverage Test Suite">
14+
<directory>tests</directory>
15+
</testsuite>
16+
</testsuites>
17+
18+
<filter>
19+
<whitelist>
20+
<directory>src</directory>
21+
</whitelist>
22+
</filter>
23+
24+
<listeners>
25+
<listener class="Symfony\Bridge\PhpUnit\CoverageListener">
26+
<arguments>
27+
<null/>
28+
<boolean>true</boolean>
29+
</arguments>
30+
</listener>
31+
</listeners>
32+
</phpunit>
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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="tests/bootstrap.php"
8+
failOnRisky="true"
9+
failOnWarning="true"
10+
>
11+
12+
<testsuites>
13+
<testsuite name="Fixtures/coverage Test Suite">
14+
<directory>tests</directory>
15+
</testsuite>
16+
</testsuites>
17+
18+
<filter>
19+
<whitelist>
20+
<directory>src</directory>
21+
</whitelist>
22+
</filter>
23+
</phpunit>
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 PhpUnitCoverageTest;
13+
14+
class Bar
15+
{
16+
private $foo;
17+
18+
public function __construct(Foo $foo)
19+
{
20+
$this->foo = $foo;
21+
}
22+
23+
public function barZ()
24+
{
25+
$this->foo->fooZ();
26+
27+
return 'bar';
28+
}
29+
}
+20Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 PhpUnitCoverageTest;
13+
14+
class Foo
15+
{
16+
public function fooZ()
17+
{
18+
return 'foo';
19+
}
20+
}
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 PhpUnitCoverageTest\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
16+
class BarTest extends TestCase
17+
{
18+
public function testBar()
19+
{
20+
if (!class_exists('PhpUnitCoverageTest\Foo')) {
21+
$this->markTestSkipped('This test is not part of the main Symfony test suite. It\'s here to test the CoverageListener.');
22+
}
23+
24+
$foo = new \PhpUnitCoverageTest\Foo();
25+
$bar = new \PhpUnitCoverageTest\Bar($foo);
26+
27+
$this->assertSame('bar', $bar->barZ());
28+
}
29+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.