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