Search code examples
unit-testingphpunitcode-coverageclover

PHPUnit HTML and Clover coverage reports differ due to codeCoverageIgnore


I'm using PHPUnit 3.5.14 and have a suite of tests which covers 100% of my PHP application excluding certain portions with // @codeCoverageIgnore[Start|End]. The HTML coverage report shows 100% coverage. But when I generate a Clover XML coverage report, which I'd like Jenkins to read to enforce the 100% coverage requirement, it shows all of my ignored code as not covered.

For example, I have a controller class with 20 methods, one of which looks like this:

// @codeCoverageIgnoreStart
/**
 * Gets an instance of Foo.  Abstracted for testing.
 *
 * @param array $options The constructor argument
 *
 * @return Foo
 */
protected function _getFoo(array $options)
{
    return new Foo($options);
}
// @codeCoverageIgnoreEnd

The HTML coverage report shows 20 methods covered including the one entirely ignored:

pic: coverage report excerpt

enter image description here https://i.sstatic.net/AvpN5.png

But the Clover XML report shows 19/20 methods covered and doesn't mention _getFoo:

<class name="CampaignController" namespace="global" (...)>
  <metrics methods="20" coveredmethods="19" conditionals="0" coveredconditionals="0" statements="532" coveredstatements="532" elements="552" coveredelements="551"/>

...

  <line num="592" type="stmt" count="1"/>
  <line num="593" type="stmt" count="1"/>
  <line num="615" type="method" name="createAction" crap="2" count="2"/>
  <line num="617" type="stmt" count="2"/>

(The _getFoo lines at the top are lines 596-608.)

The logging section of my PHPUnit configuration looks like this:

<logging>
    <log type="coverage-html" target="../public/build/coverage" charset="UTF-8"
        yui="true" highlight="true" lowUpperBound="90" highLowerBound="100"/>
    <log type="coverage-clover" target="../public/build/test-coverage.xml"/>
</logging>

Is there a way to configure the Clover coverage log entry, or change my coverage ignore comments, so that the Clover report indicates 100% coverage to match the HTML report?


Solution

  • The problem lies in PHP_CodeCoverage_Report_Clover::process(). While it correctly ignores marked lines while adding up the number of lines in a method, it gets the list of methods from PHP_Token_Stream which doesn't know about those code coverage comments. I created issue #54 at github which should be relatively easy to fix.

    BTW, reading PHP_CodeCoverage::getLinesToBeIgnored() which PHP_CodeCoverage_Report_Clover uses, it appears that you can ignore an entire class or method by adding @codeCoverageIgnore to its docblock.

    /**
     * Gets an instance of Foo.  Abstracted for testing.
     *
     * @param array $options The constructor argument
     * @return Foo
     *
     * @codeCoverageIgnore
     */
    

    While this won't solve the problem, it is easier than using matching // comments.

    Update: If you want to try out a fix, replace the foreach loop over the methods inside PHP_CodeCoverage_Report_Clover::process() with this modified version.

    foreach ($_class['methods'] as $methodName => $method) {
        $methodCount        = 0;
        $methodLines        = 0;
        $methodLinesCovered = 0;
    
        for ($i  = $method['startLine'];
             $i <= $method['endLine'];
             $i++) {
            if (isset($ignoredLines[$i])) {
                continue;
            }
    
            $add   = TRUE;
            $count = 0;
    
            if (isset($files[$filename][$i])) {
                if ($files[$filename][$i] != -2) {
                    $classStatistics['statements']++;
                    $methodLines++;
                }
    
                if (is_array($files[$filename][$i])) {
                    $classStatistics['coveredStatements']++;
                    $methodLinesCovered++;
                    $count = count($files[$filename][$i]);
                }
    
                else if ($files[$filename][$i] == -2) {
                    $add = FALSE;
                }
            } else {
                $add = FALSE;
            }
    
            $methodCount = max($methodCount, $count);
    
            if ($add) {
                $lines[$i] = array(
                  'count' => $count,
                  'type'  => 'stmt'
                );
            }
        }
    
        if ($methodLines > 0) {
            $classStatistics['methods']++;
    
            if ($methodCount > 0) {
                $classStatistics['coveredMethods']++;
            }
    
            $lines[$method['startLine']] = array(
              'count' => $methodCount,
              'crap'  => PHP_CodeCoverage_Util::crap(
                           $method['ccn'],
                           PHP_CodeCoverage_Util::percent(
                             $methodLinesCovered,
                             $methodLines
                           )
                         ),
              'type'  => 'method',
              'name'  => $methodName
            );
        }
    }