]> src.bluestatic.org Git - isso.git/blob - docs/PHPUnitTestReporter.php
Switching from calls to trigger_error() to throwing generic exceptions for client...
[isso.git] / docs / PHPUnitTestReporter.php
1 <?php
2 /*=====================================================================*\
3 || ###################################################################
4 || # PHPUnit2 Web Report [#]version[#]
5 || # Copyright ©2002-[#]year[#] Iris Studios, Inc.
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version [#]gpl[#] of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 require_once('PHPUnit2/Framework/TestListener.php');
23
24 /**
25 * PHPUnit2 HTML Output Generator Listener
26 *
27 * This class is used to to run PHPUnit2 tests normally and generate an
28 * HTML report at the end, which details all the tests. To use this,
29 * you need to attach this listener:
30 *
31 * <code>$result = new PHPUnit2_Framework_TestResult;
32 * $result->addListener(new ISI_PHPUnit2_Output_HTML_Listener);
33 * $suite->run($result);</code>
34 *
35 * @author Iris Studios, Inc.
36 * @copyright Copyright ©2002 - [#]year[#], Iris Studios, Inc.
37 * @version $Revision$
38 *
39 */
40 class ISI_PHPUnit2_Output_HTML_Listener implements PHPUnit2_Framework_TestListener
41 {
42 /**
43 * A successful test
44 * @var integer
45 */
46 const TEST_SUCCESS = 1;
47
48 /**
49 * An incomplete test
50 * @var integer
51 */
52 const TEST_INCOMPLETE = 2;
53
54 /**
55 * A failed test
56 * @var integer
57 */
58 const TEST_FAIL = 3;
59
60 /**
61 * An array of all the tests run in a given suite
62 * @var array
63 */
64 protected $tests = array();
65
66 /**
67 * An array of arrays of all the errors in a given failed test in a suite
68 * @var array
69 */
70 protected $errors = array();
71
72 /**
73 * An array of all the incomplete test messages for a test in a suite
74 * @var array
75 */
76 protected $incompletes = array();
77
78 /**
79 * Statistics for a given test suite
80 * @var array
81 */
82 protected $stats = array();
83
84 /**
85 * The name of the currently-running test suite
86 * @var string
87 */
88 private $currentSuite;
89
90 /**
91 * The name of the currently-running individual test
92 * @var string
93 */
94 private $currentTest;
95
96 // ###################################################################
97 /**
98 * A test errored out
99 *
100 * @param object PHPUnit2_Framework_Test
101 * @param except Thrown execption
102 */
103 public function addError(PHPUnit2_Framework_Test $test, Exception $e)
104 {
105 $this->errors[ $this->currentSuite ][ $this->currentTest ][] = new ISI_Private_Exception($e);
106 }
107
108 // ###################################################################
109 /**
110 * A test failed
111 *
112 * @param object PHPUnit2_Framework_Test
113 * @param object PHPUnit2_Framework_AssertionFailedError
114 */
115 public function addFailure(PHPUnit2_Framework_Test $test, PHPUnit2_Framework_AssertionFailedError $e)
116 {
117 $this->errors[ $this->currentSuite ][ $this->currentTest ][] = new ISI_Private_Exception($e);
118 $this->tests[ $this->currentSuite ][ $this->currentTest ] = self::TEST_FAIL;
119 $this->stats[ $this->currentSuite ]['fail']++;
120 }
121
122 // ###################################################################
123 /**
124 * A test reported being an incomplete implementation
125 *
126 * @param object PHPUnit2_Framework_Test
127 * @param except The thrown exception notice
128 */
129 public function addIncompleteTest(PHPUnit2_Framework_Test $test, Exception $e)
130 {
131 $this->incompletes[ $this->currentSuite ][ $this->currentTest ] = new ISI_Private_Exception($e);
132 $this->tests[ $this->currentSuite ][ $this->currentTest ] = self::TEST_INCOMPLETE;
133 $this->stats[ $this->currentSuite ]['incomplete']++;
134 }
135
136 // ###################################################################
137 /**
138 * Start test suite
139 *
140 * @param object A PHPUnit2_Framework_TestSuite
141 */
142 public function startTestSuite(PHPUnit2_Framework_TestSuite $suite)
143 {
144 $this->currentSuite = $suite->getName();
145 if ($this->currentSuite == '')
146 {
147 return;
148 }
149
150 $this->tests[ $this->currentSuite ] = array();
151 $this->stats[ $this->currentSuite ] = array('total' => 0, 'fail' => 0, 'incomplete' => 0);
152 }
153
154 // ###################################################################
155 /**
156 * Test suite ended
157 *
158 * @param object PHPUnit2_Framework_TestSuite
159 */
160 public function endTestSuite(PHPUnit2_Framework_TestSuite $suite)
161 {
162 $this->currentSuite = null;
163 }
164
165 // ###################################################################
166 /**
167 * An individual test started
168 *
169 * @param object The PHPUnit2_Framework_Test
170 */
171 public function startTest(PHPUnit2_Framework_Test $test)
172 {
173 $this->currentTest = $test->getName();
174 if ($this->currentTest == '')
175 {
176 return;
177 }
178
179 $this->tests[ $this->currentSuite ][ $this->currentTest ] = self::TEST_SUCCESS;
180 $this->stats[ $this->currentSuite ]['total']++;
181 }
182
183 // ###################################################################
184 /**
185 * An individual test ended
186 *
187 * @param object The PHPUnit2_Framework_Test
188 */
189 public function endTest(PHPUnit2_Framework_Test $test)
190 {
191 $this->currentTest = null;
192 }
193
194 // ###################################################################
195 /**
196 * Destructor: create the HTML output
197 */
198 public function __destruct()
199 {
200 $stats = array('total' => 0, 'incomplete' => 0, 'fail' => 0, 'success' => 0);
201 foreach ($this->stats AS $statGroup)
202 {
203 foreach ($statGroup AS $statType => $count)
204 {
205 $stats["$statType"] += $count;
206 }
207 }
208 $stats['success'] = $stats['total'] - ($stats['incomplete'] + $stats['fail']);
209
210 $output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
211 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
212 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
213 <head>
214 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
215 <title>Unit Test Results</title>
216
217 <style type="text/css">
218 <!--
219
220 body
221 {
222 font-size: 12px;
223 font-family: "Trebuchet MS", Verdana, Helvetica, sans-serif;
224 }
225
226 .suite
227 {
228 background-color: rgb(255, 255, 255);
229
230 border-color: rgb(222, 222, 222);
231 border-style: solid;
232 border-width: 1px;
233 }
234
235 .suiteHead
236 {
237 background-color: rgb(222, 222, 222);
238
239 font-weight: bold;
240
241 margin: 2px;
242 padding: 3px;
243 }
244
245 .testGroup
246 {
247 padding: 4px;
248 }
249
250 .testIncomplete
251 {
252 border-color: rgb(222, 230, 255);
253 border-style: dashed;
254 border-width: 1px;
255
256 margin-bottom: 5px;
257 }
258
259 .testIncompleteHead
260 {
261 background-color: rgb(222, 230, 255);
262
263 margin: 1px;
264 padding: 2px;
265 }
266
267 .blueText
268 {
269 color: rgb(111, 133, 255);
270 }
271
272 .testSuccess
273 {
274 border-color: rgb(216, 255, 216);
275 border-style: dashed;
276 border-width: 1px;
277
278 margin-bottom: 5px;
279 }
280
281 .testSuccessHead
282 {
283 background-color: rgb(216, 255, 216);
284
285 margin: 1px;
286 padding: 2px;
287 }
288
289 .greenText
290 {
291 color: rgb(76, 168, 78);
292 }
293
294 .testFail
295 {
296 border-color: rgb(255, 226, 229);
297 border-style: dashed;
298 border-width: 1px;
299
300 margin-bottom: 5px;
301 }
302
303 .testFailHead
304 {
305 background-color: rgb(255, 226, 229);
306
307 margin: 1px;
308 padding: 2px;
309 }
310
311 .redText
312 {
313 color: rgb(168, 65, 63);
314 }
315
316 .textPadding
317 {
318 padding: 3px;
319 }
320
321 .message
322 {
323 margin-bottom: 4px;
324 }
325
326 .messageLast
327 {
328 margin-bottom: 0px;
329 }
330
331 .location
332 {
333 margin-left: 20px;
334
335 font-style: italic;
336 }
337
338 //-->
339 </style>
340 </head>
341 <body>
342
343 <h1>Unit Test Results</h1>
344
345 <div>
346 <div>The following is a report of how each unit test performed. If any errors or exceptions were thrown, they have been recorded below. Tests have been broken down into their test suite groups. Your overall statistics are here:</div>
347
348 <br />
349
350 <div>Total Tests: <strong>' . $stats['total'] . '</strong></div>
351 <div>Number Success: <strong class="greenText">' . $stats['success'] . '</strong></div>
352 <div>Number Fail: <strong class="redText">' . $stats['fail'] . '</strong></div>
353 <div>Number Incomplete: <strong class="blueText">' . $stats['incomplete'] . '</strong></div>
354
355 <br />
356
357 <div><em>Total Success Rate: <strong class="' . ($stats['success'] == $stats['total'] ? 'green' : 'red') . 'Text">' . round(100 * ($stats['success'] / $stats['total']), 1) . '%</strong></em></div>
358 </div>
359
360 <br />
361
362 ';
363
364 foreach ($this->tests AS $suite => $tests)
365 {
366 $successPercent = round(100 * (($this->stats["$suite"]['total'] - ($this->stats["$suite"]['fail'] + $this->stats["$suite"]['incomplete'])) / $this->stats["$suite"]['total']), 1);
367
368 $output .= '<div class="suite">';
369 $output .= "\n\t" . '<div class="suiteHead">';
370 $output .= "\n\t\t" . '<span style="float: right">' . $this->stats["$suite"]['total'] . ' Tests, ' . $successPercent . '% Success' . '</span>';
371 $output .= "\n\t\t" . $suite;
372 $output .= "\n\t" . '</div>';
373 $output .= "\n\t" . '<div class="testGroup">';
374 foreach ($tests AS $name => $statusCode)
375 {
376 switch ($statusCode)
377 {
378 case self::TEST_SUCCESS:
379 $cssName = 'Success';
380 $cssColor = 'green';
381
382 $info = '';
383 break;
384
385 case self::TEST_INCOMPLETE:
386 $cssName = 'Incomplete';
387 $cssColor = 'blue';
388
389 $info = $this->formatInfo($this->incompletes["$suite"]["$name"]);
390 break;
391
392 case self::TEST_FAIL:
393 $cssName = 'Fail';
394 $cssColor = 'red';
395
396 $info = $this->formatInfo($this->errors["$suite"]["$name"]);
397 break;
398 }
399
400 $output .= "\n\t\t" . '<div class="test' . $cssName . ' ' . $cssColor . 'Text' . ($info ? ' negateMargin' : '') . '">';
401 $output .= "\n\t\t\t" . '<div class="test' . $cssName . 'Head">' . $name . '</div>';
402
403 if ($info)
404 {
405 $output .= "\n\t\t\t" . '<div class="textPadding">';
406 $output .= $info;
407 $output .= "\n\t\t\t" . '</div class="textPadding">';
408 }
409
410 $output .= "\n\t\t" . '</div>';
411 }
412 $output .= "\n\t" . '</div>';
413 $output .= "\n" . '</div>';
414 $output .= "\n\n" . '<br />' . "\n\n";
415 }
416
417 $output .= '
418 </body>
419 </html>';
420
421 file_put_contents('./UnitTestReport.html', $output);
422 }
423
424 // ###################################################################
425 /**
426 * Formats an error array into a bulleted list
427 *
428 * @param mixed Error/information list
429 *
430 * @return string Formatted HTML
431 */
432 private function formatInfo($list)
433 {
434 if (!is_array($list))
435 {
436 $list = array($list);
437 }
438
439 $count = sizeof($list);
440 foreach ($list AS $exc)
441 {
442 $i++;
443
444 if ($exc->getMessage() != '')
445 {
446 $output = "\n\t\t\t\t" . '<div class="message' . ($count == $i ? ' messageLast' : '') . '">';
447 $output .= "\n\t\t\t\t\t" . '<div>&bull; ' . htmlspecialchars($exc->getMessage()) . '</div>';
448 }
449
450 $output .= "\n\t\t\t\t\t" . '<div class="location">in ' . $exc->getPath() . ' at line ' . $exc->getLine() . '</div>';
451
452 if ($exc->getMessage() != '')
453 {
454 $output .= "\n\t\t\t\t" . '</div>';
455 }
456 }
457
458 return $output;
459 }
460 }
461
462 /**
463 * Iris Studios Private Exception Handler
464 *
465 * This class is just a form of an exception that ignores the stack trace
466 * because it's too long.
467 *
468 * @author Iris Studios, Inc.
469 * @copyright Copyright ©2002 - [#]year[#], Iris Studios, Inc.
470 * @version $Revision$
471 *
472 */
473 class ISI_Private_Exception
474 {
475 /**
476 * Exception context message
477 * @var string
478 */
479 private $message = '';
480
481 /**
482 * File path
483 * @var string
484 */
485 private $path = '';
486
487 /**
488 * Line number of the exception
489 * @var integer
490 */
491 private $line = 0;
492
493 // ###################################################################
494 /**
495 * Constructor: accept exception
496 *
497 * @param except A given exception
498 */
499 public function __construct(Exception $e)
500 {
501 $this->message = $e->getMessage();
502 $this->path = $e->getFile();
503 $this->line = $e->getLine();
504 }
505
506 // ###################################################################
507 /**
508 * Returns the context message
509 *
510 * @return string Context message
511 */
512 public function getMessage()
513 {
514 return $this->message;
515 }
516
517 // ###################################################################
518 /**
519 * Returns the file path
520 *
521 * @return string File path
522 */
523 public function getPath()
524 {
525 return $this->path;
526 }
527
528 // ###################################################################
529 /**
530 * Returns the line number
531 *
532 * @return integer Line number
533 */
534 public function getLine()
535 {
536 return $this->line;
537 }
538 }
539
540 /*=====================================================================*\
541 || ###################################################################
542 || # $HeadURL$
543 || # $Id$
544 || ###################################################################
545 \*=====================================================================*/
546 ?>