mirror of https://github.com/scrapy/scrapy.git
Reactorless tests 1: the test env (#6952)
* Reactorless tests. * Updates after merging master. * Update a new import. * Updates after merging master. * Update testenv:no-reactor-pinned. * Foundations for the reactorless mode. * Fixes after merging master. * Updates after merging reactorless-base. * Add simple subprocess tests for reactorless AsyncCrawler*. * More reactorless tests. * Refactor AsyncCrawlerProcess.start(). * More checks. * Fix test_reactorless_import_hook. * More tests. * Call install_reactor() before asyncio.run(). * Cleanup. * Rephrase. * Fix a test. * Fixes. * Rephrasing. * Set TELNETCONSOLE_ENABLED=False in the reactorless mode. * Fix a new import. * Clarify --reactor marks. * Cleanup. * More docs/comments. * Update test_fallback_workflow(). * Update TestDeferredFFromCoroF. * Typing improvements.
This commit is contained in:
parent
54d8562fb0
commit
3186ccf5d5
|
|
@ -32,6 +32,9 @@ jobs:
|
||||||
- python-version: "3.13"
|
- python-version: "3.13"
|
||||||
env:
|
env:
|
||||||
TOXENV: default-reactor
|
TOXENV: default-reactor
|
||||||
|
- python-version: "3.13"
|
||||||
|
env:
|
||||||
|
TOXENV: no-reactor
|
||||||
- python-version: pypy3.11
|
- python-version: pypy3.11
|
||||||
env:
|
env:
|
||||||
TOXENV: pypy3
|
TOXENV: pypy3
|
||||||
|
|
@ -43,6 +46,9 @@ jobs:
|
||||||
- python-version: "3.10.19"
|
- python-version: "3.10.19"
|
||||||
env:
|
env:
|
||||||
TOXENV: default-reactor-pinned
|
TOXENV: default-reactor-pinned
|
||||||
|
- python-version: "3.10.19"
|
||||||
|
env:
|
||||||
|
TOXENV: no-reactor-pinned
|
||||||
- python-version: pypy3.11
|
- python-version: pypy3.11
|
||||||
env:
|
env:
|
||||||
TOXENV: pypy3-pinned
|
TOXENV: pypy3-pinned
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ jobs:
|
||||||
- python-version: "3.13"
|
- python-version: "3.13"
|
||||||
env:
|
env:
|
||||||
TOXENV: default-reactor
|
TOXENV: default-reactor
|
||||||
|
- python-version: "3.13"
|
||||||
|
env:
|
||||||
|
TOXENV: no-reactor
|
||||||
|
|
||||||
# pinned deps
|
# pinned deps
|
||||||
- python-version: "3.10.11"
|
- python-version: "3.10.11"
|
||||||
|
|
|
||||||
33
conftest.py
33
conftest.py
|
|
@ -8,6 +8,7 @@ import pytest
|
||||||
from twisted.web.http import H2_ENABLED
|
from twisted.web.http import H2_ENABLED
|
||||||
|
|
||||||
from scrapy.utils.reactor import set_asyncio_event_loop_policy
|
from scrapy.utils.reactor import set_asyncio_event_loop_policy
|
||||||
|
from scrapy.utils.reactorless import install_reactor_import_hook
|
||||||
from tests.keys import generate_keys
|
from tests.keys import generate_keys
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
|
||||||
|
|
@ -25,10 +26,6 @@ collect_ignore = [
|
||||||
# not a test, but looks like a test
|
# not a test, but looks like a test
|
||||||
"scrapy/utils/testproc.py",
|
"scrapy/utils/testproc.py",
|
||||||
"scrapy/utils/testsite.py",
|
"scrapy/utils/testsite.py",
|
||||||
"tests/ftpserver.py",
|
|
||||||
"tests/mockserver.py",
|
|
||||||
"tests/pipelines.py",
|
|
||||||
"tests/spiders.py",
|
|
||||||
# contains scripts to be run by tests/test_crawler.py::AsyncCrawlerProcessSubprocess
|
# contains scripts to be run by tests/test_crawler.py::AsyncCrawlerProcessSubprocess
|
||||||
*_py_files("tests/AsyncCrawlerProcess"),
|
*_py_files("tests/AsyncCrawlerProcess"),
|
||||||
# contains scripts to be run by tests/test_crawler.py::AsyncCrawlerRunnerSubprocess
|
# contains scripts to be run by tests/test_crawler.py::AsyncCrawlerRunnerSubprocess
|
||||||
|
|
@ -56,6 +53,17 @@ if not H2_ENABLED:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser, pluginmanager):
|
||||||
|
if pluginmanager.hasplugin("twisted"):
|
||||||
|
return
|
||||||
|
# add the full choice set so that pytest doesn't complain about invalid choices in some cases
|
||||||
|
parser.addoption(
|
||||||
|
"--reactor",
|
||||||
|
default="none",
|
||||||
|
choices=["asyncio", "default", "none"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def mockserver() -> Generator[MockServer]:
|
def mockserver() -> Generator[MockServer]:
|
||||||
with MockServer() as mockserver:
|
with MockServer() as mockserver:
|
||||||
|
|
@ -72,17 +80,26 @@ def pytest_configure(config):
|
||||||
# Needed on Windows to switch from proactor to selector for Twisted reactor compatibility.
|
# Needed on Windows to switch from proactor to selector for Twisted reactor compatibility.
|
||||||
# If we decide to run tests with both, we will need to add a new option and check it here.
|
# If we decide to run tests with both, we will need to add a new option and check it here.
|
||||||
set_asyncio_event_loop_policy()
|
set_asyncio_event_loop_policy()
|
||||||
|
elif config.getoption("--reactor") == "none":
|
||||||
|
install_reactor_import_hook()
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
# Skip tests based on reactor markers
|
# Skip tests based on reactor markers
|
||||||
reactor = item.config.getoption("--reactor")
|
reactor = item.config.getoption("--reactor")
|
||||||
|
|
||||||
if item.get_closest_marker("only_asyncio") and reactor != "asyncio":
|
if item.get_closest_marker("requires_reactor") and reactor == "none":
|
||||||
pytest.skip("This test is only run with --reactor=asyncio")
|
pytest.skip('This test is only run when the --reactor value is not "none"')
|
||||||
|
|
||||||
if item.get_closest_marker("only_not_asyncio") and reactor == "asyncio":
|
if item.get_closest_marker("only_asyncio") and reactor not in {"asyncio", "none"}:
|
||||||
pytest.skip("This test is only run without --reactor=asyncio")
|
pytest.skip(
|
||||||
|
'This test is only run when the --reactor value is "asyncio" (default) or "none"'
|
||||||
|
)
|
||||||
|
|
||||||
|
if item.get_closest_marker("only_not_asyncio") and reactor in {"asyncio", "none"}:
|
||||||
|
pytest.skip(
|
||||||
|
'This test is only run when the --reactor value is not "asyncio" (default) or "none"'
|
||||||
|
)
|
||||||
|
|
||||||
# Skip tests requiring optional dependencies
|
# Skip tests requiring optional dependencies
|
||||||
optional_deps = [
|
optional_deps = [
|
||||||
|
|
|
||||||
|
|
@ -227,8 +227,9 @@ addopts = [
|
||||||
xfail_strict = true
|
xfail_strict = true
|
||||||
python_files = ["test_*.py", "test_*/__init__.py"]
|
python_files = ["test_*.py", "test_*/__init__.py"]
|
||||||
markers = [
|
markers = [
|
||||||
"only_asyncio: marks tests as only enabled when --reactor=asyncio is passed",
|
"only_asyncio: marks tests that require the asyncio loop to be used",
|
||||||
"only_not_asyncio: marks tests as only enabled when --reactor=asyncio is not passed",
|
"only_not_asyncio: marks tests that require the asyncio loop to not be used",
|
||||||
|
"requires_reactor: marks tests that require a reactor",
|
||||||
"requires_uvloop: marks tests as only enabled when uvloop is known to be working",
|
"requires_uvloop: marks tests as only enabled when uvloop is known to be working",
|
||||||
"requires_botocore: marks tests that need botocore (but not boto3)",
|
"requires_botocore: marks tests that need botocore (but not boto3)",
|
||||||
"requires_boto3: marks tests that need botocore and boto3",
|
"requires_boto3: marks tests that need botocore and boto3",
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from unittest import mock
|
||||||
from twisted.trial.unittest import SkipTest
|
from twisted.trial.unittest import SkipTest
|
||||||
from twisted.web.client import Agent
|
from twisted.web.client import Agent
|
||||||
|
|
||||||
from scrapy.crawler import CrawlerRunner
|
from scrapy.crawler import AsyncCrawlerRunner, CrawlerRunner, CrawlerRunnerBase
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.utils.boto import is_botocore_available
|
from scrapy.utils.boto import is_botocore_available
|
||||||
from scrapy.utils.deprecate import create_deprecated_class
|
from scrapy.utils.deprecate import create_deprecated_class
|
||||||
|
|
@ -116,16 +116,24 @@ def get_reactor_settings() -> dict[str, Any]:
|
||||||
|
|
||||||
``Crawler._apply_settings()`` checks that the installed reactor matches the
|
``Crawler._apply_settings()`` checks that the installed reactor matches the
|
||||||
settings, so tests that run the crawler in the current process may need to
|
settings, so tests that run the crawler in the current process may need to
|
||||||
pass a correct ``"TWISTED_REACTOR"`` setting value when creating it.
|
pass a correct :setting:`TWISTED_REACTOR` setting value when creating it.
|
||||||
"""
|
"""
|
||||||
if not is_reactor_installed():
|
|
||||||
raise RuntimeError(
|
|
||||||
"get_reactor_settings() called without an installed reactor,"
|
|
||||||
" you may need to install a reactor explicitly when running your tests."
|
|
||||||
)
|
|
||||||
settings: dict[str, Any] = {}
|
settings: dict[str, Any] = {}
|
||||||
if not is_asyncio_reactor_installed():
|
if is_reactor_installed():
|
||||||
settings["TWISTED_REACTOR"] = None
|
if not is_asyncio_reactor_installed():
|
||||||
|
settings["TWISTED_REACTOR"] = None
|
||||||
|
else:
|
||||||
|
# We are either running Scrapy tests for the reactorless mode, or
|
||||||
|
# running some 3rd-party library tests for the reactorless mode, or
|
||||||
|
# running some 3rd-party library tests without initializing a reactor
|
||||||
|
# properly. The first two cases are fine, but we cannot distinguish the
|
||||||
|
# last one from them.
|
||||||
|
settings["TWISTED_ENABLED"] = False
|
||||||
|
settings["DOWNLOAD_HANDLERS"] = {
|
||||||
|
"ftp": None,
|
||||||
|
"http": None,
|
||||||
|
"https": None,
|
||||||
|
}
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -144,7 +152,11 @@ def get_crawler(
|
||||||
**get_reactor_settings(),
|
**get_reactor_settings(),
|
||||||
**(settings_dict or {}),
|
**(settings_dict or {}),
|
||||||
}
|
}
|
||||||
runner = CrawlerRunner(settings)
|
runner: CrawlerRunnerBase
|
||||||
|
if is_reactor_installed():
|
||||||
|
runner = CrawlerRunner(settings)
|
||||||
|
else:
|
||||||
|
runner = AsyncCrawlerRunner(settings)
|
||||||
crawler = runner.create_crawler(spidercls or DefaultSpider)
|
crawler = runner.create_crawler(spidercls or DefaultSpider)
|
||||||
crawler._apply_settings()
|
crawler._apply_settings()
|
||||||
return crawler
|
return crawler
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import itertools
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy import Spider
|
from scrapy import Spider
|
||||||
from scrapy.crawler import Crawler, CrawlerRunner
|
from scrapy.crawler import AsyncCrawlerRunner, Crawler, CrawlerRunner
|
||||||
from scrapy.exceptions import NotConfigured
|
from scrapy.exceptions import NotConfigured
|
||||||
from scrapy.settings import BaseSettings, Settings
|
from scrapy.settings import BaseSettings, Settings
|
||||||
from scrapy.utils.test import get_crawler, get_reactor_settings
|
from scrapy.utils.test import get_crawler, get_reactor_settings
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class SimpleAddon:
|
class SimpleAddon:
|
||||||
|
|
@ -109,9 +108,15 @@ class TestAddonManager:
|
||||||
crawler = get_crawler(settings_dict=settings_dict)
|
crawler = get_crawler(settings_dict=settings_dict)
|
||||||
assert crawler.settings.getint("KEY") == 15
|
assert crawler.settings.getint("KEY") == 15
|
||||||
|
|
||||||
|
runner_cls = (
|
||||||
|
CrawlerRunner
|
||||||
|
if settings_dict.get("TWISTED_ENABLED", True)
|
||||||
|
else AsyncCrawlerRunner
|
||||||
|
)
|
||||||
|
|
||||||
settings = Settings(settings_dict)
|
settings = Settings(settings_dict)
|
||||||
settings.set("KEY", 0, priority="default")
|
settings.set("KEY", 0, priority="default")
|
||||||
runner = CrawlerRunner(settings)
|
runner = runner_cls(settings)
|
||||||
crawler = runner.create_crawler(Spider)
|
crawler = runner.create_crawler(Spider)
|
||||||
crawler._apply_settings()
|
crawler._apply_settings()
|
||||||
assert crawler.settings.getint("KEY") == 15
|
assert crawler.settings.getint("KEY") == 15
|
||||||
|
|
@ -123,44 +128,39 @@ class TestAddonManager:
|
||||||
}
|
}
|
||||||
settings = Settings(settings_dict)
|
settings = Settings(settings_dict)
|
||||||
settings.set("KEY", 0, priority="default")
|
settings.set("KEY", 0, priority="default")
|
||||||
runner = CrawlerRunner(settings)
|
runner = runner_cls(settings)
|
||||||
crawler = runner.create_crawler(Spider)
|
crawler = runner.create_crawler(Spider)
|
||||||
assert crawler.settings.getint("KEY") == 20
|
assert crawler.settings.getint("KEY") == 20
|
||||||
|
|
||||||
def test_fallback_workflow(self):
|
def test_fallback_workflow(self):
|
||||||
FALLBACK_SETTING = "MY_FALLBACK_DOWNLOAD_HANDLER"
|
FALLBACK_SETTING = "MY_FALLBACK_SCHEDULER"
|
||||||
|
|
||||||
class AddonWithFallback:
|
class AddonWithFallback:
|
||||||
def update_settings(self, settings):
|
def update_settings(self, settings):
|
||||||
if not settings.get(FALLBACK_SETTING):
|
if not settings.get(FALLBACK_SETTING):
|
||||||
settings.set(
|
settings.set(
|
||||||
FALLBACK_SETTING,
|
FALLBACK_SETTING,
|
||||||
settings.getwithbase("DOWNLOAD_HANDLERS")["https"],
|
settings.get("SCHEDULER"),
|
||||||
"addon",
|
"addon",
|
||||||
)
|
)
|
||||||
settings["DOWNLOAD_HANDLERS"]["https"] = "AddonHandler"
|
settings["SCHEDULER"] = "AddonScheduler"
|
||||||
|
|
||||||
settings_dict = {
|
settings_dict = {
|
||||||
"ADDONS": {AddonWithFallback: 1},
|
"ADDONS": {AddonWithFallback: 1},
|
||||||
}
|
}
|
||||||
crawler = get_crawler(settings_dict=settings_dict)
|
crawler = get_crawler(settings_dict=settings_dict)
|
||||||
|
assert crawler.settings.get("SCHEDULER") == "AddonScheduler"
|
||||||
assert (
|
assert (
|
||||||
crawler.settings.getwithbase("DOWNLOAD_HANDLERS")["https"] == "AddonHandler"
|
crawler.settings.get(FALLBACK_SETTING) == "scrapy.core.scheduler.Scheduler"
|
||||||
)
|
|
||||||
assert (
|
|
||||||
crawler.settings.get(FALLBACK_SETTING)
|
|
||||||
== "scrapy.core.downloader.handlers.http11.HTTP11DownloadHandler"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
settings_dict = {
|
settings_dict = {
|
||||||
"ADDONS": {AddonWithFallback: 1},
|
"ADDONS": {AddonWithFallback: 1},
|
||||||
"DOWNLOAD_HANDLERS": {"https": "UserHandler"},
|
"SCHEDULER": "UserScheduler",
|
||||||
}
|
}
|
||||||
crawler = get_crawler(settings_dict=settings_dict)
|
crawler = get_crawler(settings_dict=settings_dict)
|
||||||
assert (
|
assert crawler.settings.get("SCHEDULER") == "AddonScheduler"
|
||||||
crawler.settings.getwithbase("DOWNLOAD_HANDLERS")["https"] == "AddonHandler"
|
assert crawler.settings.get(FALLBACK_SETTING) == "UserScheduler"
|
||||||
)
|
|
||||||
assert crawler.settings.get(FALLBACK_SETTING) == "UserHandler"
|
|
||||||
|
|
||||||
def test_logging_message(self):
|
def test_logging_message(self):
|
||||||
class LoggedAddon:
|
class LoggedAddon:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import (
|
from tests.spiders import (
|
||||||
|
|
@ -9,6 +7,7 @@ from tests.spiders import (
|
||||||
MaxItemsAndRequestsSpider,
|
MaxItemsAndRequestsSpider,
|
||||||
SlowSpider,
|
SlowSpider,
|
||||||
)
|
)
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class TestCloseSpider:
|
class TestCloseSpider:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
from unittest import TextTestResult
|
from unittest import TextTestResult
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
from twisted.python import failure
|
from twisted.python import failure
|
||||||
|
|
||||||
from scrapy import FormRequest
|
from scrapy import FormRequest
|
||||||
|
|
@ -19,6 +18,7 @@ from scrapy.spidermiddlewares.httperror import HttpError
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class DemoItem(Item):
|
class DemoItem(Item):
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@ from scrapy.core.downloader.contextfactory import (
|
||||||
from scrapy.core.downloader.handlers.http11 import _RequestBodyProducer
|
from scrapy.core.downloader.handlers.http11 import _RequestBodyProducer
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.settings import Settings
|
from scrapy.settings import Settings
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.misc import build_from_crawler
|
from scrapy.utils.misc import build_from_crawler
|
||||||
from scrapy.utils.python import to_bytes
|
from scrapy.utils.python import to_bytes
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http_resources import PayloadResource
|
from tests.mockserver.http_resources import PayloadResource
|
||||||
from tests.mockserver.utils import ssl_context_factory
|
from tests.mockserver.utils import ssl_context_factory
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.spiders import SimpleSpider
|
from tests.spiders import SimpleSpider
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from urllib.parse import urlencode, urlparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks, succeed
|
from twisted.internet.defer import succeed
|
||||||
from twisted.internet.ssl import Certificate
|
from twisted.internet.ssl import Certificate
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ from scrapy.crawler import CrawlerRunner
|
||||||
from scrapy.exceptions import CloseSpider, ScrapyDeprecationWarning, StopDownload
|
from scrapy.exceptions import CloseSpider, ScrapyDeprecationWarning, StopDownload
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
from scrapy.http.response import Response
|
from scrapy.http.response import Response
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.engine import format_engine_status, get_engine_status
|
from scrapy.utils.engine import format_engine_status, get_engine_status
|
||||||
from scrapy.utils.python import to_unicode
|
from scrapy.utils.python import to_unicode
|
||||||
from scrapy.utils.test import get_crawler, get_reactor_settings
|
from scrapy.utils.test import get_crawler, get_reactor_settings
|
||||||
|
|
@ -55,6 +55,7 @@ from tests.spiders import (
|
||||||
StartGoodAndBadOutput,
|
StartGoodAndBadOutput,
|
||||||
StartItemSpider,
|
StartItemSpider,
|
||||||
)
|
)
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from scrapy.statscollectors import StatsCollector
|
from scrapy.statscollectors import StatsCollector
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ from typing import Any
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
from pexpect.popen_spawn import PopenSpawn
|
from pexpect.popen_spawn import PopenSpawn
|
||||||
from twisted.internet.defer import Deferred, inlineCallbacks
|
from twisted.internet.defer import Deferred
|
||||||
from w3lib import __version__ as w3lib_version
|
from w3lib import __version__ as w3lib_version
|
||||||
from zope.interface.exceptions import MultipleInvalid
|
from zope.interface.exceptions import MultipleInvalid
|
||||||
|
|
||||||
|
|
@ -31,11 +31,7 @@ from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.extensions.throttle import AutoThrottle
|
from scrapy.extensions.throttle import AutoThrottle
|
||||||
from scrapy.settings import Settings, default_settings
|
from scrapy.settings import Settings, default_settings
|
||||||
from scrapy.utils.asyncio import call_later
|
from scrapy.utils.asyncio import call_later
|
||||||
from scrapy.utils.defer import (
|
from scrapy.utils.defer import deferred_from_coro, maybe_deferred_to_future
|
||||||
deferred_f_from_coro_f,
|
|
||||||
deferred_from_coro,
|
|
||||||
maybe_deferred_to_future,
|
|
||||||
)
|
|
||||||
from scrapy.utils.log import (
|
from scrapy.utils.log import (
|
||||||
_uninstall_scrapy_root_handler,
|
_uninstall_scrapy_root_handler,
|
||||||
configure_logging,
|
configure_logging,
|
||||||
|
|
@ -45,6 +41,7 @@ from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler, get_reactor_settings
|
from scrapy.utils.test import get_crawler, get_reactor_settings
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.utils import get_script_run_env
|
from tests.utils import get_script_run_env
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
BASE_SETTINGS: dict[str, Any] = {}
|
BASE_SETTINGS: dict[str, Any] = {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ if TYPE_CHECKING:
|
||||||
from twisted.protocols.ftp import FTPFactory
|
from twisted.protocols.ftp import FTPFactory
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.requires_reactor
|
||||||
|
|
||||||
|
|
||||||
class TestFTPBase(ABC):
|
class TestFTPBase(ABC):
|
||||||
username = "scrapy"
|
username = "scrapy"
|
||||||
password = "passwd"
|
password = "passwd"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ if TYPE_CHECKING:
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.requires_reactor
|
||||||
|
|
||||||
|
|
||||||
class HTTP10DownloadHandlerMixin:
|
class HTTP10DownloadHandlerMixin:
|
||||||
@property
|
@property
|
||||||
def download_handler_cls(self) -> type[DownloadHandlerProtocol]:
|
def download_handler_cls(self) -> type[DownloadHandlerProtocol]:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from scrapy.core.downloader.handlers.http11 import HTTP11DownloadHandler
|
from scrapy.core.downloader.handlers.http11 import HTTP11DownloadHandler
|
||||||
from tests.test_downloader_handlers_http_base import (
|
from tests.test_downloader_handlers_http_base import (
|
||||||
TestHttp11Base,
|
TestHttp11Base,
|
||||||
|
|
@ -21,6 +23,9 @@ if TYPE_CHECKING:
|
||||||
from scrapy.core.downloader.handlers import DownloadHandlerProtocol
|
from scrapy.core.downloader.handlers import DownloadHandlerProtocol
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.requires_reactor
|
||||||
|
|
||||||
|
|
||||||
class HTTP11DownloadHandlerMixin:
|
class HTTP11DownloadHandlerMixin:
|
||||||
@property
|
@property
|
||||||
def download_handler_cls(self) -> type[DownloadHandlerProtocol]:
|
def download_handler_cls(self) -> type[DownloadHandlerProtocol]:
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,13 @@ if TYPE_CHECKING:
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.mockserver.proxy_echo import ProxyEchoMockServer
|
from tests.mockserver.proxy_echo import ProxyEchoMockServer
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
|
||||||
not H2_ENABLED, reason="HTTP/2 support in Twisted is not enabled"
|
pytestmark = [
|
||||||
)
|
pytest.mark.requires_reactor,
|
||||||
|
pytest.mark.skipif(
|
||||||
|
not H2_ENABLED, reason="HTTP/2 support in Twisted is not enabled"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class H2DownloadHandlerMixin:
|
class H2DownloadHandlerMixin:
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
from scrapy.responsetypes import responsetypes
|
from scrapy.responsetypes import responsetypes
|
||||||
from scrapy.utils.boto import is_botocore_available
|
from scrapy.utils.boto import is_botocore_available
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.misc import build_from_crawler
|
from scrapy.utils.misc import build_from_crawler
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
|
|
||||||
class DummyDH:
|
class DummyDH:
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,7 @@ from scrapy.exceptions import (
|
||||||
UnsupportedURLSchemeError,
|
UnsupportedURLSchemeError,
|
||||||
)
|
)
|
||||||
from scrapy.http import Headers, HtmlResponse, Request, Response, TextResponse
|
from scrapy.http import Headers, HtmlResponse, Request, Response, TextResponse
|
||||||
from scrapy.utils.defer import (
|
from scrapy.utils.defer import deferred_from_coro, maybe_deferred_to_future
|
||||||
deferred_f_from_coro_f,
|
|
||||||
deferred_from_coro,
|
|
||||||
maybe_deferred_to_future,
|
|
||||||
)
|
|
||||||
from scrapy.utils.misc import build_from_crawler
|
from scrapy.utils.misc import build_from_crawler
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
|
@ -38,6 +34,7 @@ from tests import NON_EXISTING_RESOLVABLE
|
||||||
from tests.mockserver.proxy_echo import ProxyEchoMockServer
|
from tests.mockserver.proxy_echo import ProxyEchoMockServer
|
||||||
from tests.mockserver.simple_https import SimpleMockServer
|
from tests.mockserver.simple_https import SimpleMockServer
|
||||||
from tests.spiders import SingleRequestSpider
|
from tests.spiders import SingleRequestSpider
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import AsyncGenerator, Generator
|
from collections.abc import AsyncGenerator, Generator
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ from scrapy.core.downloader.middleware import DownloaderMiddlewareManager
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning, _InvalidOutput
|
from scrapy.exceptions import ScrapyDeprecationWarning, _InvalidOutput
|
||||||
from scrapy.http import Request, Response
|
from scrapy.http import Request, Response
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.python import to_bytes
|
from scrapy.utils.python import to_bytes
|
||||||
from scrapy.utils.test import get_crawler, get_from_asyncio_queue
|
from scrapy.utils.test import get_crawler, get_from_asyncio_queue
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,9 @@ from scrapy.http import Request, Response, TextResponse
|
||||||
from scrapy.http.request import NO_CALLBACK
|
from scrapy.http.request import NO_CALLBACK
|
||||||
from scrapy.settings import Settings
|
from scrapy.settings import Settings
|
||||||
from scrapy.utils.asyncio import call_later
|
from scrapy.utils.asyncio import call_later
|
||||||
from scrapy.utils.defer import (
|
from scrapy.utils.defer import deferred_from_coro, maybe_deferred_to_future
|
||||||
deferred_f_from_coro_f,
|
|
||||||
deferred_from_coro,
|
|
||||||
maybe_deferred_to_future,
|
|
||||||
)
|
|
||||||
from tests.test_robotstxt_interface import rerp_available
|
from tests.test_robotstxt_interface import rerp_available
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from scrapy.crawler import Crawler
|
from scrapy.crawler import Crawler
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,16 @@ import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy import Request
|
from scrapy import Request
|
||||||
from scrapy.core.downloader import Downloader, Slot
|
from scrapy.core.downloader import Downloader, Slot
|
||||||
from scrapy.crawler import CrawlerRunner
|
from scrapy.crawler import CrawlerRunner
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import MetaSpider
|
from tests.spiders import MetaSpider
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class DownloaderSlotsSettingsTestSpider(MetaSpider):
|
class DownloaderSlotsSettingsTestSpider(MetaSpider):
|
||||||
|
|
@ -85,6 +84,7 @@ class TestCrawl:
|
||||||
assert max(list(error_delta.values())) < tolerance
|
assert max(list(error_delta.values())) < tolerance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a reactor or an event loop for Downloader._slot_gc_loop
|
||||||
def test_params():
|
def test_params():
|
||||||
params = {
|
params = {
|
||||||
"concurrency": 1,
|
"concurrency": 1,
|
||||||
|
|
@ -109,6 +109,7 @@ def test_params():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a reactor or an event loop for Downloader._slot_gc_loop
|
||||||
def test_get_slot_deprecated_spider_arg():
|
def test_get_slot_deprecated_spider_arg():
|
||||||
crawler = get_crawler(DefaultSpider)
|
crawler = get_crawler(DefaultSpider)
|
||||||
crawler.spider = crawler._create_spider()
|
crawler.spider = crawler._create_spider()
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ from itemadapter import ItemAdapter
|
||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy import signals
|
from scrapy import signals
|
||||||
from scrapy.core.engine import ExecutionEngine, _Slot
|
from scrapy.core.engine import ExecutionEngine, _Slot
|
||||||
|
|
@ -29,7 +28,6 @@ from scrapy.linkextractors import LinkExtractor
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
from scrapy.utils.defer import (
|
from scrapy.utils.defer import (
|
||||||
_schedule_coro,
|
_schedule_coro,
|
||||||
deferred_f_from_coro_f,
|
|
||||||
deferred_from_coro,
|
deferred_from_coro,
|
||||||
maybe_deferred_to_future,
|
maybe_deferred_to_future,
|
||||||
)
|
)
|
||||||
|
|
@ -37,6 +35,7 @@ from scrapy.utils.signal import disconnect_all
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests import get_testdata
|
from tests import get_testdata
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from scrapy.core.scheduler import Scheduler
|
from scrapy.core.scheduler import Scheduler
|
||||||
|
|
@ -599,6 +598,7 @@ class TestEngineDownload(TestEngineDownloadAsync):
|
||||||
return await maybe_deferred_to_future(engine.download(request))
|
return await maybe_deferred_to_future(engine.download(request))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a reactor or an event loop for _Slot.heartbeat
|
||||||
def test_request_scheduled_signal(caplog):
|
def test_request_scheduled_signal(caplog):
|
||||||
class TestScheduler(BaseScheduler):
|
class TestScheduler(BaseScheduler):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ from typing import TYPE_CHECKING
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
|
|
||||||
from scrapy import Request, Spider, signals
|
from scrapy import Request, Spider, signals
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.test_scheduler import MemoryScheduler
|
from tests.test_scheduler import MemoryScheduler
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from scrapy.exceptions import StopDownload
|
from scrapy.exceptions import StopDownload
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from tests.test_engine import (
|
from tests.test_engine import (
|
||||||
AttrsItemsSpider,
|
AttrsItemsSpider,
|
||||||
CrawlerRun,
|
CrawlerRun,
|
||||||
|
|
@ -12,6 +11,7 @@ from tests.test_engine import (
|
||||||
MySpider,
|
MySpider,
|
||||||
TestEngineBase,
|
TestEngineBase,
|
||||||
)
|
)
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from scrapy.exceptions import StopDownload
|
from scrapy.exceptions import StopDownload
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from tests.test_engine import (
|
from tests.test_engine import (
|
||||||
AttrsItemsSpider,
|
AttrsItemsSpider,
|
||||||
CrawlerRun,
|
CrawlerRun,
|
||||||
|
|
@ -12,6 +11,7 @@ from tests.test_engine import (
|
||||||
MySpider,
|
MySpider,
|
||||||
TestEngineBase,
|
TestEngineBase,
|
||||||
)
|
)
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ from __future__ import annotations
|
||||||
import datetime
|
import datetime
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from scrapy.extensions.periodic_log import PeriodicLog
|
from scrapy.extensions.periodic_log import PeriodicLog
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
|
||||||
|
|
@ -86,6 +88,7 @@ class TestPeriodicLog:
|
||||||
assert extension({"PERIODIC_LOG_DELTA": True, "LOGSTATS_INTERVAL": 60})
|
assert extension({"PERIODIC_LOG_DELTA": True, "LOGSTATS_INTERVAL": 60})
|
||||||
assert extension({"PERIODIC_LOG_DELTA": "True", "LOGSTATS_INTERVAL": 60})
|
assert extension({"PERIODIC_LOG_DELTA": "True", "LOGSTATS_INTERVAL": 60})
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a reactor or an event loop for PeriodicLog.task
|
||||||
def test_log_delta(self):
|
def test_log_delta(self):
|
||||||
def emulate(settings=None):
|
def emulate(settings=None):
|
||||||
spider = MetaSpider()
|
spider = MetaSpider()
|
||||||
|
|
@ -143,6 +146,7 @@ class TestPeriodicLog:
|
||||||
and ("downloader/" in k and "bytes" not in k),
|
and ("downloader/" in k and "bytes" not in k),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a reactor or an event loop for PeriodicLog.task
|
||||||
def test_log_stats(self):
|
def test_log_stats(self):
|
||||||
def emulate(settings=None):
|
def emulate(settings=None):
|
||||||
spider = MetaSpider()
|
spider = MetaSpider()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
from twisted.conch.telnet import ITelnetProtocol
|
from twisted.conch.telnet import ITelnetProtocol
|
||||||
from twisted.cred import credentials
|
from twisted.cred import credentials
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.extensions.telnet import TelnetConsole
|
from scrapy.extensions.telnet import TelnetConsole
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class TestTelnetExtension:
|
class TestTelnetExtension:
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import lxml.etree
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
from w3lib.url import file_uri_to_path, path_to_file_uri
|
from w3lib.url import file_uri_to_path, path_to_file_uri
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from zope.interface.verify import verifyObject
|
from zope.interface.verify import verifyObject
|
||||||
|
|
@ -50,12 +49,13 @@ from scrapy.extensions.feedexport import (
|
||||||
StdoutFeedStorage,
|
StdoutFeedStorage,
|
||||||
)
|
)
|
||||||
from scrapy.settings import Settings
|
from scrapy.settings import Settings
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.python import to_unicode
|
from scrapy.utils.python import to_unicode
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.ftp import MockFTPServer
|
from tests.mockserver.ftp import MockFTPServer
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import ItemSpider
|
from tests.spiders import ItemSpider
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,12 @@ if TYPE_CHECKING:
|
||||||
from scrapy.core.http2.protocol import H2ClientProtocol
|
from scrapy.core.http2.protocol import H2ClientProtocol
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
pytestmark = [
|
||||||
not H2_ENABLED, reason="HTTP/2 support in Twisted is not enabled"
|
pytest.mark.requires_reactor,
|
||||||
)
|
pytest.mark.skipif(
|
||||||
|
not H2_ENABLED, reason="HTTP/2 support in Twisted is not enabled"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def generate_random_string(size: int) -> str:
|
def generate_random_string(size: int) -> str:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
from scrapy.exceptions import DropItem
|
from scrapy.exceptions import DropItem
|
||||||
|
|
@ -13,6 +12,7 @@ from scrapy.spiders import Spider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import ItemSpider
|
from tests.spiders import ItemSpider
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class CustomItem(Item):
|
class CustomItem(Item):
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class TestLogStats:
|
||||||
self.stats.set_value("response_received_count", 4802)
|
self.stats.set_value("response_received_count", 4802)
|
||||||
self.stats.set_value("item_scraped_count", 3201)
|
self.stats.set_value("item_scraped_count", 3201)
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a reactor or an event loop for LogStats.task
|
||||||
def test_stats_calculations(self):
|
def test_stats_calculations(self):
|
||||||
logstats = LogStats.from_crawler(self.crawler)
|
logstats = LogStats.from_crawler(self.crawler)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
from email.charset import Charset
|
from email.charset import Charset
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.internet._sslverify import ClientTLSOptions
|
from twisted.internet._sslverify import ClientTLSOptions
|
||||||
|
|
||||||
from scrapy.mail import MailSender
|
from scrapy.mail import MailSender
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
class TestMailSender:
|
class TestMailSender:
|
||||||
def test_send(self):
|
def test_send(self):
|
||||||
mailsender = MailSender(debug=True)
|
mailsender = MailSender(debug=True)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
from w3lib.url import add_or_replace_parameter
|
from w3lib.url import add_or_replace_parameter
|
||||||
|
|
||||||
from scrapy import Spider, signals
|
from scrapy import Spider, signals
|
||||||
|
|
@ -15,6 +14,7 @@ from scrapy.utils.misc import load_object
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import SimpleSpider
|
from tests.spiders import SimpleSpider
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from scrapy.crawler import Crawler
|
from scrapy.crawler import Crawler
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ from urllib.parse import urlparse
|
||||||
import attr
|
import attr
|
||||||
import pytest
|
import pytest
|
||||||
from itemadapter import ItemAdapter
|
from itemadapter import ItemAdapter
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.exceptions import NotConfigured
|
from scrapy.exceptions import NotConfigured
|
||||||
from scrapy.http import Request, Response
|
from scrapy.http import Request, Response
|
||||||
|
|
@ -32,10 +31,10 @@ from scrapy.pipelines.files import (
|
||||||
S3FilesStore,
|
S3FilesStore,
|
||||||
)
|
)
|
||||||
from scrapy.settings import Settings
|
from scrapy.settings import Settings
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.ftp import MockFTPServer
|
from tests.mockserver.ftp import MockFTPServer
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
from .test_pipeline_media import _mocked_download_func
|
from .test_pipeline_media import _mocked_download_func
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,12 @@ from scrapy.http import Request, Response
|
||||||
from scrapy.http.request import NO_CALLBACK
|
from scrapy.http.request import NO_CALLBACK
|
||||||
from scrapy.pipelines.files import FileException
|
from scrapy.pipelines.files import FileException
|
||||||
from scrapy.pipelines.media import MediaPipeline
|
from scrapy.pipelines.media import MediaPipeline
|
||||||
from scrapy.utils.defer import _defer_sleep_async, deferred_f_from_coro_f
|
from scrapy.utils.defer import _defer_sleep_async
|
||||||
from scrapy.utils.log import failure_to_exc_info
|
from scrapy.utils.log import failure_to_exc_info
|
||||||
from scrapy.utils.signal import disconnect_all
|
from scrapy.utils.signal import disconnect_all
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
|
|
||||||
async def _mocked_download_func(request):
|
async def _mocked_download_func(request):
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,11 @@ from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.pipelines import ItemPipelineManager
|
from scrapy.pipelines import ItemPipelineManager
|
||||||
from scrapy.utils.asyncio import call_later
|
from scrapy.utils.asyncio import call_later
|
||||||
from scrapy.utils.conf import build_component_list
|
from scrapy.utils.conf import build_component_list
|
||||||
from scrapy.utils.defer import (
|
from scrapy.utils.defer import deferred_to_future, maybe_deferred_to_future
|
||||||
deferred_f_from_coro_f,
|
|
||||||
deferred_to_future,
|
|
||||||
maybe_deferred_to_future,
|
|
||||||
)
|
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler, get_from_asyncio_queue
|
from scrapy.utils.test import get_crawler, get_from_asyncio_queue
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
|
|
||||||
class SimplePipeline:
|
class SimplePipeline:
|
||||||
|
|
@ -251,6 +248,9 @@ class TestPipeline:
|
||||||
|
|
||||||
|
|
||||||
class TestCustomPipelineManager:
|
class TestCustomPipelineManager:
|
||||||
|
# needs a reactor or an event loop for is_asyncio_available()
|
||||||
|
# (for ItemPipelineManager.process_item())
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
def test_deprecated_process_item_spider_arg(self) -> None:
|
def test_deprecated_process_item_spider_arg(self) -> None:
|
||||||
class CustomPipelineManager(ItemPipelineManager):
|
class CustomPipelineManager(ItemPipelineManager):
|
||||||
def process_item(self, item, spider): # pylint: disable=useless-parent-delegation
|
def process_item(self, item, spider): # pylint: disable=useless-parent-delegation
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ from urllib.parse import urlsplit, urlunsplit
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import SimpleSpider, SingleRequestSpider
|
from tests.spiders import SimpleSpider, SingleRequestSpider
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class MitmProxy:
|
class MitmProxy:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy import Request, signals
|
from scrapy import Request, signals
|
||||||
from scrapy.http.response import Response
|
from scrapy.http.response import Response
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import SingleRequestSpider
|
from tests.spiders import SingleRequestSpider
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
OVERRIDDEN_URL = "https://example.org"
|
OVERRIDDEN_URL = "https://example.org"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import MockServerSpider
|
from tests.spiders import MockServerSpider
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class InjectArgumentsDownloaderMiddleware:
|
class InjectArgumentsDownloaderMiddleware:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.signals import request_left_downloader
|
from scrapy.signals import request_left_downloader
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class SignalCatcherSpider(Spider):
|
class SignalCatcherSpider(Spider):
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ from collections import deque
|
||||||
from typing import Any, NamedTuple
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.core.downloader import Downloader
|
from scrapy.core.downloader import Downloader
|
||||||
from scrapy.core.scheduler import BaseScheduler, Scheduler
|
from scrapy.core.scheduler import BaseScheduler, Scheduler
|
||||||
|
|
@ -20,6 +19,7 @@ from scrapy.utils.httpobj import urlparse_cached
|
||||||
from scrapy.utils.misc import load_object
|
from scrapy.utils.misc import load_object
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class MemoryScheduler(BaseScheduler):
|
class MemoryScheduler(BaseScheduler):
|
||||||
|
|
@ -99,6 +99,9 @@ class MockCrawler(Crawler):
|
||||||
self.stats = load_object(self.settings["STATS_CLASS"])(self)
|
self.stats = load_object(self.settings["STATS_CLASS"])(self)
|
||||||
|
|
||||||
|
|
||||||
|
# needs a reactor or an event loop for is_asyncio_available()
|
||||||
|
# (for _schedule_coro())
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
class SchedulerHandler(ABC):
|
class SchedulerHandler(ABC):
|
||||||
jobdir = None
|
jobdir = None
|
||||||
|
|
||||||
|
|
@ -243,6 +246,9 @@ _URLS_WITH_SLOTS = [
|
||||||
|
|
||||||
|
|
||||||
class TestMigration:
|
class TestMigration:
|
||||||
|
# needs a reactor or an event loop for is_asyncio_available()
|
||||||
|
# (for _schedule_coro())
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
def test_migration(self, tmpdir):
|
def test_migration(self, tmpdir):
|
||||||
class PrevSchedulerHandler(SchedulerHandler):
|
class PrevSchedulerHandler(SchedulerHandler):
|
||||||
jobdir = tmpdir
|
jobdir = tmpdir
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ from urllib.parse import urljoin
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.core.scheduler import BaseScheduler
|
from scrapy.core.scheduler import BaseScheduler
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
|
|
@ -14,6 +13,7 @@ from scrapy.utils.httpobj import urlparse_cached
|
||||||
from scrapy.utils.request import fingerprint
|
from scrapy.utils.request import fingerprint
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
PATHS = ["/a", "/b", "/c"]
|
PATHS = ["/a", "/b", "/c"]
|
||||||
URLS = [urljoin("https://example.org", p) for p in PATHS]
|
URLS = [urljoin("https://example.org", p) for p in PATHS]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import pytest
|
import pytest
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy import Request, Spider, signals
|
from scrapy import Request, Spider, signals
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.test import get_crawler, get_from_asyncio_queue
|
from scrapy.utils.test import get_crawler, get_from_asyncio_queue
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class ItemSpider(Spider):
|
class ItemSpider(Spider):
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
from w3lib.url import safe_url_string
|
from w3lib.url import safe_url_string
|
||||||
|
|
||||||
from scrapy import signals
|
from scrapy import signals
|
||||||
|
|
@ -29,9 +28,9 @@ from scrapy.spiders import (
|
||||||
XMLFeedSpider,
|
XMLFeedSpider,
|
||||||
)
|
)
|
||||||
from scrapy.spiders.init import InitSpider
|
from scrapy.spiders.init import InitSpider
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.test import get_crawler, get_reactor_settings
|
from scrapy.utils.test import get_crawler, get_reactor_settings
|
||||||
from tests import get_testdata, tests_datadir
|
from tests import get_testdata, tests_datadir
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f, inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class TestSpider:
|
class TestSpider:
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ from testfixtures import LogCapture
|
||||||
|
|
||||||
from scrapy import Spider, signals
|
from scrapy import Spider, signals
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
|
||||||
from .utils import twisted_sleep
|
from .utils import twisted_sleep
|
||||||
|
from .utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
SLEEP_SECONDS = 0.1
|
SLEEP_SECONDS = 0.1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,10 @@ from scrapy.http import Request, Response
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
from scrapy.utils.asyncgen import collect_asyncgen
|
from scrapy.utils.asyncgen import collect_asyncgen
|
||||||
from scrapy.utils.asyncio import call_later
|
from scrapy.utils.asyncio import call_later
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.spider import DefaultSpider
|
from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
|
|
||||||
from scrapy.http import Request, Response
|
from scrapy.http import Request, Response
|
||||||
from scrapy.spidermiddlewares.httperror import HttpError, HttpErrorMiddleware
|
from scrapy.spidermiddlewares.httperror import HttpError, HttpErrorMiddleware
|
||||||
|
|
@ -12,6 +11,7 @@ from scrapy.utils.spider import DefaultSpider
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
from tests.spiders import MockServerSpider
|
from tests.spiders import MockServerSpider
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class _HttpErrorSpider(MockServerSpider):
|
class _HttpErrorSpider(MockServerSpider):
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
|
|
||||||
from scrapy import Request, Spider
|
from scrapy import Request, Spider
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.mockserver.http import MockServer
|
from tests.mockserver.http import MockServer
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
|
|
||||||
class _BaseSpiderMiddleware:
|
class _BaseSpiderMiddleware:
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ import pytest
|
||||||
|
|
||||||
from scrapy import Spider, signals
|
from scrapy import Spider, signals
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.test_spider_start import SLEEP_SECONDS
|
from tests.test_spider_start import SLEEP_SECONDS
|
||||||
|
|
||||||
from .utils import twisted_sleep
|
from .utils import twisted_sleep
|
||||||
|
from .utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
ITEM_A = {"id": "a"}
|
ITEM_A = {"id": "a"}
|
||||||
ITEM_B = {"id": "b"}
|
ITEM_B = {"id": "b"}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
from scrapy.spidermiddlewares.start import StartSpiderMiddleware
|
from scrapy.spidermiddlewares.start import StartSpiderMiddleware
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.misc import build_from_crawler
|
from scrapy.utils.misc import build_from_crawler
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
|
|
||||||
class TestMiddleware:
|
class TestMiddleware:
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.extensions.corestats import CoreStats
|
from scrapy.extensions.corestats import CoreStats
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
from scrapy.statscollectors import DummyStatsCollector, StatsCollector
|
from scrapy.statscollectors import DummyStatsCollector, StatsCollector
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
from tests.spiders import SimpleSpider
|
from tests.spiders import SimpleSpider
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from scrapy.crawler import Crawler
|
from scrapy.crawler import Crawler
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from scrapy.utils.asyncgen import as_async_generator, collect_asyncgen
|
from scrapy.utils.asyncgen import as_async_generator, collect_asyncgen
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
|
|
||||||
class TestAsyncgenUtils:
|
class TestAsyncgenUtils:
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,14 @@ from scrapy.utils.asyncio import (
|
||||||
_parallel_asyncio,
|
_parallel_asyncio,
|
||||||
is_asyncio_available,
|
is_asyncio_available,
|
||||||
)
|
)
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
|
|
||||||
|
|
||||||
class TestAsyncio:
|
class TestAsyncio:
|
||||||
|
@pytest.mark.requires_reactor # needs a reactor or an event loop for is_asyncio_available()
|
||||||
def test_is_asyncio_available(self, reactor_pytest: str) -> None:
|
def test_is_asyncio_available(self, reactor_pytest: str) -> None:
|
||||||
# the result should depend only on the pytest --reactor argument
|
# the result should depend only on the pytest --reactor argument
|
||||||
assert is_asyncio_available() == (reactor_pytest == "asyncio")
|
assert is_asyncio_available() == (reactor_pytest == "asyncio")
|
||||||
|
|
@ -103,6 +104,7 @@ class TestParallelAsyncio:
|
||||||
assert max_parallel_count[0] <= self.CONCURRENT_ITEMS
|
assert max_parallel_count[0] <= self.CONCURRENT_ITEMS
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a running event loop for AsyncioLoopingCall.start()
|
||||||
@pytest.mark.only_asyncio
|
@pytest.mark.only_asyncio
|
||||||
class TestAsyncioLoopingCall:
|
class TestAsyncioLoopingCall:
|
||||||
def test_looping_call(self):
|
def test_looping_call(self):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ from asyncio import Future
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from twisted.internet.defer import Deferred, inlineCallbacks, succeed
|
from twisted.internet.defer import Deferred, succeed
|
||||||
|
from twisted.internet.defer import inlineCallbacks as inlineCallbacks_orig
|
||||||
|
|
||||||
from scrapy.utils.asyncgen import as_async_generator, collect_asyncgen
|
from scrapy.utils.asyncgen import as_async_generator, collect_asyncgen
|
||||||
from scrapy.utils.defer import (
|
from scrapy.utils.defer import (
|
||||||
|
|
@ -19,6 +20,7 @@ from scrapy.utils.defer import (
|
||||||
mustbe_deferred,
|
mustbe_deferred,
|
||||||
parallel_async,
|
parallel_async,
|
||||||
)
|
)
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import AsyncGenerator, Awaitable, Callable, Generator
|
from collections.abc import AsyncGenerator, Awaitable, Callable, Generator
|
||||||
|
|
@ -107,6 +109,7 @@ class TestIterErrback:
|
||||||
assert isinstance(errors[0].value, ZeroDivisionError)
|
assert isinstance(errors[0].value, ZeroDivisionError)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
class TestAiterErrback:
|
class TestAiterErrback:
|
||||||
@deferred_f_from_coro_f
|
@deferred_f_from_coro_f
|
||||||
async def test_aiter_errback_good(self):
|
async def test_aiter_errback_good(self):
|
||||||
|
|
@ -134,6 +137,7 @@ class TestAiterErrback:
|
||||||
assert isinstance(errors[0].value, ZeroDivisionError)
|
assert isinstance(errors[0].value, ZeroDivisionError)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
class TestAsyncDefTestsuite:
|
class TestAsyncDefTestsuite:
|
||||||
@deferred_f_from_coro_f
|
@deferred_f_from_coro_f
|
||||||
async def test_deferred_f_from_coro_f(self):
|
async def test_deferred_f_from_coro_f(self):
|
||||||
|
|
@ -304,7 +308,7 @@ class TestDeferredFromCoro:
|
||||||
|
|
||||||
|
|
||||||
class TestDeferredFFromCoroF:
|
class TestDeferredFFromCoroF:
|
||||||
@inlineCallbacks
|
@inlineCallbacks_orig
|
||||||
def _assert_result(
|
def _assert_result(
|
||||||
self, c_f: Callable[[], Awaitable[int]]
|
self, c_f: Callable[[], Awaitable[int]]
|
||||||
) -> Generator[Deferred[Any], Any, None]:
|
) -> Generator[Deferred[Any], Any, None]:
|
||||||
|
|
@ -342,6 +346,7 @@ class TestDeferredFFromCoroF:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.only_asyncio
|
@pytest.mark.only_asyncio
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
class TestDeferredToFuture:
|
class TestDeferredToFuture:
|
||||||
@deferred_f_from_coro_f
|
@deferred_f_from_coro_f
|
||||||
async def test_deferred(self):
|
async def test_deferred(self):
|
||||||
|
|
@ -377,6 +382,9 @@ class TestDeferredToFuture:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.only_asyncio
|
@pytest.mark.only_asyncio
|
||||||
|
# needs a reactor or an event loop for is_asyncio_available()
|
||||||
|
# (for maybe_deferred_to_future())
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
class TestMaybeDeferredToFutureAsyncio:
|
class TestMaybeDeferredToFutureAsyncio:
|
||||||
@deferred_f_from_coro_f
|
@deferred_f_from_coro_f
|
||||||
async def test_deferred(self):
|
async def test_deferred(self):
|
||||||
|
|
@ -412,6 +420,9 @@ class TestMaybeDeferredToFutureAsyncio:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.only_not_asyncio
|
@pytest.mark.only_not_asyncio
|
||||||
|
# needs a reactor or an event loop for is_asyncio_available()
|
||||||
|
# (for maybe_deferred_to_future())
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
class TestMaybeDeferredToFutureNotAsyncio:
|
class TestMaybeDeferredToFutureNotAsyncio:
|
||||||
def test_deferred(self):
|
def test_deferred(self):
|
||||||
d = Deferred()
|
d = Deferred()
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, TypeVar
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from scrapy.utils.asyncgen import as_async_generator, collect_asyncgen
|
from scrapy.utils.asyncgen import as_async_generator, collect_asyncgen
|
||||||
from scrapy.utils.defer import aiter_errback, deferred_f_from_coro_f
|
from scrapy.utils.defer import aiter_errback
|
||||||
from scrapy.utils.python import (
|
from scrapy.utils.python import (
|
||||||
MutableAsyncChain,
|
MutableAsyncChain,
|
||||||
MutableChain,
|
MutableChain,
|
||||||
|
|
@ -20,6 +20,7 @@ from scrapy.utils.python import (
|
||||||
to_unicode,
|
to_unicode,
|
||||||
without_none_values,
|
without_none_values,
|
||||||
)
|
)
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterable, Mapping
|
from collections.abc import Iterable, Mapping
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,22 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from scrapy.utils.defer import deferred_f_from_coro_f
|
|
||||||
from scrapy.utils.reactor import (
|
from scrapy.utils.reactor import (
|
||||||
_asyncio_reactor_path,
|
_asyncio_reactor_path,
|
||||||
install_reactor,
|
install_reactor,
|
||||||
is_asyncio_reactor_installed,
|
is_asyncio_reactor_installed,
|
||||||
set_asyncio_event_loop,
|
set_asyncio_event_loop,
|
||||||
)
|
)
|
||||||
|
from tests.utils.decorators import deferred_f_from_coro_f
|
||||||
|
|
||||||
|
|
||||||
class TestAsyncio:
|
class TestAsyncio:
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
def test_is_asyncio_reactor_installed(self, reactor_pytest: str) -> None:
|
def test_is_asyncio_reactor_installed(self, reactor_pytest: str) -> None:
|
||||||
# the result should depend only on the pytest --reactor argument
|
# the result should depend only on the pytest --reactor argument
|
||||||
assert is_asyncio_reactor_installed() == (reactor_pytest == "asyncio")
|
assert is_asyncio_reactor_installed() == (reactor_pytest == "asyncio")
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
def test_install_asyncio_reactor(self):
|
def test_install_asyncio_reactor(self):
|
||||||
from twisted.internet import reactor as original_reactor
|
from twisted.internet import reactor as original_reactor
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import pytest
|
||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
from scrapy.utils.asyncio import call_later
|
from scrapy.utils.asyncio import call_later
|
||||||
|
|
@ -15,6 +14,7 @@ from scrapy.utils.signal import (
|
||||||
send_catch_log_deferred,
|
send_catch_log_deferred,
|
||||||
)
|
)
|
||||||
from scrapy.utils.test import get_from_asyncio_queue
|
from scrapy.utils.test import get_from_asyncio_queue
|
||||||
|
from tests.utils.decorators import inlineCallbacks
|
||||||
|
|
||||||
|
|
||||||
class TestSendCatchLog:
|
class TestSendCatchLog:
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ from tests.mockserver.http_resources import (
|
||||||
from tests.mockserver.utils import ssl_context_factory
|
from tests.mockserver.utils import ssl_context_factory
|
||||||
from tests.test_core_downloader import TestContextFactoryBase
|
from tests.test_core_downloader import TestContextFactoryBase
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.requires_reactor
|
||||||
|
|
||||||
|
|
||||||
def getPage(url, contextFactory=None, response_transform=None, *args, **kwargs):
|
def getPage(url, contextFactory=None, response_transform=None, *args, **kwargs):
|
||||||
"""Adapted version of twisted.web.client.getPage"""
|
"""Adapted version of twisted.web.client.getPage"""
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ def test_stderr_log_handler() -> None:
|
||||||
assert c == 0
|
assert c == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor # needs a running event loop for asyncio.all_tasks()
|
||||||
@pytest.mark.only_asyncio
|
@pytest.mark.only_asyncio
|
||||||
def test_pending_asyncio_tasks() -> None:
|
def test_pending_asyncio_tasks() -> None:
|
||||||
"""Test that there are no pending asyncio tasks."""
|
"""Test that there are no pending asyncio tasks."""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
from typing import TYPE_CHECKING, Any, ParamSpec
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from twisted.internet.defer import Deferred
|
||||||
|
from twisted.internet.defer import inlineCallbacks as inlineCallbacks_orig
|
||||||
|
|
||||||
|
from scrapy.utils.defer import deferred_from_coro
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Awaitable, Callable, Generator
|
||||||
|
|
||||||
|
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
|
def inlineCallbacks(
|
||||||
|
f: Callable[_P, Generator[Deferred[Any], Any, None]],
|
||||||
|
) -> Callable[_P, Deferred[None]]:
|
||||||
|
"""Like :func:`twisted.internet.defer.inlineCallbacks`, but marks the
|
||||||
|
decorated function with ``@pytest.mark.requires_reactor``."""
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
|
@wraps(f)
|
||||||
|
@inlineCallbacks_orig
|
||||||
|
def wrapper(
|
||||||
|
*args: _P.args, **kwargs: _P.kwargs
|
||||||
|
) -> Generator[Deferred[Any], Any, None]:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def deferred_f_from_coro_f(
|
||||||
|
coro_f: Callable[_P, Awaitable[None]],
|
||||||
|
) -> Callable[_P, Deferred[None]]:
|
||||||
|
"""Like :func:`scrapy.utils.defer.deferred_f_from_coro_f`, but marks the
|
||||||
|
decorated function with ``@pytest.mark.requires_reactor``."""
|
||||||
|
|
||||||
|
@pytest.mark.requires_reactor
|
||||||
|
@wraps(coro_f)
|
||||||
|
def f(*coro_args: _P.args, **coro_kwargs: _P.kwargs) -> Deferred[None]:
|
||||||
|
return deferred_from_coro(coro_f(*coro_args, **coro_kwargs))
|
||||||
|
|
||||||
|
return f
|
||||||
11
tox.ini
11
tox.ini
|
|
@ -161,6 +161,10 @@ commands = {[pinned]commands}
|
||||||
commands =
|
commands =
|
||||||
{[testenv]commands} --reactor=default
|
{[testenv]commands} --reactor=default
|
||||||
|
|
||||||
|
[testenv:no-reactor]
|
||||||
|
commands =
|
||||||
|
{[testenv]commands} -p no:twisted --reactor=none
|
||||||
|
|
||||||
[testenv:default-reactor-pinned]
|
[testenv:default-reactor-pinned]
|
||||||
basepython = {[pinned]basepython}
|
basepython = {[pinned]basepython}
|
||||||
deps = {[testenv:pinned]deps}
|
deps = {[testenv:pinned]deps}
|
||||||
|
|
@ -168,6 +172,13 @@ commands = {[pinned]commands} --reactor=default
|
||||||
setenv =
|
setenv =
|
||||||
{[pinned]setenv}
|
{[pinned]setenv}
|
||||||
|
|
||||||
|
[testenv:no-reactor-pinned]
|
||||||
|
basepython = {[pinned]basepython}
|
||||||
|
deps = {[testenv:pinned]deps}
|
||||||
|
commands = {[pinned]commands} -p no:twisted --reactor=none
|
||||||
|
setenv =
|
||||||
|
{[pinned]setenv}
|
||||||
|
|
||||||
[testenv:pypy3]
|
[testenv:pypy3]
|
||||||
basepython = pypy3
|
basepython = pypy3
|
||||||
commands =
|
commands =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue