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: protected $_buffer = '';
34:
35: 36: 37: 38: 39: 40:
41: public function paintHeader() {
42: $this->_headerSent = true;
43: ob_start();
44: $this->sendContentType();
45: $this->sendNoCacheHeaders();
46: $this->paintDocumentStart();
47: $this->paintTestMenu();
48: echo "<ul class='tests'>\n";
49: $this->_buffer = ob_get_clean();
50: }
51:
52: 53: 54: 55: 56:
57: public function sendContentType() {
58: if (!headers_sent()) {
59: header('Content-Type: text/html; charset=' . Configure::read('App.encoding'));
60: }
61: }
62:
63: 64: 65: 66: 67:
68: public function paintDocumentStart() {
69: $baseDir = $this->params['baseDir'];
70: include CAKE . 'TestSuite' . DS . 'templates' . DS . 'header.php';
71: }
72:
73: 74: 75: 76: 77: 78:
79: public function paintTestMenu() {
80: $cases = $this->baseUrl() . '?show=cases';
81: $plugins = App::objects('plugin', null, false);
82: sort($plugins);
83: include CAKE . 'TestSuite' . DS . 'templates' . DS . 'menu.php';
84: }
85:
86: 87: 88: 89: 90:
91: public function testCaseList() {
92: $testCases = parent::testCaseList();
93: $core = $this->params['core'];
94: $plugin = $this->params['plugin'];
95:
96: $buffer = "<h3>App Test Cases:</h3>\n<ul>";
97: $urlExtra = null;
98: if ($core) {
99: $buffer = "<h3>Core Test Cases:</h3>\n<ul>";
100: $urlExtra = '&core=true';
101: } elseif ($plugin) {
102: $buffer = "<h3>" . Inflector::humanize($plugin) . " Test Cases:</h3>\n<ul>";
103: $urlExtra = '&plugin=' . $plugin;
104: }
105:
106: if (count($testCases) < 1) {
107: $buffer .= "<strong>EMPTY</strong>";
108: }
109:
110: foreach ($testCases as $testCase) {
111: $title = explode(DS, str_replace('.test.php', '', $testCase));
112: $title[count($title) - 1] = Inflector::camelize($title[count($title) - 1]);
113: $title = implode(' / ', $title);
114: $buffer .= "<li><a href='" . $this->baseUrl() . "?case=" . urlencode($testCase) . $urlExtra . "'>" . $title . "</a></li>\n";
115: }
116: $buffer .= "</ul>\n";
117: echo $buffer;
118: }
119:
120: 121: 122: 123: 124: 125: 126:
127: public function sendNoCacheHeaders() {
128: if (!headers_sent()) {
129: header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
130: header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
131: header("Cache-Control: no-store, no-cache, must-revalidate");
132: header("Cache-Control: post-check=0, pre-check=0", false);
133: header("Pragma: no-cache");
134: }
135: }
136:
137: 138: 139: 140: 141: 142: 143:
144: public function paintFooter($result) {
145: echo $this->_buffer;
146: ob_end_flush();
147:
148: $colour = ($result->failureCount() + $result->errorCount() > 0 ? "red" : "green");
149: echo "</ul>\n";
150: echo "<div style=\"";
151: echo "padding: 8px; margin: 1em 0; background-color: $colour; color: white;";
152: echo "\">";
153: echo ($result->count() - $result->skippedCount()) . "/" . $result->count();
154: echo " test methods complete:\n";
155: echo "<strong>" . count($result->passed()) . "</strong> passes, ";
156: echo "<strong>" . $result->failureCount() . "</strong> fails, ";
157: echo "<strong>" . $this->numAssertions . "</strong> assertions and ";
158: echo "<strong>" . $result->errorCount() . "</strong> exceptions.";
159: echo "</div>\n";
160: echo '<div style="padding:0 0 5px;">';
161: echo '<p><strong>Time:</strong> ' . $result->time() . ' seconds</p>';
162: echo '<p><strong>Peak memory:</strong> ' . number_format(memory_get_peak_usage()) . ' bytes</p>';
163: echo $this->_paintLinks();
164: echo '</div>';
165: if (isset($this->params['codeCoverage']) && $this->params['codeCoverage']) {
166: $coverage = $result->getCodeCoverage();
167: if (method_exists($coverage, 'getSummary')) {
168: $report = $coverage->getSummary();
169: echo $this->paintCoverage($report);
170: }
171: if (method_exists($coverage, 'getData')) {
172: $report = $coverage->getData();
173: echo $this->paintCoverage($report);
174: }
175: }
176: $this->paintDocumentEnd();
177: }
178:
179: 180: 181: 182: 183: 184:
185: public function paintCoverage(array $coverage) {
186: App::uses('HtmlCoverageReport', 'TestSuite/Coverage');
187:
188: $reporter = new HtmlCoverageReport($coverage, $this);
189: echo $reporter->report();
190: }
191:
192: 193: 194: 195: 196:
197: protected function _paintLinks() {
198: $show = $query = array();
199: if (!empty($this->params['case'])) {
200: $show['show'] = 'cases';
201: }
202:
203: if (!empty($this->params['core'])) {
204: $show['core'] = $query['core'] = 'true';
205: }
206: if (!empty($this->params['plugin'])) {
207: $show['plugin'] = $query['plugin'] = $this->params['plugin'];
208: }
209: if (!empty($this->params['case'])) {
210: $query['case'] = $this->params['case'];
211: }
212: list($show, $query) = $this->_getQueryLink();
213:
214: echo "<p><a href='" . $this->baseUrl() . $show . "'>Run more tests</a> | <a href='" . $this->baseUrl() . $query . "&show_passes=1'>Show Passes</a> | \n";
215: echo "<a href='" . $this->baseUrl() . $query . "&debug=1'>Enable Debug Output</a> | \n";
216: echo "<a href='" . $this->baseUrl() . $query . "&code_coverage=true'>Analyze Code Coverage</a> | \n";
217: echo "<a href='" . $this->baseUrl() . $query . "&code_coverage=true&show_passes=1&debug=1'>All options enabled</a></p>\n";
218: }
219:
220: 221: 222: 223: 224: 225:
226: protected function _queryString($url) {
227: $out = '?';
228: $params = array();
229: foreach ($url as $key => $value) {
230: $params[] = "$key=$value";
231: }
232: $out .= implode('&', $params);
233: return $out;
234: }
235:
236: 237: 238: 239: 240:
241: public function paintDocumentEnd() {
242: $baseDir = $this->params['baseDir'];
243: include CAKE . 'TestSuite' . DS . 'templates' . DS . 'footer.php';
244: if (ob_get_length()) {
245: ob_end_flush();
246: }
247: }
248:
249: 250: 251: 252: 253: 254: 255: 256: 257: 258:
259: public function paintFail($message, $test) {
260: ob_start();
261: $trace = $this->_getStackTrace($message);
262: $className = get_class($test);
263: $testName = $className . '::' . $test->getName() . '()';
264:
265: $actualMsg = $expectedMsg = null;
266: if (method_exists($message, 'getComparisonFailure')) {
267: $failure = $message->getComparisonFailure();
268: if (is_object($failure)) {
269: $actualMsg = $failure->getActualAsString();
270: $expectedMsg = $failure->getExpectedAsString();
271: }
272: }
273:
274: echo "<li class='fail'>\n";
275: echo "<span>Failed</span>";
276: echo "<div class='msg'><pre>" . $this->_htmlEntities($message->toString());
277:
278: if ((is_string($actualMsg) && is_string($expectedMsg)) || (is_array($actualMsg) && is_array($expectedMsg))) {
279:
280: $diffs = "";
281: if (class_exists('PHPUnit_Util_Diff')) {
282: $diffs = PHPUnit_Util_Diff::diff($expectedMsg, $actualMsg);
283: } elseif (class_exists('SebastianBergmann\Diff\Differ')) {
284: $differ = new SebastianBergmann\Diff\Differ();
285: $diffs = $differ->diff($expectedMsg, $actualMsg);
286: }
287:
288: echo "<br />" . $this->_htmlEntities($diffs);
289: }
290:
291: echo "</pre></div>\n";
292: echo "<div class='msg'>" . __d('cake_dev', 'Test case: %s', $testName) . "</div>\n";
293: if (strpos($className, "PHPUnit_") === false) {
294: list($show, $query) = $this->_getQueryLink();
295: echo "<div class='msg'><a href='" . $this->baseUrl() . $query . "&filter=" . $test->getName() . "'>" . __d('cake_dev', 'Rerun only this test: %s', $testName) . "</a></div>\n";
296: }
297: echo "<div class='msg'>" . __d('cake_dev', 'Stack trace:') . '<br />' . $trace . "</div>\n";
298: echo "</li>\n";
299: $this->_buffer .= ob_get_clean();
300: }
301:
302: 303: 304: 305: 306: 307: 308: 309: 310:
311: public function paintPass(PHPUnit_Framework_Test $test, $time = null) {
312: ob_start();
313: if (isset($this->params['showPasses']) && $this->params['showPasses']) {
314: echo "<li class='pass'>\n";
315: echo "<span>Passed</span> ";
316:
317: echo "<br />" . $this->_htmlEntities($test->getName()) . " ($time seconds)\n";
318: echo "</li>\n";
319: }
320: $this->_buffer .= ob_get_clean();
321: }
322:
323: 324: 325: 326: 327: 328: 329:
330: public function paintException($message, $test) {
331: ob_start();
332: $trace = $this->_getStackTrace($message);
333: $testName = get_class($test) . '(' . $test->getName() . ')';
334:
335: echo "<li class='fail'>\n";
336: echo "<span>" . get_class($message) . "</span>";
337:
338: echo "<div class='msg'>" . $this->_htmlEntities($message->getMessage()) . "</div>\n";
339: echo "<div class='msg'>" . __d('cake_dev', 'Test case: %s', $testName) . "</div>\n";
340: echo "<div class='msg'>" . __d('cake_dev', 'Stack trace:') . '<br />' . $trace . "</div>\n";
341: echo "</li>\n";
342: $this->_buffer .= ob_get_clean();
343: }
344:
345: 346: 347: 348: 349: 350: 351:
352: public function paintSkip($message, $test) {
353: ob_start();
354: echo "<li class='skipped'>\n";
355: echo "<span>Skipped</span> ";
356: echo $test->getName() . ': ' . $this->_htmlEntities($message->getMessage());
357: echo "</li>\n";
358: $this->_buffer .= ob_get_clean();
359: }
360:
361: 362: 363: 364: 365: 366:
367: public function paintFormattedMessage($message) {
368: echo '<pre>' . $this->_htmlEntities($message) . '</pre>';
369: }
370:
371: 372: 373: 374: 375: 376:
377: protected function _htmlEntities($message) {
378: return htmlentities($message, ENT_COMPAT, $this->_characterSet);
379: }
380:
381: 382: 383: 384: 385: 386:
387: protected function _getStackTrace(Exception $e) {
388: $trace = $e->getTrace();
389: $out = array();
390: foreach ($trace as $frame) {
391: if (isset($frame['file']) && isset($frame['line'])) {
392: $out[] = $frame['file'] . ' : ' . $frame['line'];
393: } elseif (isset($frame['class']) && isset($frame['function'])) {
394: $out[] = $frame['class'] . '::' . $frame['function'];
395: } else {
396: $out[] = '[internal]';
397: }
398: }
399: return implode('<br />', $out);
400: }
401:
402: 403: 404: 405: 406: 407:
408: public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {
409: if (!$this->_headerSent) {
410: $this->paintHeader();
411: }
412: $this->_buffer .= '<h2>' . __d('cake_dev', 'Running %s', $suite->getName()) . '</h2>';
413: }
414:
415: 416: 417: 418: 419:
420: protected function _getQueryLink() {
421: $show = $query = array();
422: if (!empty($this->params['case'])) {
423: $show['show'] = 'cases';
424: }
425:
426: if (!empty($this->params['core'])) {
427: $show['core'] = $query['core'] = 'true';
428: }
429: if (!empty($this->params['plugin'])) {
430: $show['plugin'] = $query['plugin'] = $this->params['plugin'];
431: }
432: if (!empty($this->params['case'])) {
433: $query['case'] = $this->params['case'];
434: }
435: if (!empty($this->params['filter'])) {
436: $query['filter'] = $this->params['filter'];
437: }
438: $show = $this->_queryString($show);
439: $query = $this->_queryString($query);
440: return array($show, $query);
441: }
442:
443: }
444: