Skip to content

Commit 376c4f1

Browse files
authored
Update thread related tests to 3.14.5 (RustPython#8018)
1 parent e840544 commit 376c4f1

6 files changed

Lines changed: 724 additions & 88 deletions

File tree

Lib/test/test_thread.py

Lines changed: 212 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
from test.support import threading_helper
66
import _thread as thread
77
import time
8+
import warnings
89
import weakref
910

1011
from test import lock_tests
1112

13+
threading_helper.requires_working_threading(module=True)
14+
1215
NUMTASKS = 10
1316
NUMTRIPS = 3
14-
POLL_SLEEP = 0.010 # seconds = 10 ms
1517

1618
_print_mutex = thread.allocate_lock()
1719

@@ -74,6 +76,14 @@ def test_stack_size(self):
7476
thread.stack_size(0)
7577
self.assertEqual(thread.stack_size(), 0, "stack_size not reset to default")
7678

79+
with self.assertRaises(ValueError):
80+
# 123 bytes is too small
81+
thread.stack_size(123)
82+
83+
with self.assertRaises(ValueError):
84+
# size must be positive
85+
thread.stack_size(-4096)
86+
7787
@unittest.skipIf(os.name not in ("nt", "posix"), 'test meant for nt and posix')
7888
def test_nt_and_posix_stack_size(self):
7989
try:
@@ -119,22 +129,28 @@ def task():
119129

120130
with threading_helper.wait_threads_exit():
121131
thread.start_new_thread(task, ())
122-
while not started:
123-
time.sleep(POLL_SLEEP)
132+
for _ in support.sleeping_retry(support.LONG_TIMEOUT):
133+
if started:
134+
break
124135
self.assertEqual(thread._count(), orig + 1)
136+
125137
# Allow the task to finish.
126138
mut.release()
139+
127140
# The only reliable way to be sure that the thread ended from the
128-
# interpreter's point of view is to wait for the function object to be
129-
# destroyed.
141+
# interpreter's point of view is to wait for the function object to
142+
# be destroyed.
130143
done = []
131144
wr = weakref.ref(task, lambda _: done.append(None))
132145
del task
133-
while not done:
134-
time.sleep(POLL_SLEEP)
146+
147+
for _ in support.sleeping_retry(support.LONG_TIMEOUT):
148+
if done:
149+
break
135150
support.gc_collect() # For PyPy or other GCs.
136151
self.assertEqual(thread._count(), orig)
137152

153+
@unittest.expectedFailure # TODO: RUSTPYTHON
138154
def test_unraisable_exception(self):
139155
def task():
140156
started.release()
@@ -148,11 +164,190 @@ def task():
148164
started.acquire()
149165

150166
self.assertEqual(str(cm.unraisable.exc_value), "task failed")
151-
self.assertIs(cm.unraisable.object, task)
167+
self.assertIsNone(cm.unraisable.object)
152168
self.assertEqual(cm.unraisable.err_msg,
153-
"Exception ignored in thread started by")
169+
f"Exception ignored in thread started by {task!r}")
154170
self.assertIsNotNone(cm.unraisable.exc_traceback)
155171

