77import time
88import weakref
99from concurrent .futures import TimeoutError
10+ from itertools import islice
1011
1112import pytest
1213
@@ -140,9 +141,11 @@ def test_map(executor):
140141 results = list (executor .map (lambda x : x + 1 , range (10 )))
141142 assert results == [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]
142143
144+ results = list (executor .map (lambda x , y : x + y , range (10 ), range (9 )))
145+ assert results == [0 , 2 , 4 , 6 , 8 , 10 , 12 , 14 , 16 ]
143146
144- @ pytest . mark . parametrize ( "cancel" , [ True , False ])
145- def test_map_timeout (executor , cancel ):
147+
148+ def test_map_timeout (executor ):
146149 """Test that map with timeout raises TimeoutError and cancels futures"""
147150 results = []
148151
@@ -158,15 +161,61 @@ def func(x):
158161 duration = time .monotonic () - start
159162 assert duration < 0.05
160163
164+ executor .shutdown (wait = True )
165+ # only about half of the tasks should have completed
166+ # because the max number of workers is 5 and the rest of
167+ # the tasks were not started at the time of the cancel.
168+ assert set (results ) != {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }
169+
170+
171+ def test_map_error (executor ):
172+ """Test that map with an exception will raise, and remaining tasks are cancelled"""
173+ results = []
174+
175+ def func (x ):
176+ nonlocal results
177+ time .sleep (0.05 )
178+ if len (results ) == 5 :
179+ raise ValueError ("Test error" )
180+ results .append (x )
181+ return x
182+
183+ with pytest .raises (ValueError ):
184+ list (executor .map (func , range (15 )))
185+
186+ executor .shutdown (wait = True , cancel_futures = False )
187+ assert len (results ) <= 10 , "Final 5 at least should have been cancelled"
188+
189+
190+ @pytest .mark .parametrize ("cancel" , [True , False ])
191+ def test_map_shutdown (executor , cancel ):
192+ results = []
193+
194+ def func (x ):
195+ nonlocal results
196+ time .sleep (0.05 )
197+ results .append (x )
198+ return x
199+
200+ # Get the first few results.
201+ # Keep the iterator alive so that it isn't closed when its reference is dropped.
202+ m = executor .map (func , range (15 ))
203+ values = list (islice (m , 5 ))
204+ assert values == [0 , 1 , 2 , 3 , 4 ]
205+
161206 executor .shutdown (wait = True , cancel_futures = cancel )
162- if not cancel :
163- # they were not cancelled
164- assert set (results ) == {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }
207+ if cancel :
208+ assert len (results ) < 15 , "Some tasks should have been cancelled"
165209 else :
166- # only about half of the tasks should have completed
167- # because the max number of workers is 5 and the rest of
168- # the tasks were not started at the time of the cancel.
169- assert set (results ) != {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }
210+ assert len (results ) == 15 , "All tasks should have been completed"
211+
212+
213+ def test_map_start (executor ):
214+ """Test that map starts tasks immediately, before iterating"""
215+ e = threading .Event ()
216+ m = executor .map (lambda x : (e .set (), x ), range (1 ))
217+ e .wait (timeout = 0.1 )
218+ assert list (m ) == [(None , 0 )]
170219
171220
172221def test_context (executor ):
0 commit comments