Skip to content

Commit

Permalink
[3.12] pythongh-112536: Add --tsan test for reasonable TSAN execution…
Browse files Browse the repository at this point in the history
… times. (pythongh-116601) (python#116929)

(cherry picked from commit ebf29b3)

Co-authored-by: Donghee Na <donghee.na@python.org>
  • Loading branch information
pitrou and corona10 authored Mar 18, 2024
1 parent 2ac1b48 commit fcb2301
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Lib/test/libregrtest/cmdline.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## reference: https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions
race:get_allocator_unlocked
race:set_allocator_unlocked

0 comments on commit fcb2301

Please sign in to comment.