From 3c02adba8c59d6fa0d12e8dd66580a0b87f2e696 Mon Sep 17 00:00:00 2001 From: Dennis Muth Date: Wed, 29 Apr 2020 20:51:03 +0200 Subject: [PATCH] Fixes pull.simple.RunOnce by correctly deciding to call sync/async poll() --- pnp/plugins/pull/simple.py | 10 +++- tests/dummies/__init__.py | 0 tests/dummies/polling.py | 36 ++++++++++++ tests/plugins/pull/test_simple_run_once.py | 65 ++++++++++++++++++++++ tests/plugins/pull/test_trigger_web.py | 46 ++------------- 5 files changed, 115 insertions(+), 42 deletions(-) create mode 100644 tests/dummies/__init__.py create mode 100644 tests/dummies/polling.py create mode 100644 tests/plugins/pull/test_simple_run_once.py diff --git a/pnp/plugins/pull/simple.py b/pnp/plugins/pull/simple.py index c28ba324..581610cd 100644 --- a/pnp/plugins/pull/simple.py +++ b/pnp/plugins/pull/simple.py @@ -4,6 +4,7 @@ import time from datetime import datetime +import asyncio from box import Box from . import PullBase, Polling, AsyncPullBase @@ -122,13 +123,18 @@ def __init__(self, poll=None, **kwargs): @property def can_exit(self) -> bool: - return True + return True # pragma: no cover async def async_pull(self) -> None: if not self.wrapped: self.notify({}) # Just notify about an empty dict else: - self.notify(self.wrapped.poll()) + if self.wrapped.supports_async_poll: + res = await self.wrapped.async_poll() + else: + loop = asyncio.get_event_loop() + res = await loop.run_in_executor(None, self.wrapped.poll) + self.notify(res) class Repeat(AsyncPullBase): diff --git a/tests/dummies/__init__.py b/tests/dummies/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/dummies/polling.py b/tests/dummies/polling.py new file mode 100644 index 00000000..1c98af42 --- /dev/null +++ b/tests/dummies/polling.py @@ -0,0 +1,36 @@ +from pnp.plugins.pull import Polling, AsyncPolling, PullBase + + +class SyncPollingDummy(Polling): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def poll(self): + return 42 + + +class AsyncPollingDummy(AsyncPolling): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def poll(self): + raise Exception("Don't call the sync version of pull") + + async def async_poll(self): + return 42 + + +class ErrorPollingDummy(Polling): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def poll(self): + raise Exception("Crash on purpose!") + + +class NoPollingDummy(PullBase): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def poll(self): + raise Exception("Do not call me!") diff --git a/tests/plugins/pull/test_simple_run_once.py b/tests/plugins/pull/test_simple_run_once.py new file mode 100644 index 00000000..de105c2e --- /dev/null +++ b/tests/plugins/pull/test_simple_run_once.py @@ -0,0 +1,65 @@ +import time + +import pytest + +from pnp.plugins.pull.simple import RunOnce +from . import make_runner, start_runner + + +def test_init_with_no_poll(): + with pytest.raises(TypeError, match="The component to wrap has to be a polling component"): + RunOnce(name='pytest', poll={'plugin': 'tests.dummies.polling.NoPollingDummy'}) + + +def test_pull_without_wrapped_poll(): + events = [] + def callback(plugin, payload): + events.append(payload) + + dut = RunOnce(name='pytest') + runner = make_runner(dut, callback) + with start_runner(runner): + time.sleep(0.01) + + assert events == [{}] + + +@pytest.mark.asyncio +async def test_async_pull_without_wrapped_poll(): + events = [] + def callback(plugin, payload): + events.append(payload) + + dut = RunOnce(name='pytest') + runner = make_runner(dut, callback) + with start_runner(runner): + time.sleep(0.01) + + assert events == [{}] + + +@pytest.mark.asyncio +async def test_async_pull_with_sync_wrapped_poll(): + events = [] + def callback(plugin, payload): + events.append(payload) + + dut = RunOnce(name='pytest', poll={'plugin': 'tests.dummies.polling.SyncPollingDummy'}) + runner = make_runner(dut, callback) + with start_runner(runner): + time.sleep(0.01) + + assert events == [42] + + +@pytest.mark.asyncio +async def test_async_pull_with_async_wrapped_poll(): + events = [] + def callback(plugin, payload): + events.append(payload) + + dut = RunOnce(name='pytest', poll={'plugin': 'tests.dummies.polling.AsyncPollingDummy'}) + dut.on_payload = callback + await dut.async_pull() + + assert events == [42] diff --git a/tests/plugins/pull/test_trigger_web.py b/tests/plugins/pull/test_trigger_web.py index 0d2283e4..1daece31 100644 --- a/tests/plugins/pull/test_trigger_web.py +++ b/tests/plugins/pull/test_trigger_web.py @@ -4,44 +4,10 @@ import pytest import requests -from pnp.plugins.pull import trigger, PullBase -from pnp.plugins.pull import AsyncPolling, Polling - +from pnp.plugins.pull import trigger from . import make_runner, start_runner -class DummyPoll(Polling): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def poll(self): - return 42 - - -class AsyncDummyPoll(AsyncPolling): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - async def async_poll(self): - return 42 - - -class ErrorneousDummyPoll(Polling): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def poll(self): - raise Exception("Crash on purpose!") - - -class NonPollingDummy(PullBase): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def poll(self): - raise Exception("Do not call me!") - - def get_free_tcp_port(): tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp.bind(('', 0)) @@ -52,7 +18,7 @@ def get_free_tcp_port(): def test_init_endpoint(): free_port = get_free_tcp_port() - wrapped = {'plugin': 'tests.plugins.pull.test_trigger_web.DummyPoll'} + wrapped = {'plugin': 'tests.dummies.polling.SyncPollingDummy'} dut = trigger.Web(name='pytest', port=free_port, poll=wrapped) assert dut.endpoint == '/trigger' dut = trigger.Web(name='pytest', port=free_port, poll=wrapped, endpoint='trigger') @@ -67,7 +33,7 @@ def test_init_endpoint(): def test_init_non_polling_pull(): free_port = get_free_tcp_port() - wrapped = {'plugin': 'tests.plugins.pull.test_trigger_web.NonPollingDummy'} + wrapped = {'plugin': 'tests.dummies.polling.NoPollingDummy'} with pytest.raises(TypeError) as tex: trigger.Web(name='pytest', port=free_port, poll=wrapped) @@ -75,8 +41,8 @@ def test_init_non_polling_pull(): @pytest.mark.parametrize("wrapped", [ - {'plugin': 'tests.plugins.pull.test_trigger_web.DummyPoll'}, - {'plugin': 'tests.plugins.pull.test_trigger_web.AsyncDummyPoll'} + {'plugin': 'tests.dummies.polling.SyncPollingDummy'}, + {'plugin': 'tests.dummies.polling.AsyncPollingDummy'} ]) def test_pull(wrapped): events = [] @@ -103,7 +69,7 @@ def callback(plugin, payload): def test_pull_for_error(): free_port = get_free_tcp_port() - wrapped = {'plugin': 'tests.plugins.pull.test_trigger_web.ErrorneousDummyPoll'} + wrapped = {'plugin': 'tests.dummies.polling.ErrorPollingDummy'} dut = trigger.Web(name='pytest', port=free_port, poll=wrapped) runner = make_runner(dut, lambda: None)