Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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.

test_defgen.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.internet.defer.deferredGenerator} and related APIs.
  5. """
  6. import traceback
  7. from twisted.internet import defer, reactor, task
  8. from twisted.internet.defer import (
  9. Deferred,
  10. deferredGenerator,
  11. inlineCallbacks,
  12. returnValue,
  13. waitForDeferred,
  14. )
  15. from twisted.python.util import runWithWarningsSuppressed
  16. from twisted.trial import unittest
  17. from twisted.trial.util import suppress as SUPPRESS
  18. def getThing():
  19. d = Deferred()
  20. reactor.callLater(0, d.callback, "hi")
  21. return d
  22. def getOwie():
  23. d = Deferred()
  24. def CRAP():
  25. d.errback(ZeroDivisionError("OMG"))
  26. reactor.callLater(0, CRAP)
  27. return d
  28. # NOTE: most of the tests in DeferredGeneratorTests are duplicated
  29. # with slightly different syntax for the InlineCallbacksTests below.
  30. class TerminalException(Exception):
  31. pass
  32. class BaseDefgenTests:
  33. """
  34. This class sets up a bunch of test cases which will test both
  35. deferredGenerator and inlineCallbacks based generators. The subclasses
  36. DeferredGeneratorTests and InlineCallbacksTests each provide the actual
  37. generator implementations tested.
  38. """
  39. def testBasics(self):
  40. """
  41. Test that a normal deferredGenerator works. Tests yielding a
  42. deferred which callbacks, as well as a deferred errbacks. Also
  43. ensures returning a final value works.
  44. """
  45. return self._genBasics().addCallback(self.assertEqual, "WOOSH")
  46. def testBuggy(self):
  47. """
  48. Ensure that a buggy generator properly signals a Failure
  49. condition on result deferred.
  50. """
  51. return self.assertFailure(self._genBuggy(), ZeroDivisionError)
  52. def testNothing(self):
  53. """Test that a generator which never yields results in None."""
  54. return self._genNothing().addCallback(self.assertEqual, None)
  55. def testHandledTerminalFailure(self):
  56. """
  57. Create a Deferred Generator which yields a Deferred which fails and
  58. handles the exception which results. Assert that the Deferred
  59. Generator does not errback its Deferred.
  60. """
  61. return self._genHandledTerminalFailure().addCallback(self.assertEqual, None)
  62. def testHandledTerminalAsyncFailure(self):
  63. """
  64. Just like testHandledTerminalFailure, only with a Deferred which fires
  65. asynchronously with an error.
  66. """
  67. d = defer.Deferred()
  68. deferredGeneratorResultDeferred = self._genHandledTerminalAsyncFailure(d)
  69. d.errback(TerminalException("Handled Terminal Failure"))
  70. return deferredGeneratorResultDeferred.addCallback(self.assertEqual, None)
  71. def testStackUsage(self):
  72. """
  73. Make sure we don't blow the stack when yielding immediately
  74. available deferreds.
  75. """
  76. return self._genStackUsage().addCallback(self.assertEqual, 0)
  77. def testStackUsage2(self):
  78. """
  79. Make sure we don't blow the stack when yielding immediately
  80. available values.
  81. """
  82. return self._genStackUsage2().addCallback(self.assertEqual, 0)
  83. def deprecatedDeferredGenerator(f):
  84. """
  85. Calls L{deferredGenerator} while suppressing the deprecation warning.
  86. @param f: Function to call
  87. @return: Return value of function.
  88. """
  89. return runWithWarningsSuppressed(
  90. [
  91. SUPPRESS(
  92. message="twisted.internet.defer.deferredGenerator was " "deprecated"
  93. )
  94. ],
  95. deferredGenerator,
  96. f,
  97. )
  98. class DeferredGeneratorTests(BaseDefgenTests, unittest.TestCase):
  99. # First provide all the generator impls necessary for BaseDefgenTests
  100. @deprecatedDeferredGenerator
  101. def _genBasics(self):
  102. x = waitForDeferred(getThing())
  103. yield x
  104. x = x.getResult()
  105. self.assertEqual(x, "hi")
  106. ow = waitForDeferred(getOwie())
  107. yield ow
  108. try:
  109. ow.getResult()
  110. except ZeroDivisionError as e:
  111. self.assertEqual(str(e), "OMG")
  112. yield "WOOSH"
  113. return
  114. @deprecatedDeferredGenerator
  115. def _genBuggy(self):
  116. yield waitForDeferred(getThing())
  117. 1 // 0
  118. @deprecatedDeferredGenerator
  119. def _genNothing(self):
  120. if False:
  121. yield 1
  122. @deprecatedDeferredGenerator
  123. def _genHandledTerminalFailure(self):
  124. x = waitForDeferred(defer.fail(TerminalException("Handled Terminal Failure")))
  125. yield x
  126. try:
  127. x.getResult()
  128. except TerminalException:
  129. pass
  130. @deprecatedDeferredGenerator
  131. def _genHandledTerminalAsyncFailure(self, d):
  132. x = waitForDeferred(d)
  133. yield x
  134. try:
  135. x.getResult()
  136. except TerminalException:
  137. pass
  138. def _genStackUsage(self):
  139. for x in range(5000):
  140. # Test with yielding a deferred
  141. x = waitForDeferred(defer.succeed(1))
  142. yield x
  143. x = x.getResult()
  144. yield 0
  145. _genStackUsage = deprecatedDeferredGenerator(_genStackUsage)
  146. def _genStackUsage2(self):
  147. for x in range(5000):
  148. # Test with yielding a random value
  149. yield 1
  150. yield 0
  151. _genStackUsage2 = deprecatedDeferredGenerator(_genStackUsage2)
  152. # Tests unique to deferredGenerator
  153. def testDeferredYielding(self):
  154. """
  155. Ensure that yielding a Deferred directly is trapped as an
  156. error.
  157. """
  158. # See the comment _deferGenerator about d.callback(Deferred).
  159. def _genDeferred():
  160. yield getThing()
  161. _genDeferred = deprecatedDeferredGenerator(_genDeferred)
  162. return self.assertFailure(_genDeferred(), TypeError)
  163. suppress = [
  164. SUPPRESS(message="twisted.internet.defer.waitForDeferred was " "deprecated")
  165. ]
  166. class InlineCallbacksTests(BaseDefgenTests, unittest.TestCase):
  167. # First provide all the generator impls necessary for BaseDefgenTests
  168. def _genBasics(self):
  169. x = yield getThing()
  170. self.assertEqual(x, "hi")
  171. try:
  172. yield getOwie()
  173. except ZeroDivisionError as e:
  174. self.assertEqual(str(e), "OMG")
  175. returnValue("WOOSH")
  176. _genBasics = inlineCallbacks(_genBasics)
  177. def _genBuggy(self):
  178. yield getThing()
  179. 1 / 0
  180. _genBuggy = inlineCallbacks(_genBuggy)
  181. def _genNothing(self):
  182. if False:
  183. yield 1
  184. _genNothing = inlineCallbacks(_genNothing)
  185. def _genHandledTerminalFailure(self):
  186. try:
  187. yield defer.fail(TerminalException("Handled Terminal Failure"))
  188. except TerminalException:
  189. pass
  190. _genHandledTerminalFailure = inlineCallbacks(_genHandledTerminalFailure)
  191. def _genHandledTerminalAsyncFailure(self, d):
  192. try:
  193. yield d
  194. except TerminalException:
  195. pass
  196. _genHandledTerminalAsyncFailure = inlineCallbacks(_genHandledTerminalAsyncFailure)
  197. def _genStackUsage(self):
  198. for x in range(5000):
  199. # Test with yielding a deferred
  200. yield defer.succeed(1)
  201. returnValue(0)
  202. _genStackUsage = inlineCallbacks(_genStackUsage)
  203. def _genStackUsage2(self):
  204. for x in range(5000):
  205. # Test with yielding a random value
  206. yield 1
  207. returnValue(0)
  208. _genStackUsage2 = inlineCallbacks(_genStackUsage2)
  209. # Tests unique to inlineCallbacks
  210. def testYieldNonDeferred(self):
  211. """
  212. Ensure that yielding a non-deferred passes it back as the
  213. result of the yield expression.
  214. @return: A L{twisted.internet.defer.Deferred}
  215. @rtype: L{twisted.internet.defer.Deferred}
  216. """
  217. def _test():
  218. yield 5
  219. returnValue(5)
  220. _test = inlineCallbacks(_test)
  221. return _test().addCallback(self.assertEqual, 5)
  222. def testReturnNoValue(self):
  223. """Ensure a standard python return results in a None result."""
  224. def _noReturn():
  225. yield 5
  226. return
  227. _noReturn = inlineCallbacks(_noReturn)
  228. return _noReturn().addCallback(self.assertEqual, None)
  229. def testReturnValue(self):
  230. """Ensure that returnValue works."""
  231. def _return():
  232. yield 5
  233. returnValue(6)
  234. _return = inlineCallbacks(_return)
  235. return _return().addCallback(self.assertEqual, 6)
  236. def test_nonGeneratorReturn(self):
  237. """
  238. Ensure that C{TypeError} with a message about L{inlineCallbacks} is
  239. raised when a non-generator returns something other than a generator.
  240. """
  241. def _noYield():
  242. return 5
  243. _noYield = inlineCallbacks(_noYield)
  244. self.assertIn("inlineCallbacks", str(self.assertRaises(TypeError, _noYield)))
  245. def test_nonGeneratorReturnValue(self):
  246. """
  247. Ensure that C{TypeError} with a message about L{inlineCallbacks} is
  248. raised when a non-generator calls L{returnValue}.
  249. """
  250. def _noYield():
  251. returnValue(5)
  252. _noYield = inlineCallbacks(_noYield)
  253. self.assertIn("inlineCallbacks", str(self.assertRaises(TypeError, _noYield)))
  254. def test_internalDefGenReturnValueDoesntLeak(self):
  255. """
  256. When one inlineCallbacks calls another, the internal L{_DefGen_Return}
  257. flow control exception raised by calling L{defer.returnValue} doesn't
  258. leak into tracebacks captured in the caller.
  259. """
  260. clock = task.Clock()
  261. @inlineCallbacks
  262. def _returns():
  263. """
  264. This is the inner function using returnValue.
  265. """
  266. yield task.deferLater(clock, 0)
  267. returnValue("actual-value-not-used-for-the-test")
  268. @inlineCallbacks
  269. def _raises():
  270. try:
  271. yield _returns()
  272. raise TerminalException("boom returnValue")
  273. except TerminalException:
  274. return traceback.format_exc()
  275. d = _raises()
  276. clock.advance(0)
  277. tb = self.successResultOf(d)
  278. # The internal exception is not in the traceback.
  279. self.assertNotIn("_DefGen_Return", tb)
  280. # No other extra exception is in the traceback.
  281. self.assertNotIn(
  282. "During handling of the above exception, another exception occurred", tb
  283. )
  284. # Our targeted exception is in the traceback
  285. self.assertIn("test_defgen.TerminalException: boom returnValue", tb)
  286. def test_internalStopIterationDoesntLeak(self):
  287. """
  288. When one inlineCallbacks calls another, the internal L{StopIteration}
  289. flow control exception generated when the inner generator returns
  290. doesn't leak into tracebacks captured in the caller.
  291. This is similar to C{test_internalDefGenReturnValueDoesntLeak} but the
  292. inner function uses the "normal" return statemement rather than the
  293. C{returnValue} helper.
  294. """
  295. clock = task.Clock()
  296. @inlineCallbacks
  297. def _returns():
  298. yield task.deferLater(clock, 0)
  299. return 6
  300. @inlineCallbacks
  301. def _raises():
  302. try:
  303. yield _returns()
  304. raise TerminalException("boom normal return")
  305. except TerminalException:
  306. return traceback.format_exc()
  307. d = _raises()
  308. clock.advance(0)
  309. tb = self.successResultOf(d)
  310. # The internal exception is not in the traceback.
  311. self.assertNotIn("StopIteration", tb)
  312. # No other extra exception is in the traceback.
  313. self.assertNotIn(
  314. "During handling of the above exception, another exception occurred", tb
  315. )
  316. # Our targeted exception is in the traceback
  317. self.assertIn("test_defgen.TerminalException: boom normal return", tb)
  318. class DeprecateDeferredGeneratorTests(unittest.SynchronousTestCase):
  319. """
  320. Tests that L{DeferredGeneratorTests} and L{waitForDeferred} are
  321. deprecated.
  322. """
  323. def test_deferredGeneratorDeprecated(self):
  324. """
  325. L{deferredGenerator} is deprecated.
  326. """
  327. @deferredGenerator
  328. def decoratedFunction():
  329. yield None
  330. warnings = self.flushWarnings([self.test_deferredGeneratorDeprecated])
  331. self.assertEqual(len(warnings), 1)
  332. self.assertEqual(warnings[0]["category"], DeprecationWarning)
  333. self.assertEqual(
  334. warnings[0]["message"],
  335. "twisted.internet.defer.deferredGenerator was deprecated in "
  336. "Twisted 15.0.0; please use "
  337. "twisted.internet.defer.inlineCallbacks instead",
  338. )
  339. def test_waitForDeferredDeprecated(self):
  340. """
  341. L{waitForDeferred} is deprecated.
  342. """
  343. d = Deferred()
  344. waitForDeferred(d)
  345. warnings = self.flushWarnings([self.test_waitForDeferredDeprecated])
  346. self.assertEqual(len(warnings), 1)
  347. self.assertEqual(warnings[0]["category"], DeprecationWarning)
  348. self.assertEqual(
  349. warnings[0]["message"],
  350. "twisted.internet.defer.waitForDeferred was deprecated in "
  351. "Twisted 15.0.0; please use "
  352. "twisted.internet.defer.inlineCallbacks instead",
  353. )