Skip to content

Commit 175194b

Browse files
authored
Add tee test to test_itertools.py module.
1 parent 3dab9e0 commit 175194b

1 file changed

Lines changed: 80 additions & 8 deletions

File tree

Lib/test/test_free_threading/test_itertools.py

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import unittest
2-
from itertools import accumulate, batched, chain, combinations_with_replacement, cycle, permutations, zip_longest
2+
from itertools import (
3+
accumulate,
4+
batched,
5+
chain,
6+
combinations_with_replacement,
7+
cycle,
8+
permutations,
9+
tee,
10+
zip_longest,
11+
)
312
from test.support import threading_helper
413

514

@@ -15,20 +24,23 @@ def work_iterator(it):
1524

1625

1726
class ItertoolsThreading(unittest.TestCase):
18-
1927
@threading_helper.reap_threads
2028
def test_accumulate(self):
2129
number_of_iterations = 10
2230
for _ in range(number_of_iterations):
2331
it = accumulate(tuple(range(40)))
24-
threading_helper.run_concurrently(work_iterator, nthreads=10, args=[it])
32+
threading_helper.run_concurrently(
33+
work_iterator, nthreads=10, args=[it]
34+
)
2535

2636
@threading_helper.reap_threads
2737
def test_batched(self):
2838
number_of_iterations = 10
2939
for _ in range(number_of_iterations):
3040
it = batched(tuple(range(1000)), 2)
31-
threading_helper.run_concurrently(work_iterator, nthreads=10, args=[it])
41+
threading_helper.run_concurrently(
42+
work_iterator, nthreads=10, args=[it]
43+
)
3244

3345
@threading_helper.reap_threads
3446
def test_cycle(self):
@@ -46,28 +58,88 @@ def test_chain(self):
4658
number_of_iterations = 10
4759
for _ in range(number_of_iterations):
4860
it = chain(*[(1,)] * 200)
49-
threading_helper.run_concurrently(work_iterator, nthreads=6, args=[it])
61+
threading_helper.run_concurrently(
62+
work_iterator, nthreads=6, args=[it]
63+
)
5064

5165
@threading_helper.reap_threads
5266
def test_combinations_with_replacement(self):
5367
number_of_iterations = 6
5468
for _ in range(number_of_iterations):
5569
it = combinations_with_replacement(tuple(range(2)), 2)
56-
threading_helper.run_concurrently(work_iterator, nthreads=6, args=[it])
70+
threading_helper.run_concurrently(
71+
work_iterator, nthreads=6, args=[it]
72+
)
5773

5874
@threading_helper.reap_threads
5975
def test_permutations(self):
6076
number_of_iterations = 6
6177
for _ in range(number_of_iterations):
6278
it = permutations(tuple(range(4)), 2)
63-
threading_helper.run_concurrently(work_iterator, nthreads=6, args=[it])
79+
threading_helper.run_concurrently(
80+
work_iterator, nthreads=6, args=[it]
81+
)
6482

6583
@threading_helper.reap_threads
6684
def test_zip_longest(self):
6785
number_of_iterations = 10
6886
for _ in range(number_of_iterations):
6987
it = zip_longest(list(range(4)), list(range(8)), fillvalue=0)
70-
threading_helper.run_concurrently(work_iterator, nthreads=10, args=[it])
88+
threading_helper.run_concurrently(
89+
work_iterator, nthreads=10, args=[it]
90+
)
91+
92+
93+
class TestTeeConcurrent(unittest.TestCase):
94+
# itertools.tee branches share a linked list of internal data cells.
95+
# Concurrent iteration must not corrupt that shared state or crash the
96+
# free-threaded build. A crash shows up as the interpreter dying (not as a
97+
# caught exception); tee is documented as not thread-safe, so a
98+
# ``RuntimeError`` from the re-entrancy guard is an allowed outcome and is
99+
# tolerated here.
100+
101+
def test_same_branch(self):
102+
# Many threads consume the same tee branch.
103+
errors = []
104+
105+
def consume(it):
106+
try:
107+
for _ in it:
108+
pass
109+
except RuntimeError:
110+
pass
111+
except Exception as e:
112+
errors.append(e)
113+
114+
for _ in range(100):
115+
a, _ = tee(iter(range(2000)), 2)
116+
threading_helper.run_concurrently(consume, nthreads=8, args=(a,))
117+
118+
self.assertEqual(errors, [], msg=f"unexpected errors: {errors}")
119+
120+
def test_sibling_branches(self):
121+
# Each thread consumes a different sibling branch of the same tee.
122+
errors = []
123+
124+
def make_worker(it):
125+
def consume():
126+
try:
127+
for _ in it:
128+
pass
129+
except RuntimeError:
130+
pass
131+
except Exception as e:
132+
errors.append(e)
133+
134+
return consume
135+
136+
for _ in range(100):
137+
branches = tee(iter(range(4000)), 8)
138+
threading_helper.run_concurrently(
139+
[make_worker(it) for it in branches]
140+
)
141+
142+
self.assertEqual(errors, [], msg=f"unexpected errors: {errors}")
71143

72144

73145
if __name__ == "__main__":

0 commit comments

Comments
 (0)