Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 106 additions & 27 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

import test.support
from test.support import threading_helper
from test.support import threading_helper, requires_subprocess
from test.support import verbose, cpython_only, os_helper
from test.support.import_helper import import_module
from test.support.script_helper import assert_python_ok, assert_python_failure
Expand All @@ -25,16 +25,14 @@
from test import lock_tests
from test import support

threading_helper.requires_working_threading(module=True)

# Between fork() and exec(), only async-safe functions are allowed (issues
# #12316 and #11870), and fork() from a worker thread is known to trigger
# problems with some operating systems (issue #3863): skip problematic tests
# on platforms known to behave badly.
platforms_to_skip = ('netbsd5', 'hp-ux11')

# Is Python built with Py_DEBUG macro defined?
Py_DEBUG = hasattr(sys, 'gettotalrefcount')


def restore_default_excepthook(testcase):
testcase.addCleanup(setattr, threading, 'excepthook', threading.excepthook)
Expand Down Expand Up @@ -123,6 +121,33 @@ def func(): pass
thread = threading.Thread(target=func)
self.assertEqual(thread.name, "Thread-5 (func)")

def test_args_argument(self):
# bpo-45735: Using list or tuple as *args* in constructor could
# achieve the same effect.
num_list = [1]
num_tuple = (1,)

str_list = ["str"]
str_tuple = ("str",)

list_in_tuple = ([1],)
tuple_in_list = [(1,)]

test_cases = (
(num_list, lambda arg: self.assertEqual(arg, 1)),
(num_tuple, lambda arg: self.assertEqual(arg, 1)),
(str_list, lambda arg: self.assertEqual(arg, "str")),
(str_tuple, lambda arg: self.assertEqual(arg, "str")),
(list_in_tuple, lambda arg: self.assertEqual(arg, [1])),
(tuple_in_list, lambda arg: self.assertEqual(arg, (1,)))
)

for args, target in test_cases:
with self.subTest(target=target, args=args):
t = threading.Thread(target=target, args=args)
t.start()
t.join()

@cpython_only
def test_disallow_instantiation(self):
# Ensure that the type disallows instantiation (bpo-43916)
Expand Down Expand Up @@ -509,8 +534,7 @@ def test_daemon_param(self):
t = threading.Thread(daemon=True)
self.assertTrue(t.daemon)

@unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, exit_handler needs lock._at_fork_reinit')
@unittest.skipUnless(hasattr(os, 'fork'), 'needs os.fork()')
@support.requires_fork()
def test_fork_at_exit(self):
# bpo-42350: Calling os.fork() after threading._shutdown() must
# not log an error.
Expand Down Expand Up @@ -538,7 +562,7 @@ def exit_handler():
self.assertEqual(out, b'')
self.assertEqual(err.rstrip(), b'child process ok')

@unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()')
@support.requires_fork()
def test_dummy_thread_after_fork(self):
# Issue #14308: a dummy thread in the active list doesn't mess up
# the after-fork mechanism.
Expand All @@ -565,8 +589,7 @@ def background_thread(evt):
self.assertEqual(out, b'')
self.assertEqual(err, b'')

@unittest.skipUnless(hasattr(sys, 'getswitchinterval'), "TODO: RUSTPYTHON, needs sys.getswitchinterval()")
@unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
@support.requires_fork()
def test_is_alive_after_fork(self):
# Try hard to trigger #18418: is_alive() could sometimes be True on
# threads that vanished after a fork.
Expand Down Expand Up @@ -600,7 +623,7 @@ def f():
th.start()
th.join()

