From 778cc0c9a6044251c4f9ac2697aab480a4ca5b5b Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Tue, 3 Sep 2024 05:18:22 +0200 Subject: [PATCH 1/6] Make Tests for `telegram.ext` Independent of Networking By cutting of the internet connection in the fixtures --- tests/auxil/networking.py | 22 ++++++- tests/auxil/pytest_classes.py | 11 ++-- tests/conftest.py | 53 ++++------------- tests/ext/conftest.py | 97 +++++++++++++++++++++++++++++++ tests/ext/test_callbackcontext.py | 6 +- tests/ext/test_updater.py | 26 +++++---- 6 files changed, 153 insertions(+), 62 deletions(-) create mode 100644 tests/ext/conftest.py diff --git a/tests/auxil/networking.py b/tests/auxil/networking.py index 7c20da7ac94..50beb4ab959 100644 --- a/tests/auxil/networking.py +++ b/tests/auxil/networking.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. from pathlib import Path -from typing import Optional +from typing import Optional, Tuple import pytest from httpx import AsyncClient, AsyncHTTPTransport, Response @@ -26,7 +26,7 @@ from telegram._utils.strings import TextEncoding from telegram._utils.types import ODVInput from telegram.error import BadRequest, RetryAfter, TimedOut -from telegram.request import HTTPXRequest, RequestData +from telegram.request import BaseRequest, HTTPXRequest, RequestData class NonchalantHttpxRequest(HTTPXRequest): @@ -60,6 +60,24 @@ async def _request_wrapper( pytest.xfail(f"Ignoring TimedOut error: {e}") +class OfflineHTTPXRequest(HTTPXRequest): + """This Request class disallows making requests to Telegram's servers. + Use this in tests that should not depend on the network. + """ + + async def do_request( + self, + url: str, + method: str, + request_data: Optional[RequestData] = None, + read_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, + write_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, + connect_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, + pool_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, + ) -> Tuple[int, bytes]: + raise RuntimeError("OfflineHTTPXRequest: Network access disallowed") + + async def expect_bad_request(func, message, reason): """ Wrapper for testing bot functions expected to result in an :class:`telegram.error.BadRequest`. diff --git a/tests/auxil/pytest_classes.py b/tests/auxil/pytest_classes.py index 1b976b02e6c..6e16939178f 100644 --- a/tests/auxil/pytest_classes.py +++ b/tests/auxil/pytest_classes.py @@ -25,7 +25,7 @@ from tests.auxil.ci_bots import BOT_INFO_PROVIDER from tests.auxil.constants import PRIVATE_KEY from tests.auxil.envvars import TEST_WITH_OPT_DEPS -from tests.auxil.networking import NonchalantHttpxRequest +from tests.auxil.networking import NonchalantHttpxRequest, OfflineHTTPXRequest def _get_bot_user(token: str) -> User: @@ -93,17 +93,20 @@ class PytestUpdater(Updater): pass -def make_bot(bot_info=None, **kwargs): +def make_bot(bot_info=None, offline: bool = False, **kwargs): """ Tests are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot """ token = kwargs.pop("token", (bot_info or {}).get("token")) private_key = kwargs.pop("private_key", PRIVATE_KEY) kwargs.pop("token", None) + + request_class = OfflineHTTPXRequest if offline else NonchalantHttpxRequest + return PytestExtBot( token=token, private_key=private_key if TEST_WITH_OPT_DEPS else None, - request=NonchalantHttpxRequest(8), - get_updates_request=NonchalantHttpxRequest(1), + request=request_class(8), + get_updates_request=request_class(1), **kwargs, ) diff --git a/tests/conftest.py b/tests/conftest.py index c721605bdb5..e8ef01cabac 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,15 +37,14 @@ Update, User, ) -from telegram.ext import ApplicationBuilder, Defaults, Updater -from telegram.ext.filters import MessageFilter, UpdateFilter +from telegram.ext import Defaults from tests.auxil.build_messages import DATE from tests.auxil.ci_bots import BOT_INFO_PROVIDER from tests.auxil.constants import PRIVATE_KEY from tests.auxil.envvars import RUN_TEST_OFFICIAL, TEST_WITH_OPT_DEPS from tests.auxil.files import data_file from tests.auxil.networking import NonchalantHttpxRequest -from tests.auxil.pytest_classes import PytestApplication, PytestBot, make_bot +from tests.auxil.pytest_classes import PytestBot, make_bot from tests.auxil.timezones import BasicTimezone if TEST_WITH_OPT_DEPS: @@ -124,6 +123,15 @@ async def bot(bot_info): yield _bot +@pytest.fixture(scope="session") +async def offline_bot(bot_info): + """Makes an offline Bot instance with the given bot_info + Note that in tests/ext we also override the `bot` fixture to return the offline bot instead. + """ + async with make_bot(bot_info, offline=True) as _bot: + yield _bot + + @pytest.fixture def one_time_bot(bot_info): """A function scoped bot since the session bot would shutdown when `async with app` finishes""" @@ -211,28 +219,6 @@ def subscription_channel_id(bot_info): return bot_info["subscription_channel_id"] -@pytest.fixture -async def app(bot_info): - # We build a new bot each time so that we use `app` in a context manager without problems - application = ( - ApplicationBuilder().bot(make_bot(bot_info)).application_class(PytestApplication).build() - ) - yield application - if application.running: - await application.stop() - await application.shutdown() - - -@pytest.fixture -async def updater(bot_info): - # We build a new bot each time so that we use `updater` in a context manager without problems - up = Updater(bot=make_bot(bot_info), update_queue=asyncio.Queue()) - yield up - if up.running: - await up.stop() - await up.shutdown() - - @pytest.fixture def thumb_file(): with data_file("thumb.jpg").open("rb") as f: @@ -245,23 +231,6 @@ def class_thumb_file(): yield f -@pytest.fixture( - scope="class", - params=[{"class": MessageFilter}, {"class": UpdateFilter}], - ids=["MessageFilter", "UpdateFilter"], -) -def mock_filter(request): - class MockFilter(request.param["class"]): - def __init__(self): - super().__init__() - self.tested = False - - def filter(self, _): - self.tested = True - - return MockFilter() - - def _get_false_update_fixture_decorator_params(): message = Message(1, DATE, Chat(1, ""), from_user=User(1, "", False), text="test") params = [ diff --git a/tests/ext/conftest.py b/tests/ext/conftest.py new file mode 100644 index 00000000000..da1a372be55 --- /dev/null +++ b/tests/ext/conftest.py @@ -0,0 +1,97 @@ +import asyncio + +import pytest + +from telegram.ext import ApplicationBuilder, Updater +from telegram.ext.filters import MessageFilter, UpdateFilter +from tests.auxil.constants import PRIVATE_KEY +from tests.auxil.envvars import TEST_WITH_OPT_DEPS +from tests.auxil.networking import OfflineHTTPXRequest +from tests.auxil.pytest_classes import PytestApplication, PytestBot, make_bot + +# This module overrides the bot fixtures defined in the global conftest.py to use the offline bot. +# We don't want the tests on telegram.ext to depend on the network, so we use the offline bot +# instead. + + +@pytest.fixture(scope="session") +async def bot(bot_info, offline_bot): + return offline_bot + + +@pytest.fixture +async def app(bot_info): + # We build a new bot each time so that we use `app` in a context manager without problems + application = ( + ApplicationBuilder() + .bot(make_bot(bot_info, offline=True)) + .application_class(PytestApplication) + .build() + ) + yield application + if application.running: + await application.stop() + await application.shutdown() + + +@pytest.fixture +async def updater(bot_info): + # We build a new bot each time so that we use `updater` in a context manager without problems + up = Updater(bot=make_bot(bot_info, offline=True), update_queue=asyncio.Queue()) + yield up + if up.running: + await up.stop() + await up.shutdown() + + +@pytest.fixture +def one_time_bot(bot_info): + """A function scoped bot since the session bot would shutdown when `async with app` finishes""" + return make_bot(bot_info, offline=True) + + +@pytest.fixture(scope="session") +async def cdc_bot(bot_info): + """Makes an ExtBot instance with the given bot_info that uses arbitrary callback_data""" + async with make_bot(bot_info, arbitrary_callback_data=True, offline=True) as _bot: + yield _bot + + +@pytest.fixture(scope="session") +async def raw_bot(bot_info): + """Makes an regular Bot instance with the given bot_info""" + async with PytestBot( + bot_info["token"], + private_key=PRIVATE_KEY if TEST_WITH_OPT_DEPS else None, + request=OfflineHTTPXRequest(8), + get_updates_request=OfflineHTTPXRequest(1), + ) as _bot: + yield _bot + + +@pytest.fixture +async def on_time_raw_bot(bot_info): + """Makes an regular Bot instance with the given bot_info""" + return PytestBot( + bot_info["token"], + private_key=PRIVATE_KEY if TEST_WITH_OPT_DEPS else None, + request=OfflineHTTPXRequest(8), + get_updates_request=OfflineHTTPXRequest(1), + ) + + +@pytest.fixture( + scope="class", + params=[{"class": MessageFilter}, {"class": UpdateFilter}], + ids=["MessageFilter", "UpdateFilter"], +) +def mock_filter(request): + class MockFilter(request.param["class"]): + def __init__(self): + super().__init__() + self.tested = False + + def filter(self, _): + self.tested = True + + return MockFilter() diff --git a/tests/ext/test_callbackcontext.py b/tests/ext/test_callbackcontext.py index 0a099f64f15..9a5f64e6f21 100644 --- a/tests/ext/test_callbackcontext.py +++ b/tests/ext/test_callbackcontext.py @@ -20,7 +20,6 @@ import pytest from telegram import ( - Bot, CallbackQuery, Chat, InlineKeyboardButton, @@ -194,8 +193,7 @@ def test_application_attribute(self, app): callback_context = CallbackContext(app) assert callback_context.application is app - def test_drop_callback_data_exception(self, bot, app): - non_ext_bot = Bot(bot.token) + def test_drop_callback_data_exception(self, bot, app, raw_bot): update = Update( 0, message=Message(0, None, Chat(1, "chat"), from_user=User(1, "user", False)) ) @@ -206,7 +204,7 @@ def test_drop_callback_data_exception(self, bot, app): callback_context.drop_callback_data(None) try: - app.bot = non_ext_bot + app.bot = raw_bot with pytest.raises(RuntimeError, match="telegram.Bot does not allow for"): callback_context.drop_callback_data(None) finally: diff --git a/tests/ext/test_updater.py b/tests/ext/test_updater.py index 84a86c988da..98b5a77cb17 100644 --- a/tests/ext/test_updater.py +++ b/tests/ext/test_updater.py @@ -35,7 +35,7 @@ from tests.auxil.envvars import TEST_WITH_OPT_DEPS from tests.auxil.files import TEST_DATA_PATH, data_file from tests.auxil.networking import send_webhook_message -from tests.auxil.pytest_classes import PytestBot, make_bot +from tests.auxil.pytest_classes import make_bot from tests.auxil.slots import mro_slots UNIX_AVAILABLE = False @@ -246,13 +246,12 @@ async def get_updates(*args, **kwargs): await asyncio.sleep(0.1) return [] - orig_del_webhook = updater.bot.delete_webhook - async def delete_webhook(*args, **kwargs): # Dropping pending updates is done by passing the parameter to delete_webhook if kwargs.get("drop_pending_updates"): self.message_count += 1 - return await orig_del_webhook(*args, **kwargs) + await asyncio.sleep(0) + return True monkeypatch.setattr(updater.bot, "get_updates", get_updates) monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) @@ -264,7 +263,6 @@ async def delete_webhook(*args, **kwargs): await updates.join() await updater.stop() assert not updater.running - assert not (await updater.bot.get_webhook_info()).url if drop_pending_updates: assert self.message_count == 1 else: @@ -281,7 +279,6 @@ async def delete_webhook(*args, **kwargs): await updates.join() await updater.stop() assert not updater.running - assert not (await updater.bot.get_webhook_info()).url self.received = [] self.message_count = 0 @@ -505,7 +502,7 @@ async def do_request(*args, **kwargs): async with updater: # Patch within the context so that updater.bot.initialize can still be called # by the context manager - monkeypatch.setattr(HTTPXRequest, "do_request", do_request) + monkeypatch.setattr(updater.bot.request, "do_request", do_request) if exception_class == InvalidToken: with pytest.raises(InvalidToken, match="1"): @@ -705,14 +702,23 @@ async def delete_webhook(*args, **kwargs): "unix", [None, "file_path", "socket_object"] if UNIX_AVAILABLE else [None] ) async def test_webhook_basic( - self, monkeypatch, updater, drop_pending_updates, ext_bot, secret_token, unix, file_path + self, + monkeypatch, + updater, + drop_pending_updates, + ext_bot, + secret_token, + unix, + file_path, + one_time_bot, + on_time_raw_bot, ): # Testing with both ExtBot and Bot to make sure any logic in WebhookHandler # that depends on this distinction works if ext_bot and not isinstance(updater.bot, ExtBot): - updater.bot = ExtBot(updater.bot.token) + updater.bot = one_time_bot if not ext_bot and type(updater.bot) is not Bot: - updater.bot = PytestBot(updater.bot.token) + updater.bot = on_time_raw_bot async def delete_webhook(*args, **kwargs): # Dropping pending updates is done by passing the parameter to delete_webhook From 63256eb2faa9144a83fb707f420b9b47abe97332 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:20:36 +0200 Subject: [PATCH 2/6] Fix Tests and try speeding up a bit --- tests/auxil/networking.py | 13 +++++++++++-- tests/auxil/pytest_classes.py | 4 ++-- tests/ext/conftest.py | 10 +++++----- tests/ext/test_application.py | 28 +++++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/tests/auxil/networking.py b/tests/auxil/networking.py index 50beb4ab959..8c4b5a3288b 100644 --- a/tests/auxil/networking.py +++ b/tests/auxil/networking.py @@ -60,11 +60,20 @@ async def _request_wrapper( pytest.xfail(f"Ignoring TimedOut error: {e}") -class OfflineHTTPXRequest(HTTPXRequest): +class OfflineRequest(BaseRequest): """This Request class disallows making requests to Telegram's servers. Use this in tests that should not depend on the network. """ + async def initialize(self) -> None: + pass + + async def shutdown(self) -> None: + pass + + def __init__(self, *args, **kwargs): + pass + async def do_request( self, url: str, @@ -75,7 +84,7 @@ async def do_request( connect_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, pool_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, ) -> Tuple[int, bytes]: - raise RuntimeError("OfflineHTTPXRequest: Network access disallowed") + raise RuntimeError("OfflineRequest: Network access disallowed") async def expect_bad_request(func, message, reason): diff --git a/tests/auxil/pytest_classes.py b/tests/auxil/pytest_classes.py index 6e16939178f..b80945b6704 100644 --- a/tests/auxil/pytest_classes.py +++ b/tests/auxil/pytest_classes.py @@ -25,7 +25,7 @@ from tests.auxil.ci_bots import BOT_INFO_PROVIDER from tests.auxil.constants import PRIVATE_KEY from tests.auxil.envvars import TEST_WITH_OPT_DEPS -from tests.auxil.networking import NonchalantHttpxRequest, OfflineHTTPXRequest +from tests.auxil.networking import NonchalantHttpxRequest, OfflineRequest def _get_bot_user(token: str) -> User: @@ -101,7 +101,7 @@ def make_bot(bot_info=None, offline: bool = False, **kwargs): private_key = kwargs.pop("private_key", PRIVATE_KEY) kwargs.pop("token", None) - request_class = OfflineHTTPXRequest if offline else NonchalantHttpxRequest + request_class = OfflineRequest if offline else NonchalantHttpxRequest return PytestExtBot( token=token, diff --git a/tests/ext/conftest.py b/tests/ext/conftest.py index da1a372be55..6c1c81ab144 100644 --- a/tests/ext/conftest.py +++ b/tests/ext/conftest.py @@ -6,7 +6,7 @@ from telegram.ext.filters import MessageFilter, UpdateFilter from tests.auxil.constants import PRIVATE_KEY from tests.auxil.envvars import TEST_WITH_OPT_DEPS -from tests.auxil.networking import OfflineHTTPXRequest +from tests.auxil.networking import OfflineRequest from tests.auxil.pytest_classes import PytestApplication, PytestBot, make_bot # This module overrides the bot fixtures defined in the global conftest.py to use the offline bot. @@ -63,8 +63,8 @@ async def raw_bot(bot_info): async with PytestBot( bot_info["token"], private_key=PRIVATE_KEY if TEST_WITH_OPT_DEPS else None, - request=OfflineHTTPXRequest(8), - get_updates_request=OfflineHTTPXRequest(1), + request=OfflineRequest(), + get_updates_request=OfflineRequest(), ) as _bot: yield _bot @@ -75,8 +75,8 @@ async def on_time_raw_bot(bot_info): return PytestBot( bot_info["token"], private_key=PRIVATE_KEY if TEST_WITH_OPT_DEPS else None, - request=OfflineHTTPXRequest(8), - get_updates_request=OfflineHTTPXRequest(1), + request=OfflineRequest(), + get_updates_request=OfflineRequest(), ) diff --git a/tests/ext/test_application.py b/tests/ext/test_application.py index c423c5d5fbf..baf168f0fe4 100644 --- a/tests/ext/test_application.py +++ b/tests/ext/test_application.py @@ -432,7 +432,7 @@ def test_builder(self, app): @pytest.mark.parametrize("job_queue", [True, False]) @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") - async def test_start_stop_processing_updates(self, one_time_bot, job_queue): + async def test_start_stop_processing_updates(self, one_time_bot, job_queue, monkeypatch): # TODO: repeat a similar test for create_task, persistence processing and job queue if job_queue: app = ApplicationBuilder().bot(one_time_bot).build() @@ -442,6 +442,16 @@ async def test_start_stop_processing_updates(self, one_time_bot, job_queue): async def callback(u, c): self.received = u + async def get_updates(*args, **kwargs): + await asyncio.sleep(0) + return [] + + async def delete_webhook(*args, **kwargs): + return True + + monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + assert not app.running assert not app.updater.running if job_queue: @@ -473,6 +483,8 @@ async def callback(u, c): await app.updater.start_polling() except TelegramError: pytest.xfail("start_polling timed out") + except BaseException as e: + pytest.fail(f"Unexpected exception: {e}") else: await app.stop() assert not app.running @@ -2161,6 +2173,11 @@ def after(_, name): .build() ) + async def delete_webhook(*args, **kwargs): + return True + + monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + app.run_polling(close_loop=False) # This checks two things: @@ -2221,6 +2238,9 @@ async def get_updates(*args, **kwargs): await asyncio.sleep(0) return [] + async def delete_webhook(*args, **kwargs): + return True + for cls, method, entry in [ (Application, "initialize", "app_initialize"), (Application, "start", "app_start"), @@ -2250,6 +2270,7 @@ def after(_, name): .build() ) monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) app.add_handler(TypeHandler(object, update_logger_callback), group=-10) app.add_handler(TypeHandler(object, handler_callback)) @@ -2312,6 +2333,9 @@ def _after_shutdown(*args, **kwargs): return _after_shutdown + async def delete_webhook(*args, **kwargs): + return True + monkeypatch.setattr(Application, method, raise_method) monkeypatch.setattr( Application, @@ -2322,6 +2346,8 @@ def _after_shutdown(*args, **kwargs): Updater, "shutdown", call_after(Updater.shutdown, after_shutdown("updater")) ) app = ApplicationBuilder().bot(one_time_bot).build() + + monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) with pytest.raises(RuntimeError, match="Test Exception"): app.run_polling(close_loop=False) From 321b2d4e84022847debc2228843ae6232504ba07 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:21:25 +0200 Subject: [PATCH 3/6] Review --- tests/ext/conftest.py | 2 +- tests/ext/test_updater.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ext/conftest.py b/tests/ext/conftest.py index 6c1c81ab144..7573afb3a38 100644 --- a/tests/ext/conftest.py +++ b/tests/ext/conftest.py @@ -70,7 +70,7 @@ async def raw_bot(bot_info): @pytest.fixture -async def on_time_raw_bot(bot_info): +async def one_time_raw_bot(bot_info): """Makes an regular Bot instance with the given bot_info""" return PytestBot( bot_info["token"], diff --git a/tests/ext/test_updater.py b/tests/ext/test_updater.py index 98b5a77cb17..362c55e9695 100644 --- a/tests/ext/test_updater.py +++ b/tests/ext/test_updater.py @@ -711,14 +711,14 @@ async def test_webhook_basic( unix, file_path, one_time_bot, - on_time_raw_bot, + one_time_raw_bot, ): # Testing with both ExtBot and Bot to make sure any logic in WebhookHandler # that depends on this distinction works if ext_bot and not isinstance(updater.bot, ExtBot): updater.bot = one_time_bot if not ext_bot and type(updater.bot) is not Bot: - updater.bot = on_time_raw_bot + updater.bot = one_time_raw_bot async def delete_webhook(*args, **kwargs): # Dropping pending updates is done by passing the parameter to delete_webhook From 017925a6b2bfbe7591023bbfaa6e14e434a8797b Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 8 Sep 2024 14:36:31 +0200 Subject: [PATCH 4/6] =?UTF-8?q?Way=20more=20test=20fixing=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/auxil/networking.py | 2 +- tests/ext/conftest.py | 12 ++- tests/ext/test_application.py | 83 +++++-------------- .../test_inlinequeryhandler.py | 0 tests/{ => ext}/test_pollhandler.py | 0 tests/ext/test_updater.py | 71 +++++----------- 6 files changed, 54 insertions(+), 114 deletions(-) rename tests/{_inline => ext}/test_inlinequeryhandler.py (100%) rename tests/{ => ext}/test_pollhandler.py (100%) diff --git a/tests/auxil/networking.py b/tests/auxil/networking.py index 8c4b5a3288b..a695eb232f7 100644 --- a/tests/auxil/networking.py +++ b/tests/auxil/networking.py @@ -84,7 +84,7 @@ async def do_request( connect_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, pool_timeout: ODVInput[float] = BaseRequest.DEFAULT_NONE, ) -> Tuple[int, bytes]: - raise RuntimeError("OfflineRequest: Network access disallowed") + pytest.fail("OfflineRequest: Network access disallowed in this test") async def expect_bad_request(func, message, reason): diff --git a/tests/ext/conftest.py b/tests/ext/conftest.py index 7573afb3a38..39d34390a15 100644 --- a/tests/ext/conftest.py +++ b/tests/ext/conftest.py @@ -19,8 +19,12 @@ async def bot(bot_info, offline_bot): return offline_bot +async def return_true(*args, **kwargs): + return True + + @pytest.fixture -async def app(bot_info): +async def app(bot_info, monkeypatch): # We build a new bot each time so that we use `app` in a context manager without problems application = ( ApplicationBuilder() @@ -28,6 +32,8 @@ async def app(bot_info): .application_class(PytestApplication) .build() ) + monkeypatch.setattr(application.bot, "delete_webhook", return_true) + monkeypatch.setattr(application.bot, "set_webhook", return_true) yield application if application.running: await application.stop() @@ -35,9 +41,11 @@ async def app(bot_info): @pytest.fixture -async def updater(bot_info): +async def updater(bot_info, monkeypatch): # We build a new bot each time so that we use `updater` in a context manager without problems up = Updater(bot=make_bot(bot_info, offline=True), update_queue=asyncio.Queue()) + monkeypatch.setattr(up.bot, "delete_webhook", return_true) + monkeypatch.setattr(up.bot, "set_webhook", return_true) yield up if up.running: await up.stop() diff --git a/tests/ext/test_application.py b/tests/ext/test_application.py index baf168f0fe4..80cebc2c176 100644 --- a/tests/ext/test_application.py +++ b/tests/ext/test_application.py @@ -483,8 +483,6 @@ async def delete_webhook(*args, **kwargs): await app.updater.start_polling() except TelegramError: pytest.xfail("start_polling timed out") - except BaseException as e: - pytest.fail(f"Unexpected exception: {e}") else: await app.stop() assert not app.running @@ -1796,12 +1794,6 @@ def thread_target(): def test_run_webhook_basic(self, app, monkeypatch, caplog): assertions = {} - async def delete_webhook(*args, **kwargs): - return True - - async def set_webhook(*args, **kwargs): - return True - def thread_target(): waited = 0 while not app.running: @@ -1832,8 +1824,6 @@ def thread_target(): assertions["updater_not_running"] = not app.updater.running assertions["job_queue_not_running"] = not app.job_queue.scheduler.running - monkeypatch.setattr(app.bot, "set_webhook", set_webhook) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) app.add_handler(TypeHandler(object, self.callback_set_count(42))) thread = Thread(target=thread_target) @@ -1869,17 +1859,6 @@ def thread_target(): def test_run_webhook_post_init(self, one_time_bot, monkeypatch): events = [] - async def delete_webhook(*args, **kwargs): - return True - - async def set_webhook(*args, **kwargs): - return True - - async def get_updates(*args, **kwargs): - # This makes sure that other coroutines have a chance of running as well - await asyncio.sleep(0) - return [] - def thread_target(): waited = 0 while not app.running: @@ -1901,8 +1880,7 @@ async def post_init(app: Application) -> None: .build() ) app.bot._unfreeze() - monkeypatch.setattr(app.bot, "set_webhook", set_webhook) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr( app, "initialize", call_after(app.initialize, lambda _: events.append("init")) ) @@ -1935,17 +1913,6 @@ async def post_init(app: Application) -> None: def test_run_webhook_post_shutdown(self, one_time_bot, monkeypatch): events = [] - async def delete_webhook(*args, **kwargs): - return True - - async def set_webhook(*args, **kwargs): - return True - - async def get_updates(*args, **kwargs): - # This makes sure that other coroutines have a chance of running as well - await asyncio.sleep(0) - return [] - def thread_target(): waited = 0 while not app.running: @@ -1967,8 +1934,7 @@ async def post_shutdown(app: Application) -> None: .build() ) app.bot._unfreeze() - monkeypatch.setattr(app.bot, "set_webhook", set_webhook) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr( app, "shutdown", call_after(app.shutdown, lambda _: events.append("shutdown")) ) @@ -2005,17 +1971,6 @@ async def post_shutdown(app: Application) -> None: def test_run_webhook_post_stop(self, one_time_bot, monkeypatch): events = [] - async def delete_webhook(*args, **kwargs): - return True - - async def set_webhook(*args, **kwargs): - return True - - async def get_updates(*args, **kwargs): - # This makes sure that other coroutines have a chance of running as well - await asyncio.sleep(0) - return [] - def thread_target(): waited = 0 while not app.running: @@ -2037,8 +1992,7 @@ async def post_stop(app: Application) -> None: .build() ) app.bot._unfreeze() - monkeypatch.setattr(app.bot, "set_webhook", set_webhook) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr(app, "stop", call_after(app.stop, lambda _: events.append("stop"))) monkeypatch.setattr( app.updater, @@ -2173,11 +2127,6 @@ def after(_, name): .build() ) - async def delete_webhook(*args, **kwargs): - return True - - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) - app.run_polling(close_loop=False) # This checks two things: @@ -2336,6 +2285,10 @@ def _after_shutdown(*args, **kwargs): async def delete_webhook(*args, **kwargs): return True + async def get_updates(*args, **kwargs): + await asyncio.sleep(0) + return [] + monkeypatch.setattr(Application, method, raise_method) monkeypatch.setattr( Application, @@ -2346,8 +2299,9 @@ async def delete_webhook(*args, **kwargs): Updater, "shutdown", call_after(Updater.shutdown, after_shutdown("updater")) ) app = ApplicationBuilder().bot(one_time_bot).build() - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr(app.bot, "get_updates", get_updates) + with pytest.raises(RuntimeError, match="Test Exception"): app.run_polling(close_loop=False) @@ -2444,7 +2398,13 @@ def signal_handler_test(*args, **kwargs): received_signals.append(args[0]) loop = asyncio.get_event_loop() + + async def get_updates(*args, **kwargs): + await asyncio.sleep(0) + return [] + monkeypatch.setattr(loop, "add_signal_handler", signal_handler_test) + monkeypatch.setattr(app.bot, "get_updates", get_updates) def abort_app(): raise SystemExit @@ -2526,12 +2486,6 @@ async def get_updates(*args, **kwargs): await asyncio.sleep(0) return [] - async def delete_webhook(*args, **kwargs): - return True - - async def set_webhook(*args, **kwargs): - return True - async def post_init(app): # Simply calling app.update_queue.put_nowait(method) in the thread_target doesn't work # for some reason (probably threading magic), so we use an event from the thread_target @@ -2542,6 +2496,9 @@ async def task(app): app.create_task(task(app)) + async def return_true(*args, **kwargs): + return True + app = ( ApplicationBuilder() .application_class(PytestApplication) @@ -2550,8 +2507,6 @@ async def task(app): .build() ) monkeypatch.setattr(app.bot, "get_updates", get_updates) - monkeypatch.setattr(app.bot, "set_webhook", set_webhook) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) events = [] monkeypatch.setattr( @@ -2569,6 +2524,8 @@ async def task(app): "shutdown", call_after(app.shutdown, lambda _: events.append("app.shutdown")), ) + monkeypatch.setattr(app.bot, "set_webhook", return_true) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) def thread_target(): waited = 0 diff --git a/tests/_inline/test_inlinequeryhandler.py b/tests/ext/test_inlinequeryhandler.py similarity index 100% rename from tests/_inline/test_inlinequeryhandler.py rename to tests/ext/test_inlinequeryhandler.py diff --git a/tests/test_pollhandler.py b/tests/ext/test_pollhandler.py similarity index 100% rename from tests/test_pollhandler.py rename to tests/ext/test_pollhandler.py diff --git a/tests/ext/test_updater.py b/tests/ext/test_updater.py index 362c55e9695..5ca6effdc14 100644 --- a/tests/ext/test_updater.py +++ b/tests/ext/test_updater.py @@ -30,7 +30,6 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram.error import InvalidToken, RetryAfter, TelegramError, TimedOut from telegram.ext import ExtBot, InvalidCallbackData, Updater -from telegram.request import HTTPXRequest from tests.auxil.build_messages import make_message, make_message_update from tests.auxil.envvars import TEST_WITH_OPT_DEPS from tests.auxil.files import TEST_DATA_PATH, data_file @@ -179,14 +178,15 @@ async def test_start_without_initialize(self, updater, method): @pytest.mark.parametrize("method", ["start_polling", "start_webhook"]) async def test_shutdown_while_running(self, updater, method, monkeypatch): - async def set_webhook(*args, **kwargs): - return True - - monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) - ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port + async def get_updates(*args, **kwargs): + await asyncio.sleep(0) + return [] + + monkeypatch.setattr(updater.bot, "get_updates", get_updates) + async with updater: if "webhook" in method: await getattr(updater, method)( @@ -408,7 +408,13 @@ async def get_updates(*args, **kwargs): assert log_found - async def test_start_polling_already_running(self, updater): + async def test_start_polling_already_running(self, updater, monkeypatch): + async def get_updates(*args, **kwargs): + await asyncio.sleep(0) + return [] + + monkeypatch.setattr(updater.bot, "get_updates", get_updates) + async with updater: await updater.start_polling() task = asyncio.create_task(updater.start_polling()) @@ -495,15 +501,13 @@ async def get_updates(*args, **kwargs): async def test_start_polling_bootstrap_retries( self, updater, monkeypatch, exception_class, retries ): - async def do_request(*args, **kwargs): + async def delete_webhook(*args, **kwargs): self.message_count += 1 raise exception_class(str(self.message_count)) - async with updater: - # Patch within the context so that updater.bot.initialize can still be called - # by the context manager - monkeypatch.setattr(updater.bot.request, "do_request", do_request) + monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) + async with updater: if exception_class == InvalidToken: with pytest.raises(InvalidToken, match="1"): await updater.start_polling(bootstrap_retries=retries) @@ -879,12 +883,6 @@ async def test_no_unix(self, updater): await updater.start_webhook(unix="DoesntMatter", webhook_url="TOKEN") async def test_start_webhook_already_running(self, updater, monkeypatch): - async def return_true(*args, **kwargs): - return True - - monkeypatch.setattr(updater.bot, "set_webhook", return_true) - monkeypatch.setattr(updater.bot, "delete_webhook", return_true) - ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port async with updater: @@ -983,12 +981,12 @@ async def test_webhook_arbitrary_callback_data( extensively in test_bot.py in conjunction with get_updates.""" updater = Updater(bot=cdc_bot, update_queue=asyncio.Queue()) - async def return_true(*args, **kwargs): + async def set_webhook(*args, **kwargs): return True + monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) + try: - monkeypatch.setattr(updater.bot, "set_webhook", return_true) - monkeypatch.setattr(updater.bot, "delete_webhook", return_true) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port @@ -1031,11 +1029,6 @@ async def return_true(*args, **kwargs): updater.bot.callback_data_cache.clear_callback_queries() async def test_webhook_invalid_ssl(self, monkeypatch, updater): - async def return_true(*args, **kwargs): - return True - - monkeypatch.setattr(updater.bot, "set_webhook", return_true) - monkeypatch.setattr(updater.bot, "delete_webhook", return_true) ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port @@ -1062,9 +1055,6 @@ async def set_webhook(**kwargs): self.test_flag.append(bool(kwargs.get("certificate"))) return True - async def return_true(*args, **kwargs): - return True - orig_wh_server_init = WebhookServer.__init__ def webhook_server_init(*args, **kwargs): @@ -1072,7 +1062,7 @@ def webhook_server_init(*args, **kwargs): orig_wh_server_init(*args, **kwargs) monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) - monkeypatch.setattr(updater.bot, "delete_webhook", return_true) + monkeypatch.setattr( "telegram.ext._utils.webhookhandler.WebhookServer.__init__", webhook_server_init ) @@ -1094,15 +1084,13 @@ def webhook_server_init(*args, **kwargs): async def test_start_webhook_bootstrap_retries( self, updater, monkeypatch, exception_class, retries ): - async def do_request(*args, **kwargs): + async def set_webhook(*args, **kwargs): self.message_count += 1 raise exception_class(str(self.message_count)) - async with updater: - # Patch within the context so that updater.bot.initialize can still be called - # by the context manager - monkeypatch.setattr(HTTPXRequest, "do_request", do_request) + monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) + async with updater: if exception_class == InvalidToken: with pytest.raises(InvalidToken, match="1"): await updater.start_webhook(bootstrap_retries=retries) @@ -1113,11 +1101,6 @@ async def do_request(*args, **kwargs): ) async def test_webhook_invalid_posts(self, updater, monkeypatch): - async def return_true(*args, **kwargs): - return True - - monkeypatch.setattr(updater.bot, "set_webhook", return_true) - monkeypatch.setattr(updater.bot, "delete_webhook", return_true) ip = "127.0.0.1" port = randrange(1024, 49152) @@ -1152,17 +1135,9 @@ async def return_true(*args, **kwargs): await updater.stop() async def test_webhook_update_de_json_fails(self, monkeypatch, updater, caplog): - async def delete_webhook(*args, **kwargs): - return True - - async def set_webhook(*args, **kwargs): - return True - def de_json_fails(*args, **kwargs): raise TypeError("Invalid input") - monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) - monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) orig_de_json = Update.de_json monkeypatch.setattr(Update, "de_json", de_json_fails) From 98d0890bb9a90c31c540714b6ea8b6922dd8840d Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:33:23 +0200 Subject: [PATCH 5/6] fix unix-only tests --- tests/ext/test_application.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/ext/test_application.py b/tests/ext/test_application.py index 80cebc2c176..9de105bcc7f 100644 --- a/tests/ext/test_application.py +++ b/tests/ext/test_application.py @@ -1591,6 +1591,9 @@ def thread_target(): async def post_init(app: Application) -> None: events.append("post_init") + async def delete_webhook(*args, **kwargs): + return True + app = ( Application.builder() .application_class(PytestApplication) @@ -1608,6 +1611,7 @@ async def post_init(app: Application) -> None: "start_polling", call_after(app.updater.start_polling, lambda _: events.append("start_polling")), ) + monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) thread = Thread(target=thread_target) thread.start() @@ -1637,6 +1641,9 @@ def thread_target(): os.kill(os.getpid(), signal.SIGINT) + async def delete_webhook(*args, **kwargs): + return True + async def post_shutdown(app: Application) -> None: events.append("post_shutdown") @@ -1657,6 +1664,7 @@ async def post_shutdown(app: Application) -> None: "shutdown", call_after(app.updater.shutdown, lambda _: events.append("updater.shutdown")), ) + monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) thread = Thread(target=thread_target) thread.start() @@ -1690,6 +1698,9 @@ def thread_target(): os.kill(os.getpid(), signal.SIGINT) + async def delete_webhook(*args, **kwargs): + return True + async def post_stop(app: Application) -> None: events.append("post_stop") @@ -1713,6 +1724,7 @@ async def post_stop(app: Application) -> None: "shutdown", call_after(app.updater.shutdown, lambda _: events.append("updater.shutdown")), ) + monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) thread = Thread(target=thread_target) thread.start() @@ -1872,6 +1884,9 @@ def thread_target(): async def post_init(app: Application) -> None: events.append("post_init") + async def return_true(*args, **kwargs): + return True + app = ( Application.builder() .post_init(post_init) @@ -1889,6 +1904,8 @@ async def post_init(app: Application) -> None: "start_webhook", call_after(app.updater.start_webhook, lambda _: events.append("start_webhook")), ) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) + monkeypatch.setattr(app.bot, "set_webhook", return_true) thread = Thread(target=thread_target) thread.start() @@ -1926,6 +1943,9 @@ def thread_target(): async def post_shutdown(app: Application) -> None: events.append("post_shutdown") + async def return_true(*args, **kwargs): + return True + app = ( Application.builder() .application_class(PytestApplication) @@ -1943,6 +1963,8 @@ async def post_shutdown(app: Application) -> None: "shutdown", call_after(app.updater.shutdown, lambda _: events.append("updater.shutdown")), ) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) + monkeypatch.setattr(app.bot, "set_webhook", return_true) thread = Thread(target=thread_target) thread.start() @@ -1984,6 +2006,9 @@ def thread_target(): async def post_stop(app: Application) -> None: events.append("post_stop") + async def return_true(*args, **kwargs): + return True + app = ( Application.builder() .application_class(PytestApplication) @@ -2004,6 +2029,8 @@ async def post_stop(app: Application) -> None: "shutdown", call_after(app.updater.shutdown, lambda _: events.append("updater.shutdown")), ) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) + monkeypatch.setattr(app.bot, "set_webhook", return_true) thread = Thread(target=thread_target) thread.start() From c25ee5e9372207963dc789bf959c4c99cef30d1d Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:58:16 +0200 Subject: [PATCH 6/6] Reduce code duplication --- tests/auxil/monkeypatch.py | 29 ++++++++++ tests/ext/conftest.py | 5 +- tests/ext/test_application.py | 101 ++++++---------------------------- tests/ext/test_updater.py | 27 ++------- 4 files changed, 52 insertions(+), 110 deletions(-) create mode 100644 tests/auxil/monkeypatch.py diff --git a/tests/auxil/monkeypatch.py b/tests/auxil/monkeypatch.py new file mode 100644 index 00000000000..087ea80232d --- /dev/null +++ b/tests/auxil/monkeypatch.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio + + +async def return_true(*args, **kwargs): + return True + + +async def empty_get_updates(*args, **kwargs): + # The `await` gives the event loop a chance to run other tasks + await asyncio.sleep(0) + return [] diff --git a/tests/ext/conftest.py b/tests/ext/conftest.py index 39d34390a15..f1a877b6e27 100644 --- a/tests/ext/conftest.py +++ b/tests/ext/conftest.py @@ -6,6 +6,7 @@ from telegram.ext.filters import MessageFilter, UpdateFilter from tests.auxil.constants import PRIVATE_KEY from tests.auxil.envvars import TEST_WITH_OPT_DEPS +from tests.auxil.monkeypatch import return_true from tests.auxil.networking import OfflineRequest from tests.auxil.pytest_classes import PytestApplication, PytestBot, make_bot @@ -19,10 +20,6 @@ async def bot(bot_info, offline_bot): return offline_bot -async def return_true(*args, **kwargs): - return True - - @pytest.fixture async def app(bot_info, monkeypatch): # We build a new bot each time so that we use `app` in a context manager without problems diff --git a/tests/ext/test_application.py b/tests/ext/test_application.py index 9de105bcc7f..2826f4cad99 100644 --- a/tests/ext/test_application.py +++ b/tests/ext/test_application.py @@ -60,6 +60,7 @@ from tests.auxil.asyncio_helpers import call_after from tests.auxil.build_messages import make_message_update from tests.auxil.files import PROJECT_ROOT_PATH +from tests.auxil.monkeypatch import empty_get_updates, return_true from tests.auxil.networking import send_webhook_message from tests.auxil.pytest_classes import PytestApplication, PytestUpdater, make_bot from tests.auxil.slots import mro_slots @@ -442,15 +443,8 @@ async def test_start_stop_processing_updates(self, one_time_bot, job_queue, monk async def callback(u, c): self.received = u - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - - async def delete_webhook(*args, **kwargs): - return True - - monkeypatch.setattr(app.bot, "get_updates", get_updates) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) assert not app.running assert not app.updater.running @@ -1529,11 +1523,6 @@ def thread_target(): def test_run_polling_timeout_deprecation_warnings( self, timeout_name, monkeypatch, recwarn, app ): - async def get_updates(*args, **kwargs): - # This makes sure that other coroutines have a chance of running as well - await asyncio.sleep(0) - return [] - def thread_target(): waited = 0 while not app.running: @@ -1546,7 +1535,7 @@ def thread_target(): os.kill(os.getpid(), signal.SIGINT) - monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) thread = Thread(target=thread_target) thread.start() @@ -1573,11 +1562,6 @@ def thread_target(): def test_run_polling_post_init(self, one_time_bot, monkeypatch): events = [] - async def get_updates(*args, **kwargs): - # This makes sure that other coroutines have a chance of running as well - await asyncio.sleep(0) - return [] - def thread_target(): waited = 0 while not app.running: @@ -1591,9 +1575,6 @@ def thread_target(): async def post_init(app: Application) -> None: events.append("post_init") - async def delete_webhook(*args, **kwargs): - return True - app = ( Application.builder() .application_class(PytestApplication) @@ -1602,7 +1583,7 @@ async def delete_webhook(*args, **kwargs): .build() ) app.bot._unfreeze() - monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) monkeypatch.setattr( app, "initialize", call_after(app.initialize, lambda _: events.append("init")) ) @@ -1611,7 +1592,7 @@ async def delete_webhook(*args, **kwargs): "start_polling", call_after(app.updater.start_polling, lambda _: events.append("start_polling")), ) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) thread = Thread(target=thread_target) thread.start() @@ -1626,11 +1607,6 @@ async def delete_webhook(*args, **kwargs): def test_run_polling_post_shutdown(self, one_time_bot, monkeypatch): events = [] - async def get_updates(*args, **kwargs): - # This makes sure that other coroutines have a chance of running as well - await asyncio.sleep(0) - return [] - def thread_target(): waited = 0 while not app.running: @@ -1641,9 +1617,6 @@ def thread_target(): os.kill(os.getpid(), signal.SIGINT) - async def delete_webhook(*args, **kwargs): - return True - async def post_shutdown(app: Application) -> None: events.append("post_shutdown") @@ -1655,7 +1628,7 @@ async def post_shutdown(app: Application) -> None: .build() ) app.bot._unfreeze() - monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) monkeypatch.setattr( app, "shutdown", call_after(app.shutdown, lambda _: events.append("shutdown")) ) @@ -1664,7 +1637,7 @@ async def post_shutdown(app: Application) -> None: "shutdown", call_after(app.updater.shutdown, lambda _: events.append("updater.shutdown")), ) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) thread = Thread(target=thread_target) thread.start() @@ -1683,11 +1656,6 @@ async def post_shutdown(app: Application) -> None: def test_run_polling_post_stop(self, one_time_bot, monkeypatch): events = [] - async def get_updates(*args, **kwargs): - # This makes sure that other coroutines have a chance of running as well - await asyncio.sleep(0) - return [] - def thread_target(): waited = 0 while not app.running: @@ -1698,9 +1666,6 @@ def thread_target(): os.kill(os.getpid(), signal.SIGINT) - async def delete_webhook(*args, **kwargs): - return True - async def post_stop(app: Application) -> None: events.append("post_stop") @@ -1712,7 +1677,7 @@ async def post_stop(app: Application) -> None: .build() ) app.bot._unfreeze() - monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) monkeypatch.setattr(app, "stop", call_after(app.stop, lambda _: events.append("stop"))) monkeypatch.setattr( app.updater, @@ -1724,7 +1689,7 @@ async def post_stop(app: Application) -> None: "shutdown", call_after(app.updater.shutdown, lambda _: events.append("updater.shutdown")), ) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) thread = Thread(target=thread_target) thread.start() @@ -1884,9 +1849,6 @@ def thread_target(): async def post_init(app: Application) -> None: events.append("post_init") - async def return_true(*args, **kwargs): - return True - app = ( Application.builder() .post_init(post_init) @@ -1943,9 +1905,6 @@ def thread_target(): async def post_shutdown(app: Application) -> None: events.append("post_shutdown") - async def return_true(*args, **kwargs): - return True - app = ( Application.builder() .application_class(PytestApplication) @@ -2006,9 +1965,6 @@ def thread_target(): async def post_stop(app: Application) -> None: events.append("post_stop") - async def return_true(*args, **kwargs): - return True - app = ( Application.builder() .application_class(PytestApplication) @@ -2210,13 +2166,6 @@ async def update_logger_callback(update, context): async def callback(*args, **kwargs): called_callbacks.add(kwargs["name"]) - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - - async def delete_webhook(*args, **kwargs): - return True - for cls, method, entry in [ (Application, "initialize", "app_initialize"), (Application, "start", "app_start"), @@ -2245,8 +2194,8 @@ def after(_, name): .post_shutdown(functools.partial(callback, name="post_shutdown")) .build() ) - monkeypatch.setattr(app.bot, "get_updates", get_updates) - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) app.add_handler(TypeHandler(object, update_logger_callback), group=-10) app.add_handler(TypeHandler(object, handler_callback)) @@ -2309,13 +2258,6 @@ def _after_shutdown(*args, **kwargs): return _after_shutdown - async def delete_webhook(*args, **kwargs): - return True - - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - monkeypatch.setattr(Application, method, raise_method) monkeypatch.setattr( Application, @@ -2326,8 +2268,8 @@ async def get_updates(*args, **kwargs): Updater, "shutdown", call_after(Updater.shutdown, after_shutdown("updater")) ) app = ApplicationBuilder().bot(one_time_bot).build() - monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) - monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "delete_webhook", return_true) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) with pytest.raises(RuntimeError, match="Test Exception"): app.run_polling(close_loop=False) @@ -2426,12 +2368,8 @@ def signal_handler_test(*args, **kwargs): loop = asyncio.get_event_loop() - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - monkeypatch.setattr(loop, "add_signal_handler", signal_handler_test) - monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) def abort_app(): raise SystemExit @@ -2509,10 +2447,6 @@ def test_stop_running(self, one_time_bot, monkeypatch, method): called_stop_running = threading.Event() assertions = {} - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - async def post_init(app): # Simply calling app.update_queue.put_nowait(method) in the thread_target doesn't work # for some reason (probably threading magic), so we use an event from the thread_target @@ -2523,9 +2457,6 @@ async def task(app): app.create_task(task(app)) - async def return_true(*args, **kwargs): - return True - app = ( ApplicationBuilder() .application_class(PytestApplication) @@ -2533,7 +2464,7 @@ async def return_true(*args, **kwargs): .post_init(post_init) .build() ) - monkeypatch.setattr(app.bot, "get_updates", get_updates) + monkeypatch.setattr(app.bot, "get_updates", empty_get_updates) events = [] monkeypatch.setattr( diff --git a/tests/ext/test_updater.py b/tests/ext/test_updater.py index 5ca6effdc14..b2b218cc137 100644 --- a/tests/ext/test_updater.py +++ b/tests/ext/test_updater.py @@ -33,6 +33,7 @@ from tests.auxil.build_messages import make_message, make_message_update from tests.auxil.envvars import TEST_WITH_OPT_DEPS from tests.auxil.files import TEST_DATA_PATH, data_file +from tests.auxil.monkeypatch import empty_get_updates, return_true from tests.auxil.networking import send_webhook_message from tests.auxil.pytest_classes import make_bot from tests.auxil.slots import mro_slots @@ -181,11 +182,7 @@ async def test_shutdown_while_running(self, updater, method, monkeypatch): ip = "127.0.0.1" port = randrange(1024, 49152) # Select random port - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - - monkeypatch.setattr(updater.bot, "get_updates", get_updates) + monkeypatch.setattr(updater.bot, "get_updates", empty_get_updates) async with updater: if "webhook" in method: @@ -381,11 +378,8 @@ async def get_updates(*args, **kwargs): assert log_found async def test_polling_mark_updates_as_read_failure(self, monkeypatch, updater, caplog): - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - monkeypatch.setattr(updater.bot, "get_updates", get_updates) + monkeypatch.setattr(updater.bot, "get_updates", empty_get_updates) async with updater: await updater.start_polling() @@ -409,11 +403,8 @@ async def get_updates(*args, **kwargs): assert log_found async def test_start_polling_already_running(self, updater, monkeypatch): - async def get_updates(*args, **kwargs): - await asyncio.sleep(0) - return [] - monkeypatch.setattr(updater.bot, "get_updates", get_updates) + monkeypatch.setattr(updater.bot, "get_updates", empty_get_updates) async with updater: await updater.start_polling() @@ -730,10 +721,7 @@ async def delete_webhook(*args, **kwargs): self.message_count += 1 return True - async def set_webhook(*args, **kwargs): - return True - - monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) + monkeypatch.setattr(updater.bot, "set_webhook", return_true) monkeypatch.setattr(updater.bot, "delete_webhook", delete_webhook) ip = "127.0.0.1" @@ -981,10 +969,7 @@ async def test_webhook_arbitrary_callback_data( extensively in test_bot.py in conjunction with get_updates.""" updater = Updater(bot=cdc_bot, update_queue=asyncio.Queue()) - async def set_webhook(*args, **kwargs): - return True - - monkeypatch.setattr(updater.bot, "set_webhook", set_webhook) + monkeypatch.setattr(updater.bot, "set_webhook", return_true) try: