From 35f1d039d9982d02c014be1f15ba3b275273b3ef Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Sun, 27 Oct 2024 15:38:16 +0100 Subject: [PATCH 01/12] Repeatedly poll futures instead of infinite wait Windows can't interrupt the underlying syscall so we have to yield control to the python interpreter to detect ctrl-c once in a while --- synchronicity/synchronizer.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/synchronicity/synchronizer.py b/synchronicity/synchronizer.py index 0af5daf..290ad5d 100644 --- a/synchronicity/synchronizer.py +++ b/synchronicity/synchronizer.py @@ -317,7 +317,14 @@ async def wrapper_coro(): fut = asyncio.run_coroutine_threadsafe(wrapper_coro(), loop) try: - value = fut.result() + while 1: + try: + # poll every second to give Windows a chance to abort on Ctrl-C + # + value = fut.result(timeout=0.1) + break + except concurrent.futures.TimeoutError: + pass except KeyboardInterrupt as exc: # in case there is a keyboard interrupt while we are waiting # we cancel the *underlying* coro_task (unlike what fut.cancel() would do) @@ -326,9 +333,10 @@ async def wrapper_coro(): loop.call_soon_threadsafe(coro_task.cancel) try: value = fut.result() - except concurrent.futures.CancelledError: + except concurrent.futures.CancelledError as expected_cancellation: # we *expect* this cancellation, but defer to the passed coro to potentially # intercept and treat the cancellation some other way + expected_cancellation.__suppress_context__ = True raise exc # if cancel - re-raise the original KeyboardInterrupt again if getattr(original_func, self._output_translation_attr, True): From 647243abac57a56c18f6865afca71186ac590e29 Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Sun, 27 Oct 2024 21:42:41 +0100 Subject: [PATCH 02/12] Enable windows and mac tests in ci --- .github/workflows/ci.yml | 17 ++++++++++++++--- requirements.dev.txt | 1 + test/shutdown_test.py | 37 +++++++++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfd629b..a3f7b10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,14 +3,25 @@ on: push jobs: build: - runs-on: ubuntu-latest strategy: + fail-fast: false # run all variants across python versions/os to completion matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + os: ["ubuntu-latest"] + include: + - os: "macos-12" # x86-64 + python-version: "3.10" + - os: "macos-14" # ARM64 (M1) + python-version: "3.10" + - os: "windows-latest" + python-version: "3.10" + + runs-on: ${{ matrix.os }} + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/requirements.dev.txt b/requirements.dev.txt index b1f8eea..3b908f4 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -5,3 +5,4 @@ pytest==7.4.4 pytest-asyncio==0.23.3 pre-commit==3.5.0 ruff==0.2.0 +console-ctrl==0.1.0 diff --git a/test/shutdown_test.py b/test/shutdown_test.py index 9b7623d..943e01b 100644 --- a/test/shutdown_test.py +++ b/test/shutdown_test.py @@ -4,11 +4,32 @@ import sys from pathlib import Path +class PopenWithCtrlC(subprocess.Popen): + def __init__(self, *args, creationflags=0, **kwargs): + if sys.platform == "win32": + # needed on windows to separate ctrl-c lifecycle of subprocess from parent: + creationflags = creationflags | subprocess.CREATE_NEW_CONSOLE # type: ignore + + super().__init__(*args, **kwargs, creationflags=creationflags) + + def send_ctrl_c(self): + # platform independent way to replicate the behavior of Ctrl-C:ing a cli app + if sys.platform == "win32": + # windows doesn't support sigint, and subprocess.CTRL_C_EVENT has a bunch + # of gotchas since it's bound to a console which is the same for the parent + # process by default, and can't be sent using the python standard library + # to a separate process's console + import console_ctrl + + console_ctrl.send_ctrl_c(self.pid) # noqa [E731] + else: + self.send_signal(signal.SIGINT) + def test_shutdown(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown.py" - p = subprocess.Popen( + p = PopenWithCtrlC( [sys.executable, fn], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -16,7 +37,7 @@ def test_shutdown(): ) for i in range(2): # this number doesn't matter, it's a while loop assert p.stdout.readline() == b"running\n" - p.send_signal(signal.SIGINT) + p.send_ctrl_c() assert p.stdout.readline() == b"cancelled\n" assert p.stdout.readline() == b"handled cancellation\n" assert p.stdout.readline() == b"exit async\n" @@ -40,7 +61,7 @@ async def a(): def test_shutdown_during_ctx_mgr_setup(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown_ctx_mgr.py" - p = subprocess.Popen( + p = PopenWithCtrlC( [sys.executable, fn, "enter"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -48,7 +69,7 @@ def test_shutdown_during_ctx_mgr_setup(): ) for i in range(2): # this number doesn't matter, it's a while loop assert p.stdout.readline() == b"enter\n" - p.send_signal(signal.SIGINT) + p.send_ctrl_c() assert p.stdout.readline() == b"exit\n" assert p.stdout.readline() == b"keyboard interrupt\n" assert p.stderr.read() == b"" @@ -57,7 +78,7 @@ def test_shutdown_during_ctx_mgr_setup(): def test_shutdown_during_ctx_mgr_yield(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown_ctx_mgr.py" - p = subprocess.Popen( + p = PopenWithCtrlC( [sys.executable, fn, "yield"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -65,7 +86,7 @@ def test_shutdown_during_ctx_mgr_yield(): ) for i in range(2): # this number doesn't matter, it's a while loop assert p.stdout.readline() == b"in ctx\n" - p.send_signal(signal.SIGINT) + p.send_ctrl_c() assert p.stdout.readline() == b"exit\n" assert p.stdout.readline() == b"keyboard interrupt\n" assert p.stderr.read() == b"" @@ -73,7 +94,7 @@ def test_shutdown_during_ctx_mgr_yield(): def test_shutdown_during_async_run(): fn = Path(__file__).parent / "support" / "_shutdown_async_run.py" - p = subprocess.Popen( + p = PopenWithCtrlC( [sys.executable, fn], stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -82,7 +103,7 @@ def test_shutdown_during_async_run(): ) for i in range(2): # this number doesn't matter, it's a while loop assert p.stdout.readline() == "running\n" - p.send_signal(signal.SIGINT) + p.send_ctrl_c() assert p.stdout.readline() == "cancelled\n" assert p.stdout.readline() == "handled cancellation\n" assert p.stdout.readline() == "exit async\n" From ae631f891cde2f3870aa11ae1491541f39b76ebb Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Sun, 27 Oct 2024 21:44:37 +0100 Subject: [PATCH 03/12] ruff --- test/shutdown_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/shutdown_test.py b/test/shutdown_test.py index 943e01b..2ff8a0a 100644 --- a/test/shutdown_test.py +++ b/test/shutdown_test.py @@ -4,6 +4,7 @@ import sys from pathlib import Path + class PopenWithCtrlC(subprocess.Popen): def __init__(self, *args, creationflags=0, **kwargs): if sys.platform == "win32": From ed6fc321a45cc3e3256d4663d0f283269e5d4576 Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Sun, 27 Oct 2024 21:54:47 +0100 Subject: [PATCH 04/12] skip some tests --- test/fork_test.py | 3 +++ test/type_stub_e2e_test.py | 1 + 2 files changed, 4 insertions(+) diff --git a/test/fork_test.py b/test/fork_test.py index 4eb1bf2..3e2f40b 100644 --- a/test/fork_test.py +++ b/test/fork_test.py @@ -2,7 +2,10 @@ import sys from pathlib import Path +import pytest + +@pytest.mark.skipif(sys.platform == "win32", reason="Windows can't fork") def test_fork_restarts_loop(): p = subprocess.Popen( [sys.executable, Path(__file__).parent / "support" / "_forker.py"], diff --git a/test/type_stub_e2e_test.py b/test/type_stub_e2e_test.py index c52aec0..f46831d 100644 --- a/test/type_stub_e2e_test.py +++ b/test/type_stub_e2e_test.py @@ -84,6 +84,7 @@ async def a() -> None: ), ], ) +@pytest.mark.skipif(sys.platform == "win32", reason="temp_assertion_file permissions issues on github actions (windows)") def test_failing_assertion(interface_file, failing_assertion, error_matches): # since there appears to be no good way of asserting failing type checks (and skipping to the next assertion) # we use the assertion file as a template to insert statements that should fail type checking From 13a3d44ea943e58abdbcc8774e6a692c80292524 Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Sun, 27 Oct 2024 22:42:18 +0100 Subject: [PATCH 05/12] Fix tests on windows + fix ctrl-c windows bug for async synchronicity calls --- synchronicity/synchronizer.py | 11 +++++++-- test/shutdown_test.py | 42 +++++++++++++++++------------------ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/synchronicity/synchronizer.py b/synchronicity/synchronizer.py index 290ad5d..59401a4 100644 --- a/synchronicity/synchronizer.py +++ b/synchronicity/synchronizer.py @@ -373,8 +373,15 @@ async def run_coro(): try: # The shield here prevents a cancelled caller from cancelling c_fut directly # so that we can instead cancel the underlying coro_task and wait for it - # to be handled - value = await asyncio.shield(a_fut) + # to bubble up. + # the loop + wait_for timeout is for windows ctrl-C compatibility + while 1: + try: + value = await asyncio.wait_for(asyncio.shield(a_fut), timeout=0.1) + break + except asyncio.TimeoutError: + continue + except asyncio.CancelledError: if a_fut.cancelled(): raise # cancellation came from within c_fut diff --git a/test/shutdown_test.py b/test/shutdown_test.py index 2ff8a0a..2aa4430 100644 --- a/test/shutdown_test.py +++ b/test/shutdown_test.py @@ -31,23 +31,24 @@ def test_shutdown(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown.py" p = PopenWithCtrlC( - [sys.executable, fn], + [sys.executable, "-u", fn], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env={"PYTHONUNBUFFERED": "1"}, + encoding="utf8" ) for i in range(2): # this number doesn't matter, it's a while loop - assert p.stdout.readline() == b"running\n" + assert p.stdout.readline() == "running\n" p.send_ctrl_c() - assert p.stdout.readline() == b"cancelled\n" - assert p.stdout.readline() == b"handled cancellation\n" - assert p.stdout.readline() == b"exit async\n" + assert p.stdout.readline() == "cancelled\n" + assert p.stdout.readline() == "handled cancellation\n" + assert p.stdout.readline() == "exit async\n" assert ( - p.stdout.readline() == b"keyboard interrupt\n" + p.stdout.readline() == "keyboard interrupt\n" ) # we want the keyboard interrupt to come *after* the running function has been cancelled! stderr_content = p.stderr.read() - assert b"Traceback" not in stderr_content + print("stderr:", stderr_content) + assert "Traceback" not in stderr_content def test_keyboard_interrupt_reraised_as_is(synchronizer): @@ -63,34 +64,34 @@ def test_shutdown_during_ctx_mgr_setup(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown_ctx_mgr.py" p = PopenWithCtrlC( - [sys.executable, fn, "enter"], + [sys.executable, "-u", fn, "enter"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env={"PYTHONUNBUFFERED": "1"}, + encoding="utf8", ) for i in range(2): # this number doesn't matter, it's a while loop - assert p.stdout.readline() == b"enter\n" + assert p.stdout.readline() == "enter\n" p.send_ctrl_c() - assert p.stdout.readline() == b"exit\n" - assert p.stdout.readline() == b"keyboard interrupt\n" - assert p.stderr.read() == b"" + assert p.stdout.readline() == "exit\n" + assert p.stdout.readline() == "keyboard interrupt\n" + assert p.stderr.read() == "" def test_shutdown_during_ctx_mgr_yield(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown_ctx_mgr.py" p = PopenWithCtrlC( - [sys.executable, fn, "yield"], + [sys.executable, "-u", fn, "yield"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env={"PYTHONUNBUFFERED": "1"}, + encoding="utf8" ) for i in range(2): # this number doesn't matter, it's a while loop - assert p.stdout.readline() == b"in ctx\n" + assert p.stdout.readline() == "in ctx\n" p.send_ctrl_c() - assert p.stdout.readline() == b"exit\n" - assert p.stdout.readline() == b"keyboard interrupt\n" - assert p.stderr.read() == b"" + assert p.stdout.readline() == "exit\n" + assert p.stdout.readline() == "keyboard interrupt\n" + assert p.stderr.read() == "" def test_shutdown_during_async_run(): @@ -99,7 +100,6 @@ def test_shutdown_during_async_run(): [sys.executable, fn], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env={"PYTHONUNBUFFERED": "1"}, encoding="utf-8", ) for i in range(2): # this number doesn't matter, it's a while loop From 57d2ef09353ab1733b1fcaa2f6dd7df0ba044afb Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Mon, 28 Oct 2024 08:40:30 +0100 Subject: [PATCH 06/12] ruff --- test/fork_test.py | 3 +-- test/shutdown_test.py | 12 ++---------- test/type_stub_e2e_test.py | 4 +++- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/test/fork_test.py b/test/fork_test.py index 3e2f40b..6b70c33 100644 --- a/test/fork_test.py +++ b/test/fork_test.py @@ -1,9 +1,8 @@ +import pytest import subprocess import sys from pathlib import Path -import pytest - @pytest.mark.skipif(sys.platform == "win32", reason="Windows can't fork") def test_fork_restarts_loop(): diff --git a/test/shutdown_test.py b/test/shutdown_test.py index 2aa4430..5601589 100644 --- a/test/shutdown_test.py +++ b/test/shutdown_test.py @@ -30,12 +30,7 @@ def send_ctrl_c(self): def test_shutdown(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown.py" - p = PopenWithCtrlC( - [sys.executable, "-u", fn], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf8" - ) + p = PopenWithCtrlC([sys.executable, "-u", fn], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf8") for i in range(2): # this number doesn't matter, it's a while loop assert p.stdout.readline() == "running\n" p.send_ctrl_c() @@ -81,10 +76,7 @@ def test_shutdown_during_ctx_mgr_yield(): # We run it in a separate process so we can simulate interrupting it fn = Path(__file__).parent / "support" / "_shutdown_ctx_mgr.py" p = PopenWithCtrlC( - [sys.executable, "-u", fn, "yield"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf8" + [sys.executable, "-u", fn, "yield"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf8" ) for i in range(2): # this number doesn't matter, it's a while loop assert p.stdout.readline() == "in ctx\n" diff --git a/test/type_stub_e2e_test.py b/test/type_stub_e2e_test.py index f46831d..2ce66c3 100644 --- a/test/type_stub_e2e_test.py +++ b/test/type_stub_e2e_test.py @@ -84,7 +84,9 @@ async def a() -> None: ), ], ) -@pytest.mark.skipif(sys.platform == "win32", reason="temp_assertion_file permissions issues on github actions (windows)") +@pytest.mark.skipif( + sys.platform == "win32", reason="temp_assertion_file permissions issues on github actions (windows)" +) def test_failing_assertion(interface_file, failing_assertion, error_matches): # since there appears to be no good way of asserting failing type checks (and skipping to the next assertion) # we use the assertion file as a template to insert statements that should fail type checking From 25690a3a7f554671d56112b1ec8c3e8ebb9ad8e3 Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Mon, 28 Oct 2024 08:47:00 +0100 Subject: [PATCH 07/12] Fix test --- test/shutdown_test.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/shutdown_test.py b/test/shutdown_test.py index 5601589..7d27939 100644 --- a/test/shutdown_test.py +++ b/test/shutdown_test.py @@ -89,19 +89,24 @@ def test_shutdown_during_ctx_mgr_yield(): def test_shutdown_during_async_run(): fn = Path(__file__).parent / "support" / "_shutdown_async_run.py" p = PopenWithCtrlC( - [sys.executable, fn], + [sys.executable, "-u", fn], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) + def line(): + # debugging help + l = p.stdout.readline() + print(l) + return l for i in range(2): # this number doesn't matter, it's a while loop - assert p.stdout.readline() == "running\n" + assert line() == "running\n" p.send_ctrl_c() - assert p.stdout.readline() == "cancelled\n" - assert p.stdout.readline() == "handled cancellation\n" - assert p.stdout.readline() == "exit async\n" + assert line() == "cancelled\n" + assert line() == "handled cancellation\n" + assert line() == "exit async\n" assert ( - p.stdout.readline() == "keyboard interrupt\n" + line() == "keyboard interrupt\n" ) # we want the keyboard interrupt to come *after* the running function has been cancelled! stderr_content = p.stderr.read() From 74c99f2e811143d34de33a3f5cb9981f07fa0afa Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Mon, 28 Oct 2024 09:16:23 +0100 Subject: [PATCH 08/12] ruff --- test/shutdown_test.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/shutdown_test.py b/test/shutdown_test.py index 7d27939..bb1120d 100644 --- a/test/shutdown_test.py +++ b/test/shutdown_test.py @@ -94,11 +94,13 @@ def test_shutdown_during_async_run(): stderr=subprocess.PIPE, encoding="utf-8", ) + def line(): # debugging help - l = p.stdout.readline() - print(l) - return l + line_data = p.stdout.readline() + print(line_data) + return line_data + for i in range(2): # this number doesn't matter, it's a while loop assert line() == "running\n" p.send_ctrl_c() From 0008483dbe807be18e7664e07768288acc34d554 Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Mon, 28 Oct 2024 09:18:57 +0100 Subject: [PATCH 09/12] update ruff --- .pre-commit-config.yaml | 4 ++-- requirements.dev.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27b7e98..07f556c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.2.0" + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.7.1" hooks: - id: ruff # Autofix, and respect `exclude` and `extend-exclude` settings. diff --git a/requirements.dev.txt b/requirements.dev.txt index 3b908f4..e88049c 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -4,5 +4,5 @@ mypy-extensions==1.0.0 pytest==7.4.4 pytest-asyncio==0.23.3 pre-commit==3.5.0 -ruff==0.2.0 +ruff==0.7.1 console-ctrl==0.1.0 From 0d0cb81833c1ff0ab4effb705f9fed31354cef09 Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Mon, 28 Oct 2024 09:21:51 +0100 Subject: [PATCH 10/12] test flake on mac --- test/nowrap_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/nowrap_test.py b/test/nowrap_test.py index fa37aa2..5e96017 100644 --- a/test/nowrap_test.py +++ b/test/nowrap_test.py @@ -23,8 +23,8 @@ def test_nowrap(): t0 = time.time() assert my_obj.f(111) == 12321 - assert 0.19 < time.time() - t0 < 0.21 + assert 0.15 < time.time() - t0 < 0.25 t0 = time.time() assert my_obj.g(111) == 1367631 - assert 0.19 < time.time() - t0 < 0.21 + assert 0.15 < time.time() - t0 < 0.25 From 5ade25fdeb2373a77dda3504729f659c09008eca Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Mon, 28 Oct 2024 09:29:32 +0100 Subject: [PATCH 11/12] Use default event loop instead of overriding, checking to see if it still causes problems --- synchronicity/synchronizer.py | 7 ------- test/shutdown_test.py | 9 +++------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/synchronicity/synchronizer.py b/synchronicity/synchronizer.py index 59401a4..1a53c21 100644 --- a/synchronicity/synchronizer.py +++ b/synchronicity/synchronizer.py @@ -8,7 +8,6 @@ import functools import inspect import os -import platform import threading import types import typing @@ -105,12 +104,6 @@ def __init__( self._owner_pid = None self._stopping = None - if platform.system() == "Windows": - # default event loop policy on windows spits out errors when - # closing the event loop, so use WindowsSelectorEventLoopPolicy instead - # https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - # Special attribute we use to go from wrapped <-> original self._wrapped_attr = "_sync_wrapped_%d" % id(self) self._original_attr = "_sync_original_%d" % id(self) diff --git a/test/shutdown_test.py b/test/shutdown_test.py index bb1120d..b8d477d 100644 --- a/test/shutdown_test.py +++ b/test/shutdown_test.py @@ -41,9 +41,7 @@ def test_shutdown(): p.stdout.readline() == "keyboard interrupt\n" ) # we want the keyboard interrupt to come *after* the running function has been cancelled! - stderr_content = p.stderr.read() - print("stderr:", stderr_content) - assert "Traceback" not in stderr_content + assert p.stderr.read().strip() == "" def test_keyboard_interrupt_reraised_as_is(synchronizer): @@ -83,7 +81,7 @@ def test_shutdown_during_ctx_mgr_yield(): p.send_ctrl_c() assert p.stdout.readline() == "exit\n" assert p.stdout.readline() == "keyboard interrupt\n" - assert p.stderr.read() == "" + assert p.stderr.read().strip() == "" def test_shutdown_during_async_run(): @@ -111,5 +109,4 @@ def line(): line() == "keyboard interrupt\n" ) # we want the keyboard interrupt to come *after* the running function has been cancelled! - stderr_content = p.stderr.read() - assert "Traceback" not in stderr_content + assert p.stderr.read().strip() == "" From 5ecb3cf9b9568a177a0874a973d1d0071a750d3b Mon Sep 17 00:00:00 2001 From: Elias Freider Date: Fri, 7 Feb 2025 17:16:18 +0100 Subject: [PATCH 12/12] Disable gevent test on windows --- test/gevent_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/gevent_test.py b/test/gevent_test.py index 9edab80..d66e007 100644 --- a/test/gevent_test.py +++ b/test/gevent_test.py @@ -5,6 +5,9 @@ @pytest.mark.skipif(sys.version_info >= (3, 13), reason="gevent seems broken on Python 3.13") +@pytest.mark.skipif( + sys.platform == "win32", reason="gevent support broken on Windows, probably due to event loop patching" +) def test_gevent(): # Run it in a separate process because gevent modifies a lot of modules fn = Path(__file__).parent / "support" / "_gevent.py"