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