Skip to content

bpo-40275: Adding a threading_helper in test.support #20263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 27, 2020
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
145 changes: 78 additions & 67 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -838,18 +838,6 @@ The :mod:`test.support` module defines the following functions:
.. versionadded:: 3.9


.. function:: wait_threads_exit(timeout=60.0)

Context manager to wait until all threads created in the ``with`` statement
exit.


.. function:: start_threads(threads, unlock=None)

Context manager to start *threads*. It attempts to join the threads upon
exit.


.. function:: calcobjsize(fmt)

Return :func:`struct.calcsize` for ``nP{fmt}0n`` or, if ``gettotalrefcount``
Expand Down Expand Up @@ -988,11 +976,6 @@ The :mod:`test.support` module defines the following functions:
the trace function.


.. decorator:: reap_threads(func)

Decorator to ensure the threads are cleaned up even if the test fails.


.. decorator:: bigmemtest(size, memuse, dry_run=True)

Decorator for bigmem tests.
Expand Down Expand Up @@ -1110,23 +1093,6 @@ The :mod:`test.support` module defines the following functions:
preserve internal cache.


.. function:: threading_setup()

Return current thread count and copy of dangling threads.


.. function:: threading_cleanup(*original_values)

Cleanup up threads not specified in *original_values*. Designed to emit
a warning if a test leaves running threads in the background.


.. function:: join_thread(thread, timeout=30.0)

Join a *thread* within *timeout*. Raise an :exc:`AssertionError` if thread
is still alive after *timeout* seconds.


.. function:: reap_children()

Use this at the end of ``test_main`` whenever sub-processes are started.
Expand All @@ -1140,39 +1106,6 @@ The :mod:`test.support` module defines the following functions:
is raised.


.. function:: catch_threading_exception()

Context manager catching :class:`threading.Thread` exception using
:func:`threading.excepthook`.

Attributes set when an exception is catched:

* ``exc_type``
* ``exc_value``
* ``exc_traceback``
* ``thread``

See :func:`threading.excepthook` documentation.

These attributes are deleted at the context manager exit.

Usage::

with support.catch_threading_exception() as cm:
# code spawning a thread which raises an exception
...

# check the thread exception, use cm attributes:
# exc_type, exc_value, exc_traceback, thread
...

# exc_type, exc_value, exc_traceback, thread attributes of cm no longer
# exists at this point
# (to avoid reference cycles)

.. versionadded:: 3.8


.. function:: catch_unraisable_exception()

Context manager catching unraisable exception using
Expand Down Expand Up @@ -1628,3 +1561,81 @@ The module defines the following class:
.. method:: BytecodeTestCase.assertNotInBytecode(x, opname, argval=_UNSPECIFIED)

Throws :exc:`AssertionError` if *opname* is found.


:mod:`test.support.threading_helper` --- Utilities for threading tests
======================================================================

.. module:: test.support.threading_helper
:synopsis: Support for threading tests.

The :mod:`test.support.threading_helper` module provides support for threading tests.

.. versionadded:: 3.10


.. function:: join_thread(thread, timeout=None)

Join a *thread* within *timeout*. Raise an :exc:`AssertionError` if thread
is still alive after *timeout* seconds.


.. decorator:: reap_threads(func)

Decorator to ensure the threads are cleaned up even if the test fails.


.. function:: start_threads(threads, unlock=None)

Context manager to start *threads*. It attempts to join the threads upon
exit.


.. function:: threading_cleanup(*original_values)

Cleanup up threads not specified in *original_values*. Designed to emit
a warning if a test leaves running threads in the background.


.. function:: threading_setup()

Return current thread count and copy of dangling threads.


.. function:: wait_threads_exit(timeout=None)

Context manager to wait until all threads created in the ``with`` statement
exit.


.. function:: catch_threading_exception()

Context manager catching :class:`threading.Thread` exception using
:func:`threading.excepthook`.

Attributes set when an exception is catched:

