getPathFilter(); $coverageData = $this->filterCoverageDataByPath($pathFilter); if (empty($coverageData)) { return '

No files to generate coverage for

'; } $output = $this->coverageScript(); $output .= <<Code coverage results Toggle all files HTML; foreach ($coverageData as $file => $coverageData) { $fileData = file($file); $output .= $this->generateDiff($file, $fileData, $coverageData); } $percentCovered = 100; if ($this->_total > 0) { $percentCovered = round(100 * $this->_covered / $this->_total, 2); } $output .= '
Overall coverage: ' . $percentCovered . '%
'; return $output; } /** * Generates an HTML diff for $file based on $coverageData. * * Handles both PHPUnit3.5 and 3.6 formats. * * 3.5 uses -1 for uncovered, and -2 for dead. * 3.6 uses array() for uncovered and null for dead. * * @param string $filename Name of the file having coverage generated * @param array $fileLines File data as an array. See file() for how to get one of these. * @param array $coverageData Array of coverage data to use to generate HTML diffs with * @return string HTML diff. */ public function generateDiff($filename, $fileLines, $coverageData) { $output = ''; $diff = array(); list($covered, $total) = $this->_calculateCoveredLines($fileLines, $coverageData); $this->_covered += $covered; $this->_total += $total; //shift line numbers forward one; array_unshift($fileLines, ' '); unset($fileLines[0]); foreach ($fileLines as $lineno => $line) { $class = 'ignored'; $coveringTests = array(); if (!empty($coverageData[$lineno]) && is_array($coverageData[$lineno])) { $coveringTests = array(); foreach ($coverageData[$lineno] as $test) { $class = (is_array($test) && isset($test['id'])) ? $test['id'] : $test; $testReflection = new ReflectionClass(current(explode('::', $class))); $this->_testNames[] = $this->_guessSubjectName($testReflection); $coveringTests[] = $class; } $class = 'covered'; } elseif (isset($coverageData[$lineno]) && ($coverageData[$lineno] === -1 || $coverageData[$lineno] === array())) { $class = 'uncovered'; } elseif (array_key_exists($lineno, $coverageData) && ($coverageData[$lineno] === -2 || $coverageData[$lineno] === null)) { $class .= ' dead'; } $diff[] = $this->_paintLine($line, $lineno, $class, $coveringTests); } $percentCovered = 100; if ($total > 0) { $percentCovered = round(100 * $covered / $total, 2); } $output .= $this->coverageHeader($filename, $percentCovered); $output .= implode("", $diff); $output .= $this->coverageFooter(); return $output; } /** * Guess the class name the test was for based on the test case filename. * * @param ReflectionClass $testReflection The class to reflect * @return string Possible test subject name. */ protected function _guessSubjectName($testReflection) { $basename = basename($testReflection->getFilename()); if (strpos($basename, '.test') !== false) { list($subject, ) = explode('.', $basename, 2); return $subject; } $subject = str_replace('Test.php', '', $basename); return $subject; } /** * Renders the HTML for a single line in the HTML diff. * * @param string $line The line content. * @param int $linenumber The line number * @param string $class The classname to use. * @param array $coveringTests The tests covering the line. * @return string */ protected function _paintLine($line, $linenumber, $class, $coveringTests) { $coveredBy = ''; if (!empty($coveringTests)) { $coveredBy = "Covered by:\n"; foreach ($coveringTests as $test) { $coveredBy .= $test . "\n"; } } return sprintf( '
%s%s
', $class, $coveredBy, $linenumber, htmlspecialchars($line) ); } /** * generate some javascript for the coverage report. * * @return string */ public function coverageScript() { return << function coverage_show_hide(selector) { var element = document.getElementById(selector); element.style.display = (element.style.display === 'none') ? '' : 'none'; } function coverage_toggle_all() { var divs = document.querySelectorAll('div.coverage-container'); var i = divs.length; while (i--) { if (divs[i] && divs[i].className.indexOf('primary') == -1) { divs[i].style.display = (divs[i].style.display === 'none') ? '' : 'none'; } } } HTML; } /** * Generate an HTML snippet for coverage headers * * @param string $filename The file name being covered * @param string $percent The percentage covered * @return string */ public function coverageHeader($filename, $percent) { $filename = basename($filename); list($file) = explode('.', $filename); $display = in_array($file, $this->_testNames) ? 'block' : 'none'; $primary = $display === 'block' ? 'primary' : ''; return <<

$filename Code coverage: $percent%

"; } }