@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
@support.requires_fork()
@unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
def test_main_thread_after_fork(self):
code = """if 1:
Expand All @@ -622,7 +645,7 @@ def test_main_thread_after_fork(self):
self.assertEqual(data, "MainThread\nTrue\nTrue\n")

@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
@support.requires_fork()
@unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
@unittest.skipIf(os.name != 'posix', "test needs POSIX semantics")
def test_main_thread_after_fork_from_nonmain_thread(self):
Expand Down Expand Up @@ -837,6 +860,7 @@ def callback():
callback()
finally:
sys.settrace(old_trace)
threading.settrace(old_trace)

def test_gettrace(self):
def noop_trace(frame, event, arg):
Expand All @@ -850,6 +874,37 @@ def noop_trace(frame, event, arg):
finally:
threading.settrace(old_trace)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_gettrace_all_threads(self):
def fn(*args): pass
old_trace = threading.gettrace()
first_check = threading.Event()
second_check = threading.Event()

trace_funcs = []
def checker():
trace_funcs.append(sys.gettrace())
first_check.set()
second_check.wait()
trace_funcs.append(sys.gettrace())

try:
t = threading.Thread(target=checker)
t.start()
first_check.wait()
threading.settrace_all_threads(fn)
second_check.set()
t.join()
self.assertEqual(trace_funcs, [None, fn])
self.assertEqual(threading.gettrace(), fn)
self.assertEqual(sys.gettrace(), fn)
finally:
threading.settrace_all_threads(old_trace)

self.assertEqual(threading.gettrace(), old_trace)
self.assertEqual(sys.gettrace(), old_trace)

def test_getprofile(self):
def fn(*args): pass
old_profile = threading.getprofile()
Expand All @@ -859,6 +914,37 @@ def fn(*args): pass
finally:
threading.setprofile(old_profile)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_getprofile_all_threads(self):
def fn(*args): pass
old_profile = threading.getprofile()
first_check = threading.Event()
second_check = threading.Event()

profile_funcs = []
def checker():
profile_funcs.append(sys.getprofile())
first_check.set()
second_check.wait()
profile_funcs.append(sys.getprofile())

try:
t = threading.Thread(target=checker)
t.start()
first_check.wait()
threading.setprofile_all_threads(fn)
second_check.set()
t.join()
self.assertEqual(profile_funcs, [None, fn])
self.assertEqual(threading.getprofile(), fn)
self.assertEqual(sys.getprofile(), fn)
finally:
threading.setprofile_all_threads(old_profile)

self.assertEqual(threading.getprofile(), old_profile)
self.assertEqual(sys.getprofile(), old_profile)

@cpython_only
def test_shutdown_locks(self):
for daemon in (False, True):
Expand Down Expand Up @@ -929,16 +1015,6 @@ def noop(): pass
threading.Thread(target=noop).start()
# Thread.join() is not called

@unittest.skipUnless(Py_DEBUG, 'need debug build (Py_DEBUG)')
def test_debug_deprecation(self):
# bpo-44584: The PYTHONTHREADDEBUG environment variable is deprecated
rc, out, err = assert_python_ok("-Wdefault", "-c", "pass",
PYTHONTHREADDEBUG="1")
msg = (b'DeprecationWarning: The threading debug '
b'(PYTHONTHREADDEBUG environment variable) '
b'is deprecated and will be removed in Python 3.12')
self.assertIn(msg, err)

def test_import_from_another_thread(self):
# bpo-1596321: If the threading module is first import from a thread
# different than the main thread, threading._shutdown() must handle
Expand Down Expand Up @@ -1006,7 +1082,7 @@ def test_1_join_on_shutdown(self):
"""
self._run_and_join(script)

@unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
@support.requires_fork()
@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
# TODO: RUSTPYTHON need to fix test_1_join_on_shutdown then this might work
@unittest.expectedFailure
Expand All @@ -1029,7 +1105,7 @@ def test_2_join_in_forked_process(self):
"""
self._run_and_join(script)

@unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
@support.requires_fork()
@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
@unittest.skip("TODO: RUSTPYTHON, flaky test")
def test_3_join_in_forked_from_thread(self):
Expand Down Expand Up @@ -1075,8 +1151,9 @@ def test_4_daemon_threads(self):

def random_io():
'''Loop for a while sleeping random tiny amounts and doing some I/O.'''
import test.test_threading as mod
while True:
with open(os.__file__, 'rb') as in_f:
with open(mod.__file__, 'rb') as in_f:
stuff = in_f.read(200)
with open(os.devnull, 'wb') as null_f:
null_f.write(stuff)
Expand All @@ -1100,7 +1177,7 @@ def main():
rc, out, err = assert_python_ok('-c', script)
self.assertFalse(err)

@unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
@support.requires_fork()
@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
def test_reinit_tls_after_fork(self):
# Issue #13817: fork() would deadlock in a multithreaded program with
Expand All @@ -1124,8 +1201,9 @@ def do_fork_and_wait():
for t in threads:
t.join()

@unittest.skipUnless(hasattr(sys, '_current_frames'), "TODO: RUSTPYTHON, needs sys._current_frames()")
@unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
@support.requires_fork()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_clear_threads_states_after_fork(self):
# Issue #17094: check that threads states are cleared after fork()

Expand Down Expand Up @@ -1280,6 +1358,7 @@ def test_releasing_unacquired_lock(self):
self.assertRaises(RuntimeError, lock.release)

@unittest.skip("TODO: RUSTPYTHON, flaky test")
@requires_subprocess()
def test_recursion_limit(self):
# Issue 9670
# test that excessive recursion within a non-main thread causes
Expand Down
11 changes: 11 additions & 0 deletions Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ def settrace(func):
global _trace_hook
_trace_hook = func

# TODO: RUSTPYTHON
def settrace_all_threads(func):
"""Set a trace function for all threads started from the threading module
and all Python threads that are currently executing.

The func will be passed to sys.settrace() for each thread, before its run()
method is called.
"""
settrace(func)
_sys._settraceallthreads(func)

def gettrace():
"""Get the trace function as set by threading.settrace()."""
return _trace_hook
Expand Down
Loading