|
1 | 1 | # Some simple queue module tests, plus some failure conditions
|
2 | 2 | # to ensure the Queue locks remain stable.
|
| 3 | +import collections |
3 | 4 | import queue
|
| 5 | +import random |
4 | 6 | import time
|
5 | 7 | import unittest
|
6 | 8 | from test import support
|
@@ -90,7 +92,7 @@ def setUp(self):
|
90 | 92 | self.cum = 0
|
91 | 93 | self.cumlock = threading.Lock()
|
92 | 94 |
|
93 |
| - def simple_queue_test(self, q): |
| 95 | + def basic_queue_test(self, q): |
94 | 96 | if q.qsize():
|
95 | 97 | raise RuntimeError("Call this function with an empty queue")
|
96 | 98 | self.assertTrue(q.empty())
|
@@ -193,12 +195,12 @@ def test_queue_join(self):
|
193 | 195 | else:
|
194 | 196 | self.fail("Did not detect task count going negative")
|
195 | 197 |
|
196 |
| - def test_simple_queue(self): |
| 198 | + def test_basic(self): |
197 | 199 | # Do it a couple of times on the same queue.
|
198 | 200 | # Done twice to make sure works with same instance reused.
|
199 | 201 | q = self.type2test(QUEUE_SIZE)
|
200 |
| - self.simple_queue_test(q) |
201 |
| - self.simple_queue_test(q) |
| 202 | + self.basic_queue_test(q) |
| 203 | + self.basic_queue_test(q) |
202 | 204 |
|
203 | 205 | def test_negative_timeout_raises_exception(self):
|
204 | 206 | q = self.type2test(QUEUE_SIZE)
|
@@ -354,5 +356,144 @@ def test_failing_queue(self):
|
354 | 356 | self.failing_queue_test(q)
|
355 | 357 |
|
356 | 358 |
|
| 359 | +class SimpleQueueTest(unittest.TestCase): |
| 360 | + type2test = queue.SimpleQueue |
| 361 | + |
| 362 | + def setUp(self): |
| 363 | + self.q = self.type2test() |
| 364 | + |
| 365 | + def feed(self, q, seq, rnd): |
| 366 | + while True: |
| 367 | + try: |
| 368 | + val = seq.pop() |
| 369 | + except IndexError: |
| 370 | + return |
| 371 | + q.put(val) |
| 372 | + if rnd.random() > 0.5: |
| 373 | + time.sleep(rnd.random() * 1e-3) |
| 374 | + |
| 375 | + def consume(self, q, results, sentinel): |
| 376 | + while True: |
| 377 | + val = q.get() |
| 378 | + if val == sentinel: |
| 379 | + return |
| 380 | + results.append(val) |
| 381 | + |
| 382 | + def consume_nonblock(self, q, results, sentinel): |
| 383 | + while True: |
| 384 | + while True: |
| 385 | + try: |
| 386 | + val = q.get(block=False) |
| 387 | + except queue.Empty: |
| 388 | + time.sleep(1e-5) |
| 389 | + else: |
| 390 | + break |
| 391 | + if val == sentinel: |
| 392 | + return |
| 393 | + results.append(val) |
| 394 | + |
| 395 | + def consume_timeout(self, q, results, sentinel): |
| 396 | + while True: |
| 397 | + while True: |
| 398 | + try: |
| 399 | + val = q.get(timeout=1e-5) |
| 400 | + except queue.Empty: |
| 401 | + pass |
| 402 | + else: |
| 403 | + break |
| 404 | + if val == sentinel: |
| 405 | + return |
| 406 | + results.append(val) |
| 407 | + |
| 408 | + def run_threads(self, n_feeders, n_consumers, q, inputs, |
| 409 | + feed_func, consume_func): |
| 410 | + results = [] |
| 411 | + sentinel = None |
| 412 | + seq = inputs + [sentinel] * n_consumers |
| 413 | + seq.reverse() |
| 414 | + rnd = random.Random(42) |
| 415 | + |
| 416 | + exceptions = [] |
| 417 | + def log_exceptions(f): |
| 418 | + def wrapper(*args, **kwargs): |
| 419 | + try: |
| 420 | + f(*args, **kwargs) |
| 421 | + except BaseException as e: |
| 422 | + exceptions.append(e) |
| 423 | + return wrapper |
| 424 | + |
| 425 | + feeders = [threading.Thread(target=log_exceptions(feed_func), |
| 426 | + args=(q, seq, rnd)) |
| 427 | + for i in range(n_feeders)] |
| 428 | + consumers = [threading.Thread(target=log_exceptions(consume_func), |
| 429 | + args=(q, results, sentinel)) |
| 430 | + for i in range(n_consumers)] |
| 431 | + |
| 432 | + with support.start_threads(feeders + consumers): |
| 433 | + pass |
| 434 | + |
| 435 | + self.assertFalse(exceptions) |
| 436 | + |
| 437 | + return results |
| 438 | + |
| 439 | + def test_simple(self): |
| 440 | + q = self.q |
| 441 | + q.put(1) |
| 442 | + q.put(2) |
| 443 | + q.put(3) |
| 444 | + q.put(4) |
| 445 | + self.assertEqual(q.get(), 1) |
| 446 | + self.assertEqual(q.get(), 2) |
| 447 | + self.assertEqual(q.get(block=False), 3) |
| 448 | + self.assertEqual(q.get(timeout=0.1), 4) |
| 449 | + with self.assertRaises(queue.Empty): |
| 450 | + q.get(block=False) |
| 451 | + with self.assertRaises(queue.Empty): |
| 452 | + q.get(timeout=1e-3) |
| 453 | + |
| 454 | + def test_negative_timeout_raises_exception(self): |
| 455 | + q = self.q |
| 456 | + q.put(1) |
| 457 | + with self.assertRaises(ValueError): |
| 458 | + q.get(timeout=-1) |
| 459 | + |
| 460 | + def test_order(self): |
| 461 | + N = 3 |
| 462 | + q = self.q |
| 463 | + inputs = list(range(100)) |
| 464 | + results = self.run_threads(N, 1, q, inputs, self.feed, self.consume) |
| 465 | + |
| 466 | + # One consumer => results appended in well-defined order |
| 467 | + self.assertEqual(results, inputs) |
| 468 | + |
| 469 | + def test_many_threads(self): |
| 470 | + N = 50 |
| 471 | + q = self.q |
| 472 | + inputs = list(range(10000)) |
| 473 | + results = self.run_threads(N, N, q, inputs, self.feed, self.consume) |
| 474 | + |
| 475 | + # Multiple consumers without synchronization append the |
| 476 | + # results in random order |
| 477 | + self.assertEqual(sorted(results), inputs) |
| 478 | + |
| 479 | + def test_many_threads_nonblock(self): |
| 480 | + N = 50 |
| 481 | + q = self.q |
| 482 | + inputs = list(range(10000)) |
| 483 | + results = self.run_threads(N, N, q, inputs, |
| 484 | + self.feed, self.consume_nonblock) |
| 485 | + |
| 486 | + self.assertEqual(sorted(results), inputs) |
| 487 | + |
| 488 | + def test_many_threads_timeout(self): |
| 489 | + N = 50 |
| 490 | + q = self.q |
| 491 | + inputs = list(range(10000)) |
| 492 | + results = self.run_threads(N, N, q, inputs, |
| 493 | + self.feed, self.consume_timeout) |
| 494 | + |
| 495 | + self.assertEqual(sorted(results), inputs) |
| 496 | + |
| 497 | + |
357 | 498 | if __name__ == "__main__":
|
358 | 499 | unittest.main()
|
0 commit comments