You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_asynctest.py 14KB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. # -*- test-case-name: twisted.trial.test -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Things likely to be used by writers of unit tests.
  6. Maintainer: Jonathan Lange
  7. """
  8. from __future__ import division, absolute_import
  9. import inspect
  10. import warnings
  11. from zope.interface import implementer
  12. # We can't import reactor at module-level because this code runs before trial
  13. # installs a user-specified reactor, installing the default reactor and
  14. # breaking reactor installation. See also #6047.
  15. from twisted.internet import defer, utils
  16. from twisted.python import failure
  17. from twisted.trial import itrial, util
  18. from twisted.trial._synctest import (
  19. FailTest, SkipTest, SynchronousTestCase)
  20. _wait_is_running = []
  21. @implementer(itrial.ITestCase)
  22. class TestCase(SynchronousTestCase):
  23. """
  24. A unit test. The atom of the unit testing universe.
  25. This class extends L{SynchronousTestCase} which extends C{unittest.TestCase}
  26. from the standard library. The main feature is the ability to return
  27. C{Deferred}s from tests and fixture methods and to have the suite wait for
  28. those C{Deferred}s to fire. Also provides new assertions such as
  29. L{assertFailure}.
  30. @ivar timeout: A real number of seconds. If set, the test will
  31. raise an error if it takes longer than C{timeout} seconds.
  32. If not set, util.DEFAULT_TIMEOUT_DURATION is used.
  33. """
  34. def __init__(self, methodName='runTest'):
  35. """
  36. Construct an asynchronous test case for C{methodName}.
  37. @param methodName: The name of a method on C{self}. This method should
  38. be a unit test. That is, it should be a short method that calls some of
  39. the assert* methods. If C{methodName} is unspecified,
  40. L{SynchronousTestCase.runTest} will be used as the test method. This is
  41. mostly useful for testing Trial.
  42. """
  43. super(TestCase, self).__init__(methodName)
  44. def assertFailure(self, deferred, *expectedFailures):
  45. """
  46. Fail if C{deferred} does not errback with one of C{expectedFailures}.
  47. Returns the original Deferred with callbacks added. You will need
  48. to return this Deferred from your test case.
  49. """
  50. def _cb(ignore):
  51. raise self.failureException(
  52. "did not catch an error, instead got %r" % (ignore,))
  53. def _eb(failure):
  54. if failure.check(*expectedFailures):
  55. return failure.value
  56. else:
  57. output = ('\nExpected: %r\nGot:\n%s'
  58. % (expectedFailures, str(failure)))
  59. raise self.failureException(output)
  60. return deferred.addCallbacks(_cb, _eb)
  61. failUnlessFailure = assertFailure
  62. def _run(self, methodName, result):
  63. from twisted.internet import reactor
  64. timeout = self.getTimeout()
  65. def onTimeout(d):
  66. e = defer.TimeoutError("%r (%s) still running at %s secs"
  67. % (self, methodName, timeout))
  68. f = failure.Failure(e)
  69. # try to errback the deferred that the test returns (for no gorram
  70. # reason) (see issue1005 and test_errorPropagation in
  71. # test_deferred)
  72. try:
  73. d.errback(f)
  74. except defer.AlreadyCalledError:
  75. # if the deferred has been called already but the *back chain
  76. # is still unfinished, crash the reactor and report timeout
  77. # error ourself.
  78. reactor.crash()
  79. self._timedOut = True # see self._wait
  80. todo = self.getTodo()
  81. if todo is not None and todo.expected(f):
  82. result.addExpectedFailure(self, f, todo)
  83. else:
  84. result.addError(self, f)
  85. onTimeout = utils.suppressWarnings(
  86. onTimeout, util.suppress(category=DeprecationWarning))
  87. method = getattr(self, methodName)
  88. if inspect.isgeneratorfunction(method):
  89. exc = TypeError(
  90. '%r is a generator function and therefore will never run' % (
  91. method,))
  92. return defer.fail(exc)
  93. d = defer.maybeDeferred(
  94. utils.runWithWarningsSuppressed, self._getSuppress(), method)
  95. call = reactor.callLater(timeout, onTimeout, d)
  96. d.addBoth(lambda x : call.active() and call.cancel() or x)
  97. return d
  98. def __call__(self, *args, **kwargs):
  99. return self.run(*args, **kwargs)
  100. def deferSetUp(self, ignored, result):
  101. d = self._run('setUp', result)
  102. d.addCallbacks(self.deferTestMethod, self._ebDeferSetUp,
  103. callbackArgs=(result,),
  104. errbackArgs=(result,))
  105. return d
  106. def _ebDeferSetUp(self, failure, result):
  107. if failure.check(SkipTest):
  108. result.addSkip(self, self._getSkipReason(self.setUp, failure.value))
  109. else:
  110. result.addError(self, failure)
  111. if failure.check(KeyboardInterrupt):
  112. result.stop()
  113. return self.deferRunCleanups(None, result)
  114. def deferTestMethod(self, ignored, result):
  115. d = self._run(self._testMethodName, result)
  116. d.addCallbacks(self._cbDeferTestMethod, self._ebDeferTestMethod,
  117. callbackArgs=(result,),
  118. errbackArgs=(result,))
  119. d.addBoth(self.deferRunCleanups, result)
  120. d.addBoth(self.deferTearDown, result)
  121. return d
  122. def _cbDeferTestMethod(self, ignored, result):
  123. if self.getTodo() is not None:
  124. result.addUnexpectedSuccess(self, self.getTodo())
  125. else:
  126. self._passed = True
  127. return ignored
  128. def _ebDeferTestMethod(self, f, result):
  129. todo = self.getTodo()
  130. if todo is not None and todo.expected(f):
  131. result.addExpectedFailure(self, f, todo)
  132. elif f.check(self.failureException, FailTest):
  133. result.addFailure(self, f)
  134. elif f.check(KeyboardInterrupt):
  135. result.addError(self, f)
  136. result.stop()
  137. elif f.check(SkipTest):
  138. result.addSkip(
  139. self,
  140. self._getSkipReason(getattr(self, self._testMethodName), f.value))
  141. else:
  142. result.addError(self, f)
  143. def deferTearDown(self, ignored, result):
  144. d = self._run('tearDown', result)
  145. d.addErrback(self._ebDeferTearDown, result)
  146. return d
  147. def _ebDeferTearDown(self, failure, result):
  148. result.addError(self, failure)
  149. if failure.check(KeyboardInterrupt):
  150. result.stop()
  151. self._passed = False
  152. def deferRunCleanups(self, ignored, result):
  153. """
  154. Run any scheduled cleanups and report errors (if any to the result
  155. object.
  156. """
  157. d = self._runCleanups()
  158. d.addCallback(self._cbDeferRunCleanups, result)
  159. return d
  160. def _cbDeferRunCleanups(self, cleanupResults, result):
  161. for flag, testFailure in cleanupResults:
  162. if flag == defer.FAILURE:
  163. result.addError(self, testFailure)
  164. if testFailure.check(KeyboardInterrupt):
  165. result.stop()
  166. self._passed = False
  167. def _cleanUp(self, result):
  168. try:
  169. clean = util._Janitor(self, result).postCaseCleanup()
  170. if not clean:
  171. self._passed = False
  172. except:
  173. result.addError(self, failure.Failure())
  174. self._passed = False
  175. for error in self._observer.getErrors():
  176. result.addError(self, error)
  177. self._passed = False
  178. self.flushLoggedErrors()
  179. self._removeObserver()
  180. if self._passed:
  181. result.addSuccess(self)
  182. def _classCleanUp(self, result):
  183. try:
  184. util._Janitor(self, result).postClassCleanup()
  185. except:
  186. result.addError(self, failure.Failure())
  187. def _makeReactorMethod(self, name):
  188. """
  189. Create a method which wraps the reactor method C{name}. The new
  190. method issues a deprecation warning and calls the original.
  191. """
  192. def _(*a, **kw):
  193. warnings.warn("reactor.%s cannot be used inside unit tests. "
  194. "In the future, using %s will fail the test and may "
  195. "crash or hang the test run."
  196. % (name, name),
  197. stacklevel=2, category=DeprecationWarning)
  198. return self._reactorMethods[name](*a, **kw)
  199. return _
  200. def _deprecateReactor(self, reactor):
  201. """
  202. Deprecate C{iterate}, C{crash} and C{stop} on C{reactor}. That is,
  203. each method is wrapped in a function that issues a deprecation
  204. warning, then calls the original.
  205. @param reactor: The Twisted reactor.
  206. """
  207. self._reactorMethods = {}
  208. for name in ['crash', 'iterate', 'stop']:
  209. self._reactorMethods[name] = getattr(reactor, name)
  210. setattr(reactor, name, self._makeReactorMethod(name))
  211. def _undeprecateReactor(self, reactor):
  212. """
  213. Restore the deprecated reactor methods. Undoes what
  214. L{_deprecateReactor} did.
  215. @param reactor: The Twisted reactor.
  216. """
  217. for name, method in self._reactorMethods.items():
  218. setattr(reactor, name, method)
  219. self._reactorMethods = {}
  220. def _runCleanups(self):
  221. """
  222. Run the cleanups added with L{addCleanup} in order.
  223. @return: A C{Deferred} that fires when all cleanups are run.
  224. """
  225. def _makeFunction(f, args, kwargs):
  226. return lambda: f(*args, **kwargs)
  227. callables = []
  228. while len(self._cleanups) > 0:
  229. f, args, kwargs = self._cleanups.pop()
  230. callables.append(_makeFunction(f, args, kwargs))
  231. return util._runSequentially(callables)
  232. def _runFixturesAndTest(self, result):
  233. """
  234. Really run C{setUp}, the test method, and C{tearDown}. Any of these may
  235. return L{defer.Deferred}s. After they complete, do some reactor cleanup.
  236. @param result: A L{TestResult} object.
  237. """
  238. from twisted.internet import reactor
  239. self._deprecateReactor(reactor)
  240. self._timedOut = False
  241. try:
  242. d = self.deferSetUp(None, result)
  243. try:
  244. self._wait(d)
  245. finally:
  246. self._cleanUp(result)
  247. self._classCleanUp(result)
  248. finally:
  249. self._undeprecateReactor(reactor)
  250. def addCleanup(self, f, *args, **kwargs):
  251. """
  252. Extend the base cleanup feature with support for cleanup functions which
  253. return Deferreds.
  254. If the function C{f} returns a Deferred, C{TestCase} will wait until the
  255. Deferred has fired before proceeding to the next function.
  256. """
  257. return super(TestCase, self).addCleanup(f, *args, **kwargs)
  258. def getSuppress(self):
  259. return self._getSuppress()
  260. def getTimeout(self):
  261. """
  262. Returns the timeout value set on this test. Checks on the instance
  263. first, then the class, then the module, then packages. As soon as it
  264. finds something with a C{timeout} attribute, returns that. Returns
  265. L{util.DEFAULT_TIMEOUT_DURATION} if it cannot find anything. See
  266. L{TestCase} docstring for more details.
  267. """
  268. timeout = util.acquireAttribute(self._parents, 'timeout',
  269. util.DEFAULT_TIMEOUT_DURATION)
  270. try:
  271. return float(timeout)
  272. except (ValueError, TypeError):
  273. # XXX -- this is here because sometimes people will have methods
  274. # called 'timeout', or set timeout to 'orange', or something
  275. # Particularly, test_news.NewsTestCase and ReactorCoreTestCase
  276. # both do this.
  277. warnings.warn("'timeout' attribute needs to be a number.",
  278. category=DeprecationWarning)
  279. return util.DEFAULT_TIMEOUT_DURATION
  280. def _wait(self, d, running=_wait_is_running):
  281. """Take a Deferred that only ever callbacks. Block until it happens.
  282. """
  283. if running:
  284. raise RuntimeError("_wait is not reentrant")
  285. from twisted.internet import reactor
  286. results = []
  287. def append(any):
  288. if results is not None:
  289. results.append(any)
  290. def crash(ign):
  291. if results is not None:
  292. reactor.crash()
  293. crash = utils.suppressWarnings(
  294. crash, util.suppress(message=r'reactor\.crash cannot be used.*',
  295. category=DeprecationWarning))
  296. def stop():
  297. reactor.crash()
  298. stop = utils.suppressWarnings(
  299. stop, util.suppress(message=r'reactor\.crash cannot be used.*',
  300. category=DeprecationWarning))
  301. running.append(None)
  302. try:
  303. d.addBoth(append)
  304. if results:
  305. # d might have already been fired, in which case append is
  306. # called synchronously. Avoid any reactor stuff.
  307. return
  308. d.addBoth(crash)
  309. reactor.stop = stop
  310. try:
  311. reactor.run()
  312. finally:
  313. del reactor.stop
  314. # If the reactor was crashed elsewhere due to a timeout, hopefully
  315. # that crasher also reported an error. Just return.
  316. # _timedOut is most likely to be set when d has fired but hasn't
  317. # completed its callback chain (see self._run)
  318. if results or self._timedOut: #defined in run() and _run()
  319. return
  320. # If the timeout didn't happen, and we didn't get a result or
  321. # a failure, then the user probably aborted the test, so let's
  322. # just raise KeyboardInterrupt.
  323. # FIXME: imagine this:
  324. # web/test/test_webclient.py:
  325. # exc = self.assertRaises(error.Error, wait, method(url))
  326. #
  327. # wait() will raise KeyboardInterrupt, and assertRaises will
  328. # swallow it. Therefore, wait() raising KeyboardInterrupt is
  329. # insufficient to stop trial. A suggested solution is to have
  330. # this code set a "stop trial" flag, or otherwise notify trial
  331. # that it should really try to stop as soon as possible.
  332. raise KeyboardInterrupt()
  333. finally:
  334. results = None
  335. running.pop()