From 882d59e5560fcf1a8d9bf2ebf22cd6d94d778c12 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 8 Oct 2026 20:39:45 +0100 Subject: [PATCH 1/4] Add tsan to docker test matrix --- .github/workflows/test-docker.yml | 1 + 1 file changed, 1 insertion(+) 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, From 536945031c7d2aa2ba6326f786763cb367ed3796 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 8 Oct 2026 23:04:28 +0100 Subject: [PATCH 2/4] pytest hack to error on TSAN stderr warning --- Tests/conftest.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/conftest.py b/Tests/conftest.py index 1f32bbedff3..224d1e04482 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -3,6 +3,7 @@ import io import sys import sysconfig +import os import pytest @@ -12,6 +13,7 @@ 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 +62,14 @@ def pytest_configure(config: pytest.Config) -> None: except Exception: # valgrind is already installed pass + +@pytest.fixture(autouse=TSAN_ENABLED) +def tsan_parse(capfd) -> 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: + raise Exception("ThreadSanitizer Warning") From d3e17f2fd9002b1a80beb10a835a1a029b05450f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 22:05:27 +0000 Subject: [PATCH 3/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/conftest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/conftest.py b/Tests/conftest.py index 224d1e04482..9a7e6c33e65 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -1,9 +1,9 @@ from __future__ import annotations import io +import os import sys import sysconfig -import os import pytest @@ -13,7 +13,8 @@ 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' +TSAN_ENABLED = os.environ.get("PILLOW_TSAN_TEST") == "1" + def pytest_report_header(config: pytest.Config) -> str: try: @@ -63,6 +64,7 @@ def pytest_configure(config: pytest.Config) -> None: # valgrind is already installed pass + @pytest.fixture(autouse=TSAN_ENABLED) def tsan_parse(capfd) -> None: # TSAN has two error modes, return a non-zero return code from the process @@ -70,6 +72,6 @@ def tsan_parse(capfd) -> None: # process, so we're hacking around this by reading stderr and looking for the # TSAN Warning. yield - out,err = capfd.readouterr() + out, err = capfd.readouterr() if "WARNING: ThreadSanitizer" in err: raise Exception("ThreadSanitizer Warning") From e8d38f465a240363e3ab3fcf8f1587aaebdfd9d3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 9 Oct 2026 12:49:38 +1100 Subject: [PATCH 4/4] Lint fix --- Tests/conftest.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/conftest.py b/Tests/conftest.py index 9a7e6c33e65..864688427d7 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -7,6 +7,10 @@ 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 @@ -66,7 +70,7 @@ def pytest_configure(config: pytest.Config) -> None: @pytest.fixture(autouse=TSAN_ENABLED) -def tsan_parse(capfd) -> None: +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 @@ -74,4 +78,5 @@ def tsan_parse(capfd) -> None: yield out, err = capfd.readouterr() if "WARNING: ThreadSanitizer" in err: - raise Exception("ThreadSanitizer Warning") + msg = "ThreadSanitizer Warning" + raise Exception(msg)