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 (count($testCases) < 1) {
99: $buffer .= "<strong>EMPTY</strong>";
100: }
101:
102: foreach ($testCases as $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:
175: public function paintCoverage(array $coverage) {
176: App::uses('HtmlCoverageReport', 'TestSuite/Coverage');
177:
178: $reporter = new HtmlCoverageReport($coverage, $this);
179: echo $reporter->report();
180: }
181:
182: 183: 184: 185: 186:
187: protected function _paintLinks() {
188: $show = $query = array();
189: if (!empty($this->params['case'])) {
190: $show['show'] = 'cases';
191: }
192:
193: if (!empty($this->params['core'])) {
194: $show['core'] = $query['core'] = 'true';
195: }
196: if (!empty($this->params['plugin'])) {
197: $show['plugin'] = $query['plugin'] = $this->params['plugin'];
198: }
199: if (!empty($this->params['case'])) {
200: $query['case'] = $this->params['case'];
201: }
202: $show = $this->_queryString($show);
203: $query = $this->_queryString($query);
204:
205: echo "<p><a href='" . $this->baseUrl() . $show . "'>Run more tests</a> | <a href='" . $this->baseUrl() . $query . "&show_passes=1'>Show Passes</a> | \n";
206: echo "<a href='" . $this->baseUrl() . $query . "&debug=1'>Enable Debug Output</a> | \n";
207: echo "<a href='" . $this->baseUrl() . $query . "&code_coverage=true'>Analyze Code Coverage</a></p>\n";
208: }
209:
210: 211: 212: 213: 214: 215:
216: protected function _queryString($url) {
217: $out = '?';
218: $params = array();
219: foreach ($url as $key => $value) {
220: $params[] = "$key=$value";
221: }
222: $out .= implode('&', $params);
223: return $out;
224: }
225:
226: 227: 228: 229: 230:
231: public function paintDocumentEnd() {
232: $baseDir = $this->params['baseDir'];
233: include CAKE . 'TestSuite' . DS . 'templates' . DS . 'footer.php';
234: if (ob_get_length()) {
235: ob_end_flush();
236: }
237: }
238:
239: 240: 241: 242: 243: 244: 245: 246: 247: 248:
249: public function paintFail($message, $test) {
250: $trace = $this->_getStackTrace($message);
251: $testName = get_class($test) . '(' . $test->getName() . ')';
252:
253: $actualMsg = $expectedMsg = null;
254: if (method_exists($message, 'getComparisonFailure')) {
255: $failure = $message->getComparisonFailure();
256: if (is_object($failure)) {
257: $actualMsg = $failure->getActualAsString();
258: $expectedMsg = $failure->getExpectedAsString();
259: }
260: }
261:
262: echo "<li class='fail'>\n";
263: echo "<span>Failed</span>";
264: echo "<div class='msg'><pre>" . $this->_htmlEntities($message->toString());
265:
266: if ((is_string($actualMsg) && is_string($expectedMsg)) || (is_array($actualMsg) && is_array($expectedMsg))) {
267: echo "<br />" . PHPUnit_Util_Diff::diff($expectedMsg, $actualMsg);
268: }
269:
270: echo "</pre></div>\n";
271: echo "<div class='msg'>" . __d('cake_dev', 'Test case: %s', $testName) . "</div>\n";
272: echo "<div class='msg'>" . __d('cake_dev', 'Stack trace:') . '<br />' . $trace . "</div>\n";
273: echo "</li>\n";
274: }
275:
276: 277: 278: 279: 280: 281: 282: 283: 284:
285: public function paintPass(PHPUnit_Framework_Test $test, $time = null) {
286: if (isset($this->params['showPasses']) && $this->params['showPasses']) {
287: echo "<li class='pass'>\n";
288: echo "<span>Passed</span> ";
289:
290: echo "<br />" . $this->_htmlEntities($test->getName()) . " ($time seconds)\n";
291: echo "</li>\n";
292: }
293: }
294:
295: 296: 297: 298: 299: 300: 301:
302: public function paintException($message, $test) {
303: $trace = $this->_getStackTrace($message);
304: $testName = get_class($test) . '(' . $test->getName() . ')';
305:
306: echo "<li class='fail'>\n";
307: echo "<span>" . get_class($message) . "</span>";
308:
309: echo "<div class='msg'>" . $this->_htmlEntities($message->getMessage()) . "</div>\n";
310: echo "<div class='msg'>" . __d('cake_dev', 'Test case: %s', $testName) . "</div>\n";
311: echo "<div class='msg'>" . __d('cake_dev', 'Stack trace:') . '<br />' . $trace . "</div>\n";
312: echo "</li>\n";
313: }
314:
315: 316: 317: 318: 319: 320: 321:
322: public function paintSkip($message, $test) {
323: echo "<li class='skipped'>\n";
324: echo "<span>Skipped</span> ";
325: echo $test->getName() . ': ' . $this->_htmlEntities($message->getMessage());
326: echo "</li>\n";
327: }
328:
329: 330: 331: 332: 333: 334:
335: public function paintFormattedMessage($message) {
336: echo '<pre>' . $this->_htmlEntities($message) . '</pre>';
337: }
338:
339: 340: 341: 342: 343: 344:
345: protected function _htmlEntities($message) {
346: return htmlentities($message, ENT_COMPAT, $this->_characterSet);
347: }
348:
349: 350: 351: 352: 353: 354:
355: protected function _getStackTrace(Exception $e) {
356: $trace = $e->getTrace();
357: $out = array();
358: foreach ($trace as $frame) {
359: if (isset($frame['file']) && isset($frame['line'])) {
360: $out[] = $frame['file'] . ' : ' . $frame['line'];
361: } elseif (isset($frame['class']) && isset($frame['function'])) {
362: $out[] = $frame['class'] . '::' . $frame['function'];
363: } else {
364: $out[] = '[internal]';
365: }
366: }
367: return implode('<br />', $out);
368: }
369:
370: 371: 372: 373: 374: 375:
376: public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {
377: if (!$this->_headerSent) {
378: echo $this->paintHeader();
379: }
380: echo '<h2>' . __d('cake_dev', 'Running %s', $suite->getName()) . '</h2>';
381: }
382:
383: }
384: