diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 8f86792e820..937f5903b5f 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -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 @@ -25,6 +25,7 @@ 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 @@ -32,9 +33,6 @@ # 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) @@ -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) @@ -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. @@ -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. @@ -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. @@ -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: @@ -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): @@ -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): @@ -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() @@ -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): @@ -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 @@ -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 @@ -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): @@ -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) @@ -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 @@ -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() @@ -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 diff --git a/Lib/threading.py b/Lib/threading.py index 668126523d5..811ea94a126 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -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