* ``exc_type``
* ``exc_value``
* ``exc_traceback``
* ``thread``

See :func:`threading.excepthook` documentation.

These attributes are deleted at the context manager exit.

Usage::

with threading_helper.catch_threading_exception() as cm:
# code spawning a thread which raises an exception
...

# check the thread exception, use cm attributes:
# exc_type, exc_value, exc_traceback, thread
...

# exc_type, exc_value, exc_traceback, thread attributes of cm no longer
# exists at this point
# (to avoid reference cycles)

.. versionadded:: 3.8
5 changes: 3 additions & 2 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import test.support.script_helper
from test import support
from test.support import socket_helper
from test.support import threading_helper


# Skip tests if _multiprocessing wasn't built.
Expand Down Expand Up @@ -81,7 +82,7 @@ def close_queue(queue):
def join_process(process):
# Since multiprocessing.Process has the same API than threading.Thread
# (join() and is_alive(), the support function can be reused
support.join_thread(process)
threading_helper.join_thread(process)


if os.name == "posix":
Expand Down Expand Up @@ -4234,7 +4235,7 @@ def make_finalizers():
gc.set_threshold(5, 5, 5)
threads = [threading.Thread(target=run_finalizers),
threading.Thread(target=make_finalizers)]
with test.support.start_threads(threads):
with threading_helper.start_threads(threads):
time.sleep(4.0) # Wait a bit to trigger race condition
finish = True
if exc is not None:
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/fork_wait.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import os, sys, time, unittest
import threading
from test import support
from test.support import threading_helper


LONGSLEEP = 2
Expand All @@ -21,7 +22,7 @@
class ForkWait(unittest.TestCase):

def setUp(self):
self._threading_key = support.threading_setup()
self._threading_key = threading_helper.threading_setup()
self.alive = {}
self.stop = 0
self.threads = []
Expand All @@ -33,7 +34,7 @@ def tearDown(self):
thread.join()
thread = None
self.threads.clear()
support.threading_cleanup(*self._threading_key)
threading_helper.threading_cleanup(*self._threading_key)

def f(self, id):
while not self.stop:
Expand Down
9 changes: 5 additions & 4 deletions Lib/test/lock_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import weakref

from test import support
from test.support import threading_helper


requires_fork = unittest.skipUnless(hasattr(os, 'fork'),
Expand All @@ -37,7 +38,7 @@ def __init__(self, f, n, wait_before_exit=False):
self.started = []
self.finished = []
self._can_exit = not wait_before_exit
self.wait_thread = support.wait_threads_exit()
self.wait_thread = threading_helper.wait_threads_exit()
self.wait_thread.__enter__()

def task():
Expand Down Expand Up @@ -73,10 +74,10 @@ def do_finish(self):

class BaseTestCase(unittest.TestCase):
def setUp(self):
self._threads = support.threading_setup()
self._threads = threading_helper.threading_setup()

def tearDown(self):
support.threading_cleanup(*self._threads)
threading_helper.threading_cleanup(*self._threads)
support.reap_children()

def assertTimeout(self, actual, expected):
Expand Down Expand Up @@ -239,7 +240,7 @@ def f():
lock.acquire()
phase.append(None)

with support.wait_threads_exit():
with threading_helper.wait_threads_exit():
start_new_thread(f, ())
while len(phase) == 0:
_wait()
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
from test import support
from test.support import (
TestFailed, TESTFN, run_with_locale, no_tracing,
_2G, _4G, bigmemtest, reap_threads, forget,
_2G, _4G, bigmemtest, forget,
)
from test.support import threading_helper

from pickle import bytes_types

Expand Down Expand Up @@ -1350,7 +1351,7 @@ def test_truncated_data(self):
for p in badpickles:
self.check_unpickling_error(self.truncated_errors, p)

@reap_threads
@threading_helper.reap_threads
def test_unpickle_module_race(self):
# https://bugs.python.org/issue34572
locker_module = dedent("""
Expand Down
Loading