From b5d9a8571cae45b1aebd00e33120673cb6d0b6d7 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:10:15 -0800 Subject: [PATCH 1/9] Catch and expound upon the py38 proactor add_reader() not implemented exception --- pytest_twisted.py | 38 ++++++++++++++++++++++++++++---- setup.py | 2 +- testing/test_basic.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/pytest_twisted.py b/pytest_twisted.py index b2375aa..e45a057 100644 --- a/pytest_twisted.py +++ b/pytest_twisted.py @@ -1,17 +1,30 @@ import functools import inspect import sys +import traceback import warnings import decorator import greenlet import pytest +import six from twisted.internet import error, defer from twisted.internet.threads import blockingCallFromThread from twisted.python import failure +class AddReaderNotImplementedError(Exception): + @classmethod + def build(cls): + return cls( + "Failed to install asyncio reactor. The proactor was" + " used and is lacking the needed `.add_reader()` method" + " as of Python 3.8 on Windows." + " https://twistedmatrix.com/trac/ticket/9766" + ) + + class WrongReactorAlreadyInstalledError(Exception): pass @@ -275,10 +288,27 @@ def init_qt5_reactor(): def init_asyncio_reactor(): from twisted.internet import asyncioreactor - _install_reactor( - reactor_installer=asyncioreactor.install, - reactor_type=asyncioreactor.AsyncioSelectorReactor, - ) + try: + _install_reactor( + reactor_installer=asyncioreactor.install, + reactor_type=asyncioreactor.AsyncioSelectorReactor, + ) + except NotImplementedError as e: + import asyncio + + _, _, traceback_object = sys.exc_info() + stack_summary = traceback.extract_tb(traceback_object) + source_function_name = stack_summary[-1].name + event_loop = asyncio.get_event_loop() + + if ( + source_function_name == "add_reader" + and isinstance(event_loop, asyncio.ProactorEventLoop) + ): + six.raise_from( + value=AddReaderNotImplementedError.build(), + from_value=e, + ) reactor_installers = { diff --git a/setup.py b/setup.py index 5d37155..72bb1ef 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ author_email="ralf@brainbot.com", url="https://github.com/pytest-dev/pytest-twisted", py_modules=["pytest_twisted"], - install_requires=["greenlet", "pytest>=2.3", "decorator"], + install_requires=["greenlet", "pytest>=2.3", "decorator", "six"], extras_require={"dev": ["pre-commit", "black"]}, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/testing/test_basic.py b/testing/test_basic.py index 9afbc4c..d73cbf3 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -49,6 +49,24 @@ def format_run_result_output_for_assert(run_result): ) +def proactor_add_reader_is_implemented(): + import asyncio + + add_reader = getattr(asyncio.ProactorEventLoop, "add_reader", None) + + if add_reader is None: + return False + + try: + add_reader(None, None, None) + except NotImplementedError: + return False + except: + pass + + return True + + @pytest.fixture(name="default_conftest", autouse=True) def _default_conftest(testdir): testdir.makeconftest(textwrap.dedent(""" @@ -696,3 +714,36 @@ def test_succeed(): testdir.makepyfile(test_file) rr = testdir.run(sys.executable, "-m", "pytest", "-v", *cmd_opts) assert "WrongReactorAlreadyInstalledError" in rr.stderr.str() + + +def test_add_reader_exception_expounded_upon(testdir, cmd_opts, request): + skip_if_reactor_not(request, "asyncio") + + pytest.mark.skipif( + condition=sys.platform != 'win32', + reason="Relevant only on Windows", + ) + + pytest.mark.skipif( + condition=proactor_add_reader_is_implemented(), + reason=( + "Relevant only if asyncio.ProactorEventLoop.add_reader()" + " is not implemented" + ), + ) + + # block the default conftest.py + testdir.makeconftest("") + test_file = """ + def test_succeed(): + pass + """ + testdir.makepyfile(test_file) + rr = testdir.run(sys.executable, "-m", "pytest", "-v", *cmd_opts) + assert all( + s in rr.stderr.str() + for s in [ + "AddReaderNotImplementedError", + "https://twistedmatrix.com/trac/ticket/9766", + ] + ) From 23171de2a13ede8e52211d23c626037d7c97b773 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:13:05 -0800 Subject: [PATCH 2/9] getattr() for the ProactorEventLoop itself --- testing/test_basic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/test_basic.py b/testing/test_basic.py index d73cbf3..9548b53 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -52,7 +52,8 @@ def format_run_result_output_for_assert(run_result): def proactor_add_reader_is_implemented(): import asyncio - add_reader = getattr(asyncio.ProactorEventLoop, "add_reader", None) + proactor_event_loop = getattr(asyncio, "ProactorEventLoop", None) + add_reader = getattr(proactor_event_loop, "add_reader", None) if add_reader is None: return False From 0c6a3f4121e372e6a99a96eed87f50766443e2fb Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:14:53 -0800 Subject: [PATCH 3/9] #noqa: E722 --- testing/test_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_basic.py b/testing/test_basic.py index 9548b53..a1b2c59 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -62,7 +62,7 @@ def proactor_add_reader_is_implemented(): add_reader(None, None, None) except NotImplementedError: return False - except: + except: # noqa: E722 pass return True From b1c063f6cc6d7ce32da0325b2b03cd0597b432b3 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:24:09 -0800 Subject: [PATCH 4/9] switch skipifs to decorators --- testing/test_basic.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/testing/test_basic.py b/testing/test_basic.py index a1b2c59..70478f5 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -717,22 +717,20 @@ def test_succeed(): assert "WrongReactorAlreadyInstalledError" in rr.stderr.str() +@pytest.mark.skipif( + condition=sys.platform != 'win32', + reason="Relevant only on Windows", +) +@pytest.mark.skipif( + condition=proactor_add_reader_is_implemented(), + reason=( + "Relevant only if asyncio.ProactorEventLoop.add_reader()" + " is not implemented" + ), +) def test_add_reader_exception_expounded_upon(testdir, cmd_opts, request): skip_if_reactor_not(request, "asyncio") - pytest.mark.skipif( - condition=sys.platform != 'win32', - reason="Relevant only on Windows", - ) - - pytest.mark.skipif( - condition=proactor_add_reader_is_implemented(), - reason=( - "Relevant only if asyncio.ProactorEventLoop.add_reader()" - " is not implemented" - ), - ) - # block the default conftest.py testdir.makeconftest("") test_file = """ From 8e081e9988a9e85f0b5f4c5779868ab8e3a6432f Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:30:47 -0800 Subject: [PATCH 5/9] rework proactor_add_reader_is_implemented() --- testing/test_basic.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/testing/test_basic.py b/testing/test_basic.py index 70478f5..b882208 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -50,22 +50,19 @@ def format_run_result_output_for_assert(run_result): def proactor_add_reader_is_implemented(): - import asyncio - - proactor_event_loop = getattr(asyncio, "ProactorEventLoop", None) - add_reader = getattr(proactor_event_loop, "add_reader", None) + try: + import asyncio - if add_reader is None: - return False + add_reader = asyncio.proactor_event_loop.add_reader - try: - add_reader(None, None, None) - except NotImplementedError: - return False + try: + add_reader(None, None, None) + except NotImplementedError: + return False + except: # noqa: E722 + return True except: # noqa: E722 - pass - - return True + return False @pytest.fixture(name="default_conftest", autouse=True) From 3f358b8fa488b96c2fd5cc4d56b23177c8b35d97 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:33:19 -0800 Subject: [PATCH 6/9] satisfy the qa of the noqa comments --- testing/test_basic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_basic.py b/testing/test_basic.py index b882208..0ae06d0 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -59,9 +59,9 @@ def proactor_add_reader_is_implemented(): add_reader(None, None, None) except NotImplementedError: return False - except: # noqa: E722 + except: # noqa: E722 return True - except: # noqa: E722 + except: # noqa: E722 return False From 907a8a152364a2db9f915ff42cbf0e8120609b9d Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:43:44 -0800 Subject: [PATCH 7/9] also skip if proactor is the default policy --- testing/test_basic.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/testing/test_basic.py b/testing/test_basic.py index 0ae06d0..04a0110 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -65,6 +65,18 @@ def proactor_add_reader_is_implemented(): return False +def proactor_is_default(): + try: + import asyncio + + return ( + asyncio.DefaultEventLoopPolicy + is asyncio.WindowsProactorEventLoopPolicy + ) + except: # noqa: E722 + return False + + @pytest.fixture(name="default_conftest", autouse=True) def _default_conftest(testdir): testdir.makeconftest(textwrap.dedent(""" @@ -725,6 +737,10 @@ def test_succeed(): " is not implemented" ), ) +@pytest.mark.skipif( + condition=proactor_is_default(), + reason="Proactor is not the default event loop policy." +) def test_add_reader_exception_expounded_upon(testdir, cmd_opts, request): skip_if_reactor_not(request, "asyncio") From 1f2b73f4e8515f4611770980bb370a1c4cf5cac7 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Thu, 20 Feb 2020 19:44:50 -0800 Subject: [PATCH 8/9] also skip if proactor is _not_ the default policy --- testing/test_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_basic.py b/testing/test_basic.py index 04a0110..81acab6 100755 --- a/testing/test_basic.py +++ b/testing/test_basic.py @@ -738,7 +738,7 @@ def test_succeed(): ), ) @pytest.mark.skipif( - condition=proactor_is_default(), + condition=not proactor_is_default(), reason="Proactor is not the default event loop policy." ) def test_add_reader_exception_expounded_upon(testdir, cmd_opts, request): From 4c7cf0bc33996651c4d9938f1e4815dbb64721ed Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Sat, 22 Feb 2020 13:13:46 -0500 Subject: [PATCH 9/9] Add missing raise to avoid consuming NotImplementedError --- pytest_twisted.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest_twisted.py b/pytest_twisted.py index e45a057..7d22d43 100644 --- a/pytest_twisted.py +++ b/pytest_twisted.py @@ -310,6 +310,8 @@ def init_asyncio_reactor(): from_value=e, ) + raise + reactor_installers = { "default": init_default_reactor,