統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / TestSuite / Coverage / HtmlCoverageReport.php @ 635eef61

履歴 | 表示 | アノテート | ダウンロード (6.709 KB)

1
<?php
2
/**
3
 * PHP5
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @package       Cake.TestSuite.Coverage
15
 * @since         CakePHP(tm) v 2.0
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18

    
19
App::uses('BaseCoverageReport', 'TestSuite/Coverage');
20

    
21
/**
22
 * Generates code coverage reports in HTML from data obtained from PHPUnit
23
 *
24
 * @package       Cake.TestSuite.Coverage
25
 */
26
class HtmlCoverageReport extends BaseCoverageReport {
27

    
28
/**
29
 * Holds the total number of processed rows.
30
 *
31
 * @var int
32
 */
33
        protected $_total = 0;
34

    
35
/**
36
 * Holds the total number of covered rows.
37
 *
38
 * @var int
39
 */
40
        protected $_covered = 0;
41

    
42
/**
43
 * Generates report HTML to display.
44
 *
45
 * @return string Compiled HTML report.
46
 */
47
        public function report() {
48
                $pathFilter = $this->getPathFilter();
49
                $coverageData = $this->filterCoverageDataByPath($pathFilter);
50
                if (empty($coverageData)) {
51
                        return '<h3>No files to generate coverage for</h3>';
52
                }
53
                $output = $this->coverageScript();
54
                $output .= <<<HTML
55
                <h3>Code coverage results
56
                <a href="#" onclick="coverage_toggle_all()" class="coverage-toggle">Toggle all files</a>
57
                </h3>
58
HTML;
59
                foreach ($coverageData as $file => $coverageData) {
60
                        $fileData = file($file);
61
                        $output .= $this->generateDiff($file, $fileData, $coverageData);
62
                }
63

    
64
                $percentCovered = 100;
65
                if ($this->_total > 0) {
66
                        $percentCovered = round(100 * $this->_covered / $this->_total, 2);
67
                }
68
                $output .= '<div class="total">Overall coverage: <span class="coverage">' . $percentCovered . '%</span></div>';
69
                return $output;
70
        }
71

    
72
/**
73
 * Generates an HTML diff for $file based on $coverageData.
74
 *
75
 * Handles both PHPUnit3.5 and 3.6 formats.
76
 *
77
 * 3.5 uses -1 for uncovered, and -2 for dead.
78
 * 3.6 uses array() for uncovered and null for dead.
79
 *
80
 * @param string $filename Name of the file having coverage generated
81
 * @param array $fileLines File data as an array. See file() for how to get one of these.
82
 * @param array $coverageData Array of coverage data to use to generate HTML diffs with
83
 * @return string HTML diff.
84
 */
85
        public function generateDiff($filename, $fileLines, $coverageData) {
86
                $output = '';
87
                $diff = array();
88

    
89
                list($covered, $total) = $this->_calculateCoveredLines($fileLines, $coverageData);
90
                $this->_covered += $covered;
91
                $this->_total += $total;
92

    
93
                //shift line numbers forward one;
94
                array_unshift($fileLines, ' ');
95
                unset($fileLines[0]);
96

    
97
                foreach ($fileLines as $lineno => $line) {
98
                        $class = 'ignored';
99
                        $coveringTests = array();
100
                        if (!empty($coverageData[$lineno]) && is_array($coverageData[$lineno])) {
101
                                $coveringTests = array();
102
                                foreach ($coverageData[$lineno] as $test) {
103
                                        $class = (is_array($test) && isset($test['id'])) ? $test['id'] : $test;
104
                                        $testReflection = new ReflectionClass(current(explode('::', $class)));
105
                                        $this->_testNames[] = $this->_guessSubjectName($testReflection);
106
                                        $coveringTests[] = $class;
107
                                }
108
                                $class = 'covered';
109
                        } elseif (isset($coverageData[$lineno]) && ($coverageData[$lineno] === -1 || $coverageData[$lineno] === array())) {
110
                                $class = 'uncovered';
111
                        } elseif (array_key_exists($lineno, $coverageData) && ($coverageData[$lineno] === -2 || $coverageData[$lineno] === null)) {
112
                                $class .= ' dead';
113
                        }
114
                        $diff[] = $this->_paintLine($line, $lineno, $class, $coveringTests);
115
                }
116

    
117
                $percentCovered = 100;
118
                if ($total > 0) {
119
                        $percentCovered = round(100 * $covered / $total, 2);
120
                }
121
                $output .= $this->coverageHeader($filename, $percentCovered);
122
                $output .= implode("", $diff);
123
                $output .= $this->coverageFooter();
124
                return $output;
125
        }
126

    
127
/**
128
 * Guess the class name the test was for based on the test case filename.
129
 *
130
 * @param ReflectionClass $testReflection The class to reflect
131
 * @return string Possible test subject name.
132
 */
133
        protected function _guessSubjectName($testReflection) {
134
                $basename = basename($testReflection->getFilename());
135
                if (strpos($basename, '.test') !== false) {
136
                        list($subject, ) = explode('.', $basename, 2);
137
                        return $subject;
138
                }
139
                $subject = str_replace('Test.php', '', $basename);
140
                return $subject;
141
        }
142

    
143
/**
144
 * Renders the HTML for a single line in the HTML diff.
145
 *
146
 * @param string $line The line content.
147
 * @param int $linenumber The line number
148
 * @param string $class The classname to use.
149
 * @param array $coveringTests The tests covering the line.
150
 * @return string
151
 */
152
        protected function _paintLine($line, $linenumber, $class, $coveringTests) {
153
                $coveredBy = '';
154
                if (!empty($coveringTests)) {
155
                        $coveredBy = "Covered by:\n";
156
                        foreach ($coveringTests as $test) {
157
                                $coveredBy .= $test . "\n";
158
                        }
159
                }
160

    
161
                return sprintf(
162
                        '<div class="code-line %s" title="%s"><span class="line-num">%s</span><span class="content">%s</span></div>',
163
                        $class,
164
                        $coveredBy,
165
                        $linenumber,
166
                        htmlspecialchars($line)
167
                );
168
        }
169

    
170
/**
171
 * generate some javascript for the coverage report.
172
 *
173
 * @return string
174
 */
175
        public function coverageScript() {
176
                return <<<HTML
177
                <script type="text/javascript">
178
                function coverage_show_hide(selector) {
179
                        var element = document.getElementById(selector);
180
                        element.style.display = (element.style.display === 'none') ? '' : 'none';
181
                }
182
                function coverage_toggle_all() {
183
                        var divs = document.querySelectorAll('div.coverage-container');
184
                        var i = divs.length;
185
                        while (i--) {
186
                                if (divs[i] && divs[i].className.indexOf('primary') == -1) {
187
                                        divs[i].style.display = (divs[i].style.display === 'none') ? '' : 'none';
188
                                }
189
                        }
190
                }
191
                </script>
192
HTML;
193
        }
194

    
195
/**
196
 * Generate an HTML snippet for coverage headers
197
 *
198
 * @param string $filename The file name being covered
199
 * @param string $percent The percentage covered
200
 * @return string
201
 */
202
        public function coverageHeader($filename, $percent) {
203
                $hash = md5($filename);
204
                $filename = basename($filename);
205
                list($file) = explode('.', $filename);
206
                $display = in_array($file, $this->_testNames) ? 'block' : 'none';
207
                $primary = $display === 'block' ? 'primary' : '';
208
                return <<<HTML
209
        <div class="coverage-container $primary" style="display:$display;">
210
        <h4>
211
                <a href="#coverage-$filename-$hash" onclick="coverage_show_hide('coverage-$filename-$hash');">
212
                        $filename Code coverage: $percent%
213
                </a>
214
        </h4>
215
        <div class="code-coverage-results" id="coverage-$filename-$hash" style="display:none;">
216
        <pre>
217
HTML;
218
        }
219

    
220
/**
221
 * Generate an HTML snippet for coverage footers
222
 *
223
 * @return void
224
 */
225
        public function coverageFooter() {
226
                return "</pre></div></div>";
227
        }
228

    
229
}