| import unittest |
| from itertools import ( |
| accumulate, |
| batched, |
| chain, |
| combinations_with_replacement, |
| cycle, |
| permutations, |
| tee, |
| zip_longest, |
| ) |
| from test.support import threading_helper |
| |
| |
| threading_helper.requires_working_threading(module=True) |
| |
| |
| def work_iterator(it): |
| while True: |
| try: |
| next(it) |
| except StopIteration: |
| break |
| |
| |
| class ItertoolsThreading(unittest.TestCase): |
| @threading_helper.reap_threads |
| def test_accumulate(self): |
| number_of_iterations = 10 |
| for _ in range(number_of_iterations): |
| it = accumulate(tuple(range(40))) |
| threading_helper.run_concurrently( |
| work_iterator, nthreads=10, args=[it] |
| ) |
| |
| @threading_helper.reap_threads |
| def test_batched(self): |
| number_of_iterations = 10 |
| for _ in range(number_of_iterations): |
| it = batched(tuple(range(1000)), 2) |
| threading_helper.run_concurrently( |
| work_iterator, nthreads=10, args=[it] |
| ) |
| |
| @threading_helper.reap_threads |
| def test_cycle(self): |
| def work(it): |
| for _ in range(400): |
| next(it) |
| |
| number_of_iterations = 6 |
| for _ in range(number_of_iterations): |
| it = cycle((1, 2, 3, 4)) |
| threading_helper.run_concurrently(work, nthreads=6, args=[it]) |
| |
| @threading_helper.reap_threads |
| def test_chain(self): |
| number_of_iterations = 10 |
| for _ in range(number_of_iterations): |
| it = chain(*[(1,)] * 200) |
| threading_helper.run_concurrently( |
| work_iterator, nthreads=6, args=[it] |
| ) |
| |
| @threading_helper.reap_threads |
| def test_combinations_with_replacement(self): |
| number_of_iterations = 6 |
| for _ in range(number_of_iterations): |
| it = combinations_with_replacement(tuple(range(2)), 2) |
| threading_helper.run_concurrently( |
| work_iterator, nthreads=6, args=[it] |
| ) |
| |
| @threading_helper.reap_threads |
| def test_permutations(self): |
| number_of_iterations = 6 |
| for _ in range(number_of_iterations): |
| it = permutations(tuple(range(4)), 2) |
| threading_helper.run_concurrently( |
| work_iterator, nthreads=6, args=[it] |
| ) |
| |
| @threading_helper.reap_threads |
| def test_zip_longest(self): |
| number_of_iterations = 10 |
| for _ in range(number_of_iterations): |
| it = zip_longest(list(range(4)), list(range(8)), fillvalue=0) |
| threading_helper.run_concurrently( |
| work_iterator, nthreads=10, args=[it] |
| ) |
| |
| |
| class TestTeeConcurrent(unittest.TestCase): |
| # itertools.tee branches share a linked list of internal data cells. |
| # Concurrent iteration must not corrupt that shared state or crash the |
| # free-threaded build. A crash shows up as the interpreter dying (not as a |
| # caught exception); tee is documented as not thread-safe, so a |
| # ``RuntimeError`` from the re-entrancy guard is an allowed outcome and is |
| # tolerated here. |
| |
| def test_same_branch(self): |
| # Many threads consume the same tee branch. |
| errors = [] |
| |
| def consume(it): |
| try: |
| for _ in it: |
| pass |
| except RuntimeError: |
| pass |
| except Exception as e: |
| errors.append(e) |
| |
| for _ in range(100): |
| a, _ = tee(iter(range(2000)), 2) |
| threading_helper.run_concurrently(consume, nthreads=8, args=(a,)) |
| |
| self.assertEqual(errors, [], msg=f"unexpected errors: {errors}") |
| |
| def test_sibling_branches(self): |
| # Each thread consumes a different sibling branch of the same tee. |
| errors = [] |
| |
| def make_worker(it): |
| def consume(): |
| try: |
| for _ in it: |
| pass |
| except RuntimeError: |
| pass |
| except Exception as e: |
| errors.append(e) |
| |
| return consume |
| |
| for _ in range(100): |
| branches = tee(iter(range(4000)), 8) |
| threading_helper.run_concurrently( |
| [make_worker(it) for it in branches] |
| ) |
| |
| self.assertEqual(errors, [], msg=f"unexpected errors: {errors}") |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |