1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
19:
20: App::uses('BaseCoverageReport', 'TestSuite/Coverage');
21:
22: 23: 24: 25: 26:
27: class HtmlCoverageReport extends BaseCoverageReport {
28:
29: 30: 31: 32: 33:
34: public function report() {
35: $pathFilter = $this->getPathFilter();
36: $coverageData = $this->filterCoverageDataByPath($pathFilter);
37: if (empty($coverageData)) {
38: return '<h3>No files to generate coverage for</h3>';
39: }
40: $output = $this->coverageScript();
41: $output .= <<<HTML
42: <h3>Code coverage results
43: <a href="#" onclick="coverage_toggle_all()" class="coverage-toggle">Toggle all files</a>
44: </h3>
45: HTML;
46: foreach ($coverageData as $file => $coverageData) {
47: $fileData = file($file);
48: $output .= $this->generateDiff($file, $fileData, $coverageData);
49: }
50: return $output;
51: }
52:
53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65:
66: public function generateDiff($filename, $fileLines, $coverageData) {
67: $output = '';
68: $diff = array();
69:
70: list($covered, $total) = $this->_calculateCoveredLines($fileLines, $coverageData);
71:
72:
73: array_unshift($fileLines, ' ');
74: unset($fileLines[0]);
75:
76: foreach ($fileLines as $lineno => $line) {
77: $class = 'ignored';
78: $coveringTests = array();
79: if (!empty($coverageData[$lineno]) && is_array($coverageData[$lineno])) {
80: $coveringTests = array();
81: foreach ($coverageData[$lineno] as $test) {
82: $class = (is_array($test) && isset($test['id'])) ? $test['id'] : $test;
83: $testReflection = new ReflectionClass(current(explode('::', $class)));
84: $this->_testNames[] = $this->_guessSubjectName($testReflection);
85: $coveringTests[] = $class;
86: }
87: $class = 'covered';
88: } elseif (isset($coverageData[$lineno]) && ($coverageData[$lineno] === -1 || $coverageData[$lineno] === array())) {
89: $class = 'uncovered';
90: } elseif (array_key_exists($lineno, $coverageData) && ($coverageData[$lineno] === -2 || $coverageData[$lineno] === null)) {
91: $class .= ' dead';
92: }
93: $diff[] = $this->_paintLine($line, $lineno, $class, $coveringTests);
94: }
95:
96: $percentCovered = 100;
97: if ($total > 0) {
98: $percentCovered = round(100 * $covered / $total, 2);
99: }
100: $output .= $this->coverageHeader($filename, $percentCovered);
101: $output .= implode("", $diff);
102: $output .= $this->coverageFooter();
103: return $output;
104: }
105:
106: 107: 108: 109: 110: 111:
112: protected function _guessSubjectName($testReflection) {
113: $basename = basename($testReflection->getFilename());
114: if (strpos($basename, '.test') !== false) {
115: list($subject, ) = explode('.', $basename, 2);
116: return $subject;
117: }
118: $subject = str_replace('Test.php', '', $basename);
119: return $subject;
120: }
121:
122: 123: 124: 125: 126:
127: protected function _paintLine($line, $linenumber, $class, $coveringTests) {
128: $coveredBy = '';
129: if (!empty($coveringTests)) {
130: $coveredBy = "Covered by:\n";
131: foreach ($coveringTests as $test) {
132: $coveredBy .= $test . "\n";
133: }
134: }
135:
136: return sprintf(
137: '<div class="code-line %s" title="%s"><span class="line-num">%s</span><span class="content">%s</span></div>',
138: $class,
139: $coveredBy,
140: $linenumber,
141: htmlspecialchars($line)
142: );
143: }
144:
145: 146: 147: 148: 149:
150: public function coverageScript() {
151: return <<<HTML
152: <script type="text/javascript">
153: function coverage_show_hide(selector) {
154: var element = document.getElementById(selector);
155: element.style.display = (element.style.display == 'none') ? '' : 'none';
156: }
157: function coverage_toggle_all () {
158: var divs = document.querySelectorAll('div.coverage-container');
159: var i = divs.length;
160: while (i--) {
161: if (divs[i] && divs[i].className.indexOf('primary') == -1) {
162: divs[i].style.display = (divs[i].style.display == 'none') ? '' : 'none';
163: }
164: }
165: }
166: </script>
167: HTML;
168: }
169:
170: 171: 172: 173: 174:
175: public function coverageHeader($filename, $percent) {
176: $filename = basename($filename);
177: list($file, $ext) = explode('.', $filename);
178: $display = in_array($file, $this->_testNames) ? 'block' : 'none';
179: $primary = $display == 'block' ? 'primary' : '';
180: return <<<HTML
181: <div class="coverage-container $primary" style="display:$display;">
182: <h4>
183: <a href="#coverage-$filename" onclick="coverage_show_hide('coverage-$filename');">
184: $filename Code coverage: $percent%
185: </a>
186: </h4>
187: <div class="code-coverage-results" id="coverage-$filename" style="display:none;">
188: <pre>
189: HTML;
190: }
191:
192: 193: 194: 195: 196:
197: public function coverageFooter() {
198: return "</pre></div></div>";
199: }
200: }
201: