| 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 | 
|---|