1
# -*- test-case-name: twisted.trial.test.test_runner -*-
2
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
3
# See LICENSE for details.
6
A miscellany of code used to run Trial tests.
8
Maintainer: Jonathan Lange
13
import os, types, warnings, sys, inspect, imp
14
import random, doctest, time
16
from twisted.python import reflect, log, failure, modules, filepath
17
from twisted.python.util import dsu
18
from twisted.python.compat import set
19
from twisted.python.lockfile import FilesystemLock
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
26
# These are imported so that they remain in the public API for t.trial.runner
27
from twisted.trial.unittest import suiteVisit, TestSuite
29
from zope.interface import implements
31
pyunit = __import__('unittest')
35
class _WorkingDirectoryBusy(Exception):
37
A working directory was specified to the runner, but another test run is
38
currently using that directory.
43
class _NoTrialMarker(Exception):
45
No trial marker file could be found.
47
Raised when trial attempts to remove a trial temporary working directory
48
that does not contain a marker file.
53
def isPackage(module):
54
"""Given an object return True if the object looks like a package"""
55
if not isinstance(module, types.ModuleType):
57
basename = os.path.splitext(os.path.basename(module.__file__))[0]
58
return basename == '__init__'
61
def isPackageDirectory(dirname):
62
"""Is the directory at path 'dirname' a Python package directory?
63
Returns the name of the __init__ file (it may have a weird extension)
64
if dirname is a package directory. Otherwise, returns False"""
65
for ext in zip(*imp.get_suffixes())[0]:
66
initFile = '__init__' + ext
67
if os.path.exists(os.path.join(dirname, initFile)):
72
def samefile(filename1, filename2):
74
A hacky implementation of C{os.path.samefile}. Used by L{filenameToModule}
75
when the platform doesn't provide C{os.path.samefile}. Do not use this.
77
return os.path.abspath(filename1) == os.path.abspath(filename2)
80
def filenameToModule(fn):
82
Given a filename, do whatever possible to return a module object matching
85
If the file in question is a module in Python path, properly import and
86
return that module. Otherwise, load the source manually.
88
@param fn: A filename.
89
@return: A module object.
90
@raise ValueError: If C{fn} does not exist.
92
if not os.path.exists(fn):
93
raise ValueError("%r doesn't exist" % (fn,))
95
ret = reflect.namedAny(reflect.filenameToModuleName(fn))
96
except (ValueError, AttributeError):
97
# Couldn't find module. The file 'fn' is not in PYTHONPATH
98
return _importFromFile(fn)
99
# ensure that the loaded module matches the file
100
retFile = os.path.splitext(ret.__file__)[0] + '.py'
101
# not all platforms (e.g. win32) have os.path.samefile
102
same = getattr(os.path, 'samefile', samefile)
103
if os.path.isfile(fn) and not same(fn, retFile):
104
del sys.modules[ret.__name__]
105
ret = _importFromFile(fn)
109
def _importFromFile(fn, moduleName=None):
110
fn = _resolveDirectory(fn)
112
moduleName = os.path.splitext(os.path.split(fn)[-1])[0]
113
if moduleName in sys.modules:
114
return sys.modules[moduleName]
117
module = imp.load_source(moduleName, fn, fd)
123
def _resolveDirectory(fn):
124
if os.path.isdir(fn):
125
initFile = isPackageDirectory(fn)
127
fn = os.path.join(fn, initFile)
129
raise ValueError('%r is not a package directory' % (fn,))
134
class DestructiveTestSuite(TestSuite):
136
A test suite which remove the tests once run, to minimize memory usage.
139
def run(self, result):
141
Almost the same as L{TestSuite.run}, but with C{self._tests} being
145
if result.shouldStop:
147
test = self._tests.pop(0)
153
# When an error occurs outside of any test, the user will see this string
154
# in place of a test's name.
155
NOT_IN_TEST = "<not in test>"
159
class LoggedSuite(TestSuite):
161
Any errors logged in this suite will be reported to the L{TestResult}
165
def run(self, result):
167
Run the suite, storing all errors in C{result}. If an error is logged
168
while no tests are running, then it will be added as an error to
171
@param result: A L{TestResult} object.
173
observer = unittest._logObserver
175
super(LoggedSuite, self).run(result)
177
for error in observer.getErrors():
178
result.addError(TestHolder(NOT_IN_TEST), error)
179
observer.flushErrors()
183
class DocTestSuite(TestSuite):
185
DEPRECATED in Twisted 8.0.
187
Behaves like doctest.DocTestSuite, but decorates individual TestCases so
188
they support visit and so that id() behaviour is meaningful and consistent
189
between Python versions.
192
def __init__(self, testModule):
193
warnings.warn("DocTestSuite is deprecated in Twisted 8.0.",
194
category=DeprecationWarning, stacklevel=2)
195
TestSuite.__init__(self)
196
suite = doctest.DocTestSuite(testModule)
197
for test in suite._tests: #yay encapsulation
198
self.addTest(ITestCase(test))
202
class PyUnitTestCase(object):
204
DEPRECATED in Twisted 8.0.
206
This class decorates the pyunit.TestCase class, mainly to work around the
207
differences between unittest in Python 2.3, 2.4, and 2.5. These
210
- The way doctest unittests describe themselves
211
- Where the implementation of TestCase.run is (used to be in __call__)
212
- Where the test method name is kept (mangled-private or non-mangled
215
It also implements visit, which we like.
218
def __init__(self, test):
219
warnings.warn("Deprecated in Twisted 8.0.",
220
category=DeprecationWarning)
225
cls = self._test.__class__
226
tmn = getattr(self._test, '_TestCase__testMethodName', None)
228
# python2.5's 'unittest' module is more sensible; but different.
229
tmn = self._test._testMethodName
230
return (cls.__module__ + '.' + cls.__name__ + '.' +
234
return 'PyUnitTestCase<%r>'%(self.id(),)
236
def __call__(self, results):
237
return self._test(results)
240
def visit(self, visitor):
242
Call the given visitor with the original, standard library, test case
243
that C{self} wraps. See L{unittest.TestCase.visit}.
245
Deprecated in Twisted 8.0.
247
warnings.warn("Test visitors deprecated in Twisted 8.0",
248
category=DeprecationWarning)
252
def __getattr__(self, name):
253
return getattr(self._test, name)
257
class DocTestCase(PyUnitTestCase):
259
DEPRECATED in Twisted 8.0.
264
In Python 2.4, doctests have correct id() behaviour. In Python 2.3,
265
id() returns 'runit'.
267
Here we override id() so that at least it will always contain the
268
fully qualified Python name of the doctest.
270
return self._test.shortDescription()
273
class TrialSuite(TestSuite):
275
Suite to wrap around every single test in a C{trial} run. Used internally
276
by Trial to set up things necessary for Trial tests to work, regardless of
277
what context they are run in.
280
def __init__(self, tests=()):
281
suite = LoggedSuite(tests)
282
super(TrialSuite, self).__init__([suite])
286
from twisted.internet import reactor
288
reactor.addSystemEventTrigger('after', 'shutdown',
289
lambda: d.callback(None))
290
reactor.fireSystemEvent('shutdown') # radix's suggestion
291
# As long as TestCase does crap stuff with the reactor we need to
292
# manually shutdown the reactor here, and that requires util.wait
294
# so that the shutdown event completes
295
unittest.TestCase('mktemp')._wait(d)
297
def run(self, result):
299
TestSuite.run(self, result)
306
@param thing: an object from modules (instance of PythonModule,
307
PythonAttribute), a TestCase subclass, or an instance of a TestCase.
309
if isTestCase(thing):
311
theName = reflect.qual(thing)
313
# thing from trial, or thing from modules.
314
# this monstrosity exists so that modules' objects do not have to
315
# implement id(). -jml
318
except AttributeError:
325
Returns C{True} if C{obj} is a class that contains test cases, C{False}
326
otherwise. Used to find all the tests in a module.
329
return issubclass(obj, pyunit.TestCase)
335
class TestHolder(object):
337
Placeholder for a L{TestCase} inside a reporter. As far as a L{TestResult}
338
is concerned, this looks exactly like a unit test.
341
implements(ITestCase)
343
def __init__(self, description):
345
@param description: A string to be displayed L{TestResult}.
347
self.description = description
351
return self.description
354
def shortDescription(self):
355
return self.description
359
class ErrorHolder(TestHolder):
361
Used to insert arbitrary errors into a test suite run. Provides enough
362
methods to look like a C{TestCase}, however, when it is run, it simply adds
363
an error to the C{TestResult}. The most common use-case is for when a
364
module fails to import.
367
def __init__(self, description, error):
369
@param description: A string used by C{TestResult}s to identify this
370
error. Generally, this is the name of a module that failed to import.
372
@param error: The error to be added to the result. Can be an exc_info
373
tuple or a L{twisted.python.failure.Failure}.
375
super(ErrorHolder, self).__init__(description)
380
return "<ErrorHolder description=%r error=%r>" % (self.description,
384
def run(self, result):
385
result.addError(self, self.error)
388
def __call__(self, result):
389
return self.run(result)
392
def countTestCases(self):
396
def visit(self, visitor):
398
See L{unittest.TestCase.visit}.
404
class TestLoader(object):
406
I find tests inside function, modules, files -- whatever -- then return
407
them wrapped inside a Test (either a L{TestSuite} or a L{TestCase}).
409
@ivar methodPrefix: A string prefix. C{TestLoader} will assume that all the
410
methods in a class that begin with C{methodPrefix} are test cases.
412
@ivar modulePrefix: A string prefix. Every module in a package that begins
413
with C{modulePrefix} is considered a module full of tests.
415
@ivar forceGarbageCollection: A flag applied to each C{TestCase} loaded.
416
See L{unittest.TestCase} for more information.
418
@ivar sorter: A key function used to sort C{TestCase}s, test classes,
419
modules and packages.
421
@ivar suiteFactory: A callable which is passed a list of tests (which
422
themselves may be suites of tests). Must return a test suite.
425
methodPrefix = 'test'
426
modulePrefix = 'test_'
429
self.suiteFactory = TestSuite
431
self._importErrors = []
435
Sort the given things using L{sorter}.
437
@param xs: A list of test cases, class or modules.
439
return dsu(xs, self.sorter)
441
def findTestClasses(self, module):
442
"""Given a module, return all Trial test classes"""
444
for name, val in inspect.getmembers(module):
447
return self.sort(classes)
449
def findByName(self, name):
451
Return a Python object given a string describing it.
453
@param name: a string which may be either a filename or a
454
fully-qualified Python name.
456
@return: If C{name} is a filename, return the module. If C{name} is a
457
fully-qualified Python name, return the object it refers to.
459
if os.path.exists(name):
460
return filenameToModule(name)
461
return reflect.namedAny(name)
463
def loadModule(self, module):
465
Return a test suite with all the tests from a module.
467
Included are TestCase subclasses and doctests listed in the module's
468
__doctests__ module. If that's not good for you, put a function named
469
either C{testSuite} or C{test_suite} in your module that returns a
470
TestSuite, and I'll use the results of that instead.
472
If C{testSuite} and C{test_suite} are both present, then I'll use
475
## XXX - should I add an optional parameter to disable the check for
477
## OR, should I add another method
478
if not isinstance(module, types.ModuleType):
479
raise TypeError("%r is not a module" % (module,))
480
if hasattr(module, 'testSuite'):
481
return module.testSuite()
482
elif hasattr(module, 'test_suite'):
483
return module.test_suite()
484
suite = self.suiteFactory()
485
for testClass in self.findTestClasses(module):
486
suite.addTest(self.loadClass(testClass))
487
if not hasattr(module, '__doctests__'):
489
docSuite = self.suiteFactory()
490
for doctest in module.__doctests__:
491
docSuite.addTest(self.loadDoctests(doctest))
492
return self.suiteFactory([suite, docSuite])
493
loadTestsFromModule = loadModule
495
def loadClass(self, klass):
497
Given a class which contains test cases, return a sorted list of
498
C{TestCase} instances.
500
if not (isinstance(klass, type) or isinstance(klass, types.ClassType)):
501
raise TypeError("%r is not a class" % (klass,))
502
if not isTestCase(klass):
503
raise ValueError("%r is not a test case" % (klass,))
504
names = self.getTestCaseNames(klass)
505
tests = self.sort([self._makeCase(klass, self.methodPrefix+name)
507
return self.suiteFactory(tests)
508
loadTestsFromTestCase = loadClass
510
def getTestCaseNames(self, klass):
512
Given a class that contains C{TestCase}s, return a list of names of
513
methods that probably contain tests.
515
return reflect.prefixedMethodNames(klass, self.methodPrefix)
517
def loadMethod(self, method):
519
Given a method of a C{TestCase} that represents a test, return a
520
C{TestCase} instance for that test.
522
if not isinstance(method, types.MethodType):
523
raise TypeError("%r not a method" % (method,))
524
return self._makeCase(method.im_class, method.__name__)
526
def _makeCase(self, klass, methodName):
527
return klass(methodName)
529
def loadPackage(self, package, recurse=False):
531
Load tests from a module object representing a package, and return a
532
TestSuite containing those tests.
534
Tests are only loaded from modules whose name begins with 'test_'
535
(or whatever C{modulePrefix} is set to).
537
@param package: a types.ModuleType object (or reasonable facsimilie
538
obtained by importing) which may contain tests.
540
@param recurse: A boolean. If True, inspect modules within packages
541
within the given package (and so on), otherwise, only inspect modules
542
in the package itself.
544
@raise: TypeError if 'package' is not a package.
546
@return: a TestSuite created with my suiteFactory, containing all the
549
if not isPackage(package):
550
raise TypeError("%r is not a package" % (package,))
551
pkgobj = modules.getModule(package.__name__)
553
discovery = pkgobj.walkModules()
555
discovery = pkgobj.iterModules()
557
for disco in discovery:
558
if disco.name.split(".")[-1].startswith(self.modulePrefix):
559
discovered.append(disco)
560
suite = self.suiteFactory()
561
for modinfo in self.sort(discovered):
563
module = modinfo.load()
565
thingToAdd = ErrorHolder(modinfo.name, failure.Failure())
567
thingToAdd = self.loadModule(module)
568
suite.addTest(thingToAdd)
571
def loadDoctests(self, module):
573
Return a suite of tests for all the doctests defined in C{module}.
575
@param module: A module object or a module name.
577
if isinstance(module, str):
579
module = reflect.namedAny(module)
581
return ErrorHolder(module, failure.Failure())
582
if not inspect.ismodule(module):
583
warnings.warn("trial only supports doctesting modules")
586
if sys.version_info > (2, 4):
587
# Work around Python issue2604: DocTestCase.tearDown clobbers globs
588
def saveGlobals(test):
590
Save C{test.globs} and replace it with a copy so that if
591
necessary, the original will be available for the next test
594
test._savedGlobals = getattr(test, '_savedGlobals', test.globs)
595
test.globs = test._savedGlobals.copy()
596
extraArgs['setUp'] = saveGlobals
597
return doctest.DocTestSuite(module, **extraArgs)
599
def loadAnything(self, thing, recurse=False):
601
Given a Python object, return whatever tests that are in it. Whatever
604
@param thing: A Python object. A module, method, class or package.
605
@param recurse: Whether or not to look in subpackages of packages.
608
@return: A C{TestCase} or C{TestSuite}.
610
if isinstance(thing, types.ModuleType):
612
return self.loadPackage(thing, recurse)
613
return self.loadModule(thing)
614
elif isinstance(thing, types.ClassType):
615
return self.loadClass(thing)
616
elif isinstance(thing, type):
617
return self.loadClass(thing)
618
elif isinstance(thing, types.MethodType):
619
return self.loadMethod(thing)
620
raise TypeError("No loader for %r. Unrecognized type" % (thing,))
622
def loadByName(self, name, recurse=False):
624
Given a string representing a Python object, return whatever tests
627
If C{name} is somehow inaccessible (e.g. the module can't be imported,
628
there is no Python object with that name etc) then return an
631
@param name: The fully-qualified name of a Python object.
634
thing = self.findByName(name)
636
return ErrorHolder(name, failure.Failure())
637
return self.loadAnything(thing, recurse)
638
loadTestsFromName = loadByName
640
def loadByNames(self, names, recurse=False):
642
Construct a TestSuite containing all the tests found in 'names', where
643
names is a list of fully qualified python names and/or filenames. The
644
suite returned will have no duplicate tests, even if the same object
651
things.append(self.findByName(name))
653
errors.append(ErrorHolder(name, failure.Failure()))
654
suites = [self.loadAnything(thing, recurse)
655
for thing in set(things)]
656
suites.extend(errors)
657
return self.suiteFactory(suites)
661
class DryRunVisitor(object):
663
A visitor that makes a reporter think that every test visited has run
667
def __init__(self, reporter):
669
@param reporter: A C{TestResult} object.
671
self.reporter = reporter
674
def markSuccessful(self, testCase):
676
Convince the reporter that this test has been run successfully.
678
self.reporter.startTest(testCase)
679
self.reporter.addSuccess(testCase)
680
self.reporter.stopTest(testCase)
684
class TrialRunner(object):
686
A specialised runner that the trial front end uses.
692
def _getDebugger(self):
697
print "readline module not available"
698
hasattr(sys, 'exc_clear') and sys.exc_clear()
699
for path in ('.pdbrc', 'pdbrc'):
700
if os.path.exists(path):
702
rcFile = file(path, 'r')
704
hasattr(sys, 'exc_clear') and sys.exc_clear()
706
dbg.rcLines.extend(rcFile.readlines())
710
def _removeSafely(self, path):
712
Safely remove a path, recursively.
714
If C{path} does not contain a node named C{"_trial_marker"}, a
715
L{_NoTrialMarker} exception is raised and the path is not removed.
717
@type path: L{twisted.python.filepath.FilePath}
718
@param path: The absolute path to a test directory
720
if not path.child('_trial_marker').exists():
721
raise _NoTrialMarker(
722
'%r is not a trial temporary path, refusing to remove it'
728
print ("could not remove %r, caught OSError [Errno %s]: %s"
729
% (path, e.errno, e.strerror))
731
newPath = filepath.FilePath('_trial_temp_old%s'
732
% random.randint(0, 99999999))
735
print ("could not rename path, caught OSError [Errno %s]: %s"
736
% (e.errno, e.strerror))
740
def _setUpTestdir(self):
741
self._tearDownLogFile()
742
currentDir = os.getcwd()
743
base = filepath.FilePath(self.workingDirectory)
747
testdir = base.sibling('%s-%d' % (base.basename(), counter))
751
self._testDirLock = FilesystemLock(testdir.path + '.lock')
752
if self._testDirLock.lock():
755
# It exists though - delete it
756
self._removeSafely(testdir)
760
if self.workingDirectory == '_trial_temp':
763
raise _WorkingDirectoryBusy()
766
os.chdir(testdir.path)
767
file('_trial_marker', 'w').close()
771
def _tearDownTestdir(self, oldDir):
773
self._testDirLock.unlock()
777
def _makeResult(self):
778
reporter = self.reporterFactory(self.stream, self.tbformat,
779
self.rterrors, self._log)
780
if self.uncleanWarnings:
781
reporter = UncleanWarningsReporterWrapper(reporter)
784
def __init__(self, reporterFactory,
789
tracebackFormat='default',
790
realTimeErrors=False,
791
uncleanWarnings=False,
792
workingDirectory=None,
793
forceGarbageCollection=False):
794
self.reporterFactory = reporterFactory
795
self.logfile = logfile
798
self.tbformat = tracebackFormat
799
self.rterrors = realTimeErrors
800
self.uncleanWarnings = uncleanWarnings
802
self.workingDirectory = workingDirectory or '_trial_temp'
803
self._logFileObserver = None
804
self._logFileObject = None
805
self._forceGarbageCollection = forceGarbageCollection
807
self.run = util.profiled(self.run, 'profile.data')
809
def _tearDownLogFile(self):
810
if self._logFileObserver is not None:
811
log.removeObserver(self._logFileObserver.emit)
812
self._logFileObserver = None
813
if self._logFileObject is not None:
814
self._logFileObject.close()
815
self._logFileObject = None
817
def _setUpLogFile(self):
818
self._tearDownLogFile()
819
if self.logfile == '-':
822
logFile = file(self.logfile, 'a')
823
self._logFileObject = logFile
824
self._logFileObserver = log.FileLogObserver(logFile)
825
log.startLoggingWithObserver(self._logFileObserver.emit, 0)
830
Run the test or suite and return a result object.
832
test = unittest.decorate(test, ITestCase)
833
if self._forceGarbageCollection:
834
test = unittest.decorate(
835
test, unittest._ForceGarbageCollectionDecorator)
836
return self._runWithoutDecoration(test)
839
def _runWithoutDecoration(self, test):
841
Private helper that runs the given test but doesn't decorate it.
843
result = self._makeResult()
844
# decorate the suite with reactor cleanup and log starting
845
# This should move out of the runner and be presumed to be
847
suite = TrialSuite([test])
848
startTime = time.time()
849
if self.mode == self.DRY_RUN:
850
for single in unittest._iterateTests(suite):
851
result.startTest(single)
852
result.addSuccess(single)
853
result.stopTest(single)
855
if self.mode == self.DEBUG:
856
# open question - should this be self.debug() instead.
857
debugger = self._getDebugger()
858
run = lambda: debugger.runcall(suite.run, result)
860
run = lambda: suite.run(result)
862
oldDir = self._setUpTestdir()
867
self._tearDownLogFile()
868
self._tearDownTestdir(oldDir)
870
endTime = time.time()
871
done = getattr(result, 'done', None)
874
"%s should implement done() but doesn't. Falling back to "
875
"printErrors() and friends." % reflect.qual(result.__class__),
876
category=DeprecationWarning, stacklevel=3)
878
result.writeln(result.separator)
879
result.writeln('Ran %d tests in %.3fs', result.testsRun,
882
result.printSummary()
888
def runUntilFailure(self, test):
890
Repeatedly run C{test} until it fails.
895
self.stream.write("Test Pass %d\n" % (count,))
897
result = self.run(test)
899
result = self._runWithoutDecoration(test)
900
if result.testsRun == 0:
902
if not result.wasSuccessful():