172+
def test_join_thread(self):
173+
finished = []
174+
175+
def task():
176+
time.sleep(0.05)
177+
finished.append(thread.get_ident())
178+
179+
with threading_helper.wait_threads_exit():
180+
handle = thread.start_joinable_thread(task)
181+
handle.join()
182+
self.assertEqual(len(finished), 1)
183+
self.assertEqual(handle.ident, finished[0])
184+
185+
def test_join_thread_already_exited(self):
186+
def task():
187+
pass
188+
189+
with threading_helper.wait_threads_exit():
190+
handle = thread.start_joinable_thread(task)
191+
time.sleep(0.05)
192+
handle.join()
193+
194+
def test_join_several_times(self):
195+
def task():
196+
pass
197+
198+
with threading_helper.wait_threads_exit():
199+
handle = thread.start_joinable_thread(task)
200+
handle.join()
201+
# Subsequent join() calls should succeed
202+
handle.join()
203+
204+
def test_joinable_not_joined(self):
205+
handle_destroyed = thread.allocate_lock()
206+
handle_destroyed.acquire()
207+
208+
def task():
209+
handle_destroyed.acquire()
210+
211+
with threading_helper.wait_threads_exit():
212+
handle = thread.start_joinable_thread(task)
213+
del handle
214+
handle_destroyed.release()
215+
216+
def test_join_from_self(self):
217+
errors = []
218+
handles = []
219+
start_joinable_thread_returned = thread.allocate_lock()
220+
start_joinable_thread_returned.acquire()
221+
task_tried_to_join = thread.allocate_lock()
222+
task_tried_to_join.acquire()
223+
224+
def task():
225+
start_joinable_thread_returned.acquire()
226+
try:
227+
handles[0].join()
228+
except Exception as e:
229+
errors.append(e)
230+
finally:
231+
task_tried_to_join.release()
232+
233+
with threading_helper.wait_threads_exit():
234+
handle = thread.start_joinable_thread(task)
235+
handles.append(handle)
236+
start_joinable_thread_returned.release()
237+
# Can still join after joining failed in other thread
238+
task_tried_to_join.acquire()
239+
handle.join()
240+
241+
assert len(errors) == 1
242+
with self.assertRaisesRegex(RuntimeError, "Cannot join current thread"):
243+
raise errors[0]
244+
245+
def test_join_then_self_join(self):
246+
# make sure we can't deadlock in the following scenario with
247+
# threads t0 and t1 (see comment in `ThreadHandle_join()` for more
248+
# details):
249+
#
250+
# - t0 joins t1
251+
# - t1 self joins
252+
def make_lock():
253+
lock = thread.allocate_lock()
254+
lock.acquire()
255+
return lock
256+
257+
error = None
258+
self_joiner_handle = None
259+
self_joiner_started = make_lock()
260+
self_joiner_barrier = make_lock()
261+
def self_joiner():
262+
nonlocal error
263+
264+
self_joiner_started.release()
265+
self_joiner_barrier.acquire()
266+
267+
try:
268+
self_joiner_handle.join()
269+
except Exception as e:
270+
error = e
271+
272+
joiner_started = make_lock()
273+
def joiner():
274+
joiner_started.release()
275+
self_joiner_handle.join()
276+
277+
with threading_helper.wait_threads_exit():
278+
self_joiner_handle = thread.start_joinable_thread(self_joiner)
279+
# Wait for the self-joining thread to start
280+
self_joiner_started.acquire()
281+
282+
# Start the thread that joins the self-joiner
283+
joiner_handle = thread.start_joinable_thread(joiner)
284+
285+
# Wait for the joiner to start
286+
joiner_started.acquire()
287+
288+
# Not great, but I don't think there's a deterministic way to make
289+
# sure that the self-joining thread has been joined.
290+
time.sleep(0.1)
291+
292+
# Unblock the self-joiner
293+
self_joiner_barrier.release()
294+
295+
self_joiner_handle.join()
296+
joiner_handle.join()
297+
298+
with self.assertRaisesRegex(RuntimeError, "Cannot join current thread"):
299+
raise error
300+
301+
def test_join_with_timeout(self):
302+
lock = thread.allocate_lock()
303+
lock.acquire()
304+
305+
def thr():
306+
lock.acquire()
307+
308+
with threading_helper.wait_threads_exit():
309+
handle = thread.start_joinable_thread(thr)
310+
handle.join(0.1)
311+
self.assertFalse(handle.is_done())
312+
lock.release()
313+
handle.join()
314+
self.assertTrue(handle.is_done())
315+
316+
def test_join_unstarted(self):
317+
handle = thread._ThreadHandle()
318+
with self.assertRaisesRegex(RuntimeError, "thread not started"):
319+
handle.join()
320+
321+
def test_set_done_unstarted(self):
322+
handle = thread._ThreadHandle()
323+
with self.assertRaisesRegex(RuntimeError, "thread not started"):
324+
handle._set_done()
325+
326+
@unittest.skipIf(__import__("sys").platform == "linux", "TODO: RUSTPYTHON; panic")
327+
def test_start_duplicate_handle(self):
328+
lock = thread.allocate_lock()
329+
lock.acquire()
330+
331+
def func():
332+
lock.acquire()
333+
334+
handle = thread._ThreadHandle()
335+
with threading_helper.wait_threads_exit():
336+
thread.start_joinable_thread(func, handle=handle)
337+
with self.assertRaisesRegex(RuntimeError, "thread already started"):
338+
thread.start_joinable_thread(func, handle=handle)
339+
lock.release()
340+
handle.join()
341+
342+
@unittest.skipIf(__import__("sys").platform == "linux", "TODO: RUSTPYTHON; panic")
343+
def test_start_with_none_handle(self):
344+
def func():
345+
pass
346+
347+
with threading_helper.wait_threads_exit():
348+
handle = thread.start_joinable_thread(func, handle=None)
349+
handle.join()
350+
156351

157352
class Barrier:
158353
def __init__(self, num_threads):
@@ -224,19 +419,21 @@ class TestForkInThread(unittest.TestCase):
224419
def setUp(self):
225420
self.read_fd, self.write_fd = os.pipe()
226421

227-
@unittest.skipUnless(hasattr(os, 'fork'), 'need os.fork')
422+
@support.requires_fork()
228423
@threading_helper.reap_threads
229424
def test_forkinthread(self):
230425
pid = None
231426

232427
def fork_thread(read_fd, write_fd):
233428
nonlocal pid
234429

235-
# fork in a thread
236-
pid = os.fork()
237-
if pid:
238-
# parent process
239-
return
430+
# Ignore the warning about fork with threads.
431+
with warnings.catch_warnings(category=DeprecationWarning,
432+
action="ignore"):
433+
# fork in a thread (DANGER, undefined per POSIX)
434+
if (pid := os.fork()):
435+
# parent process
436+
return
240437

241438
# child process
242439
try:

0 commit comments

Comments
 (0)