1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18: App::uses('CakeBaseReporter', 'TestSuite/Reporter');
19:
20: 21: 22: 23: 24: 25:
26: class CakeHtmlReporter extends CakeBaseReporter {
27:
28: 29: 30: 31: 32: 33:
34: public function paintHeader() {
35: $this->_headerSent = true;
36: $this->sendContentType();
37: $this->sendNoCacheHeaders();
38: $this->paintDocumentStart();
39: $this->paintTestMenu();
40: echo "<ul class='tests'>\n";
41: }
42:
43: 44: 45: 46: 47:
48: public function sendContentType() {
49: if (!headers_sent()) {
50: header('Content-Type: text/html; charset=' . Configure::read('App.encoding'));
51: }
52: }
53:
54: 55: 56: 57: 58:
59: public function paintDocumentStart() {
60: ob_start();
61: $baseDir = $this->params['baseDir'];
62: include CAKE . 'TestSuite' . DS . 'templates' . DS . 'header.php';
63: }
64:
65: 66: 67: 68: 69: 70:
71: public function paintTestMenu() {
72: $cases = $this->baseUrl() . '?show=cases';
73: $plugins = App::objects('plugin', null, false);
74: sort($plugins);
75: include CAKE . 'TestSuite' . DS . 'templates' . DS . 'menu.php';
76: }
77:
78: 79: 80: 81: 82:
83: public function testCaseList() {
84: $testCases = parent::testCaseList();
85: $core = $this->params['core'];
86: $plugin = $this->params['plugin'];
87:
88: $buffer = "<h3>App Test Cases:</h3>\n<ul>";
89: $urlExtra = null;
90: if ($core) {
91: $buffer = "<h3>Core Test Cases:</h3>\n<ul>";
92: $urlExtra = '&core=true';
93: } elseif ($plugin) {
94: $buffer = "<h3>" . Inflector::humanize($plugin) . " Test Cases:</h3>\n<ul>";
95: $urlExtra = '&plugin=' . $plugin;
96: }
97:
98: if (1 > count($testCases)) {
99: $buffer .= "<strong>EMPTY</strong>";
100: }
101:
102: foreach ($testCases as $testCaseFile => $testCase) {
103: $title = explode(DS, str_replace('.test.php', '', $testCase));
104: $title[count($title) - 1] = Inflector::camelize($title[count($title) - 1]);
105: $title = implode(' / ', $title);
106: $buffer .= "<li><a href='" . $this->baseUrl() . "?case=" . urlencode($testCase) . $urlExtra . "'>" . $title . "</a></li>\n";
107: }
108: $buffer .= "</ul>\n";
109: echo $buffer;
110: }
111:
112: 113: 114: 115: 116: 117: 118:
119: public function sendNoCacheHeaders() {
120: if (!headers_sent()) {
121: header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
122: header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
123: header("Cache-Control: no-store, no-cache, must-revalidate");
124: header("Cache-Control: post-check=0, pre-check=0", false);
125: header("Pragma: no-cache");
126: }
127: }
128:
129: 130: 131: 132: 133: 134: 135:
136: public function paintFooter($result) {
137: ob_end_flush();
138: $colour = ($result->failureCount() + $result->errorCount() > 0 ? "red" : "green");
139: echo "</ul>\n";
140: echo "<div style=\"";
141: echo "padding: 8px; margin: 1em 0; background-color: $colour; color: white;";
142: echo "\">";
143: echo ($result->count() - $result->skippedCount()) . "/" . $result->count();
144: echo " test methods complete:\n";
145: echo "<strong>" . count($result->passed()) . "</strong> passes, ";
146: echo "<strong>" . $result->failureCount() . "</strong> fails, ";
147: echo "<strong>" . $this->numAssertions . "</strong> assertions and ";
148: echo "<strong>" . $result->errorCount() . "</strong> exceptions.";
149: echo "</div>\n";
150: echo '<div style="padding:0 0 5px;">';
151: echo '<p><strong>Time:</strong> ' . $result->time() . ' seconds</p>';
152: echo '<p><strong>Peak memory:</strong> ' . number_format(memory_get_peak_usage()) . ' bytes</p>';
153: echo $this->_paintLinks();
154: echo '</div>';
155: if (isset($this->params['codeCoverage']) && $this->params['codeCoverage']) {
156: $coverage = $result->getCodeCoverage();
157: if (method_exists($coverage, 'getSummary')) {
158: $report = $coverage->getSummary();
159: echo $this->paintCoverage($report);
160: }
161: if (method_exists($coverage, 'getData')) {
162: $report = $coverage->getData();
163: echo $this->paintCoverage($report);
164: }
165: }
166: $this->paintDocumentEnd();
167: }
168:
169: 170: 171: 172: 173:
174: public function paintCoverage(array $coverage) {
175: App::uses('HtmlCoverageReport', 'TestSuite/Coverage');
176:
177: $reporter = new HtmlCoverageReport($coverage, $this);
178: echo $reporter->report();
179: }
180:
181: 182: 183: 184: 185:
186: protected function _paintLinks() {
187: $show = $query = array();
188: if (!empty($this->params['case'])) {
189: $show['show'] = 'cases';
190: }
191:
192: if (!empty($this->params['core'])) {
193: $show['core'] = $query['core'] = 'true';
194: }
195: if (!empty($this->params['plugin'])) {
196: $show['plugin'] = $query['plugin'] = $this->params['plugin'];
197: }
198: if (!empty($this->params['case'])) {
199: $query['case'] = $this->params['case'];
200: }
201: $show = $this->_queryString($show);
202: $query = $this->_queryString($query);
203:
204: echo "<p><a href='" . $this->baseUrl() . $show . "'>Run more tests</a> | <a href='" . $this->baseUrl() . $query . "&show_passes=1'>Show Passes</a> | \n";
205: echo " <a href='" . $this->baseUrl() . $query . "&code_coverage=true'>Analyze Code Coverage</a></p>\n";
206: }
207:
208: 209: 210: 211: 212: 213:
214: protected function _queryString($url) {
215: $out = '?';
216: $params = array();
217: foreach ($url as $key => $value) {
218: $params[] = "$key=$value";
219: }
220: $out .= implode('&', $params);
221: return $out;
222: }
223:
224: 225: 226: 227: 228:
229: public function paintDocumentEnd() {
230: $baseDir = $this->params['baseDir'];
231: include CAKE . 'TestSuite' . DS . 'templates' . DS . 'footer.php';
232: if (ob_get_length()) {
233: ob_end_flush();
234: }
235: }
236:
237: 238: 239: 240: 241: 242: 243: 244: 245:
246: public function paintFail($message, $test) {
247: $trace = $this->_getStackTrace($message);
248: $testName = get_class($test) . '(' . $test->getName() . ')';
249:
250: echo "<li class='fail'>\n";
251: echo "<span>Failed</span>";
252: echo "<div class='msg'><pre>" . $this->_htmlEntities($message->toString()) . "</pre></div>\n";
253: echo "<div class='msg'>" . __d('cake_dev', 'Test case: %s', $testName) . "</div>\n";
254: echo "<div class='msg'>" . __d('cake_dev', 'Stack trace:') . '<br />' . $trace . "</div>\n";
255: echo "</li>\n";
256: }
257:
258: 259: 260: 261: 262: 263: 264: 265: 266:
267: public function paintPass(PHPUnit_Framework_Test $test, $time = null) {
268: if (isset($this->params['showPasses']) && $this->params['showPasses']) {
269: echo "<li class='pass'>\n";
270: echo "<span>Passed</span> ";
271:
272: echo "<br />" . $this->_htmlEntities($test->getName()) . " ($time seconds)\n";
273: echo "</li>\n";
274: }
275: }
276:
277: 278: 279: 280: 281: 282:
283: public function paintException($message, $test) {
284: $trace = $this->_getStackTrace($message);
285: $testName = get_class($test) . '(' . $test->getName() . ')';
286:
287: echo "<li class='fail'>\n";
288: echo "<span>" . get_class($message) . "</span>";
289:
290: echo "<div class='msg'>" . $this->_htmlEntities($message->getMessage()) . "</div>\n";
291: echo "<div class='msg'>" . __d('cake_dev', 'Test case: %s', $testName) . "</div>\n";
292: echo "<div class='msg'>" . __d('cake_dev', 'Stack trace:') . '<br />' . $trace . "</div>\n";
293: echo "</li>\n";
294: }
295:
296: 297: 298: 299: 300: 301: 302:
303: public function paintSkip($message, $test) {
304: echo "<li class='skipped'>\n";
305: echo "<span>Skipped</span> ";
306: echo $test->getName() . ': ' . $this->_htmlEntities($message->getMessage());
307: echo "</li>\n";
308: }
309:
310: 311: 312: 313: 314: 315:
316: public function paintFormattedMessage($message) {
317: echo '<pre>' . $this->_htmlEntities($message) . '</pre>';
318: }
319:
320: 321: 322: 323: 324: 325:
326: protected function _htmlEntities($message) {
327: return htmlentities($message, ENT_COMPAT, $this->_characterSet);
328: }
329:
330: 331: 332: 333: 334: 335:
336: protected function _getStackTrace(Exception $e) {
337: $trace = $e->getTrace();
338: $out = array();
339: foreach ($trace as $frame) {
340: if (isset($frame['file']) && isset($frame['line'])) {
341: $out[] = $frame['file'] . ' : ' . $frame['line'];
342: } elseif (isset($frame['class']) && isset($frame['function'])) {
343: $out[] = $frame['class'] . '::' . $frame['function'];
344: } else {
345: $out[] = '[internal]';
346: }
347: }
348: return implode('<br />', $out);
349: }
350:
351: 352: 353: 354: 355:
356: public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {
357: if (!$this->_headerSent) {
358: echo $this->paintHeader();
359: }
360: echo '<h2>' . __d('cake_dev', 'Running %s', $suite->getName()) . '</h2>';
361: }
362:
363: }
364: