File: 1.01.7b/server/test/phpunit.php (View as HTML)

  1: <?php
  2: //
  3: // PHP framework for testing, based on the design of "JUnit".
  4: //
  5: // Written by Fred Yankowski <fred@ontosys.com>
  6: //            OntoSys, Inc  <http://www.OntoSys.com>
  7: //
  8: // $Id: phpunit.php,v 1.1 2002/03/30 19:32:17 bmatzelle Exp $
  9: 
 10: // Copyright (c) 2000 Fred Yankowski
 11: 
 12: // Permission is hereby granted, free of charge, to any person
 13: // obtaining a copy of this software and associated documentation
 14: // files (the "Software"), to deal in the Software without
 15: // restriction, including without limitation the rights to use, copy,
 16: // modify, merge, publish, distribute, sublicense, and/or sell copies
 17: // of the Software, and to permit persons to whom the Software is
 18: // furnished to do so, subject to the following conditions:
 19: //
 20: // The above copyright notice and this permission notice shall be
 21: // included in all copies or substantial portions of the Software.
 22: //
 23: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 24: // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 25: // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 26: // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 27: // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 28: // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 29: // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 30: // SOFTWARE.
 31: //
 32: error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE |
 33: 		E_CORE_ERROR | E_CORE_WARNING);
 34: 
 35: /*
 36: interface Test {
 37:   function run(&$aTestResult);
 38:   function countTestCases();
 39: }
 40: */
 41: 
 42: function trace($msg) {
 43:   return;
 44:   print($msg);
 45:   flush();
 46: }
 47: 
 48: 
 49: class Exception {
 50:     /* Emulate a Java exception, sort of... */
 51:   var $message;
 52:   function Exception($message) {
 53:     $this->message = $message;
 54:   }
 55:   function getMessage() {
 56:     return $this->message;
 57:   }
 58: }
 59: 
 60: class Assert {
 61:   function assert($boolean, $message=0) {
 62:     if (! $boolean)
 63:       $this->fail($message);
 64:   }
 65: 
 66:   function assertEquals($expected, $actual, $message=0) {
 67:     if ($expected != $actual) {
 68:       $this->failNotEquals($expected, $actual, "expected", $message);
 69:     }
 70:   }
 71: 
 72:   function assertRegexp($regexp, $actual, $message=false) {
 73:     if (! preg_match($regexp, $actual)) {
 74:       $this->failNotEquals($regexp, $actual, "pattern", $message);
 75:     }
 76:   }
 77: 
 78:   function failNotEquals($expected, $actual, $expected_label, $message=0) {
 79:     // Private function for reporting failure to match.
 80:     $str = $message ? ($message . ' ') : '';
 81:     $str .= "($expected_label/actual)<br>";
 82:     $htmlExpected = htmlspecialchars($expected);
 83:     $htmlActual = htmlspecialchars($actual);
 84:     $str .= sprintf("<pre>%s\n--------\n%s</pre>",
 85: 		    $htmlExpected, $htmlActual);
 86:     $this->fail($str);
 87:   }
 88: }
 89: 
 90: class TestCase extends Assert /* implements Test */ {
 91:   /* Defines context for running tests.  Specific context -- such as
 92:      instance variables, global variables, global state -- is defined
 93:      by creating a subclass that specializes the setUp() and
 94:      tearDown() methods.  A specific test is defined by a subclass
 95:      that specializes the runTest() method. */
 96:   var $fName;
 97:   var $fResult;
 98:   var $fExceptions = array();
 99: 
100:   function TestCase($name) {
101:     $this->fName = $name;
102:   }
103: 
104:   function run($testResult=0) {
105:     /* Run this single test, by calling the run() method of the
106:        TestResult object which will in turn call the runBare() method
107:        of this object.  That complication allows the TestResult object
108:        to do various kinds of progress reporting as it invokes each
109:        test.  Create/obtain a TestResult object if none was passed in.
110:        Note that if a TestResult object was passed in, it must be by
111:        reference. */
112:     if (! $testResult)
113:       $testResult = $this->_createResult();
114:     $this->fResult = $testResult;
115:     $testResult->run(&$this);
116:     $this->fResult = 0;
117:     return $testResult;
118:   }
119: 
120:   function countTestCases() {
121:     return 1;
122:   }
123: 
124:   function runTest() {
125:     $name = $this->name();
126:     // Since isset($this->$name) is false, no way to run defensive checks
127:     $this->$name();
128:   }
129: 
130:   function setUp() /* expect override */ {
131:     //print("TestCase::setUp()<br>\n");
132:   }
133: 
134:   function tearDown() /* possible override */ {
135:     //print("TestCase::tearDown()<br>\n");
136:   }
137: 
138:   ////////////////////////////////////////////////////////////////
139: 
140: 
141:   function _createResult() /* protected */ {
142:     /* override this to use specialized subclass of TestResult */
143:     return new TestResult;
144:   }
145: 
146:   function fail($message=0) {
147:     //printf("TestCase::fail(%s)<br>\n", ($message) ? $message : '');
148:     /* JUnit throws AssertionFailedError here.  We just record the
149:        failure and carry on */
150:     $this->fExceptions[] = new Exception(&$message);
151:   }
152: 
153:   function error($message) {
154:     /* report error that requires correction in the test script
155:        itself, or (heaven forbid) in this testing infrastructure */
156:     printf('<b>ERROR: ' . $message . '</b><br>');
157:     $this->fResult->stop();
158:   }
159: 
160:   function failed() {
161:     return count($this->fExceptions);
162:   }
163: 
164:   function getExceptions() {
165:     return $this->fExceptions;
166:   }
167: 
168:   function name() {
169:     return $this->fName;
170:   }
171: 
172:   function runBare() {
173:     $this->setup();
174:     $this->runTest();
175:     $this->tearDown();
176:   }
177: }
178: 
179: 
180: class TestSuite /* implements Test */ {
181:   /* Compose a set of Tests (instances of TestCase or TestSuite), and
182:      run them all. */
183:   var $fTests = array();
184: 
185:   function TestSuite($classname=false) {
186:     if ($classname) {
187:       // Find all methods of the given class whose name starts with
188:       // "test" and add them to the test suite.  We are just _barely_
189:       // able to do this with PHP's limited introspection...  Note
190:       // that PHP seems to store method names in lower case, and we
191:       // have to avoid the constructor function for the TestCase class
192:       // superclass.  This will fail when $classname starts with
193:       // "Test" since that will have a constructor method that will
194:       // get matched below and then treated (incorrectly) as a test
195:       // method.  So don't name any TestCase subclasses as "Test..."!
196:       if (floor(phpversion()) >= 4) {
197: 	// PHP4 introspection, submitted by Dylan Kuhn
198: 	$names = get_class_methods($classname);
199: 	while (list($key, $method) = each($names)) {
200: 	  if (preg_match('/^test/', $method) && $method != "testcase") {  
201: 	    $this->addTest(new $classname($method));
202: 	  }
203: 	}
204:       }
205:       else {
206: 	$dummy = new $classname("dummy");
207: 	$names = (array) $dummy;
208: 	while (list($key, $value) = each($names)) {
209: 	  $type = gettype($value);
210: 	  if ($type == "user function" && preg_match('/^test/', $key)
211: 	  && $key != "testcase") {  
212: 	    $this->addTest(new $classname($key));
213: 	  }
214: 	}
215:       }
216:     }
217:   }
218: 
219:   function addTest($test) {
220:     /* Add TestCase or TestSuite to this TestSuite */
221:     $this->fTests[] = $test;
222:   }
223: 
224:   function run(&$testResult) {
225:     /* Run all TestCases and TestSuites comprising this TestSuite,
226:        accumulating results in the given TestResult object. */
227:     reset($this->fTests);
228:     while (list($na, $test) = each($this->fTests)) {
229:       if ($testResult->shouldStop())
230: 	break;
231:       $test->run(&$testResult);
232:     }
233:   }
234: 
235:   function countTestCases() {
236:     /* Number of TestCases comprising this TestSuite (including those
237:        in any constituent TestSuites) */
238:     $count = 0;
239:     reset($fTests);
240:     while (list($na, $test_case) = each($this->fTests)) {
241:       $count += $test_case->countTestCases();
242:     }
243:     return $count;
244:   }
245: }
246: 
247: 
248: class TestFailure {
249:   /* Record failure of a single TestCase, associating it with the
250:      exception(s) that occurred */
251:   var $fFailedTestName;
252:   var $fExceptions;
253: 
254:   function TestFailure(&$test, &$exceptions) {
255:     $this->fFailedTestName = $test->name();
256:     $this->fExceptions = $exceptions;
257:   }
258: 
259:   function getExceptions() {
260:       return $this->fExceptions;
261:   }
262:   function getTestName() {
263:     return $this->fFailedTestName;
264:   }
265: }
266: 
267: 
268: class TestResult {
269:   /* Collect the results of running a set of TestCases. */
270:   var $fFailures = array();
271:   var $fRunTests = 0;
272:   var $fStop = false;
273: 
274:   function TestResult() { }
275: 
276:   function _endTest($test) /* protected */ {
277:       /* specialize this for end-of-test action, such as progress
278: 	 reports  */
279:   }
280: 
281:   function getFailures() {
282:     return $this->fFailures;
283:   }
284: 
285:   function run($test) {
286:     /* Run a single TestCase in the context of this TestResult */
287:     $this->_startTest($test);
288:     $this->fRunTests++;
289: 
290:     $test->runBare();
291: 
292:     /* this is where JUnit would catch AssertionFailedError */
293:     $exceptions = $test->getExceptions();
294:     if ($exceptions)
295:       $this->fFailures[] = new TestFailure(&$test, &$exceptions);
296:     $this->_endTest($test);
297:   }
298: 
299:   function countTests() {
300:     return $this->fRunTests;
301:   }
302: 
303:   function shouldStop() {
304:     return $this->fStop;
305:   }
306: 
307:   function _startTest($test) /* protected */ {
308:       /* specialize this for start-of-test actions */
309:   }
310: 
311:   function stop() {
312:     /* set indication that the test sequence should halt */
313:     $fStop = true;
314:   }
315: 
316:   function countFailures() {
317:     return count($this->fFailures);
318:   }
319: }
320: 
321: 
322: class TextTestResult extends TestResult {
323:   /* Specialize TestResult to produce text/html report */
324:   function TextTestResult() {
325:     $this->TestResult();  // call superclass constructor
326:   }
327:   
328:   function report() {
329:     /* report result of test run */
330:     $nRun = $this->countTests();
331:     $nFailures = $this->countFailures();
332:     printf("<p>%s test%s run<br>", $nRun, ($nRun == 1) ? '' : 's');
333:     printf("%s failure%s.<br>\n", $nFailures, ($nFailures == 1) ? '' : 's');
334:     if ($nFailures == 0)
335:       return;
336: 
337:     print("<ol>\n");
338:     $failures = $this->getFailures();
339:     while (list($i, $failure) = each($failures)) {
340:       $failedTestName = $failure->getTestName();
341:       printf("<li>%s\n", $failedTestName);
342: 
343:       $exceptions = $failure->getExceptions();
344:       print("<ul>");
345:       while (list($na, $exception) = each($exceptions))
346: 	printf("<li>%s\n", $exception->getMessage());
347:       print("</ul>");
348:     }
349:     print("</ol>\n");
350:   }
351: 
352:   function _startTest($test) {
353:     printf("%s ", $test->name());
354:     flush();
355:   }
356: 
357:   function _endTest($test) {
358:     $outcome = $test->failed()
359:        ? "<font color=\"red\">FAIL</font>"
360:        : "<font color=\"green\">ok</font>";
361:     printf("$outcome<br>\n");
362:     flush();
363:   }
364: }
365: 
366: 
367: class TestRunner {
368:   /* Run a suite of tests and report results. */
369:   function run($suite) {
370:     $result = new TextTestResult;
371:     $suite->run($result);
372:     $result->report();
373:   }
374: }
375: 
376: ?>
377: