Skip to content
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

[3.12] gh-112536: Add --tsan test for reasonable TSAN execution times. (gh-116601) #116929

Merged
merged 5 commits into from Mar 18, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Lib/test/libregrtest/cmdline.py
Expand Up @@ -164,6 +164,7 @@ def __init__(self, **kwargs) -> None:
self.match_tests: TestFilter = []
self.pgo = False
self.pgo_extended = False
self.tsan = False
self.worker_json = None
self.start = None
self.timeout = None
Expand Down Expand Up @@ -333,6 +334,8 @@ def _create_parser():
help='enable Profile Guided Optimization (PGO) training')
group.add_argument('--pgo-extended', action='store_true',
help='enable extended PGO training (slower training)')
group.add_argument('--tsan', dest='tsan', action='store_true',
help='run a subset of test cases that are proper for the TSAN test')
group.add_argument('--fail-env-changed', action='store_true',
help='if a test file alters the environment, mark '
'the test as failed')
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/libregrtest/main.py
Expand Up @@ -18,6 +18,7 @@
from .runtests import RunTests, HuntRefleak
from .setup import setup_process, setup_test_dir
from .single import run_single_test, PROGRESS_MIN_TIME
from .tsan import setup_tsan_tests
from .utils import (
StrPath, StrJSON, TestName, TestList, TestTuple, TestFilter,
strip_py_suffix, count, format_duration,
Expand Down Expand Up @@ -56,6 +57,7 @@ def __init__(self, ns: Namespace, _add_python_opts: bool = False):
self.quiet: bool = ns.quiet
self.pgo: bool = ns.pgo
self.pgo_extended: bool = ns.pgo_extended
self.tsan: bool = ns.tsan

# Test results
self.results: TestResults = TestResults()
Expand Down Expand Up @@ -182,6 +184,9 @@ def find_tests(self, tests: TestList | None = None) -> tuple[TestTuple, TestList
# add default PGO tests if no tests are specified
setup_pgo_tests(self.cmdline_args, self.pgo_extended)

if self.tsan:
setup_tsan_tests(self.cmdline_args)

exclude_tests = set()
if self.exclude:
for arg in self.cmdline_args:
Expand Down
31 changes: 31 additions & 0 deletions Lib/test/libregrtest/tsan.py
@@ -0,0 +1,31 @@
# Set of tests run by default if --tsan is specified. The tests below were
# chosen because they use threads and run in a reasonable amount of time.

TSAN_TESTS = [
'test_capi',
'test_code',
'test_enum',
'test_functools',
'test_httpservers',
'test_imaplib',
'test_importlib',
'test_io',
'test_logging',
'test_queue',
'test_signal',
'test_socket',
'test_sqlite3',
'test_ssl',
'test_syslog',
'test_thread',
'test_threadedtempfile',
'test_threading',
'test_threading_local',
'test_threadsignals',
'test_weakref',
]


def setup_tsan_tests(cmdline_args):
if not cmdline_args:
cmdline_args[:] = TSAN_TESTS[:]
4 changes: 2 additions & 2 deletions Lib/test/support/script_helper.py
Expand Up @@ -64,8 +64,8 @@ class _PythonRunResult(collections.namedtuple("_PythonRunResult",
"""Helper for reporting Python subprocess run results"""
def fail(self, cmd_line):
"""Provide helpful details about failed subcommand runs"""
# Limit to 80 lines to ASCII characters
maxlen = 80 * 100
# Limit to 300 lines of ASCII characters
maxlen = 300 * 100
out, err = self.out, self.err
if len(out) > maxlen:
out = b'(... truncated stdout ...)' + out[-maxlen:]
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_threading.py
Expand Up @@ -47,6 +47,8 @@ def skip_unless_reliable_fork(test):
return unittest.skip("due to known OS bug related to thread+fork")(test)
if support.HAVE_ASAN_FORK_BUG:
return unittest.skip("libasan has a pthread_create() dead lock related to thread+fork")(test)
if support.check_sanitizer(thread=True):
return unittest.skip("TSAN doesn't support threads after fork")
return test


Expand Down Expand Up @@ -384,6 +386,10 @@ def test_finalize_running_thread(self):
# Issue 1402: the PyGILState_Ensure / _Release functions may be called
# very late on python exit: on deallocation of a running thread for
# example.
if support.check_sanitizer(thread=True):
# the thread running `time.sleep(100)` below will still be alive
# at process exit
self.skipTest("TSAN would report thread leak")
import_module("ctypes")

rc, out, err = assert_python_failure("-c", """if 1:
Expand Down Expand Up @@ -416,6 +422,11 @@ def waitingThread():
def test_finalize_with_trace(self):
# Issue1733757
# Avoid a deadlock when sys.settrace steps into threading._shutdown
if support.check_sanitizer(thread=True):
# the thread running `time.sleep(2)` below will still be alive
# at process exit
self.skipTest("TSAN would report thread leak")

assert_python_ok("-c", """if 1:
import sys, threading

Expand Down Expand Up @@ -1223,6 +1234,11 @@ def test_4_daemon_threads(self):
# Check that a daemon thread cannot crash the interpreter on shutdown
# by manipulating internal structures that are being disposed of in
# the main thread.
if support.check_sanitizer(thread=True):
# some of the threads running `random_io` below will still be alive
# at process exit
self.skipTest("TSAN would report thread leak")

script = """if True:
import os
import random
Expand Down
@@ -0,0 +1,2 @@
Add --tsan to test.regrtest for running TSAN tests in reasonable execution
times. Patch by Donghee Na.
4 changes: 2 additions & 2 deletions Modules/_testcapi/mem.c
Expand Up @@ -580,8 +580,8 @@ check_pyobject_forbidden_bytes_is_freed(PyObject *self,
static PyObject *
check_pyobject_freed_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
{
/* This test would fail if run with the address sanitizer */
#ifdef _Py_ADDRESS_SANITIZER
/* ASan or TSan would report an use-after-free error */
#if defined(_Py_ADDRESS_SANITIZER) || defined(_Py_THREAD_SANITIZER)
Py_RETURN_NONE;
#else
PyObject *op = PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type);
Expand Down
3 changes: 3 additions & 0 deletions Tools/tsan/supressions.txt
@@ -0,0 +1,3 @@
## reference: https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions
race:get_allocator_unlocked
race:set_allocator_unlocked