Improve coverage

This commit is contained in:
Adrian Chaves 2026-04-29 16:06:59 +02:00
parent 2a0825d75a
commit de950c9fa1
4 changed files with 101 additions and 0 deletions

View File

@ -0,0 +1,20 @@
import asyncio
import sys
import scrapy
from scrapy.crawler import AsyncCrawlerProcess
class SleepingSpider(scrapy.Spider):
name = "sleeping"
start_urls = ["data:,;"]
async def parse(self, response):
await asyncio.sleep(int(sys.argv[1]))
process = AsyncCrawlerProcess(settings={"TWISTED_REACTOR_ENABLED": False})
process.crawl(SleepingSpider)
process.start(stop_after_crawl=False)

View File

@ -943,6 +943,25 @@ async def test_async_crawler_process_force_stop_reactor_enabled_via_public_crawl
assert called
@pytest.mark.only_asyncio
@coroutine_test
async def test_async_crawler_process_force_stop_reactorless_without_main_task(
reactor_pytest: str,
) -> None:
if reactor_pytest != "none":
pytest.skip("This test is only for --reactor=none")
crawler_process = AsyncCrawlerProcess(
{"TWISTED_REACTOR_ENABLED": False},
install_root_handler=False,
)
assert crawler_process._reactorless_loop is not None
assert crawler_process._reactorless_main_task is None
crawler = crawler_process.create_crawler(DefaultSpider)
await crawler.stop_async(mode="force")
@pytest.mark.only_asyncio
@coroutine_test
async def test_async_crawler_process_force_stop_reactorless_without_loop(
@ -998,3 +1017,48 @@ async def test_async_crawler_process_force_stop_reactorless_with_task(
assert loop.callback is not None
loop.callback()
assert task.called is True
def test_async_crawler_process_schedule_reactorless_shutdown_without_loop() -> None:
crawler_process = object.__new__(AsyncCrawlerProcess)
crawler_process._reactorless_loop = None
crawler_process._schedule_reactorless_shutdown(mode="graceful")
def test_async_crawler_process_schedule_reactorless_shutdown_runtime_error() -> None:
crawler_process = object.__new__(AsyncCrawlerProcess)
class DummyLoop:
scheduled = False
create_task_called = False
def call_soon_threadsafe(self, callback) -> None:
self.scheduled = True
callback()
def create_task(self, coro) -> None:
self.create_task_called = True
raise RuntimeError("event loop is closing")
class DummyCoro:
closed = False
def close(self) -> None:
self.closed = True
loop = DummyLoop()
coro = DummyCoro()
def shutdown_reactorless(*, mode: str) -> DummyCoro:
assert mode == "graceful"
return coro
crawler_process._reactorless_loop = cast("asyncio.AbstractEventLoop", loop)
crawler_process._shutdown_reactorless = shutdown_reactorless
crawler_process._schedule_reactorless_shutdown(mode="graceful")
assert loop.scheduled
assert loop.create_task_called
assert coro.closed

View File

@ -420,6 +420,9 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
def test_shutdown_graceful(self) -> None:
self._test_shutdown_graceful("reactorless_sleeping.py")
def test_shutdown_graceful_stop_after_crawl_false(self) -> None:
self._test_shutdown_graceful("reactorless_sleeping_no_stop_after_crawl.py")
@coroutine_test
async def test_shutdown_forced(self) -> None:
await self._test_shutdown_forced("reactorless_sleeping.py")

View File

@ -484,6 +484,20 @@ class TestEngine(TestEngineBase):
engine._closewait.callback(None)
await maybe_deferred_to_future(stop_dfd)
@coroutine_test
async def test_stop_async_reentrant_graceful_without_spider_or_closewait(
self,
) -> None:
engine = ExecutionEngine(get_crawler(DefaultSpider), lambda _: None)
engine._stopping = True
with patch.object(
engine, "close_spider_async", new_callable=AsyncMock
) as close:
await engine.stop_async(mode="graceful")
close.assert_not_called()
@coroutine_test
async def test_handle_downloader_output_ignores_fast_cancelled_failures(
self,