PageRenderTime 58ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/win32/ppython/App/Lib/test/test_queue.py

https://gitlab.com/minoca/tools
Python | 325 lines | 247 code | 33 blank | 45 comment | 52 complexity | b11beedd04acd7ba73954bd84120b40c MD5 | raw file
  1. # Some simple queue module tests, plus some failure conditions
  2. # to ensure the Queue locks remain stable.
  3. import Queue
  4. import time
  5. import unittest
  6. from test import test_support
  7. threading = test_support.import_module('threading')
  8. QUEUE_SIZE = 5
  9. # A thread to run a function that unclogs a blocked Queue.
  10. class _TriggerThread(threading.Thread):
  11. def __init__(self, fn, args):
  12. self.fn = fn
  13. self.args = args
  14. self.startedEvent = threading.Event()
  15. threading.Thread.__init__(self)
  16. def run(self):
  17. # The sleep isn't necessary, but is intended to give the blocking
  18. # function in the main thread a chance at actually blocking before
  19. # we unclog it. But if the sleep is longer than the timeout-based
  20. # tests wait in their blocking functions, those tests will fail.
  21. # So we give them much longer timeout values compared to the
  22. # sleep here (I aimed at 10 seconds for blocking functions --
  23. # they should never actually wait that long - they should make
  24. # progress as soon as we call self.fn()).
  25. time.sleep(0.1)
  26. self.startedEvent.set()
  27. self.fn(*self.args)
  28. # Execute a function that blocks, and in a separate thread, a function that
  29. # triggers the release. Returns the result of the blocking function. Caution:
  30. # block_func must guarantee to block until trigger_func is called, and
  31. # trigger_func must guarantee to change queue state so that block_func can make
  32. # enough progress to return. In particular, a block_func that just raises an
  33. # exception regardless of whether trigger_func is called will lead to
  34. # timing-dependent sporadic failures, and one of those went rarely seen but
  35. # undiagnosed for years. Now block_func must be unexceptional. If block_func
  36. # is supposed to raise an exception, call do_exceptional_blocking_test()
  37. # instead.
  38. class BlockingTestMixin:
  39. def tearDown(self):
  40. self.t = None
  41. def do_blocking_test(self, block_func, block_args, trigger_func, trigger_args):
  42. self.t = _TriggerThread(trigger_func, trigger_args)
  43. self.t.start()
  44. self.result = block_func(*block_args)
  45. # If block_func returned before our thread made the call, we failed!
  46. if not self.t.startedEvent.is_set():
  47. self.fail("blocking function '%r' appeared not to block" %
  48. block_func)
  49. self.t.join(10) # make sure the thread terminates
  50. if self.t.is_alive():
  51. self.fail("trigger function '%r' appeared to not return" %
  52. trigger_func)
  53. return self.result
  54. # Call this instead if block_func is supposed to raise an exception.
  55. def do_exceptional_blocking_test(self,block_func, block_args, trigger_func,
  56. trigger_args, expected_exception_class):
  57. self.t = _TriggerThread(trigger_func, trigger_args)
  58. self.t.start()
  59. try:
  60. try:
  61. block_func(*block_args)
  62. except expected_exception_class:
  63. raise
  64. else:
  65. self.fail("expected exception of kind %r" %
  66. expected_exception_class)
  67. finally:
  68. self.t.join(10) # make sure the thread terminates
  69. if self.t.is_alive():
  70. self.fail("trigger function '%r' appeared to not return" %
  71. trigger_func)
  72. if not self.t.startedEvent.is_set():
  73. self.fail("trigger thread ended but event never set")
  74. class BaseQueueTest(BlockingTestMixin):
  75. def setUp(self):
  76. self.cum = 0
  77. self.cumlock = threading.Lock()
  78. def simple_queue_test(self, q):
  79. if not q.empty():
  80. raise RuntimeError, "Call this function with an empty queue"
  81. # I guess we better check things actually queue correctly a little :)
  82. q.put(111)
  83. q.put(333)
  84. q.put(222)
  85. target_order = dict(Queue = [111, 333, 222],
  86. LifoQueue = [222, 333, 111],
  87. PriorityQueue = [111, 222, 333])
  88. actual_order = [q.get(), q.get(), q.get()]
  89. self.assertEqual(actual_order, target_order[q.__class__.__name__],
  90. "Didn't seem to queue the correct data!")
  91. for i in range(QUEUE_SIZE-1):
  92. q.put(i)
  93. self.assertTrue(not q.empty(), "Queue should not be empty")
  94. self.assertTrue(not q.full(), "Queue should not be full")
  95. last = 2 * QUEUE_SIZE
  96. full = 3 * 2 * QUEUE_SIZE
  97. q.put(last)
  98. self.assertTrue(q.full(), "Queue should be full")
  99. try:
  100. q.put(full, block=0)
  101. self.fail("Didn't appear to block with a full queue")
  102. except Queue.Full:
  103. pass
  104. try:
  105. q.put(full, timeout=0.01)
  106. self.fail("Didn't appear to time-out with a full queue")
  107. except Queue.Full:
  108. pass
  109. # Test a blocking put
  110. self.do_blocking_test(q.put, (full,), q.get, ())
  111. self.do_blocking_test(q.put, (full, True, 10), q.get, ())
  112. # Empty it
  113. for i in range(QUEUE_SIZE):
  114. q.get()
  115. self.assertTrue(q.empty(), "Queue should be empty")
  116. try:
  117. q.get(block=0)
  118. self.fail("Didn't appear to block with an empty queue")
  119. except Queue.Empty:
  120. pass
  121. try:
  122. q.get(timeout=0.01)
  123. self.fail("Didn't appear to time-out with an empty queue")
  124. except Queue.Empty:
  125. pass
  126. # Test a blocking get
  127. self.do_blocking_test(q.get, (), q.put, ('empty',))
  128. self.do_blocking_test(q.get, (True, 10), q.put, ('empty',))
  129. def worker(self, q):
  130. while True:
  131. x = q.get()
  132. if x is None:
  133. q.task_done()
  134. return
  135. with self.cumlock:
  136. self.cum += x
  137. q.task_done()
  138. def queue_join_test(self, q):
  139. self.cum = 0
  140. for i in (0,1):
  141. threading.Thread(target=self.worker, args=(q,)).start()
  142. for i in xrange(100):
  143. q.put(i)
  144. q.join()
  145. self.assertEqual(self.cum, sum(range(100)),
  146. "q.join() did not block until all tasks were done")
  147. for i in (0,1):
  148. q.put(None) # instruct the threads to close
  149. q.join() # verify that you can join twice
  150. def test_queue_task_done(self):
  151. # Test to make sure a queue task completed successfully.
  152. q = self.type2test()
  153. try:
  154. q.task_done()
  155. except ValueError:
  156. pass
  157. else:
  158. self.fail("Did not detect task count going negative")
  159. def test_queue_join(self):
  160. # Test that a queue join()s successfully, and before anything else
  161. # (done twice for insurance).
  162. q = self.type2test()
  163. self.queue_join_test(q)
  164. self.queue_join_test(q)
  165. try:
  166. q.task_done()
  167. except ValueError:
  168. pass
  169. else:
  170. self.fail("Did not detect task count going negative")
  171. def test_simple_queue(self):
  172. # Do it a couple of times on the same queue.
  173. # Done twice to make sure works with same instance reused.
  174. q = self.type2test(QUEUE_SIZE)
  175. self.simple_queue_test(q)
  176. self.simple_queue_test(q)
  177. class QueueTest(BaseQueueTest, unittest.TestCase):
  178. type2test = Queue.Queue
  179. class LifoQueueTest(BaseQueueTest, unittest.TestCase):
  180. type2test = Queue.LifoQueue
  181. class PriorityQueueTest(BaseQueueTest, unittest.TestCase):
  182. type2test = Queue.PriorityQueue
  183. # A Queue subclass that can provoke failure at a moment's notice :)
  184. class FailingQueueException(Exception):
  185. pass
  186. class FailingQueue(Queue.Queue):
  187. def __init__(self, *args):
  188. self.fail_next_put = False
  189. self.fail_next_get = False
  190. Queue.Queue.__init__(self, *args)
  191. def _put(self, item):
  192. if self.fail_next_put:
  193. self.fail_next_put = False
  194. raise FailingQueueException, "You Lose"
  195. return Queue.Queue._put(self, item)
  196. def _get(self):
  197. if self.fail_next_get:
  198. self.fail_next_get = False
  199. raise FailingQueueException, "You Lose"
  200. return Queue.Queue._get(self)
  201. class FailingQueueTest(BlockingTestMixin, unittest.TestCase):
  202. def failing_queue_test(self, q):
  203. if not q.empty():
  204. raise RuntimeError, "Call this function with an empty queue"
  205. for i in range(QUEUE_SIZE-1):
  206. q.put(i)
  207. # Test a failing non-blocking put.
  208. q.fail_next_put = True
  209. try:
  210. q.put("oops", block=0)
  211. self.fail("The queue didn't fail when it should have")
  212. except FailingQueueException:
  213. pass
  214. q.fail_next_put = True
  215. try:
  216. q.put("oops", timeout=0.1)
  217. self.fail("The queue didn't fail when it should have")
  218. except FailingQueueException:
  219. pass
  220. q.put("last")
  221. self.assertTrue(q.full(), "Queue should be full")
  222. # Test a failing blocking put
  223. q.fail_next_put = True
  224. try:
  225. self.do_blocking_test(q.put, ("full",), q.get, ())
  226. self.fail("The queue didn't fail when it should have")
  227. except FailingQueueException:
  228. pass
  229. # Check the Queue isn't damaged.
  230. # put failed, but get succeeded - re-add
  231. q.put("last")
  232. # Test a failing timeout put
  233. q.fail_next_put = True
  234. try:
  235. self.do_exceptional_blocking_test(q.put, ("full", True, 10), q.get, (),
  236. FailingQueueException)
  237. self.fail("The queue didn't fail when it should have")
  238. except FailingQueueException:
  239. pass
  240. # Check the Queue isn't damaged.
  241. # put failed, but get succeeded - re-add
  242. q.put("last")
  243. self.assertTrue(q.full(), "Queue should be full")
  244. q.get()
  245. self.assertTrue(not q.full(), "Queue should not be full")
  246. q.put("last")
  247. self.assertTrue(q.full(), "Queue should be full")
  248. # Test a blocking put
  249. self.do_blocking_test(q.put, ("full",), q.get, ())
  250. # Empty it
  251. for i in range(QUEUE_SIZE):
  252. q.get()
  253. self.assertTrue(q.empty(), "Queue should be empty")
  254. q.put("first")
  255. q.fail_next_get = True
  256. try:
  257. q.get()
  258. self.fail("The queue didn't fail when it should have")
  259. except FailingQueueException:
  260. pass
  261. self.assertTrue(not q.empty(), "Queue should not be empty")
  262. q.fail_next_get = True
  263. try:
  264. q.get(timeout=0.1)
  265. self.fail("The queue didn't fail when it should have")
  266. except FailingQueueException:
  267. pass
  268. self.assertTrue(not q.empty(), "Queue should not be empty")
  269. q.get()
  270. self.assertTrue(q.empty(), "Queue should be empty")
  271. q.fail_next_get = True
  272. try:
  273. self.do_exceptional_blocking_test(q.get, (), q.put, ('empty',),
  274. FailingQueueException)
  275. self.fail("The queue didn't fail when it should have")
  276. except FailingQueueException:
  277. pass
  278. # put succeeded, but get failed.
  279. self.assertTrue(not q.empty(), "Queue should not be empty")
  280. q.get()
  281. self.assertTrue(q.empty(), "Queue should be empty")
  282. def test_failing_queue(self):
  283. # Test to make sure a queue is functioning correctly.
  284. # Done twice to the same instance.
  285. q = FailingQueue(QUEUE_SIZE)
  286. self.failing_queue_test(q)
  287. self.failing_queue_test(q)
  288. def test_main():
  289. test_support.run_unittest(QueueTest, LifoQueueTest, PriorityQueueTest,
  290. FailingQueueTest)
  291. if __name__ == "__main__":
  292. test_main()