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_pcp.py 12KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. # -*- Python -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. __version__ = "$Revision: 1.5 $"[11:-2]
  5. from twisted.protocols import pcp
  6. from twisted.trial import unittest
  7. # Goal:
  8. # Take a Protocol instance. Own all outgoing data - anything that
  9. # would go to p.transport.write. Own all incoming data - anything
  10. # that comes to p.dataReceived.
  11. # I need:
  12. # Something with the AbstractFileDescriptor interface.
  13. # That is:
  14. # - acts as a Transport
  15. # - has a method write()
  16. # - which buffers
  17. # - acts as a Consumer
  18. # - has a registerProducer, unRegisterProducer
  19. # - tells the Producer to back off (pauseProducing) when its buffer is full.
  20. # - tells the Producer to resumeProducing when its buffer is not so full.
  21. # - acts as a Producer
  22. # - calls registerProducer
  23. # - calls write() on consumers
  24. # - honors requests to pause/resume producing
  25. # - honors stopProducing, and passes it along to upstream Producers
  26. class DummyTransport:
  27. """A dumb transport to wrap around."""
  28. def __init__(self):
  29. self._writes = []
  30. def write(self, data):
  31. self._writes.append(data)
  32. def getvalue(self):
  33. return "".join(self._writes)
  34. class DummyProducer:
  35. resumed = False
  36. stopped = False
  37. paused = False
  38. def __init__(self, consumer):
  39. self.consumer = consumer
  40. def resumeProducing(self):
  41. self.resumed = True
  42. self.paused = False
  43. def pauseProducing(self):
  44. self.paused = True
  45. def stopProducing(self):
  46. self.stopped = True
  47. class DummyConsumer(DummyTransport):
  48. producer = None
  49. finished = False
  50. unregistered = True
  51. def registerProducer(self, producer, streaming):
  52. self.producer = (producer, streaming)
  53. def unregisterProducer(self):
  54. self.unregistered = True
  55. def finish(self):
  56. self.finished = True
  57. class TransportInterfaceTests(unittest.TestCase):
  58. proxyClass = pcp.BasicProducerConsumerProxy
  59. def setUp(self):
  60. self.underlying = DummyConsumer()
  61. self.transport = self.proxyClass(self.underlying)
  62. def testWrite(self):
  63. self.transport.write("some bytes")
  64. class ConsumerInterfaceTest:
  65. """Test ProducerConsumerProxy as a Consumer.
  66. Normally we have ProducingServer -> ConsumingTransport.
  67. If I am to go between (Server -> Shaper -> Transport), I have to
  68. play the role of Consumer convincingly for the ProducingServer.
  69. """
  70. def setUp(self):
  71. self.underlying = DummyConsumer()
  72. self.consumer = self.proxyClass(self.underlying)
  73. self.producer = DummyProducer(self.consumer)
  74. def testRegisterPush(self):
  75. self.consumer.registerProducer(self.producer, True)
  76. ## Consumer should NOT have called PushProducer.resumeProducing
  77. self.assertFalse(self.producer.resumed)
  78. ## I'm I'm just a proxy, should I only do resumeProducing when
  79. ## I get poked myself?
  80. # def testRegisterPull(self):
  81. # self.consumer.registerProducer(self.producer, False)
  82. # ## Consumer SHOULD have called PushProducer.resumeProducing
  83. # self.assertTrue(self.producer.resumed)
  84. def testUnregister(self):
  85. self.consumer.registerProducer(self.producer, False)
  86. self.consumer.unregisterProducer()
  87. # Now when the consumer would ordinarily want more data, it
  88. # shouldn't ask producer for it.
  89. # The most succinct way to trigger "want more data" is to proxy for
  90. # a PullProducer and have someone ask me for data.
  91. self.producer.resumed = False
  92. self.consumer.resumeProducing()
  93. self.assertFalse(self.producer.resumed)
  94. def testFinish(self):
  95. self.consumer.registerProducer(self.producer, False)
  96. self.consumer.finish()
  97. # I guess finish should behave like unregister?
  98. self.producer.resumed = False
  99. self.consumer.resumeProducing()
  100. self.assertFalse(self.producer.resumed)
  101. class ProducerInterfaceTest:
  102. """Test ProducerConsumerProxy as a Producer.
  103. Normally we have ProducingServer -> ConsumingTransport.
  104. If I am to go between (Server -> Shaper -> Transport), I have to
  105. play the role of Producer convincingly for the ConsumingTransport.
  106. """
  107. def setUp(self):
  108. self.consumer = DummyConsumer()
  109. self.producer = self.proxyClass(self.consumer)
  110. def testRegistersProducer(self):
  111. self.assertEqual(self.consumer.producer[0], self.producer)
  112. def testPause(self):
  113. self.producer.pauseProducing()
  114. self.producer.write("yakkity yak")
  115. self.assertFalse(
  116. self.consumer.getvalue(), "Paused producer should not have sent data."
  117. )
  118. def testResume(self):
  119. self.producer.pauseProducing()
  120. self.producer.resumeProducing()
  121. self.producer.write("yakkity yak")
  122. self.assertEqual(self.consumer.getvalue(), "yakkity yak")
  123. def testResumeNoEmptyWrite(self):
  124. self.producer.pauseProducing()
  125. self.producer.resumeProducing()
  126. self.assertEqual(
  127. len(self.consumer._writes), 0, "Resume triggered an empty write."
  128. )
  129. def testResumeBuffer(self):
  130. self.producer.pauseProducing()
  131. self.producer.write("buffer this")
  132. self.producer.resumeProducing()
  133. self.assertEqual(self.consumer.getvalue(), "buffer this")
  134. def testStop(self):
  135. self.producer.stopProducing()
  136. self.producer.write("yakkity yak")
  137. self.assertFalse(
  138. self.consumer.getvalue(), "Stopped producer should not have sent data."
  139. )
  140. class PCP_ConsumerInterfaceTests(ConsumerInterfaceTest, unittest.TestCase):
  141. proxyClass = pcp.BasicProducerConsumerProxy
  142. class PCPII_ConsumerInterfaceTests(ConsumerInterfaceTest, unittest.TestCase):
  143. proxyClass = pcp.ProducerConsumerProxy
  144. class PCP_ProducerInterfaceTests(ProducerInterfaceTest, unittest.TestCase):
  145. proxyClass = pcp.BasicProducerConsumerProxy
  146. class PCPII_ProducerInterfaceTests(ProducerInterfaceTest, unittest.TestCase):
  147. proxyClass = pcp.ProducerConsumerProxy
  148. class ProducerProxyTests(unittest.TestCase):
  149. """Producer methods on me should be relayed to the Producer I proxy."""
  150. proxyClass = pcp.BasicProducerConsumerProxy
  151. def setUp(self):
  152. self.proxy = self.proxyClass(None)
  153. self.parentProducer = DummyProducer(self.proxy)
  154. self.proxy.registerProducer(self.parentProducer, True)
  155. def testStop(self):
  156. self.proxy.stopProducing()
  157. self.assertTrue(self.parentProducer.stopped)
  158. class ConsumerProxyTests(unittest.TestCase):
  159. """Consumer methods on me should be relayed to the Consumer I proxy."""
  160. proxyClass = pcp.BasicProducerConsumerProxy
  161. def setUp(self):
  162. self.underlying = DummyConsumer()
  163. self.consumer = self.proxyClass(self.underlying)
  164. def testWrite(self):
  165. # NOTE: This test only valid for streaming (Push) systems.
  166. self.consumer.write("some bytes")
  167. self.assertEqual(self.underlying.getvalue(), "some bytes")
  168. def testFinish(self):
  169. self.consumer.finish()
  170. self.assertTrue(self.underlying.finished)
  171. def testUnregister(self):
  172. self.consumer.unregisterProducer()
  173. self.assertTrue(self.underlying.unregistered)
  174. class PullProducerTest:
  175. def setUp(self):
  176. self.underlying = DummyConsumer()
  177. self.proxy = self.proxyClass(self.underlying)
  178. self.parentProducer = DummyProducer(self.proxy)
  179. self.proxy.registerProducer(self.parentProducer, True)
  180. def testHoldWrites(self):
  181. self.proxy.write("hello")
  182. # Consumer should get no data before it says resumeProducing.
  183. self.assertFalse(
  184. self.underlying.getvalue(), "Pulling Consumer got data before it pulled."
  185. )
  186. def testPull(self):
  187. self.proxy.write("hello")
  188. self.proxy.resumeProducing()
  189. self.assertEqual(self.underlying.getvalue(), "hello")
  190. def testMergeWrites(self):
  191. self.proxy.write("hello ")
  192. self.proxy.write("sunshine")
  193. self.proxy.resumeProducing()
  194. nwrites = len(self.underlying._writes)
  195. self.assertEqual(
  196. nwrites, 1, "Pull resulted in %d writes instead " "of 1." % (nwrites,)
  197. )
  198. self.assertEqual(self.underlying.getvalue(), "hello sunshine")
  199. def testLateWrite(self):
  200. # consumer sends its initial pull before we have data
  201. self.proxy.resumeProducing()
  202. self.proxy.write("data")
  203. # This data should answer that pull request.
  204. self.assertEqual(self.underlying.getvalue(), "data")
  205. class PCP_PullProducerTests(PullProducerTest, unittest.TestCase):
  206. class proxyClass(pcp.BasicProducerConsumerProxy):
  207. iAmStreaming = False
  208. class PCPII_PullProducerTests(PullProducerTest, unittest.TestCase):
  209. class proxyClass(pcp.ProducerConsumerProxy):
  210. iAmStreaming = False
  211. # Buffering!
  212. class BufferedConsumerTests(unittest.TestCase):
  213. """As a consumer, ask the producer to pause after too much data."""
  214. proxyClass = pcp.ProducerConsumerProxy
  215. def setUp(self):
  216. self.underlying = DummyConsumer()
  217. self.proxy = self.proxyClass(self.underlying)
  218. self.proxy.bufferSize = 100
  219. self.parentProducer = DummyProducer(self.proxy)
  220. self.proxy.registerProducer(self.parentProducer, True)
  221. def testRegisterPull(self):
  222. self.proxy.registerProducer(self.parentProducer, False)
  223. ## Consumer SHOULD have called PushProducer.resumeProducing
  224. self.assertTrue(self.parentProducer.resumed)
  225. def testPauseIntercept(self):
  226. self.proxy.pauseProducing()
  227. self.assertFalse(self.parentProducer.paused)
  228. def testResumeIntercept(self):
  229. self.proxy.pauseProducing()
  230. self.proxy.resumeProducing()
  231. # With a streaming producer, just because the proxy was resumed is
  232. # not necessarily a reason to resume the parent producer. The state
  233. # of the buffer should decide that.
  234. self.assertFalse(self.parentProducer.resumed)
  235. def testTriggerPause(self):
  236. """Make sure I say \"when.\" """
  237. # Pause the proxy so data sent to it builds up in its buffer.
  238. self.proxy.pauseProducing()
  239. self.assertFalse(self.parentProducer.paused, "don't pause yet")
  240. self.proxy.write("x" * 51)
  241. self.assertFalse(self.parentProducer.paused, "don't pause yet")
  242. self.proxy.write("x" * 51)
  243. self.assertTrue(self.parentProducer.paused)
  244. def testTriggerResume(self):
  245. """Make sure I resumeProducing when my buffer empties."""
  246. self.proxy.pauseProducing()
  247. self.proxy.write("x" * 102)
  248. self.assertTrue(self.parentProducer.paused, "should be paused")
  249. self.proxy.resumeProducing()
  250. # Resuming should have emptied my buffer, so I should tell my
  251. # parent to resume too.
  252. self.assertFalse(self.parentProducer.paused, "Producer should have resumed.")
  253. self.assertFalse(self.proxy.producerPaused)
  254. class BufferedPullTests(unittest.TestCase):
  255. class proxyClass(pcp.ProducerConsumerProxy):
  256. iAmStreaming = False
  257. def _writeSomeData(self, data):
  258. pcp.ProducerConsumerProxy._writeSomeData(self, data[:100])
  259. return min(len(data), 100)
  260. def setUp(self):
  261. self.underlying = DummyConsumer()
  262. self.proxy = self.proxyClass(self.underlying)
  263. self.proxy.bufferSize = 100
  264. self.parentProducer = DummyProducer(self.proxy)
  265. self.proxy.registerProducer(self.parentProducer, False)
  266. def testResumePull(self):
  267. # If proxy has no data to send on resumeProducing, it had better pull
  268. # some from its PullProducer.
  269. self.parentProducer.resumed = False
  270. self.proxy.resumeProducing()
  271. self.assertTrue(self.parentProducer.resumed)
  272. def testLateWriteBuffering(self):
  273. # consumer sends its initial pull before we have data
  274. self.proxy.resumeProducing()
  275. self.proxy.write("datum" * 21)
  276. # This data should answer that pull request.
  277. self.assertEqual(self.underlying.getvalue(), "datum" * 20)
  278. # but there should be some left over
  279. self.assertEqual(self.proxy._buffer, ["datum"])
  280. # TODO:
  281. # test that web request finishing bug (when we weren't proxying
  282. # unregisterProducer but were proxying finish, web file transfers
  283. # would hang on the last block.)
  284. # test what happens if writeSomeBytes decided to write zero bytes.