1 | #!/usr/bin/env python |
---|
2 | """ This is a hacked version of PyUnit that extends its reporting capabilities |
---|
3 | with optional meta data on the test cases. It also makes it possible to |
---|
4 | separate the standard and error output streams in TextTestRunner. |
---|
5 | |
---|
6 | It's a hack rather than a set of subclasses because a) Steve had used double |
---|
7 | underscore private attributes for some things I needed access to, and b) the |
---|
8 | changes affected so many classes that it was easier just to hack it. |
---|
9 | |
---|
10 | The changes are in the following places: |
---|
11 | TestCase: |
---|
12 | - minor refactoring of __init__ and __call__ internals |
---|
13 | - added some attributes and methods for storing and retrieving meta data |
---|
14 | |
---|
15 | _TextTestResult |
---|
16 | - refactored the stream handling |
---|
17 | - incorporated all the output code from TextTestRunner |
---|
18 | - made the output of FAIL and ERROR information more flexible and |
---|
19 | incorporated the new meta data from TestCase |
---|
20 | - added a flag called 'explain' to __init__ that controls whether the new ' |
---|
21 | explanation' meta data from TestCase is printed along with tracebacks |
---|
22 | |
---|
23 | TextTestRunner |
---|
24 | - delegated all output to _TextTestResult |
---|
25 | - added 'err' and 'explain' to the __init__ signature to match the changes |
---|
26 | in _TextTestResult |
---|
27 | |
---|
28 | TestProgram |
---|
29 | - added -e and --explain as flags on the command line |
---|
30 | |
---|
31 | -- Tavis Rudd <tavis@redonions.net> (Sept 28th, 2001) |
---|
32 | |
---|
33 | - _TestTextResult.printErrorList(): print blank line after each traceback |
---|
34 | |
---|
35 | -- Mike Orr <mso@oz.net> (Nov 11, 2002) |
---|
36 | |
---|
37 | TestCase methods copied from unittest in Python 2.3: |
---|
38 | - .assertAlmostEqual(first, second, places=7, msg=None): to N decimal places. |
---|
39 | - .failIfAlmostEqual(first, second, places=7, msg=None) |
---|
40 | |
---|
41 | -- Mike Orr (Jan 5, 2004) |
---|
42 | |
---|
43 | |
---|
44 | Below is the original docstring for unittest. |
---|
45 | --------------------------------------------------------------------------- |
---|
46 | Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's |
---|
47 | Smalltalk testing framework. |
---|
48 | |
---|
49 | This module contains the core framework classes that form the basis of |
---|
50 | specific test cases and suites (TestCase, TestSuite etc.), and also a |
---|
51 | text-based utility class for running the tests and reporting the results |
---|
52 | (TextTestRunner). |
---|
53 | |
---|
54 | Simple usage: |
---|
55 | |
---|
56 | import unittest |
---|
57 | |
---|
58 | class IntegerArithmenticTestCase(unittest.TestCase): |
---|
59 | def testAdd(self): ## test method names begin 'test*' |
---|
60 | self.assertEquals((1 + 2), 3) |
---|
61 | self.assertEquals(0 + 1, 1) |
---|
62 | def testMultiply(self); |
---|
63 | self.assertEquals((0 * 10), 0) |
---|
64 | self.assertEquals((5 * 8), 40) |
---|
65 | |
---|
66 | if __name__ == '__main__': |
---|
67 | unittest.main() |
---|
68 | |
---|
69 | Further information is available in the bundled documentation, and from |
---|
70 | |
---|
71 | http://pyunit.sourceforge.net/ |
---|
72 | |
---|
73 | Copyright (c) 1999, 2000, 2001 Steve Purcell |
---|
74 | This module is free software, and you may redistribute it and/or modify |
---|
75 | it under the same terms as Python itself, so long as this copyright message |
---|
76 | and disclaimer are retained in their original form. |
---|
77 | |
---|
78 | IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, |
---|
79 | SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF |
---|
80 | THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
---|
81 | DAMAGE. |
---|
82 | |
---|
83 | THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT |
---|
84 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
---|
85 | PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, |
---|
86 | AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, |
---|
87 | SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
---|
88 | """ |
---|
89 | |
---|
90 | __author__ = "Steve Purcell" |
---|
91 | __email__ = "stephen_purcell at yahoo dot com" |
---|
92 | __revision__ = "$Revision: 1.11 $"[11:-2] |
---|
93 | |
---|
94 | |
---|
95 | ################################################## |
---|
96 | ## DEPENDENCIES ## |
---|
97 | |
---|
98 | import os |
---|
99 | import re |
---|
100 | import string |
---|
101 | import sys |
---|
102 | import time |
---|
103 | import traceback |
---|
104 | import types |
---|
105 | import pprint |
---|
106 | |
---|
107 | ################################################## |
---|
108 | ## CONSTANTS & GLOBALS |
---|
109 | |
---|
110 | try: |
---|
111 | True,False |
---|
112 | except NameError: |
---|
113 | True, False = (1==1),(1==0) |
---|
114 | |
---|
115 | ############################################################################## |
---|
116 | # Test framework core |
---|
117 | ############################################################################## |
---|
118 | |
---|
119 | |
---|
120 | class TestResult: |
---|
121 | """Holder for test result information. |
---|
122 | |
---|
123 | Test results are automatically managed by the TestCase and TestSuite |
---|
124 | classes, and do not need to be explicitly manipulated by writers of tests. |
---|
125 | |
---|
126 | Each instance holds the total number of tests run, and collections of |
---|
127 | failures and errors that occurred among those test runs. The collections |
---|
128 | contain tuples of (testcase, exceptioninfo), where exceptioninfo is a |
---|
129 | tuple of values as returned by sys.exc_info(). |
---|
130 | """ |
---|
131 | def __init__(self): |
---|
132 | self.failures = [] |
---|
133 | self.errors = [] |
---|
134 | self.testsRun = 0 |
---|
135 | self.shouldStop = 0 |
---|
136 | |
---|
137 | def startTest(self, test): |
---|
138 | "Called when the given test is about to be run" |
---|
139 | self.testsRun = self.testsRun + 1 |
---|
140 | |
---|
141 | def stopTest(self, test): |
---|
142 | "Called when the given test has been run" |
---|
143 | pass |
---|
144 | |
---|
145 | def addError(self, test, err): |
---|
146 | "Called when an error has occurred" |
---|
147 | self.errors.append((test, err)) |
---|
148 | |
---|
149 | def addFailure(self, test, err): |
---|
150 | "Called when a failure has occurred" |
---|
151 | self.failures.append((test, err)) |
---|
152 | |
---|
153 | def addSuccess(self, test): |
---|
154 | "Called when a test has completed successfully" |
---|
155 | pass |
---|
156 | |
---|
157 | def wasSuccessful(self): |
---|
158 | "Tells whether or not this result was a success" |
---|
159 | return len(self.failures) == len(self.errors) == 0 |
---|
160 | |
---|
161 | def stop(self): |
---|
162 | "Indicates that the tests should be aborted" |
---|
163 | self.shouldStop = 1 |
---|
164 | |
---|
165 | def __repr__(self): |
---|
166 | return "<%s run=%i errors=%i failures=%i>" % \ |
---|
167 | (self.__class__, self.testsRun, len(self.errors), |
---|
168 | len(self.failures)) |
---|
169 | |
---|
170 | class TestCase: |
---|
171 | """A class whose instances are single test cases. |
---|
172 | |
---|
173 | By default, the test code itself should be placed in a method named |
---|
174 | 'runTest'. |
---|
175 | |
---|
176 | If the fixture may be used for many test cases, create as |
---|
177 | many test methods as are needed. When instantiating such a TestCase |
---|
178 | subclass, specify in the constructor arguments the name of the test method |
---|
179 | that the instance is to execute. |
---|
180 | |
---|
181 | Test authors should subclass TestCase for their own tests. Construction |
---|
182 | and deconstruction of the test's environment ('fixture') can be |
---|
183 | implemented by overriding the 'setUp' and 'tearDown' methods respectively. |
---|
184 | |
---|
185 | If it is necessary to override the __init__ method, the base class |
---|
186 | __init__ method must always be called. It is important that subclasses |
---|
187 | should not change the signature of their __init__ method, since instances |
---|
188 | of the classes are instantiated automatically by parts of the framework |
---|
189 | in order to be run. |
---|
190 | """ |
---|
191 | |
---|
192 | # This attribute determines which exception will be raised when |
---|
193 | # the instance's assertion methods fail; test methods raising this |
---|
194 | # exception will be deemed to have 'failed' rather than 'errored' |
---|
195 | |
---|
196 | failureException = AssertionError |
---|
197 | |
---|
198 | # the name of the fixture. Used for displaying meta data about the test |
---|
199 | name = None |
---|
200 | |
---|
201 | def __init__(self, methodName='runTest'): |
---|
202 | """Create an instance of the class that will use the named test |
---|
203 | method when executed. Raises a ValueError if the instance does |
---|
204 | not have a method with the specified name. |
---|
205 | """ |
---|
206 | self._testMethodName = methodName |
---|
207 | self._setupTestMethod() |
---|
208 | self._setupMetaData() |
---|
209 | |
---|
210 | def _setupTestMethod(self): |
---|
211 | try: |
---|
212 | self._testMethod = getattr(self, self._testMethodName) |
---|
213 | except AttributeError: |
---|
214 | raise ValueError, "no such test method in %s: %s" % \ |
---|
215 | (self.__class__, self._testMethodName) |
---|
216 | |
---|
217 | ## meta data methods |
---|
218 | |
---|
219 | def _setupMetaData(self): |
---|
220 | """Setup the default meta data for the test case: |
---|
221 | |
---|
222 | - id: self.__class__.__name__ + testMethodName OR self.name + testMethodName |
---|
223 | - description: 1st line of Class docstring + 1st line of method docstring |
---|
224 | - explanation: rest of Class docstring + rest of method docstring |
---|
225 | |
---|
226 | """ |
---|
227 | |
---|
228 | |
---|
229 | testDoc = self._testMethod.__doc__ or '\n' |
---|
230 | testDocLines = testDoc.splitlines() |
---|
231 | |
---|
232 | testDescription = testDocLines[0].strip() |
---|
233 | if len(testDocLines) > 1: |
---|
234 | testExplanation = '\n'.join( |
---|
235 | [ln.strip() for ln in testDocLines[1:]] |
---|
236 | ).strip() |
---|
237 | else: |
---|
238 | testExplanation = '' |
---|
239 | |
---|
240 | fixtureDoc = self.__doc__ or '\n' |
---|
241 | fixtureDocLines = fixtureDoc.splitlines() |
---|
242 | fixtureDescription = fixtureDocLines[0].strip() |
---|
243 | if len(fixtureDocLines) > 1: |
---|
244 | fixtureExplanation = '\n'.join( |
---|
245 | [ln.strip() for ln in fixtureDocLines[1:]] |
---|
246 | ).strip() |
---|
247 | else: |
---|
248 | fixtureExplanation = '' |
---|
249 | |
---|
250 | if not self.name: |
---|
251 | self.name = self.__class__ |
---|
252 | self._id = "%s.%s" % (self.name, self._testMethodName) |
---|
253 | |
---|
254 | if not fixtureDescription: |
---|
255 | self._description = testDescription |
---|
256 | else: |
---|
257 | self._description = fixtureDescription + ', ' + testDescription |
---|
258 | |
---|
259 | if not fixtureExplanation: |
---|
260 | self._explanation = testExplanation |
---|
261 | else: |
---|
262 | self._explanation = ['Fixture Explanation:', |
---|
263 | '--------------------', |
---|
264 | fixtureExplanation, |
---|
265 | '', |
---|
266 | 'Test Explanation:', |
---|
267 | '-----------------', |
---|
268 | testExplanation |
---|
269 | ] |
---|
270 | self._explanation = '\n'.join(self._explanation) |
---|
271 | |
---|
272 | def id(self): |
---|
273 | return self._id |
---|
274 | |
---|
275 | def setId(self, id): |
---|
276 | self._id = id |
---|
277 | |
---|
278 | def describe(self): |
---|
279 | """Returns a one-line description of the test, or None if no |
---|
280 | description has been provided. |
---|
281 | |
---|
282 | The default implementation of this method returns the first line of |
---|
283 | the specified test method's docstring. |
---|
284 | """ |
---|
285 | return self._description |
---|
286 | |
---|
287 | shortDescription = describe |
---|
288 | |
---|
289 | def setDescription(self, descr): |
---|
290 | self._description = descr |
---|
291 | |
---|
292 | def explain(self): |
---|
293 | return self._explanation |
---|
294 | |
---|
295 | def setExplanation(self, expln): |
---|
296 | self._explanation = expln |
---|
297 | |
---|
298 | ## core methods |
---|
299 | |
---|
300 | def setUp(self): |
---|
301 | "Hook method for setting up the test fixture before exercising it." |
---|
302 | pass |
---|
303 | |
---|
304 | def run(self, result=None): |
---|
305 | return self(result) |
---|
306 | |
---|
307 | def tearDown(self): |
---|
308 | "Hook method for deconstructing the test fixture after testing it." |
---|
309 | pass |
---|
310 | |
---|
311 | def debug(self): |
---|
312 | """Run the test without collecting errors in a TestResult""" |
---|
313 | self.setUp() |
---|
314 | self._testMethod() |
---|
315 | self.tearDown() |
---|
316 | |
---|
317 | ## internal methods |
---|
318 | |
---|
319 | def defaultTestResult(self): |
---|
320 | return TestResult() |
---|
321 | |
---|
322 | def __call__(self, result=None): |
---|
323 | if result is None: |
---|
324 | result = self.defaultTestResult() |
---|
325 | |
---|
326 | result.startTest(self) |
---|
327 | try: |
---|
328 | try: |
---|
329 | self.setUp() |
---|
330 | except: |
---|
331 | result.addError(self, self.__exc_info()) |
---|
332 | return |
---|
333 | |
---|
334 | ok = 0 |
---|
335 | try: |
---|
336 | self._testMethod() |
---|
337 | ok = 1 |
---|
338 | except self.failureException, e: |
---|
339 | result.addFailure(self, self.__exc_info()) |
---|
340 | except: |
---|
341 | result.addError(self, self.__exc_info()) |
---|
342 | try: |
---|
343 | self.tearDown() |
---|
344 | except: |
---|
345 | result.addError(self, self.__exc_info()) |
---|
346 | ok = 0 |
---|
347 | if ok: |
---|
348 | result.addSuccess(self) |
---|
349 | finally: |
---|
350 | result.stopTest(self) |
---|
351 | |
---|
352 | return result |
---|
353 | |
---|
354 | def countTestCases(self): |
---|
355 | return 1 |
---|
356 | |
---|
357 | def __str__(self): |
---|
358 | return "%s (%s)" % (self._testMethodName, self.__class__) |
---|
359 | |
---|
360 | def __repr__(self): |
---|
361 | return "<%s testMethod=%s>" % \ |
---|
362 | (self.__class__, self._testMethodName) |
---|
363 | |
---|
364 | def __exc_info(self): |
---|
365 | """Return a version of sys.exc_info() with the traceback frame |
---|
366 | minimised; usually the top level of the traceback frame is not |
---|
367 | needed. |
---|
368 | """ |
---|
369 | exctype, excvalue, tb = sys.exc_info() |
---|
370 | if sys.platform[:4] == 'java': ## tracebacks look different in Jython |
---|
371 | return (exctype, excvalue, tb) |
---|
372 | newtb = tb.tb_next |
---|
373 | if newtb is None: |
---|
374 | return (exctype, excvalue, tb) |
---|
375 | return (exctype, excvalue, newtb) |
---|
376 | |
---|
377 | ## methods for use by the test cases |
---|
378 | |
---|
379 | def fail(self, msg=None): |
---|
380 | """Fail immediately, with the given message.""" |
---|
381 | raise self.failureException, msg |
---|
382 | |
---|
383 | def failIf(self, expr, msg=None): |
---|
384 | "Fail the test if the expression is true." |
---|
385 | if expr: raise self.failureException, msg |
---|
386 | |
---|
387 | def failUnless(self, expr, msg=None): |
---|
388 | """Fail the test unless the expression is true.""" |
---|
389 | if not expr: raise self.failureException, msg |
---|
390 | |
---|
391 | def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): |
---|
392 | """Fail unless an exception of class excClass is thrown |
---|
393 | by callableObj when invoked with arguments args and keyword |
---|
394 | arguments kwargs. If a different type of exception is |
---|
395 | thrown, it will not be caught, and the test case will be |
---|
396 | deemed to have suffered an error, exactly as for an |
---|
397 | unexpected exception. |
---|
398 | """ |
---|
399 | try: |
---|
400 | apply(callableObj, args, kwargs) |
---|
401 | except excClass: |
---|
402 | return |
---|
403 | else: |
---|
404 | if hasattr(excClass,'__name__'): excName = excClass.__name__ |
---|
405 | else: excName = str(excClass) |
---|
406 | raise self.failureException, excName |
---|
407 | |
---|
408 | def failUnlessEqual(self, first, second, msg=None): |
---|
409 | """Fail if the two objects are unequal as determined by the '!=' |
---|
410 | operator. |
---|
411 | """ |
---|
412 | if first != second: |
---|
413 | raise self.failureException, (msg or '%s != %s' % (first, second)) |
---|
414 | |
---|
415 | def failIfEqual(self, first, second, msg=None): |
---|
416 | """Fail if the two objects are equal as determined by the '==' |
---|
417 | operator. |
---|
418 | """ |
---|
419 | if first == second: |
---|
420 | raise self.failureException, (msg or '%s == %s' % (first, second)) |
---|
421 | |
---|
422 | def failUnlessAlmostEqual(self, first, second, places=7, msg=None): |
---|
423 | """Fail if the two objects are unequal as determined by their |
---|
424 | difference rounded to the given number of decimal places |
---|
425 | (default 7) and comparing to zero. |
---|
426 | |
---|
427 | Note that decimal places (from zero) is usually not the same |
---|
428 | as significant digits (measured from the most signficant digit). |
---|
429 | """ |
---|
430 | if round(second-first, places) != 0: |
---|
431 | raise self.failureException, \ |
---|
432 | (msg or '%s != %s within %s places' % (`first`, `second`, `places` )) |
---|
433 | |
---|
434 | def failIfAlmostEqual(self, first, second, places=7, msg=None): |
---|
435 | """Fail if the two objects are equal as determined by their |
---|
436 | difference rounded to the given number of decimal places |
---|
437 | (default 7) and comparing to zero. |
---|
438 | |
---|
439 | Note that decimal places (from zero) is usually not the same |
---|
440 | as significant digits (measured from the most signficant digit). |
---|
441 | """ |
---|
442 | if round(second-first, places) == 0: |
---|
443 | raise self.failureException, \ |
---|
444 | (msg or '%s == %s within %s places' % (`first`, `second`, `places`)) |
---|
445 | |
---|
446 | ## aliases |
---|
447 | |
---|
448 | assertEqual = assertEquals = failUnlessEqual |
---|
449 | |
---|
450 | assertNotEqual = assertNotEquals = failIfEqual |
---|
451 | |
---|
452 | assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual |
---|
453 | |
---|
454 | assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual |
---|
455 | |
---|
456 | assertRaises = failUnlessRaises |
---|
457 | |
---|
458 | assert_ = failUnless |
---|
459 | |
---|
460 | |
---|
461 | class FunctionTestCase(TestCase): |
---|
462 | """A test case that wraps a test function. |
---|
463 | |
---|
464 | This is useful for slipping pre-existing test functions into the |
---|
465 | PyUnit framework. Optionally, set-up and tidy-up functions can be |
---|
466 | supplied. As with TestCase, the tidy-up ('tearDown') function will |
---|
467 | always be called if the set-up ('setUp') function ran successfully. |
---|
468 | """ |
---|
469 | |
---|
470 | def __init__(self, testFunc, setUp=None, tearDown=None, |
---|
471 | description=None): |
---|
472 | TestCase.__init__(self) |
---|
473 | self.__setUpFunc = setUp |
---|
474 | self.__tearDownFunc = tearDown |
---|
475 | self.__testFunc = testFunc |
---|
476 | self.__description = description |
---|
477 | |
---|
478 | def setUp(self): |
---|
479 | if self.__setUpFunc is not None: |
---|
480 | self.__setUpFunc() |
---|
481 | |
---|
482 | def tearDown(self): |
---|
483 | if self.__tearDownFunc is not None: |
---|
484 | self.__tearDownFunc() |
---|
485 | |
---|
486 | def runTest(self): |
---|
487 | self.__testFunc() |
---|
488 | |
---|
489 | def id(self): |
---|
490 | return self.__testFunc.__name__ |
---|
491 | |
---|
492 | def __str__(self): |
---|
493 | return "%s (%s)" % (self.__class__, self.__testFunc.__name__) |
---|
494 | |
---|
495 | def __repr__(self): |
---|
496 | return "<%s testFunc=%s>" % (self.__class__, self.__testFunc) |
---|
497 | |
---|
498 | |
---|
499 | def describe(self): |
---|
500 | if self.__description is not None: return self.__description |
---|
501 | doc = self.__testFunc.__doc__ |
---|
502 | return doc and string.strip(string.split(doc, "\n")[0]) or None |
---|
503 | |
---|
504 | ## aliases |
---|
505 | shortDescription = describe |
---|
506 | |
---|
507 | class TestSuite: |
---|
508 | """A test suite is a composite test consisting of a number of TestCases. |
---|
509 | |
---|
510 | For use, create an instance of TestSuite, then add test case instances. |
---|
511 | When all tests have been added, the suite can be passed to a test |
---|
512 | runner, such as TextTestRunner. It will run the individual test cases |
---|
513 | in the order in which they were added, aggregating the results. When |
---|
514 | subclassing, do not forget to call the base class constructor. |
---|
515 | """ |
---|
516 | def __init__(self, tests=(), suiteName=None): |
---|
517 | self._tests = [] |
---|
518 | self._testMap = {} |
---|
519 | self.suiteName = suiteName |
---|
520 | self.addTests(tests) |
---|
521 | |
---|
522 | def __repr__(self): |
---|
523 | return "<%s tests=%s>" % (self.__class__, pprint.pformat(self._tests)) |
---|
524 | |
---|
525 | __str__ = __repr__ |
---|
526 | |
---|
527 | def countTestCases(self): |
---|
528 | cases = 0 |
---|
529 | for test in self._tests: |
---|
530 | cases = cases + test.countTestCases() |
---|
531 | return cases |
---|
532 | |
---|
533 | def addTest(self, test): |
---|
534 | self._tests.append(test) |
---|
535 | if isinstance(test, TestSuite) and test.suiteName: |
---|
536 | name = test.suiteName |
---|
537 | elif isinstance(test, TestCase): |
---|
538 | #print test, test._testMethodName |
---|
539 | name = test._testMethodName |
---|
540 | else: |
---|
541 | name = test.__class__.__name__ |
---|
542 | self._testMap[name] = test |
---|
543 | |
---|
544 | def addTests(self, tests): |
---|
545 | for test in tests: |
---|
546 | self.addTest(test) |
---|
547 | |
---|
548 | def getTestForName(self, name): |
---|
549 | return self._testMap[name] |
---|
550 | |
---|
551 | def run(self, result): |
---|
552 | return self(result) |
---|
553 | |
---|
554 | def __call__(self, result): |
---|
555 | for test in self._tests: |
---|
556 | if result.shouldStop: |
---|
557 | break |
---|
558 | test(result) |
---|
559 | return result |
---|
560 | |
---|
561 | def debug(self): |
---|
562 | """Run the tests without collecting errors in a TestResult""" |
---|
563 | for test in self._tests: test.debug() |
---|
564 | |
---|
565 | |
---|
566 | ############################################################################## |
---|
567 | # Text UI |
---|
568 | ############################################################################## |
---|
569 | |
---|
570 | class StreamWrapper: |
---|
571 | def __init__(self, out=sys.stdout, err=sys.stderr): |
---|
572 | self._streamOut = out |
---|
573 | self._streamErr = err |
---|
574 | |
---|
575 | def write(self, txt): |
---|
576 | self._streamOut.write(txt) |
---|
577 | self._streamOut.flush() |
---|
578 | |
---|
579 | def writeln(self, *lines): |
---|
580 | for line in lines: |
---|
581 | self.write(line + '\n') |
---|
582 | if not lines: |
---|
583 | self.write('\n') |
---|
584 | |
---|
585 | def writeErr(self, txt): |
---|
586 | self._streamErr.write(txt) |
---|
587 | |
---|
588 | def writelnErr(self, *lines): |
---|
589 | for line in lines: |
---|
590 | self.writeErr(line + '\n') |
---|
591 | if not lines: |
---|
592 | self.writeErr('\n') |
---|
593 | |
---|
594 | |
---|
595 | class _TextTestResult(TestResult, StreamWrapper): |
---|
596 | _separatorWidth = 70 |
---|
597 | _sep1 = '=' |
---|
598 | _sep2 = '-' |
---|
599 | _errorSep1 = '*' |
---|
600 | _errorSep2 = '-' |
---|
601 | _errorSep3 = '' |
---|
602 | |
---|
603 | def __init__(self, |
---|
604 | stream=sys.stdout, |
---|
605 | errStream=sys.stderr, |
---|
606 | verbosity=1, |
---|
607 | explain=False): |
---|
608 | |
---|
609 | TestResult.__init__(self) |
---|
610 | StreamWrapper.__init__(self, out=stream, err=errStream) |
---|
611 | |
---|
612 | self._verbosity = verbosity |
---|
613 | self._showAll = verbosity > 1 |
---|
614 | self._dots = (verbosity == 1) |
---|
615 | self._explain = explain |
---|
616 | |
---|
617 | ## startup and shutdown methods |
---|
618 | |
---|
619 | def beginTests(self): |
---|
620 | self._startTime = time.time() |
---|
621 | |
---|
622 | def endTests(self): |
---|
623 | self._stopTime = time.time() |
---|
624 | self._timeTaken = float(self._stopTime - self._startTime) |
---|
625 | |
---|
626 | def stop(self): |
---|
627 | self.shouldStop = 1 |
---|
628 | |
---|
629 | ## methods called for each test |
---|
630 | |
---|
631 | def startTest(self, test): |
---|
632 | TestResult.startTest(self, test) |
---|
633 | if self._showAll: |
---|
634 | self.write("%s (%s)" %( test.id(), test.describe() ) ) |
---|
635 | self.write(" ... ") |
---|
636 | |
---|
637 | def addSuccess(self, test): |
---|
638 | TestResult.addSuccess(self, test) |
---|
639 | if self._showAll: |
---|
640 | self.writeln("ok") |
---|
641 | elif self._dots: |
---|
642 | self.write('.') |
---|
643 | |
---|
644 | def addError(self, test, err): |
---|
645 | TestResult.addError(self, test, err) |
---|
646 | if self._showAll: |
---|
647 | self.writeln("ERROR") |
---|
648 | elif self._dots: |
---|
649 | self.write('E') |
---|
650 | if err[0] is KeyboardInterrupt: |
---|
651 | self.stop() |
---|
652 | |
---|
653 | def addFailure(self, test, err): |
---|
654 | TestResult.addFailure(self, test, err) |
---|
655 | if self._showAll: |
---|
656 | self.writeln("FAIL") |
---|
657 | elif self._dots: |
---|
658 | self.write('F') |
---|
659 | |
---|
660 | ## display methods |
---|
661 | |
---|
662 | def summarize(self): |
---|
663 | self.printErrors() |
---|
664 | self.writeSep2() |
---|
665 | run = self.testsRun |
---|
666 | self.writeln("Ran %d test%s in %.3fs" % |
---|
667 | (run, run == 1 and "" or "s", self._timeTaken)) |
---|
668 | self.writeln() |
---|
669 | if not self.wasSuccessful(): |
---|
670 | self.writeErr("FAILED (") |
---|
671 | failed, errored = map(len, (self.failures, self.errors)) |
---|
672 | if failed: |
---|
673 | self.writeErr("failures=%d" % failed) |
---|
674 | if errored: |
---|
675 | if failed: self.writeErr(", ") |
---|
676 | self.writeErr("errors=%d" % errored) |
---|
677 | self.writelnErr(")") |
---|
678 | else: |
---|
679 | self.writelnErr("OK") |
---|
680 | |
---|
681 | def writeSep1(self): |
---|
682 | self.writeln(self._sep1 * self._separatorWidth) |
---|
683 | |
---|
684 | def writeSep2(self): |
---|
685 | self.writeln(self._sep2 * self._separatorWidth) |
---|
686 | |
---|
687 | def writeErrSep1(self): |
---|
688 | self.writeln(self._errorSep1 * self._separatorWidth) |
---|
689 | |
---|
690 | def writeErrSep2(self): |
---|
691 | self.writeln(self._errorSep2 * self._separatorWidth) |
---|
692 | |
---|
693 | def printErrors(self): |
---|
694 | if self._dots or self._showAll: |
---|
695 | self.writeln() |
---|
696 | self.printErrorList('ERROR', self.errors) |
---|
697 | self.printErrorList('FAIL', self.failures) |
---|
698 | |
---|
699 | def printErrorList(self, flavour, errors): |
---|
700 | for test, err in errors: |
---|
701 | self.writeErrSep1() |
---|
702 | self.writelnErr("%s %s (%s)" % (flavour, test.id(), test.describe() )) |
---|
703 | if self._explain: |
---|
704 | expln = test.explain() |
---|
705 | if expln: |
---|
706 | self.writeErrSep2() |
---|
707 | self.writeErr( expln ) |
---|
708 | self.writelnErr() |
---|
709 | |
---|
710 | self.writeErrSep2() |
---|
711 | for line in apply(traceback.format_exception, err): |
---|
712 | for l in line.split("\n")[:-1]: |
---|
713 | self.writelnErr(l) |
---|
714 | self.writelnErr("") |
---|
715 | |
---|
716 | class TextTestRunner: |
---|
717 | def __init__(self, |
---|
718 | stream=sys.stdout, |
---|
719 | errStream=sys.stderr, |
---|
720 | verbosity=1, |
---|
721 | explain=False): |
---|
722 | |
---|
723 | self._out = stream |
---|
724 | self._err = errStream |
---|
725 | self._verbosity = verbosity |
---|
726 | self._explain = explain |
---|
727 | |
---|
728 | ## main methods |
---|
729 | |
---|
730 | def run(self, test): |
---|
731 | result = self._makeResult() |
---|
732 | result.beginTests() |
---|
733 | test( result ) |
---|
734 | result.endTests() |
---|
735 | result.summarize() |
---|
736 | |
---|
737 | return result |
---|
738 | |
---|
739 | ## internal methods |
---|
740 | |
---|
741 | def _makeResult(self): |
---|
742 | return _TextTestResult(stream=self._out, |
---|
743 | errStream=self._err, |
---|
744 | verbosity=self._verbosity, |
---|
745 | explain=self._explain, |
---|
746 | ) |
---|
747 | |
---|
748 | ############################################################################## |
---|
749 | # Locating and loading tests |
---|
750 | ############################################################################## |
---|
751 | |
---|
752 | class TestLoader: |
---|
753 | """This class is responsible for loading tests according to various |
---|
754 | criteria and returning them wrapped in a Test |
---|
755 | """ |
---|
756 | testMethodPrefix = 'test' |
---|
757 | sortTestMethodsUsing = cmp |
---|
758 | suiteClass = TestSuite |
---|
759 | |
---|
760 | def loadTestsFromTestCase(self, testCaseClass): |
---|
761 | """Return a suite of all tests cases contained in testCaseClass""" |
---|
762 | return self.suiteClass(tests=map(testCaseClass, |
---|
763 | self.getTestCaseNames(testCaseClass)), |
---|
764 | suiteName=testCaseClass.__name__) |
---|
765 | |
---|
766 | def loadTestsFromModule(self, module): |
---|
767 | """Return a suite of all tests cases contained in the given module""" |
---|
768 | tests = [] |
---|
769 | for name in dir(module): |
---|
770 | obj = getattr(module, name) |
---|
771 | if type(obj) == types.ClassType and issubclass(obj, TestCase): |
---|
772 | tests.append(self.loadTestsFromTestCase(obj)) |
---|
773 | return self.suiteClass(tests) |
---|
774 | |
---|
775 | def loadTestsFromName(self, name, module=None): |
---|
776 | """Return a suite of all tests cases given a string specifier. |
---|
777 | |
---|
778 | The name may resolve either to a module, a test case class, a |
---|
779 | test method within a test case class, or a callable object which |
---|
780 | returns a TestCase or TestSuite instance. |
---|
781 | |
---|
782 | The method optionally resolves the names relative to a given module. |
---|
783 | """ |
---|
784 | parts = string.split(name, '.') |
---|
785 | if module is None: |
---|
786 | if not parts: |
---|
787 | raise ValueError, "incomplete test name: %s" % name |
---|
788 | else: |
---|
789 | parts_copy = parts[:] |
---|
790 | while parts_copy: |
---|
791 | try: |
---|
792 | module = __import__(string.join(parts_copy,'.')) |
---|
793 | break |
---|
794 | except ImportError: |
---|
795 | del parts_copy[-1] |
---|
796 | if not parts_copy: raise |
---|
797 | parts = parts[1:] |
---|
798 | obj = module |
---|
799 | for part in parts: |
---|
800 | if isinstance(obj, TestSuite): |
---|
801 | obj = obj.getTestForName(part) |
---|
802 | else: |
---|
803 | obj = getattr(obj, part) |
---|
804 | |
---|
805 | if type(obj) == types.ModuleType: |
---|
806 | return self.loadTestsFromModule(obj) |
---|
807 | elif type(obj) == types.ClassType and issubclass(obj, TestCase): |
---|
808 | return self.loadTestsFromTestCase(obj) |
---|
809 | elif type(obj) == types.UnboundMethodType: |
---|
810 | return obj.im_class(obj.__name__) |
---|
811 | elif isinstance(obj, TestSuite): |
---|
812 | return obj |
---|
813 | elif isinstance(obj, TestCase): |
---|
814 | return obj |
---|
815 | elif callable(obj): |
---|
816 | test = obj() |
---|
817 | if not isinstance(test, TestCase) and \ |
---|
818 | not isinstance(test, TestSuite): |
---|
819 | raise ValueError, \ |
---|
820 | "calling %s returned %s, not a test" %(obj,test) |
---|
821 | return test |
---|
822 | else: |
---|
823 | raise ValueError, "don't know how to make test from: %s" % obj |
---|
824 | |
---|
825 | def loadTestsFromNames(self, names, module=None): |
---|
826 | """Return a suite of all tests cases found using the given sequence |
---|
827 | of string specifiers. See 'loadTestsFromName()'. |
---|
828 | """ |
---|
829 | suites = [] |
---|
830 | for name in names: |
---|
831 | suites.append(self.loadTestsFromName(name, module)) |
---|
832 | return self.suiteClass(suites) |
---|
833 | |
---|
834 | def getTestCaseNames(self, testCaseClass): |
---|
835 | """Return a sorted sequence of method names found within testCaseClass. |
---|
836 | """ |
---|
837 | testFnNames = [fn for fn in dir(testCaseClass) if fn.startswith(self.testMethodPrefix)] |
---|
838 | if hasattr(testCaseClass, 'runTest'): |
---|
839 | testFnNames.append('runTest') |
---|
840 | for baseclass in testCaseClass.__bases__: |
---|
841 | for testFnName in self.getTestCaseNames(baseclass): |
---|
842 | if testFnName not in testFnNames: # handle overridden methods |
---|
843 | testFnNames.append(testFnName) |
---|
844 | if self.sortTestMethodsUsing: |
---|
845 | testFnNames.sort(self.sortTestMethodsUsing) |
---|
846 | return testFnNames |
---|
847 | |
---|
848 | |
---|
849 | |
---|
850 | defaultTestLoader = TestLoader() |
---|
851 | |
---|
852 | |
---|
853 | ############################################################################## |
---|
854 | # Patches for old functions: these functions should be considered obsolete |
---|
855 | ############################################################################## |
---|
856 | |
---|
857 | def _makeLoader(prefix, sortUsing, suiteClass=None): |
---|
858 | loader = TestLoader() |
---|
859 | loader.sortTestMethodsUsing = sortUsing |
---|
860 | loader.testMethodPrefix = prefix |
---|
861 | if suiteClass: loader.suiteClass = suiteClass |
---|
862 | return loader |
---|
863 | |
---|
864 | def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): |
---|
865 | return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) |
---|
866 | |
---|
867 | def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite): |
---|
868 | return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) |
---|
869 | |
---|
870 | def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite): |
---|
871 | return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) |
---|
872 | |
---|
873 | ############################################################################## |
---|
874 | # Facilities for running tests from the command line |
---|
875 | ############################################################################## |
---|
876 | |
---|
877 | class TestProgram: |
---|
878 | """A command-line program that runs a set of tests; this is primarily |
---|
879 | for making test modules conveniently executable. |
---|
880 | """ |
---|
881 | USAGE = """\ |
---|
882 | Usage: %(progName)s [options] [test] [...] |
---|
883 | |
---|
884 | Options: |
---|
885 | -h, --help Show this message |
---|
886 | -v, --verbose Verbose output |
---|
887 | -q, --quiet Minimal output |
---|
888 | -e, --expain Output extra test details if there is a failure or error |
---|
889 | |
---|
890 | Examples: |
---|
891 | %(progName)s - run default set of tests |
---|
892 | %(progName)s MyTestSuite - run suite 'MyTestSuite' |
---|
893 | %(progName)s MyTestSuite.MyTestCase - run suite 'MyTestSuite' |
---|
894 | %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething |
---|
895 | %(progName)s MyTestCase - run all 'test*' test methods |
---|
896 | in MyTestCase |
---|
897 | """ |
---|
898 | def __init__(self, module='__main__', defaultTest=None, |
---|
899 | argv=None, testRunner=None, testLoader=defaultTestLoader, |
---|
900 | testSuite=None): |
---|
901 | if type(module) == type(''): |
---|
902 | self.module = __import__(module) |
---|
903 | for part in string.split(module,'.')[1:]: |
---|
904 | self.module = getattr(self.module, part) |
---|
905 | else: |
---|
906 | self.module = module |
---|
907 | if argv is None: |
---|
908 | argv = sys.argv |
---|
909 | self.test = testSuite |
---|
910 | self.verbosity = 1 |
---|
911 | self.explain = 0 |
---|
912 | self.defaultTest = defaultTest |
---|
913 | self.testRunner = testRunner |
---|
914 | self.testLoader = testLoader |
---|
915 | self.progName = os.path.basename(argv[0]) |
---|
916 | self.parseArgs(argv) |
---|
917 | self.runTests() |
---|
918 | |
---|
919 | def usageExit(self, msg=None): |
---|
920 | if msg: print msg |
---|
921 | print self.USAGE % self.__dict__ |
---|
922 | sys.exit(2) |
---|
923 | |
---|
924 | def parseArgs(self, argv): |
---|
925 | import getopt |
---|
926 | try: |
---|
927 | options, args = getopt.getopt(argv[1:], 'hHvqer', |
---|
928 | ['help','verbose','quiet','explain', 'raise']) |
---|
929 | for opt, value in options: |
---|
930 | if opt in ('-h','-H','--help'): |
---|
931 | self.usageExit() |
---|
932 | if opt in ('-q','--quiet'): |
---|
933 | self.verbosity = 0 |
---|
934 | if opt in ('-v','--verbose'): |
---|
935 | self.verbosity = 2 |
---|
936 | if opt in ('-e','--explain'): |
---|
937 | self.explain = True |
---|
938 | if len(args) == 0 and self.defaultTest is None and self.test is None: |
---|
939 | self.test = self.testLoader.loadTestsFromModule(self.module) |
---|
940 | return |
---|
941 | if len(args) > 0: |
---|
942 | self.testNames = args |
---|
943 | else: |
---|
944 | self.testNames = (self.defaultTest,) |
---|
945 | self.createTests() |
---|
946 | except getopt.error, msg: |
---|
947 | self.usageExit(msg) |
---|
948 | |
---|
949 | def createTests(self): |
---|
950 | if self.test == None: |
---|
951 | self.test = self.testLoader.loadTestsFromNames(self.testNames, |
---|
952 | self.module) |
---|
953 | |
---|
954 | def runTests(self): |
---|
955 | if self.testRunner is None: |
---|
956 | self.testRunner = TextTestRunner(verbosity=self.verbosity, |
---|
957 | explain=self.explain) |
---|
958 | result = self.testRunner.run(self.test) |
---|
959 | self._cleanupAfterRunningTests() |
---|
960 | sys.exit(not result.wasSuccessful()) |
---|
961 | |
---|
962 | def _cleanupAfterRunningTests(self): |
---|
963 | """A hook method that is called immediately prior to calling |
---|
964 | sys.exit(not result.wasSuccessful()) in self.runTests(). |
---|
965 | """ |
---|
966 | pass |
---|
967 | |
---|
968 | main = TestProgram |
---|
969 | |
---|
970 | |
---|
971 | ############################################################################## |
---|
972 | # Executing this module from the command line |
---|
973 | ############################################################################## |
---|
974 | |
---|
975 | if __name__ == "__main__": |
---|
976 | main(module=None) |
---|
977 | |
---|
978 | # vim: shiftwidth=4 tabstop=4 expandtab |
---|