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