diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 7fd5e32129c..d9b0bd6891b 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -42,6 +42,7 @@ jobs: arch, centos-stream-9-amd64, centos-stream-10-amd64, + cpython_sanity-3.14tsan, debian-13-trixie-x86, debian-13-trixie-amd64, fedora-43-amd64, diff --git a/Tests/conftest.py b/Tests/conftest.py index 1f32bbedff3..864688427d7 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -1,17 +1,24 @@ from __future__ import annotations import io +import os import sys import sysconfig import pytest +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Generator + FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) gil_enabled_at_start = True if FREE_THREADED_BUILD: gil_enabled_at_start = sys._is_gil_enabled() # type: ignore[attr-defined] +TSAN_ENABLED = os.environ.get("PILLOW_TSAN_TEST") == "1" + def pytest_report_header(config: pytest.Config) -> str: try: @@ -60,3 +67,16 @@ def pytest_configure(config: pytest.Config) -> None: except Exception: # valgrind is already installed pass + + +@pytest.fixture(autouse=TSAN_ENABLED) +def tsan_parse(capfd: pytest.CaptureFixture[str]) -> Generator[None]: + # TSAN has two error modes, return a non-zero return code from the process + # or stop on error. Neither of these are really useful for us in a longrunning + # process, so we're hacking around this by reading stderr and looking for the + # TSAN Warning. + yield + out, err = capfd.readouterr() + if "WARNING: ThreadSanitizer" in err: + msg = "ThreadSanitizer Warning" + raise Exception(msg)