| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.trial.test.test_runner -*- | |
| 2 | |
| 3 # | |
| 4 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
| 5 # See LICENSE for details. | |
| 6 | |
| 7 """ | |
| 8 A miscellany of code used to run Trial tests. | |
| 9 | |
| 10 Maintainer: Jonathan Lange <jml@twistedmatrix.com> | |
| 11 """ | |
| 12 | |
| 13 | |
| 14 import pdb, shutil | |
| 15 import os, types, warnings, sys, inspect, imp | |
| 16 import random, doctest, time | |
| 17 | |
| 18 from twisted.python import reflect, log, failure, modules | |
| 19 from twisted.python.util import dsu | |
| 20 from twisted.python.compat import set | |
| 21 | |
| 22 from twisted.internet import defer, interfaces | |
| 23 from twisted.trial import util, unittest | |
| 24 from twisted.trial.itrial import ITestCase | |
| 25 from twisted.trial.reporter import UncleanWarningsReporterWrapper | |
| 26 | |
| 27 # These are imported so that they remain in the public API for t.trial.runner | |
| 28 from twisted.trial.unittest import suiteVisit, TestSuite | |
| 29 | |
| 30 from zope.interface import implements | |
| 31 | |
| 32 pyunit = __import__('unittest') | |
| 33 | |
| 34 | |
| 35 def isPackage(module): | |
| 36 """Given an object return True if the object looks like a package""" | |
| 37 if not isinstance(module, types.ModuleType): | |
| 38 return False | |
| 39 basename = os.path.splitext(os.path.basename(module.__file__))[0] | |
| 40 return basename == '__init__' | |
| 41 | |
| 42 | |
| 43 def isPackageDirectory(dirname): | |
| 44 """Is the directory at path 'dirname' a Python package directory? | |
| 45 Returns the name of the __init__ file (it may have a weird extension) | |
| 46 if dirname is a package directory. Otherwise, returns False""" | |
| 47 for ext in zip(*imp.get_suffixes())[0]: | |
| 48 initFile = '__init__' + ext | |
| 49 if os.path.exists(os.path.join(dirname, initFile)): | |
| 50 return initFile | |
| 51 return False | |
| 52 | |
| 53 | |
| 54 def samefile(filename1, filename2): | |
| 55 """ | |
| 56 A hacky implementation of C{os.path.samefile}. Used by L{filenameToModule} | |
| 57 when the platform doesn't provide C{os.path.samefile}. Do not use this. | |
| 58 """ | |
| 59 return os.path.abspath(filename1) == os.path.abspath(filename2) | |
| 60 | |
| 61 def filenameToModule(fn): | |
| 62 """ | |
| 63 Given a filename, do whatever possible to return a module object matching | |
| 64 that file. | |
| 65 | |
| 66 If the file in question is a module in Python path, properly import and | |
| 67 return that module. Otherwise, load the source manually. | |
| 68 | |
| 69 @param fn: A filename. | |
| 70 @return: A module object. | |
| 71 @raise ValueError: If C{fn} does not exist. | |
| 72 """ | |
| 73 if not os.path.exists(fn): | |
| 74 raise ValueError("%r doesn't exist" % (fn,)) | |
| 75 try: | |
| 76 ret = reflect.namedAny(reflect.filenameToModuleName(fn)) | |
| 77 except (ValueError, AttributeError): | |
| 78 # Couldn't find module. The file 'fn' is not in PYTHONPATH | |
| 79 return _importFromFile(fn) | |
| 80 # ensure that the loaded module matches the file | |
| 81 retFile = os.path.splitext(ret.__file__)[0] + '.py' | |
| 82 # not all platforms (e.g. win32) have os.path.samefile | |
| 83 same = getattr(os.path, 'samefile', samefile) | |
| 84 if os.path.isfile(fn) and not same(fn, retFile): | |
| 85 del sys.modules[ret.__name__] | |
| 86 ret = _importFromFile(fn) | |
| 87 return ret | |
| 88 | |
| 89 | |
| 90 def _importFromFile(fn, moduleName=None): | |
| 91 fn = _resolveDirectory(fn) | |
| 92 if not moduleName: | |
| 93 moduleName = os.path.splitext(os.path.split(fn)[-1])[0] | |
| 94 if moduleName in sys.modules: | |
| 95 return sys.modules[moduleName] | |
| 96 fd = open(fn, 'r') | |
| 97 try: | |
| 98 module = imp.load_source(moduleName, fn, fd) | |
| 99 finally: | |
| 100 fd.close() | |
| 101 return module | |
| 102 | |
| 103 | |
| 104 def _resolveDirectory(fn): | |
| 105 if os.path.isdir(fn): | |
| 106 initFile = isPackageDirectory(fn) | |
| 107 if initFile: | |
| 108 fn = os.path.join(fn, initFile) | |
| 109 else: | |
| 110 raise ValueError('%r is not a package directory' % (fn,)) | |
| 111 return fn | |
| 112 | |
| 113 | |
| 114 | |
| 115 class DestructiveTestSuite(TestSuite): | |
| 116 """ | |
| 117 A test suite which remove the tests once run, to minimize memory usage. | |
| 118 """ | |
| 119 | |
| 120 def run(self, result): | |
| 121 """ | |
| 122 Almost the same as L{TestSuite.run}, but with C{self._tests} being | |
| 123 empty at the end. | |
| 124 """ | |
| 125 while self._tests: | |
| 126 if result.shouldStop: | |
| 127 break | |
| 128 test = self._tests.pop(0) | |
| 129 test(result) | |
| 130 return result | |
| 131 | |
| 132 | |
| 133 | |
| 134 # When an error occurs outside of any test, the user will see this string | |
| 135 # in place of a test's name. | |
| 136 NOT_IN_TEST = "<not in test>" | |
| 137 | |
| 138 | |
| 139 | |
| 140 class LoggedSuite(TestSuite): | |
| 141 """ | |
| 142 Any errors logged in this suite will be reported to the L{TestResult} | |
| 143 object. | |
| 144 """ | |
| 145 | |
| 146 def run(self, result): | |
| 147 """ | |
| 148 Run the suite, storing all errors in C{result}. If an error is logged | |
| 149 while no tests are running, then it will be added as an error to | |
| 150 C{result}. | |
| 151 | |
| 152 @param result: A L{TestResult} object. | |
| 153 """ | |
| 154 observer = unittest._logObserver | |
| 155 observer._add() | |
| 156 super(LoggedSuite, self).run(result) | |
| 157 observer._remove() | |
| 158 for error in observer.getErrors(): | |
| 159 result.addError(TestHolder(NOT_IN_TEST), error) | |
| 160 observer.flushErrors() | |
| 161 | |
| 162 | |
| 163 | |
| 164 class DocTestSuite(TestSuite): | |
| 165 """ | |
| 166 DEPRECATED in Twisted 8.0. | |
| 167 | |
| 168 Behaves like doctest.DocTestSuite, but decorates individual TestCases so | |
| 169 they support visit and so that id() behaviour is meaningful and consistent | |
| 170 between Python versions. | |
| 171 """ | |
| 172 | |
| 173 def __init__(self, testModule): | |
| 174 warnings.warn("DocTestSuite is deprecated in Twisted 8.0.", | |
| 175 category=DeprecationWarning, stacklevel=3) | |
| 176 TestSuite.__init__(self) | |
| 177 suite = doctest.DocTestSuite(testModule) | |
| 178 for test in suite._tests: #yay encapsulation | |
| 179 self.addTest(ITestCase(test)) | |
| 180 | |
| 181 | |
| 182 | |
| 183 class PyUnitTestCase(object): | |
| 184 """ | |
| 185 DEPRECATED in Twisted 8.0. | |
| 186 | |
| 187 This class decorates the pyunit.TestCase class, mainly to work around the | |
| 188 differences between unittest in Python 2.3, 2.4, and 2.5. These | |
| 189 differences are:: | |
| 190 | |
| 191 - The way doctest unittests describe themselves | |
| 192 - Where the implementation of TestCase.run is (used to be in __call__) | |
| 193 - Where the test method name is kept (mangled-private or non-mangled | |
| 194 private variable) | |
| 195 | |
| 196 It also implements visit, which we like. | |
| 197 """ | |
| 198 | |
| 199 def __init__(self, test): | |
| 200 warnings.warn("Deprecated in Twisted 8.0.", | |
| 201 category=DeprecationWarning) | |
| 202 self._test = test | |
| 203 test.id = self.id | |
| 204 | |
| 205 def id(self): | |
| 206 cls = self._test.__class__ | |
| 207 tmn = getattr(self._test, '_TestCase__testMethodName', None) | |
| 208 if tmn is None: | |
| 209 # python2.5's 'unittest' module is more sensible; but different. | |
| 210 tmn = self._test._testMethodName | |
| 211 return (cls.__module__ + '.' + cls.__name__ + '.' + | |
| 212 tmn) | |
| 213 | |
| 214 def __repr__(self): | |
| 215 return 'PyUnitTestCase<%r>'%(self.id(),) | |
| 216 | |
| 217 def __call__(self, results): | |
| 218 return self._test(results) | |
| 219 | |
| 220 | |
| 221 def visit(self, visitor): | |
| 222 """ | |
| 223 Call the given visitor with the original, standard library, test case | |
| 224 that C{self} wraps. See L{unittest.TestCase.visit}. | |
| 225 | |
| 226 Deprecated in Twisted 8.0. | |
| 227 """ | |
| 228 warnings.warn("Test visitors deprecated in Twisted 8.0", | |
| 229 category=DeprecationWarning) | |
| 230 visitor(self._test) | |
| 231 | |
| 232 | |
| 233 def __getattr__(self, name): | |
| 234 return getattr(self._test, name) | |
| 235 | |
| 236 | |
| 237 | |
| 238 class DocTestCase(PyUnitTestCase): | |
| 239 """ | |
| 240 DEPRECATED in Twisted 8.0. | |
| 241 """ | |
| 242 | |
| 243 def id(self): | |
| 244 """ | |
| 245 In Python 2.4, doctests have correct id() behaviour. In Python 2.3, | |
| 246 id() returns 'runit'. | |
| 247 | |
| 248 Here we override id() so that at least it will always contain the | |
| 249 fully qualified Python name of the doctest. | |
| 250 """ | |
| 251 return self._test.shortDescription() | |
| 252 | |
| 253 | |
| 254 class TrialSuite(TestSuite): | |
| 255 """ | |
| 256 Suite to wrap around every single test in a C{trial} run. Used internally | |
| 257 by Trial to set up things necessary for Trial tests to work, regardless of | |
| 258 what context they are run in. | |
| 259 """ | |
| 260 | |
| 261 def __init__(self, tests=()): | |
| 262 suite = LoggedSuite(tests) | |
| 263 super(TrialSuite, self).__init__([suite]) | |
| 264 | |
| 265 | |
| 266 def _bail(self): | |
| 267 from twisted.internet import reactor | |
| 268 d = defer.Deferred() | |
| 269 reactor.addSystemEventTrigger('after', 'shutdown', | |
| 270 lambda: d.callback(None)) | |
| 271 reactor.fireSystemEvent('shutdown') # radix's suggestion | |
| 272 treactor = interfaces.IReactorThreads(reactor, None) | |
| 273 if treactor is not None: | |
| 274 treactor.suggestThreadPoolSize(0) | |
| 275 # As long as TestCase does crap stuff with the reactor we need to | |
| 276 # manually shutdown the reactor here, and that requires util.wait | |
| 277 # :( | |
| 278 # so that the shutdown event completes | |
| 279 unittest.TestCase('mktemp')._wait(d) | |
| 280 | |
| 281 def run(self, result): | |
| 282 try: | |
| 283 TestSuite.run(self, result) | |
| 284 finally: | |
| 285 self._bail() | |
| 286 | |
| 287 | |
| 288 def name(thing): | |
| 289 """ | |
| 290 @param thing: an object from modules (instance of PythonModule, | |
| 291 PythonAttribute), a TestCase subclass, or an instance of a TestCase. | |
| 292 """ | |
| 293 if isTestCase(thing): | |
| 294 # TestCase subclass | |
| 295 theName = reflect.qual(thing) | |
| 296 else: | |
| 297 # thing from trial, or thing from modules. | |
| 298 # this monstrosity exists so that modules' objects do not have to | |
| 299 # implement id(). -jml | |
| 300 try: | |
| 301 theName = thing.id() | |
| 302 except AttributeError: | |
| 303 theName = thing.name | |
| 304 return theName | |
| 305 | |
| 306 | |
| 307 def isTestCase(obj): | |
| 308 """ | |
| 309 Returns C{True} if C{obj} is a class that contains test cases, C{False} | |
| 310 otherwise. Used to find all the tests in a module. | |
| 311 """ | |
| 312 try: | |
| 313 return issubclass(obj, pyunit.TestCase) | |
| 314 except TypeError: | |
| 315 return False | |
| 316 | |
| 317 | |
| 318 | |
| 319 class TestHolder(object): | |
| 320 """ | |
| 321 Placeholder for a L{TestCase} inside a reporter. As far as a L{TestResult} | |
| 322 is concerned, this looks exactly like a unit test. | |
| 323 """ | |
| 324 | |
| 325 implements(ITestCase) | |
| 326 | |
| 327 def __init__(self, description): | |
| 328 """ | |
| 329 @param description: A string to be displayed L{TestResult}. | |
| 330 """ | |
| 331 self.description = description | |
| 332 | |
| 333 | |
| 334 def id(self): | |
| 335 return self.description | |
| 336 | |
| 337 | |
| 338 def shortDescription(self): | |
| 339 return self.description | |
| 340 | |
| 341 | |
| 342 | |
| 343 class ErrorHolder(TestHolder): | |
| 344 """ | |
| 345 Used to insert arbitrary errors into a test suite run. Provides enough | |
| 346 methods to look like a C{TestCase}, however, when it is run, it simply adds | |
| 347 an error to the C{TestResult}. The most common use-case is for when a | |
| 348 module fails to import. | |
| 349 """ | |
| 350 | |
| 351 def __init__(self, description, error): | |
| 352 """ | |
| 353 @param description: A string used by C{TestResult}s to identify this | |
| 354 error. Generally, this is the name of a module that failed to import. | |
| 355 | |
| 356 @param error: The error to be added to the result. Can be an exc_info | |
| 357 tuple or a L{twisted.python.failure.Failure}. | |
| 358 """ | |
| 359 super(ErrorHolder, self).__init__(description) | |
| 360 self.error = error | |
| 361 | |
| 362 | |
| 363 def __repr__(self): | |
| 364 return "<ErrorHolder description=%r error=%r>" % (self.description, | |
| 365 self.error) | |
| 366 | |
| 367 | |
| 368 def run(self, result): | |
| 369 result.addError(self, self.error) | |
| 370 | |
| 371 | |
| 372 def __call__(self, result): | |
| 373 return self.run(result) | |
| 374 | |
| 375 | |
| 376 def countTestCases(self): | |
| 377 return 0 | |
| 378 | |
| 379 | |
| 380 def visit(self, visitor): | |
| 381 """ | |
| 382 See L{unittest.TestCase.visit}. | |
| 383 """ | |
| 384 visitor(self) | |
| 385 | |
| 386 | |
| 387 | |
| 388 class TestLoader(object): | |
| 389 """ | |
| 390 I find tests inside function, modules, files -- whatever -- then return | |
| 391 them wrapped inside a Test (either a L{TestSuite} or a L{TestCase}). | |
| 392 | |
| 393 @ivar methodPrefix: A string prefix. C{TestLoader} will assume that all the | |
| 394 methods in a class that begin with C{methodPrefix} are test cases. | |
| 395 | |
| 396 @ivar modulePrefix: A string prefix. Every module in a package that begins | |
| 397 with C{modulePrefix} is considered a module full of tests. | |
| 398 | |
| 399 @ivar forceGarbageCollection: A flag applied to each C{TestCase} loaded. | |
| 400 See L{unittest.TestCase} for more information. | |
| 401 | |
| 402 @ivar sorter: A key function used to sort C{TestCase}s, test classes, | |
| 403 modules and packages. | |
| 404 | |
| 405 @ivar suiteFactory: A callable which is passed a list of tests (which | |
| 406 themselves may be suites of tests). Must return a test suite. | |
| 407 """ | |
| 408 | |
| 409 methodPrefix = 'test' | |
| 410 modulePrefix = 'test_' | |
| 411 | |
| 412 def __init__(self): | |
| 413 self.suiteFactory = TestSuite | |
| 414 self.sorter = name | |
| 415 self._importErrors = [] | |
| 416 | |
| 417 def sort(self, xs): | |
| 418 """ | |
| 419 Sort the given things using L{sorter}. | |
| 420 | |
| 421 @param xs: A list of test cases, class or modules. | |
| 422 """ | |
| 423 return dsu(xs, self.sorter) | |
| 424 | |
| 425 def findTestClasses(self, module): | |
| 426 """Given a module, return all Trial test classes""" | |
| 427 classes = [] | |
| 428 for name, val in inspect.getmembers(module): | |
| 429 if isTestCase(val): | |
| 430 classes.append(val) | |
| 431 return self.sort(classes) | |
| 432 | |
| 433 def findByName(self, name): | |
| 434 """ | |
| 435 Return a Python object given a string describing it. | |
| 436 | |
| 437 @param name: a string which may be either a filename or a | |
| 438 fully-qualified Python name. | |
| 439 | |
| 440 @return: If C{name} is a filename, return the module. If C{name} is a | |
| 441 fully-qualified Python name, return the object it refers to. | |
| 442 """ | |
| 443 if os.path.exists(name): | |
| 444 return filenameToModule(name) | |
| 445 return reflect.namedAny(name) | |
| 446 | |
| 447 def loadModule(self, module): | |
| 448 """ | |
| 449 Return a test suite with all the tests from a module. | |
| 450 | |
| 451 Included are TestCase subclasses and doctests listed in the module's | |
| 452 __doctests__ module. If that's not good for you, put a function named | |
| 453 either C{testSuite} or C{test_suite} in your module that returns a | |
| 454 TestSuite, and I'll use the results of that instead. | |
| 455 | |
| 456 If C{testSuite} and C{test_suite} are both present, then I'll use | |
| 457 C{testSuite}. | |
| 458 """ | |
| 459 ## XXX - should I add an optional parameter to disable the check for | |
| 460 ## a custom suite. | |
| 461 ## OR, should I add another method | |
| 462 if not isinstance(module, types.ModuleType): | |
| 463 raise TypeError("%r is not a module" % (module,)) | |
| 464 if hasattr(module, 'testSuite'): | |
| 465 return module.testSuite() | |
| 466 elif hasattr(module, 'test_suite'): | |
| 467 return module.test_suite() | |
| 468 suite = self.suiteFactory() | |
| 469 for testClass in self.findTestClasses(module): | |
| 470 suite.addTest(self.loadClass(testClass)) | |
| 471 if not hasattr(module, '__doctests__'): | |
| 472 return suite | |
| 473 docSuite = self.suiteFactory() | |
| 474 for doctest in module.__doctests__: | |
| 475 docSuite.addTest(self.loadDoctests(doctest)) | |
| 476 return self.suiteFactory([suite, docSuite]) | |
| 477 loadTestsFromModule = loadModule | |
| 478 | |
| 479 def loadClass(self, klass): | |
| 480 """ | |
| 481 Given a class which contains test cases, return a sorted list of | |
| 482 C{TestCase} instances. | |
| 483 """ | |
| 484 if not (isinstance(klass, type) or isinstance(klass, types.ClassType)): | |
| 485 raise TypeError("%r is not a class" % (klass,)) | |
| 486 if not isTestCase(klass): | |
| 487 raise ValueError("%r is not a test case" % (klass,)) | |
| 488 names = self.getTestCaseNames(klass) | |
| 489 tests = self.sort([self._makeCase(klass, self.methodPrefix+name) | |
| 490 for name in names]) | |
| 491 return self.suiteFactory(tests) | |
| 492 loadTestsFromTestCase = loadClass | |
| 493 | |
| 494 def getTestCaseNames(self, klass): | |
| 495 """ | |
| 496 Given a class that contains C{TestCase}s, return a list of names of | |
| 497 methods that probably contain tests. | |
| 498 """ | |
| 499 return reflect.prefixedMethodNames(klass, self.methodPrefix) | |
| 500 | |
| 501 def loadMethod(self, method): | |
| 502 """ | |
| 503 Given a method of a C{TestCase} that represents a test, return a | |
| 504 C{TestCase} instance for that test. | |
| 505 """ | |
| 506 if not isinstance(method, types.MethodType): | |
| 507 raise TypeError("%r not a method" % (method,)) | |
| 508 return self._makeCase(method.im_class, method.__name__) | |
| 509 | |
| 510 def _makeCase(self, klass, methodName): | |
| 511 return klass(methodName) | |
| 512 | |
| 513 def loadPackage(self, package, recurse=False): | |
| 514 """ | |
| 515 Load tests from a module object representing a package, and return a | |
| 516 TestSuite containing those tests. | |
| 517 | |
| 518 Tests are only loaded from modules whose name begins with 'test_' | |
| 519 (or whatever C{modulePrefix} is set to). | |
| 520 | |
| 521 @param package: a types.ModuleType object (or reasonable facsimilie | |
| 522 obtained by importing) which may contain tests. | |
| 523 | |
| 524 @param recurse: A boolean. If True, inspect modules within packages | |
| 525 within the given package (and so on), otherwise, only inspect modules | |
| 526 in the package itself. | |
| 527 | |
| 528 @raise: TypeError if 'package' is not a package. | |
| 529 | |
| 530 @return: a TestSuite created with my suiteFactory, containing all the | |
| 531 tests. | |
| 532 """ | |
| 533 if not isPackage(package): | |
| 534 raise TypeError("%r is not a package" % (package,)) | |
| 535 pkgobj = modules.getModule(package.__name__) | |
| 536 if recurse: | |
| 537 discovery = pkgobj.walkModules() | |
| 538 else: | |
| 539 discovery = pkgobj.iterModules() | |
| 540 discovered = [] | |
| 541 for disco in discovery: | |
| 542 if disco.name.split(".")[-1].startswith(self.modulePrefix): | |
| 543 discovered.append(disco) | |
| 544 suite = self.suiteFactory() | |
| 545 for modinfo in self.sort(discovered): | |
| 546 try: | |
| 547 module = modinfo.load() | |
| 548 except: | |
| 549 thingToAdd = ErrorHolder(modinfo.name, failure.Failure()) | |
| 550 else: | |
| 551 thingToAdd = self.loadModule(module) | |
| 552 suite.addTest(thingToAdd) | |
| 553 return suite | |
| 554 | |
| 555 def loadDoctests(self, module): | |
| 556 """ | |
| 557 Return a suite of tests for all the doctests defined in C{module}. | |
| 558 | |
| 559 @param module: A module object or a module name. | |
| 560 """ | |
| 561 if isinstance(module, str): | |
| 562 try: | |
| 563 module = reflect.namedAny(module) | |
| 564 except: | |
| 565 return ErrorHolder(module, failure.Failure()) | |
| 566 if not inspect.ismodule(module): | |
| 567 warnings.warn("trial only supports doctesting modules") | |
| 568 return | |
| 569 return doctest.DocTestSuite(module) | |
| 570 | |
| 571 def loadAnything(self, thing, recurse=False): | |
| 572 """ | |
| 573 Given a Python object, return whatever tests that are in it. Whatever | |
| 574 'in' might mean. | |
| 575 | |
| 576 @param thing: A Python object. A module, method, class or package. | |
| 577 @param recurse: Whether or not to look in subpackages of packages. | |
| 578 Defaults to False. | |
| 579 | |
| 580 @return: A C{TestCase} or C{TestSuite}. | |
| 581 """ | |
| 582 if isinstance(thing, types.ModuleType): | |
| 583 if isPackage(thing): | |
| 584 return self.loadPackage(thing, recurse) | |
| 585 return self.loadModule(thing) | |
| 586 elif isinstance(thing, types.ClassType): | |
| 587 return self.loadClass(thing) | |
| 588 elif isinstance(thing, type): | |
| 589 return self.loadClass(thing) | |
| 590 elif isinstance(thing, types.MethodType): | |
| 591 return self.loadMethod(thing) | |
| 592 raise TypeError("No loader for %r. Unrecognized type" % (thing,)) | |
| 593 | |
| 594 def loadByName(self, name, recurse=False): | |
| 595 """ | |
| 596 Given a string representing a Python object, return whatever tests | |
| 597 are in that object. | |
| 598 | |
| 599 If C{name} is somehow inaccessible (e.g. the module can't be imported, | |
| 600 there is no Python object with that name etc) then return an | |
| 601 L{ErrorHolder}. | |
| 602 | |
| 603 @param name: The fully-qualified name of a Python object. | |
| 604 """ | |
| 605 try: | |
| 606 thing = self.findByName(name) | |
| 607 except: | |
| 608 return ErrorHolder(name, failure.Failure()) | |
| 609 return self.loadAnything(thing, recurse) | |
| 610 loadTestsFromName = loadByName | |
| 611 | |
| 612 def loadByNames(self, names, recurse=False): | |
| 613 """ | |
| 614 Construct a TestSuite containing all the tests found in 'names', where | |
| 615 names is a list of fully qualified python names and/or filenames. The | |
| 616 suite returned will have no duplicate tests, even if the same object | |
| 617 is named twice. | |
| 618 """ | |
| 619 things = [] | |
| 620 errors = [] | |
| 621 for name in names: | |
| 622 try: | |
| 623 things.append(self.findByName(name)) | |
| 624 except: | |
| 625 errors.append(ErrorHolder(name, failure.Failure())) | |
| 626 suites = [self.loadAnything(thing, recurse) | |
| 627 for thing in set(things)] | |
| 628 suites.extend(errors) | |
| 629 return self.suiteFactory(suites) | |
| 630 | |
| 631 | |
| 632 | |
| 633 class DryRunVisitor(object): | |
| 634 """ | |
| 635 A visitor that makes a reporter think that every test visited has run | |
| 636 successfully. | |
| 637 """ | |
| 638 | |
| 639 def __init__(self, reporter): | |
| 640 """ | |
| 641 @param reporter: A C{TestResult} object. | |
| 642 """ | |
| 643 self.reporter = reporter | |
| 644 | |
| 645 | |
| 646 def markSuccessful(self, testCase): | |
| 647 """ | |
| 648 Convince the reporter that this test has been run successfully. | |
| 649 """ | |
| 650 self.reporter.startTest(testCase) | |
| 651 self.reporter.addSuccess(testCase) | |
| 652 self.reporter.stopTest(testCase) | |
| 653 | |
| 654 | |
| 655 | |
| 656 class TrialRunner(object): | |
| 657 """ | |
| 658 A specialised runner that the trial front end uses. | |
| 659 """ | |
| 660 | |
| 661 DEBUG = 'debug' | |
| 662 DRY_RUN = 'dry-run' | |
| 663 | |
| 664 def _getDebugger(self): | |
| 665 dbg = pdb.Pdb() | |
| 666 try: | |
| 667 import readline | |
| 668 except ImportError: | |
| 669 print "readline module not available" | |
| 670 hasattr(sys, 'exc_clear') and sys.exc_clear() | |
| 671 for path in ('.pdbrc', 'pdbrc'): | |
| 672 if os.path.exists(path): | |
| 673 try: | |
| 674 rcFile = file(path, 'r') | |
| 675 except IOError: | |
| 676 hasattr(sys, 'exc_clear') and sys.exc_clear() | |
| 677 else: | |
| 678 dbg.rcLines.extend(rcFile.readlines()) | |
| 679 return dbg | |
| 680 | |
| 681 def _setUpTestdir(self): | |
| 682 self._tearDownLogFile() | |
| 683 currentDir = os.getcwd() | |
| 684 testdir = os.path.normpath(os.path.abspath(self.workingDirectory)) | |
| 685 if os.path.exists(testdir): | |
| 686 try: | |
| 687 shutil.rmtree(testdir) | |
| 688 except OSError, e: | |
| 689 print ("could not remove %r, caught OSError [Errno %s]: %s" | |
| 690 % (testdir, e.errno,e.strerror)) | |
| 691 try: | |
| 692 os.rename(testdir, | |
| 693 os.path.abspath("_trial_temp_old%s" | |
| 694 % random.randint(0, 99999999))) | |
| 695 except OSError, e: | |
| 696 print ("could not rename path, caught OSError [Errno %s]: %s" | |
| 697 % (e.errno,e.strerror)) | |
| 698 raise | |
| 699 os.mkdir(testdir) | |
| 700 os.chdir(testdir) | |
| 701 return currentDir | |
| 702 | |
| 703 def _makeResult(self): | |
| 704 reporter = self.reporterFactory(self.stream, self.tbformat, | |
| 705 self.rterrors) | |
| 706 if self.uncleanWarnings: | |
| 707 reporter = UncleanWarningsReporterWrapper(reporter) | |
| 708 return reporter | |
| 709 | |
| 710 def __init__(self, reporterFactory, | |
| 711 mode=None, | |
| 712 logfile='test.log', | |
| 713 stream=sys.stdout, | |
| 714 profile=False, | |
| 715 tracebackFormat='default', | |
| 716 realTimeErrors=False, | |
| 717 uncleanWarnings=False, | |
| 718 workingDirectory=None, | |
| 719 forceGarbageCollection=False): | |
| 720 self.reporterFactory = reporterFactory | |
| 721 self.logfile = logfile | |
| 722 self.mode = mode | |
| 723 self.stream = stream | |
| 724 self.tbformat = tracebackFormat | |
| 725 self.rterrors = realTimeErrors | |
| 726 self.uncleanWarnings = uncleanWarnings | |
| 727 self._result = None | |
| 728 self.workingDirectory = workingDirectory or '_trial_temp' | |
| 729 self._logFileObserver = None | |
| 730 self._logFileObject = None | |
| 731 self._logWarnings = False | |
| 732 self._forceGarbageCollection = forceGarbageCollection | |
| 733 if profile: | |
| 734 self.run = util.profiled(self.run, 'profile.data') | |
| 735 | |
| 736 def _setUpLogging(self): | |
| 737 self._setUpLogFile() | |
| 738 self._setUpLogWarnings() | |
| 739 | |
| 740 def _tearDownLogFile(self): | |
| 741 if self._logFileObserver is not None: | |
| 742 log.removeObserver(self._logFileObserver.emit) | |
| 743 self._logFileObserver = None | |
| 744 if self._logFileObject is not None: | |
| 745 self._logFileObject.close() | |
| 746 self._logFileObject = None | |
| 747 | |
| 748 def _setUpLogFile(self): | |
| 749 self._tearDownLogFile() | |
| 750 if self.logfile == '-': | |
| 751 logFile = sys.stdout | |
| 752 else: | |
| 753 logFile = file(self.logfile, 'a') | |
| 754 self._logFileObject = logFile | |
| 755 self._logFileObserver = log.FileLogObserver(logFile) | |
| 756 log.startLoggingWithObserver(self._logFileObserver.emit, 0) | |
| 757 | |
| 758 def _setUpLogWarnings(self): | |
| 759 if self._logWarnings: | |
| 760 return | |
| 761 def seeWarnings(x): | |
| 762 if x.has_key('warning'): | |
| 763 print | |
| 764 print x['format'] % x | |
| 765 log.addObserver(seeWarnings) | |
| 766 self._logWarnings = True | |
| 767 | |
| 768 def run(self, test): | |
| 769 """ | |
| 770 Run the test or suite and return a result object. | |
| 771 """ | |
| 772 result = self._makeResult() | |
| 773 test = unittest.decorate(test, ITestCase) | |
| 774 if self._forceGarbageCollection: | |
| 775 test = unittest.decorate( | |
| 776 test, unittest._ForceGarbageCollectionDecorator) | |
| 777 # decorate the suite with reactor cleanup and log starting | |
| 778 # This should move out of the runner and be presumed to be | |
| 779 # present | |
| 780 suite = TrialSuite([test]) | |
| 781 startTime = time.time() | |
| 782 if self.mode == self.DRY_RUN: | |
| 783 suite.visit(DryRunVisitor(result).markSuccessful) | |
| 784 elif self.mode == self.DEBUG: | |
| 785 # open question - should this be self.debug() instead. | |
| 786 debugger = self._getDebugger() | |
| 787 oldDir = self._setUpTestdir() | |
| 788 try: | |
| 789 self._setUpLogging() | |
| 790 debugger.runcall(suite.run, result) | |
| 791 finally: | |
| 792 self._tearDownLogFile() | |
| 793 os.chdir(oldDir) | |
| 794 else: | |
| 795 oldDir = self._setUpTestdir() | |
| 796 try: | |
| 797 self._setUpLogging() | |
| 798 suite.run(result) | |
| 799 finally: | |
| 800 self._tearDownLogFile() | |
| 801 os.chdir(oldDir) | |
| 802 endTime = time.time() | |
| 803 done = getattr(result, 'done', None) | |
| 804 if done is None: | |
| 805 warnings.warn( | |
| 806 "%s should implement done() but doesn't. Falling back to " | |
| 807 "printErrors() and friends." % reflect.qual(result.__class__), | |
| 808 category=DeprecationWarning, stacklevel=2) | |
| 809 result.printErrors() | |
| 810 result.writeln(result.separator) | |
| 811 result.writeln('Ran %d tests in %.3fs', result.testsRun, | |
| 812 endTime - startTime) | |
| 813 result.write('\n') | |
| 814 result.printSummary() | |
| 815 else: | |
| 816 result.done() | |
| 817 return result | |
| 818 | |
| 819 def runUntilFailure(self, test): | |
| 820 """ | |
| 821 Repeatedly run C{test} until it fails. | |
| 822 """ | |
| 823 count = 0 | |
| 824 while True: | |
| 825 count += 1 | |
| 826 self.stream.write("Test Pass %d\n" % (count,)) | |
| 827 result = self.run(test) | |
| 828 if result.testsRun == 0: | |
| 829 break | |
| 830 if not result.wasSuccessful(): | |
| 831 break | |
| 832 return result | |
| 833 | |
| OLD | NEW |