From a8164167d704f509c61f098de1f22672986d0230 Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Thu, 30 Oct 2025 08:53:46 +0000 Subject: [PATCH 1/3] Move the logic of `Actor._get_default_exit_process` for pytest to the test environment --- src/apify/_actor.py | 8 +------- tests/unit/conftest.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/apify/_actor.py b/src/apify/_actor.py index 4eb27fa0..9b1d151e 100644 --- a/src/apify/_actor.py +++ b/src/apify/_actor.py @@ -1,7 +1,6 @@ from __future__ import annotations import asyncio -import os import sys from contextlib import suppress from datetime import datetime, timedelta, timezone @@ -1288,16 +1287,11 @@ def _raise_if_not_initialized(self) -> None: raise RuntimeError('The Actor was not initialized!') def _get_default_exit_process(self) -> bool: - """Return False for IPython, Pytest, and Scrapy environments, True otherwise.""" + """Return False for IPython and Scrapy environments, True otherwise.""" if is_running_in_ipython(): self.log.debug('Running in IPython, setting default `exit_process` to False.') return False - # Check if running in Pytest by detecting the relevant environment variable. - if os.getenv('PYTEST_CURRENT_TEST'): - self.log.debug('Running in Pytest, setting default `exit_process` to False.') - return False - # Check if running in Scrapy by attempting to import it. with suppress(ImportError): import scrapy # noqa: F401 PLC0415 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index bd041b50..2ab19f1e 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -226,3 +226,21 @@ def proxyless_async_client(*args: Any, **kwargs: Any) -> impit.AsyncClient: monkeypatch.setattr(impit, 'AsyncClient', proxyless_async_client) yield monkeypatch.undo() + + +@pytest.fixture(autouse=True) +def patched_actor_exit_proc(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: + """Patch actor exit process to prevent sys.exit calls during tests.""" + + original_method = apify._actor._ActorType._get_default_exit_process + + def mock_get_default_exit_process(self: apify._actor._ActorType) -> bool: + if os.getenv('PYTEST_CURRENT_TEST'): + self.log.debug('Running in Pytest, setting default `exit_process` to False.') + return False + + return original_method(self) + + monkeypatch.setattr(apify._actor._ActorType, '_get_default_exit_process', mock_get_default_exit_process) + yield + monkeypatch.undo() From 39ae3f69f958b8c9f3f5d0241b0f2b2ff383fd84 Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Thu, 30 Oct 2025 14:13:33 +0000 Subject: [PATCH 2/3] update test --- tests/unit/actor/test_actor_log.py | 143 ++++++++++++++--------------- tests/unit/conftest.py | 18 ---- 2 files changed, 69 insertions(+), 92 deletions(-) diff --git a/tests/unit/actor/test_actor_log.py b/tests/unit/actor/test_actor_log.py index 6b0a84c1..67c5d08e 100644 --- a/tests/unit/actor/test_actor_log.py +++ b/tests/unit/actor/test_actor_log.py @@ -15,7 +15,7 @@ async def test_actor_logs_messages_correctly(caplog: pytest.LogCaptureFixture) - caplog.set_level(logging.DEBUG, logger='apify') with contextlib.suppress(RuntimeError): - async with Actor(configure_logging=False): + async with Actor(configure_logging=False, exit_process=False): # Test Actor.log Actor.log.debug('Debug message') Actor.log.info('Info message') @@ -36,77 +36,72 @@ async def test_actor_logs_messages_correctly(caplog: pytest.LogCaptureFixture) - # Test that exception in Actor.main is logged with the traceback raise RuntimeError('Dummy RuntimeError') + # We skip the first entry, as it is related to the initialization of `lazy_object_proxy.Proxy`` for `Actor`. + records = caplog.records[1:] + # Updated expected number of log records (additional debug messages added) - assert len(caplog.records) == 16 - - # Record 0: First Pytest context log - assert caplog.records[0].levelno == logging.DEBUG - assert caplog.records[0].message.startswith('Running in Pytest') - - # Record 1: Duplicate Pytest context log - assert caplog.records[1].levelno == logging.DEBUG - assert caplog.records[1].message.startswith('Running in Pytest') - - # Record 2: Logging configured - assert caplog.records[2].levelno == logging.DEBUG - assert caplog.records[2].message == 'Logging configured' - - # Record 3: Initializing Actor - assert caplog.records[3].levelno == logging.INFO - assert caplog.records[3].message == 'Initializing Actor' - - # Record 4: Configuration initialized - assert caplog.records[4].levelno == logging.DEBUG - assert caplog.records[4].message == 'Configuration initialized' - - # Record 5: Storage client initialized - assert caplog.records[5].levelno == logging.DEBUG - assert caplog.records[5].message == 'Storage client initialized' - - # Record 6: Event manager initialized - assert caplog.records[6].levelno == logging.DEBUG - assert caplog.records[6].message == 'Event manager initialized' - - # Record 7: Charging manager initialized - assert caplog.records[7].levelno == logging.DEBUG - assert caplog.records[7].message == 'Charging manager initialized' - - # Record 8: Debug message - assert caplog.records[8].levelno == logging.DEBUG - assert caplog.records[8].message == 'Debug message' - - # Record 9: Info message - assert caplog.records[9].levelno == logging.INFO - assert caplog.records[9].message == 'Info message' - - # Record 10: Warning message - assert caplog.records[10].levelno == logging.WARNING - assert caplog.records[10].message == 'Warning message' - - # Record 11: Error message - assert caplog.records[11].levelno == logging.ERROR - assert caplog.records[11].message == 'Error message' - - # Record 12: Exception message with traceback (ValueError) - assert caplog.records[12].levelno == logging.ERROR - assert caplog.records[12].message == 'Exception message' - assert caplog.records[12].exc_info is not None - assert caplog.records[12].exc_info[0] is ValueError - assert isinstance(caplog.records[12].exc_info[1], ValueError) - assert str(caplog.records[12].exc_info[1]) == 'Dummy ValueError' - - # Record 13: Multiline log message - assert caplog.records[13].levelno == logging.INFO - assert caplog.records[13].message == 'Multi\nline\nlog\nmessage' - - # Record 14: Actor failed with an exception (RuntimeError) - assert caplog.records[14].levelno == logging.ERROR - assert caplog.records[14].message == 'Actor failed with an exception' - assert caplog.records[14].exc_info is not None - assert caplog.records[14].exc_info[0] is RuntimeError - assert isinstance(caplog.records[14].exc_info[1], RuntimeError) - assert str(caplog.records[14].exc_info[1]) == 'Dummy RuntimeError' - - # Record 15: Exiting Actor - assert caplog.records[15].levelno == logging.INFO - assert caplog.records[15].message == 'Exiting Actor' + assert len(records) == 14 + + # Record 0: Logging configured + assert records[0].levelno == logging.DEBUG + assert records[0].message == 'Logging configured' + + # Record 1: Initializing Actor + assert records[1].levelno == logging.INFO + assert records[1].message == 'Initializing Actor' + + # Record 2: Configuration initialized + assert records[2].levelno == logging.DEBUG + assert records[2].message == 'Configuration initialized' + + # Record 3: Storage client initialized + assert records[3].levelno == logging.DEBUG + assert records[3].message == 'Storage client initialized' + + # Record 4: Event manager initialized + assert records[4].levelno == logging.DEBUG + assert records[4].message == 'Event manager initialized' + + # Record 5: Charging manager initialized + assert records[5].levelno == logging.DEBUG + assert records[5].message == 'Charging manager initialized' + + # Record 6: Debug message + assert records[6].levelno == logging.DEBUG + assert records[6].message == 'Debug message' + + # Record 7: Info message + assert records[7].levelno == logging.INFO + assert records[7].message == 'Info message' + + # Record 8: Warning message + assert records[8].levelno == logging.WARNING + assert records[8].message == 'Warning message' + + # Record 9: Error message + assert records[9].levelno == logging.ERROR + assert records[9].message == 'Error message' + + # Record 10: Exception message with traceback (ValueError) + assert records[10].levelno == logging.ERROR + assert records[10].message == 'Exception message' + assert records[10].exc_info is not None + assert records[10].exc_info[0] is ValueError + assert isinstance(records[10].exc_info[1], ValueError) + assert str(records[10].exc_info[1]) == 'Dummy ValueError' + + # Record 11: Multiline log message + assert records[11].levelno == logging.INFO + assert records[11].message == 'Multi\nline\nlog\nmessage' + + # Record 12: Actor failed with an exception (RuntimeError) + assert records[12].levelno == logging.ERROR + assert records[12].message == 'Actor failed with an exception' + assert records[12].exc_info is not None + assert records[12].exc_info[0] is RuntimeError + assert isinstance(records[12].exc_info[1], RuntimeError) + assert str(records[12].exc_info[1]) == 'Dummy RuntimeError' + + # Record 13: Exiting Actor + assert records[13].levelno == logging.INFO + assert records[13].message == 'Exiting Actor' diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 2ab19f1e..bd041b50 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -226,21 +226,3 @@ def proxyless_async_client(*args: Any, **kwargs: Any) -> impit.AsyncClient: monkeypatch.setattr(impit, 'AsyncClient', proxyless_async_client) yield monkeypatch.undo() - - -@pytest.fixture(autouse=True) -def patched_actor_exit_proc(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: - """Patch actor exit process to prevent sys.exit calls during tests.""" - - original_method = apify._actor._ActorType._get_default_exit_process - - def mock_get_default_exit_process(self: apify._actor._ActorType) -> bool: - if os.getenv('PYTEST_CURRENT_TEST'): - self.log.debug('Running in Pytest, setting default `exit_process` to False.') - return False - - return original_method(self) - - monkeypatch.setattr(apify._actor._ActorType, '_get_default_exit_process', mock_get_default_exit_process) - yield - monkeypatch.undo() From f3a6584ee33824fe77f039afac6be429f970bbfd Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Thu, 30 Oct 2025 14:16:10 +0000 Subject: [PATCH 3/3] clean comment --- tests/unit/actor/test_actor_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/actor/test_actor_log.py b/tests/unit/actor/test_actor_log.py index 67c5d08e..a09f1d9d 100644 --- a/tests/unit/actor/test_actor_log.py +++ b/tests/unit/actor/test_actor_log.py @@ -36,7 +36,7 @@ async def test_actor_logs_messages_correctly(caplog: pytest.LogCaptureFixture) - # Test that exception in Actor.main is logged with the traceback raise RuntimeError('Dummy RuntimeError') - # We skip the first entry, as it is related to the initialization of `lazy_object_proxy.Proxy`` for `Actor`. + # We skip the first entry, as it is related to the initialization of `lazy_object_proxy.Proxy` for `Actor`. records = caplog.records[1:] # Updated expected number of log records (additional debug messages added)