Converting tests to plain asserts, part 2. (#6699)

This commit is contained in:
Andrey Rakhmatullin 2025-03-06 23:52:41 +04:00 committed by GitHub
parent 1843a4f753
commit 5a605969bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 796 additions and 865 deletions

View File

@ -39,7 +39,7 @@ class CreateInstanceAddon:
settings.update(self.config, "addon") settings.update(self.config, "addon")
class AddonTest(unittest.TestCase): class TestAddon:
def test_update_settings(self): def test_update_settings(self):
settings = BaseSettings() settings = BaseSettings()
settings.set("KEY1", "default", priority="default") settings.set("KEY1", "default", priority="default")
@ -47,19 +47,19 @@ class AddonTest(unittest.TestCase):
addon_config = {"KEY1": "addon", "KEY2": "addon", "KEY3": "addon"} addon_config = {"KEY1": "addon", "KEY2": "addon", "KEY3": "addon"}
testaddon = get_addon_cls(addon_config)() testaddon = get_addon_cls(addon_config)()
testaddon.update_settings(settings) testaddon.update_settings(settings)
self.assertEqual(settings["KEY1"], "addon") assert settings["KEY1"] == "addon"
self.assertEqual(settings["KEY2"], "project") assert settings["KEY2"] == "project"
self.assertEqual(settings["KEY3"], "addon") assert settings["KEY3"] == "addon"
class AddonManagerTest(unittest.TestCase): class TestAddonManager(unittest.TestCase):
def test_load_settings(self): def test_load_settings(self):
settings_dict = { settings_dict = {
"ADDONS": {"tests.test_addons.SimpleAddon": 0}, "ADDONS": {"tests.test_addons.SimpleAddon": 0},
} }
crawler = get_crawler(settings_dict=settings_dict) crawler = get_crawler(settings_dict=settings_dict)
manager = crawler.addons manager = crawler.addons
self.assertIsInstance(manager.addons[0], SimpleAddon) assert isinstance(manager.addons[0], SimpleAddon)
def test_notconfigured(self): def test_notconfigured(self):
class NotConfiguredAddon: class NotConfiguredAddon:
@ -71,7 +71,7 @@ class AddonManagerTest(unittest.TestCase):
} }
crawler = get_crawler(settings_dict=settings_dict) crawler = get_crawler(settings_dict=settings_dict)
manager = crawler.addons manager = crawler.addons
self.assertFalse(manager.addons) assert not manager.addons
def test_load_settings_order(self): def test_load_settings_order(self):
# Get three addons with different settings # Get three addons with different settings
@ -86,8 +86,8 @@ class AddonManagerTest(unittest.TestCase):
settings = {"ADDONS": {a: i for i, a in enumerate(ordered_addons)}} settings = {"ADDONS": {a: i for i, a in enumerate(ordered_addons)}}
crawler = get_crawler(settings_dict=settings) crawler = get_crawler(settings_dict=settings)
manager = crawler.addons manager = crawler.addons
self.assertEqual([a.number for a in manager.addons], expected_order) assert [a.number for a in manager.addons] == expected_order
self.assertEqual(crawler.settings.getint("KEY1"), expected_order[-1]) assert crawler.settings.getint("KEY1") == expected_order[-1]
def test_build_from_crawler(self): def test_build_from_crawler(self):
settings_dict = { settings_dict = {
@ -96,8 +96,8 @@ class AddonManagerTest(unittest.TestCase):
} }
crawler = get_crawler(settings_dict=settings_dict) crawler = get_crawler(settings_dict=settings_dict)
manager = crawler.addons manager = crawler.addons
self.assertIsInstance(manager.addons[0], CreateInstanceAddon) assert isinstance(manager.addons[0], CreateInstanceAddon)
self.assertEqual(crawler.settings.get("MYADDON_KEY"), "val") assert crawler.settings.get("MYADDON_KEY") == "val"
def test_settings_priority(self): def test_settings_priority(self):
config = { config = {
@ -107,14 +107,14 @@ class AddonManagerTest(unittest.TestCase):
"ADDONS": {get_addon_cls(config): 1}, "ADDONS": {get_addon_cls(config): 1},
} }
crawler = get_crawler(settings_dict=settings_dict) crawler = get_crawler(settings_dict=settings_dict)
self.assertEqual(crawler.settings.getint("KEY"), 15) assert crawler.settings.getint("KEY") == 15
settings = Settings(settings_dict) settings = Settings(settings_dict)
settings.set("KEY", 0, priority="default") settings.set("KEY", 0, priority="default")
runner = CrawlerRunner(settings) runner = CrawlerRunner(settings)
crawler = runner.create_crawler(Spider) crawler = runner.create_crawler(Spider)
crawler._apply_settings() crawler._apply_settings()
self.assertEqual(crawler.settings.getint("KEY"), 15) assert crawler.settings.getint("KEY") == 15
settings_dict = { settings_dict = {
"KEY": 20, # priority=project "KEY": 20, # priority=project
@ -124,7 +124,7 @@ class AddonManagerTest(unittest.TestCase):
settings.set("KEY", 0, priority="default") settings.set("KEY", 0, priority="default")
runner = CrawlerRunner(settings) runner = CrawlerRunner(settings)
crawler = runner.create_crawler(Spider) crawler = runner.create_crawler(Spider)
self.assertEqual(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_DOWNLOAD_HANDLER"
@ -143,12 +143,12 @@ class AddonManagerTest(unittest.TestCase):
"ADDONS": {AddonWithFallback: 1}, "ADDONS": {AddonWithFallback: 1},
} }
crawler = get_crawler(settings_dict=settings_dict) crawler = get_crawler(settings_dict=settings_dict)
self.assertEqual( assert (
crawler.settings.getwithbase("DOWNLOAD_HANDLERS")["https"], "AddonHandler" crawler.settings.getwithbase("DOWNLOAD_HANDLERS")["https"] == "AddonHandler"
) )
self.assertEqual( assert (
crawler.settings.get(FALLBACK_SETTING), crawler.settings.get(FALLBACK_SETTING)
"scrapy.core.downloader.handlers.http.HTTPDownloadHandler", == "scrapy.core.downloader.handlers.http.HTTPDownloadHandler"
) )
settings_dict = { settings_dict = {
@ -156,10 +156,10 @@ class AddonManagerTest(unittest.TestCase):
"DOWNLOAD_HANDLERS": {"https": "UserHandler"}, "DOWNLOAD_HANDLERS": {"https": "UserHandler"},
} }
crawler = get_crawler(settings_dict=settings_dict) crawler = get_crawler(settings_dict=settings_dict)
self.assertEqual( assert (
crawler.settings.getwithbase("DOWNLOAD_HANDLERS")["https"], "AddonHandler" crawler.settings.getwithbase("DOWNLOAD_HANDLERS")["https"] == "AddonHandler"
) )
self.assertEqual(crawler.settings.get(FALLBACK_SETTING), "UserHandler") assert crawler.settings.get(FALLBACK_SETTING) == "UserHandler"
def test_logging_message(self): def test_logging_message(self):
class LoggedAddon: class LoggedAddon:
@ -199,6 +199,6 @@ class AddonManagerTest(unittest.TestCase):
settings.set("KEY", "default", priority="default") settings.set("KEY", "default", priority="default")
runner = CrawlerRunner(settings) runner = CrawlerRunner(settings)
crawler = runner.create_crawler(MySpider) crawler = runner.create_crawler(MySpider)
self.assertEqual(crawler.settings.get("KEY"), "default") assert crawler.settings.get("KEY") == "default"
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(crawler.settings.get("KEY"), "addon") assert crawler.settings.get("KEY") == "addon"

View File

@ -28,9 +28,9 @@ class TestCloseSpider(TestCase):
crawler = get_crawler(ItemSpider, {"CLOSESPIDER_ITEMCOUNT": close_on}) crawler = get_crawler(ItemSpider, {"CLOSESPIDER_ITEMCOUNT": close_on})
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
reason = crawler.spider.meta["close_reason"] reason = crawler.spider.meta["close_reason"]
self.assertEqual(reason, "closespider_itemcount") assert reason == "closespider_itemcount"
itemcount = crawler.stats.get_value("item_scraped_count") itemcount = crawler.stats.get_value("item_scraped_count")
self.assertTrue(itemcount >= close_on) assert itemcount >= close_on
@defer.inlineCallbacks @defer.inlineCallbacks
def test_closespider_pagecount(self): def test_closespider_pagecount(self):
@ -38,9 +38,9 @@ class TestCloseSpider(TestCase):
crawler = get_crawler(FollowAllSpider, {"CLOSESPIDER_PAGECOUNT": close_on}) crawler = get_crawler(FollowAllSpider, {"CLOSESPIDER_PAGECOUNT": close_on})
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
reason = crawler.spider.meta["close_reason"] reason = crawler.spider.meta["close_reason"]
self.assertEqual(reason, "closespider_pagecount") assert reason == "closespider_pagecount"
pagecount = crawler.stats.get_value("response_received_count") pagecount = crawler.stats.get_value("response_received_count")
self.assertTrue(pagecount >= close_on) assert pagecount >= close_on
@defer.inlineCallbacks @defer.inlineCallbacks
def test_closespider_pagecount_no_item(self): def test_closespider_pagecount_no_item(self):
@ -57,10 +57,10 @@ class TestCloseSpider(TestCase):
max_items=max_items, max_requests=max_requests, mockserver=self.mockserver max_items=max_items, max_requests=max_requests, mockserver=self.mockserver
) )
reason = crawler.spider.meta["close_reason"] reason = crawler.spider.meta["close_reason"]
self.assertEqual(reason, "closespider_pagecount_no_item") assert reason == "closespider_pagecount_no_item"
pagecount = crawler.stats.get_value("response_received_count") pagecount = crawler.stats.get_value("response_received_count")
itemcount = crawler.stats.get_value("item_scraped_count") itemcount = crawler.stats.get_value("item_scraped_count")
self.assertLessEqual(pagecount, close_on + itemcount) assert pagecount <= close_on + itemcount
@defer.inlineCallbacks @defer.inlineCallbacks
def test_closespider_pagecount_no_item_with_pagecount(self): def test_closespider_pagecount_no_item_with_pagecount(self):
@ -75,9 +75,9 @@ class TestCloseSpider(TestCase):
) )
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
reason = crawler.spider.meta["close_reason"] reason = crawler.spider.meta["close_reason"]
self.assertEqual(reason, "closespider_pagecount_no_item") assert reason == "closespider_pagecount_no_item"
pagecount = crawler.stats.get_value("response_received_count") pagecount = crawler.stats.get_value("response_received_count")
self.assertLess(pagecount, close_on_pagecount) assert pagecount < close_on_pagecount
@defer.inlineCallbacks @defer.inlineCallbacks
def test_closespider_errorcount(self): def test_closespider_errorcount(self):
@ -85,10 +85,10 @@ class TestCloseSpider(TestCase):
crawler = get_crawler(ErrorSpider, {"CLOSESPIDER_ERRORCOUNT": close_on}) crawler = get_crawler(ErrorSpider, {"CLOSESPIDER_ERRORCOUNT": close_on})
yield crawler.crawl(total=1000000, mockserver=self.mockserver) yield crawler.crawl(total=1000000, mockserver=self.mockserver)
reason = crawler.spider.meta["close_reason"] reason = crawler.spider.meta["close_reason"]
self.assertEqual(reason, "closespider_errorcount") assert reason == "closespider_errorcount"
key = f"spider_exceptions/{crawler.spider.exception_cls.__name__}" key = f"spider_exceptions/{crawler.spider.exception_cls.__name__}"
errorcount = crawler.stats.get_value(key) errorcount = crawler.stats.get_value(key)
self.assertTrue(errorcount >= close_on) assert errorcount >= close_on
@defer.inlineCallbacks @defer.inlineCallbacks
def test_closespider_timeout(self): def test_closespider_timeout(self):
@ -96,9 +96,9 @@ class TestCloseSpider(TestCase):
crawler = get_crawler(FollowAllSpider, {"CLOSESPIDER_TIMEOUT": close_on}) crawler = get_crawler(FollowAllSpider, {"CLOSESPIDER_TIMEOUT": close_on})
yield crawler.crawl(total=1000000, mockserver=self.mockserver) yield crawler.crawl(total=1000000, mockserver=self.mockserver)
reason = crawler.spider.meta["close_reason"] reason = crawler.spider.meta["close_reason"]
self.assertEqual(reason, "closespider_timeout") assert reason == "closespider_timeout"
total_seconds = crawler.stats.get_value("elapsed_time_seconds") total_seconds = crawler.stats.get_value("elapsed_time_seconds")
self.assertTrue(total_seconds >= close_on) assert total_seconds >= close_on
@defer.inlineCallbacks @defer.inlineCallbacks
def test_closespider_timeout_no_item(self): def test_closespider_timeout_no_item(self):
@ -106,6 +106,6 @@ class TestCloseSpider(TestCase):
crawler = get_crawler(SlowSpider, {"CLOSESPIDER_TIMEOUT_NO_ITEM": timeout}) crawler = get_crawler(SlowSpider, {"CLOSESPIDER_TIMEOUT_NO_ITEM": timeout})
yield crawler.crawl(n=3, mockserver=self.mockserver) yield crawler.crawl(n=3, mockserver=self.mockserver)
reason = crawler.spider.meta["close_reason"] reason = crawler.spider.meta["close_reason"]
self.assertEqual(reason, "closespider_timeout_no_item") assert reason == "closespider_timeout_no_item"
total_seconds = crawler.stats.get_value("elapsed_time_seconds") total_seconds = crawler.stats.get_value("elapsed_time_seconds")
self.assertTrue(total_seconds >= timeout) assert total_seconds >= timeout

View File

@ -4,7 +4,6 @@ import pstats
import shutil import shutil
import sys import sys
import tempfile import tempfile
import unittest
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
@ -12,8 +11,8 @@ from subprocess import PIPE, Popen
from scrapy.utils.test import get_testenv from scrapy.utils.test import get_testenv
class CmdlineTest(unittest.TestCase): class TestCmdline:
def setUp(self): def setup_method(self):
self.env = get_testenv() self.env = get_testenv()
tests_path = Path(__file__).parent.parent tests_path = Path(__file__).parent.parent
self.env["PYTHONPATH"] += os.pathsep + str(tests_path.parent) self.env["PYTHONPATH"] += os.pathsep + str(tests_path.parent)
@ -27,12 +26,12 @@ class CmdlineTest(unittest.TestCase):
return comm.decode(encoding) return comm.decode(encoding)
def test_default_settings(self): def test_default_settings(self):
self.assertEqual(self._execute("settings", "--get", "TEST1"), "default") assert self._execute("settings", "--get", "TEST1") == "default"
def test_override_settings_using_set_arg(self): def test_override_settings_using_set_arg(self):
self.assertEqual( assert (
self._execute("settings", "--get", "TEST1", "-s", "TEST1=override"), self._execute("settings", "--get", "TEST1", "-s", "TEST1=override")
"override", == "override"
) )
def test_profiling(self): def test_profiling(self):
@ -40,14 +39,14 @@ class CmdlineTest(unittest.TestCase):
filename = path / "res.prof" filename = path / "res.prof"
try: try:
self._execute("version", "--profile", str(filename)) self._execute("version", "--profile", str(filename))
self.assertTrue(filename.exists()) assert filename.exists()
out = StringIO() out = StringIO()
stats = pstats.Stats(str(filename), stream=out) stats = pstats.Stats(str(filename), stream=out)
stats.print_stats() stats.print_stats()
out.seek(0) out.seek(0)
stats = out.read() stats = out.read()
self.assertIn(str(Path("scrapy", "commands", "version.py")), stats) assert str(Path("scrapy", "commands", "version.py")) in stats
self.assertIn("tottime", stats) assert "tottime" in stats
finally: finally:
shutil.rmtree(path) shutil.rmtree(path)
@ -62,15 +61,14 @@ class CmdlineTest(unittest.TestCase):
"EXTENSIONS=" + json.dumps(EXTENSIONS), "EXTENSIONS=" + json.dumps(EXTENSIONS),
) )
# XXX: There's gotta be a smarter way to do this... # XXX: There's gotta be a smarter way to do this...
self.assertNotIn("...", settingsstr) assert "..." not in settingsstr
for char in ("'", "<", ">"): for char in ("'", "<", ">"):
settingsstr = settingsstr.replace(char, '"') settingsstr = settingsstr.replace(char, '"')
settingsdict = json.loads(settingsstr) settingsdict = json.loads(settingsstr)
self.assertCountEqual(settingsdict.keys(), EXTENSIONS.keys()) assert set(settingsdict.keys()) == set(EXTENSIONS.keys())
self.assertEqual(200, settingsdict[EXT_PATH]) assert settingsdict[EXT_PATH] == 200
def test_pathlib_path_as_feeds_key(self): def test_pathlib_path_as_feeds_key(self):
self.assertEqual( assert self._execute("settings", "--get", "FEEDS") == json.dumps(
self._execute("settings", "--get", "FEEDS"), {"items.csv": {"format": "csv", "fields": ["price", "name"]}}
json.dumps({"items.csv": {"format": "csv", "fields": ["price", "name"]}}),
) )

View File

@ -1,10 +1,9 @@
import sys import sys
import unittest
from pathlib import Path from pathlib import Path
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
class CmdlineCrawlPipelineTest(unittest.TestCase): class TestCmdlineCrawlPipeline:
def _execute(self, spname): def _execute(self, spname):
args = (sys.executable, "-m", "scrapy.cmdline", "crawl", spname) args = (sys.executable, "-m", "scrapy.cmdline", "crawl", spname)
cwd = Path(__file__).resolve().parent cwd = Path(__file__).resolve().parent
@ -13,7 +12,7 @@ class CmdlineCrawlPipelineTest(unittest.TestCase):
return proc.returncode return proc.returncode
def test_open_spider_normally_in_pipeline(self): def test_open_spider_normally_in_pipeline(self):
self.assertEqual(self._execute("normal"), 0) assert self._execute("normal") == 0
def test_exception_at_open_spider_in_pipeline(self): def test_exception_at_open_spider_in_pipeline(self):
self.assertEqual(self._execute("exception"), 1) assert self._execute("exception") == 1

View File

@ -3,10 +3,10 @@ from io import StringIO
from unittest.mock import Mock, PropertyMock, call, patch from unittest.mock import Mock, PropertyMock, call, patch
from scrapy.commands.check import Command, TextTestResult from scrapy.commands.check import Command, TextTestResult
from tests.test_commands import CommandTest from tests.test_commands import TestCommandBase
class CheckCommandTest(CommandTest): class TestCheckCommand(TestCommandBase):
command = "check" command = "check"
def setUp(self): def setUp(self):
@ -36,9 +36,9 @@ class CheckSpider(scrapy.Spider):
def _test_contract(self, contracts="", parse_def="pass"): def _test_contract(self, contracts="", parse_def="pass"):
self._write_contract(contracts, parse_def) self._write_contract(contracts, parse_def)
p, out, err = self.proc("check") p, out, err = self.proc("check")
self.assertNotIn("F", out) assert "F" not in out
self.assertIn("OK", err) assert "OK" in err
self.assertEqual(p.returncode, 0) assert p.returncode == 0
def test_check_returns_requests_contract(self): def test_check_returns_requests_contract(self):
contracts = """ contracts = """
@ -171,9 +171,7 @@ class CheckSpider(scrapy.Spider):
cmd.run([spider_name], Mock(list=True)) cmd.run([spider_name], Mock(list=True))
self.assertEqual( assert output.getvalue() == "FakeSpider\n * fakeMethod1\n * fakeMethod2\n"
"FakeSpider\n * fakeMethod1\n * fakeMethod2\n", output.getvalue()
)
sys.stdout = sys.__stdout__ sys.stdout = sys.__stdout__
@patch("scrapy.commands.check.ContractsManager") @patch("scrapy.commands.check.ContractsManager")

View File

@ -5,18 +5,18 @@ from tests.utils.testproc import ProcessTest
from tests.utils.testsite import SiteTest from tests.utils.testsite import SiteTest
class FetchTest(ProcessTest, SiteTest, unittest.TestCase): class TestFetchCommand(ProcessTest, SiteTest, unittest.TestCase):
command = "fetch" command = "fetch"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_output(self): def test_output(self):
_, out, _ = yield self.execute([self.url("/text")]) _, out, _ = yield self.execute([self.url("/text")])
self.assertEqual(out.strip(), b"Works") assert out.strip() == b"Works"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_redirect_default(self): def test_redirect_default(self):
_, out, _ = yield self.execute([self.url("/redirect")]) _, out, _ = yield self.execute([self.url("/redirect")])
self.assertEqual(out.strip(), b"Redirected here") assert out.strip() == b"Redirected here"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_redirect_disabled(self): def test_redirect_disabled(self):
@ -24,8 +24,8 @@ class FetchTest(ProcessTest, SiteTest, unittest.TestCase):
["--no-redirect", self.url("/redirect-no-meta-refresh")] ["--no-redirect", self.url("/redirect-no-meta-refresh")]
) )
err = err.strip() err = err.strip()
self.assertIn(b"downloader/response_status_count/302", err, err) assert b"downloader/response_status_count/302" in err, err
self.assertNotIn(b"downloader/response_status_count/200", err, err) assert b"downloader/response_status_count/200" not in err, err
@defer.inlineCallbacks @defer.inlineCallbacks
def test_headers(self): def test_headers(self):

View File

@ -1,5 +1,6 @@
import argparse import argparse
import os import os
import re
from pathlib import Path from pathlib import Path
from twisted.internet import defer from twisted.internet import defer
@ -7,18 +8,18 @@ from twisted.internet import defer
from scrapy.commands import parse from scrapy.commands import parse
from scrapy.settings import Settings from scrapy.settings import Settings
from scrapy.utils.python import to_unicode from scrapy.utils.python import to_unicode
from tests.test_commands import CommandTest from tests.test_commands import TestCommandBase
from tests.utils.testproc import ProcessTest from tests.utils.testproc import ProcessTest
from tests.utils.testsite import SiteTest from tests.utils.testsite import SiteTest
def _textmode(bstr): def _textmode(bstr: bytes) -> str:
"""Normalize input the same as writing to a file """Normalize input the same as writing to a file
and reading from it in text mode""" and reading from it in text mode"""
return to_unicode(bstr).replace(os.linesep, "\n") return to_unicode(bstr).replace(os.linesep, "\n")
class ParseCommandTest(ProcessTest, SiteTest, CommandTest): class TestParseCommand(ProcessTest, SiteTest, TestCommandBase):
command = "parse" command = "parse"
def setUp(self): def setUp(self):
@ -184,7 +185,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("DEBUG: It Works!", _textmode(stderr)) assert "DEBUG: It Works!" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_request_with_meta(self): def test_request_with_meta(self):
@ -201,7 +202,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("DEBUG: It Works!", _textmode(stderr)) assert "DEBUG: It Works!" in _textmode(stderr)
_, _, stderr = yield self.execute( _, _, stderr = yield self.execute(
[ [
@ -215,7 +216,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("DEBUG: It Works!", _textmode(stderr)) assert "DEBUG: It Works!" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_request_with_cb_kwargs(self): def test_request_with_cb_kwargs(self):
@ -233,9 +234,9 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
] ]
) )
log = _textmode(stderr) log = _textmode(stderr)
self.assertIn("DEBUG: It Works!", log) assert "DEBUG: It Works!" in log
self.assertIn( assert (
"DEBUG: request.callback signature: (response, foo=None, key=None)", log "DEBUG: request.callback signature: (response, foo=None, key=None)" in log
) )
@defer.inlineCallbacks @defer.inlineCallbacks
@ -250,7 +251,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("DEBUG: It Works!", _textmode(stderr)) assert "DEBUG: It Works!" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_pipelines(self): def test_pipelines(self):
@ -265,7 +266,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("INFO: It Works!", _textmode(stderr)) assert "INFO: It Works!" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncio_parse_items_list(self): def test_async_def_asyncio_parse_items_list(self):
@ -278,9 +279,9 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("INFO: Got response 200", _textmode(stderr)) assert "INFO: Got response 200" in _textmode(stderr)
self.assertIn("{'id': 1}", _textmode(out)) assert "{'id': 1}" in _textmode(out)
self.assertIn("{'id': 2}", _textmode(out)) assert "{'id': 2}" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncio_parse_items_single_element(self): def test_async_def_asyncio_parse_items_single_element(self):
@ -293,8 +294,8 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("INFO: Got response 200", _textmode(stderr)) assert "INFO: Got response 200" in _textmode(stderr)
self.assertIn("{'foo': 42}", _textmode(out)) assert "{'foo': 42}" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncgen_parse_loop(self): def test_async_def_asyncgen_parse_loop(self):
@ -307,9 +308,9 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("INFO: Got response 200", _textmode(stderr)) assert "INFO: Got response 200" in _textmode(stderr)
for i in range(10): for i in range(10):
self.assertIn(f"{{'foo': {i}}}", _textmode(out)) assert f"{{'foo': {i}}}" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncgen_parse_exc(self): def test_async_def_asyncgen_parse_exc(self):
@ -322,9 +323,9 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("ValueError", _textmode(stderr)) assert "ValueError" in _textmode(stderr)
for i in range(7): for i in range(7):
self.assertIn(f"{{'foo': {i}}}", _textmode(out)) assert f"{{'foo': {i}}}" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncio_parse(self): def test_async_def_asyncio_parse(self):
@ -337,29 +338,29 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
self.url("/html"), self.url("/html"),
] ]
) )
self.assertIn("DEBUG: Got response 200", _textmode(stderr)) assert "DEBUG: Got response 200" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_parse_items(self): def test_parse_items(self):
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", self.spider_name, "-c", "parse", self.url("/html")] ["--spider", self.spider_name, "-c", "parse", self.url("/html")]
) )
self.assertIn("""[{}, {'foo': 'bar'}]""", _textmode(out)) assert "[{}, {'foo': 'bar'}]" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_parse_items_no_callback_passed(self): def test_parse_items_no_callback_passed(self):
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", self.spider_name, self.url("/html")] ["--spider", self.spider_name, self.url("/html")]
) )
self.assertIn("""[{}, {'foo': 'bar'}]""", _textmode(out)) assert "[{}, {'foo': 'bar'}]" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_wrong_callback_passed(self): def test_wrong_callback_passed(self):
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", self.spider_name, "-c", "dummy", self.url("/html")] ["--spider", self.spider_name, "-c", "dummy", self.url("/html")]
) )
self.assertRegex(_textmode(out), r"""# Scraped Items -+\n\[\]""") assert re.search(r"# Scraped Items -+\n\[\]", _textmode(out))
self.assertIn("""Cannot find callback""", _textmode(stderr)) assert "Cannot find callback" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_matching_rule_callback_set(self): def test_crawlspider_matching_rule_callback_set(self):
@ -367,7 +368,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", "goodcrawl" + self.spider_name, "-r", self.url("/html")] ["--spider", "goodcrawl" + self.spider_name, "-r", self.url("/html")]
) )
self.assertIn("""[{}, {'foo': 'bar'}]""", _textmode(out)) assert "[{}, {'foo': 'bar'}]" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_matching_rule_default_callback(self): def test_crawlspider_matching_rule_default_callback(self):
@ -375,7 +376,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", "goodcrawl" + self.spider_name, "-r", self.url("/text")] ["--spider", "goodcrawl" + self.spider_name, "-r", self.url("/text")]
) )
self.assertIn("""[{}, {'nomatch': 'default'}]""", _textmode(out)) assert "[{}, {'nomatch': 'default'}]" in _textmode(out)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_spider_with_no_rules_attribute(self): def test_spider_with_no_rules_attribute(self):
@ -383,15 +384,15 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", self.spider_name, "-r", self.url("/html")] ["--spider", self.spider_name, "-r", self.url("/html")]
) )
self.assertRegex(_textmode(out), r"""# Scraped Items -+\n\[\]""") assert re.search(r"# Scraped Items -+\n\[\]", _textmode(out))
self.assertIn("""No CrawlSpider rules found""", _textmode(stderr)) assert "No CrawlSpider rules found" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_missing_callback(self): def test_crawlspider_missing_callback(self):
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", "badcrawl" + self.spider_name, "-r", self.url("/html")] ["--spider", "badcrawl" + self.spider_name, "-r", self.url("/html")]
) )
self.assertRegex(_textmode(out), r"""# Scraped Items -+\n\[\]""") assert re.search(r"# Scraped Items -+\n\[\]", _textmode(out))
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_no_matching_rule(self): def test_crawlspider_no_matching_rule(self):
@ -399,13 +400,13 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
status, out, stderr = yield self.execute( status, out, stderr = yield self.execute(
["--spider", "badcrawl" + self.spider_name, "-r", self.url("/enc-gb18030")] ["--spider", "badcrawl" + self.spider_name, "-r", self.url("/enc-gb18030")]
) )
self.assertRegex(_textmode(out), r"""# Scraped Items -+\n\[\]""") assert re.search(r"# Scraped Items -+\n\[\]", _textmode(out))
self.assertIn("""Cannot find a rule that matches""", _textmode(stderr)) assert "Cannot find a rule that matches" in _textmode(stderr)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_not_exists_with_not_matched_url(self): def test_crawlspider_not_exists_with_not_matched_url(self):
status, out, stderr = yield self.execute([self.url("/invalid_url")]) status, out, stderr = yield self.execute([self.url("/invalid_url")])
self.assertEqual(status, 0) assert status == 0
@defer.inlineCallbacks @defer.inlineCallbacks
def test_output_flag(self): def test_output_flag(self):
@ -426,11 +427,11 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
] ]
) )
self.assertTrue(file_path.exists()) assert file_path.exists()
self.assertTrue(file_path.is_file()) assert file_path.is_file()
content = '[\n{},\n{"foo": "bar"}\n]' content = '[\n{},\n{"foo": "bar"}\n]'
self.assertEqual(file_path.read_text(encoding="utf-8"), content) assert file_path.read_text(encoding="utf-8") == content
def test_parse_add_options(self): def test_parse_add_options(self):
command = parse.Command() command = parse.Command()
@ -445,7 +446,7 @@ ITEM_PIPELINES = {{'{self.project_name}.pipelines.MyPipeline': 1}}
namespace = parser.parse_args( namespace = parser.parse_args(
["--verbose", "--nolinks", "-d", "2", "--spider", self.spider_name] ["--verbose", "--nolinks", "-d", "2", "--spider", self.spider_name]
) )
self.assertTrue(namespace.nolinks) assert namespace.nolinks
self.assertEqual(namespace.depth, 2) assert namespace.depth == 2
self.assertEqual(namespace.spider, self.spider_name) assert namespace.spider == self.spider_name
self.assertTrue(namespace.verbose) assert namespace.verbose

View File

@ -13,7 +13,7 @@ from tests.utils.testproc import ProcessTest
from tests.utils.testsite import SiteTest from tests.utils.testsite import SiteTest
class ShellTest(ProcessTest, SiteTest, unittest.TestCase): class TestShellCommand(ProcessTest, SiteTest, unittest.TestCase):
command = "shell" command = "shell"
@defer.inlineCallbacks @defer.inlineCallbacks
@ -40,14 +40,14 @@ class ShellTest(ProcessTest, SiteTest, unittest.TestCase):
def test_response_selector_html(self): def test_response_selector_html(self):
xpath = "response.xpath(\"//p[@class='one']/text()\").get()" xpath = "response.xpath(\"//p[@class='one']/text()\").get()"
_, out, _ = yield self.execute([self.url("/html"), "-c", xpath]) _, out, _ = yield self.execute([self.url("/html"), "-c", xpath])
self.assertEqual(out.strip(), b"Works") assert out.strip() == b"Works"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_response_encoding_gb18030(self): def test_response_encoding_gb18030(self):
_, out, _ = yield self.execute( _, out, _ = yield self.execute(
[self.url("/enc-gb18030"), "-c", "response.encoding"] [self.url("/enc-gb18030"), "-c", "response.encoding"]
) )
self.assertEqual(out.strip(), b"gb18030") assert out.strip() == b"gb18030"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_redirect(self): def test_redirect(self):
@ -79,7 +79,7 @@ class ShellTest(ProcessTest, SiteTest, unittest.TestCase):
url = self.url("/redirect-no-meta-refresh") url = self.url("/redirect-no-meta-refresh")
code = f"fetch('{url}')" code = f"fetch('{url}')"
errcode, out, errout = yield self.execute(["-c", code]) errcode, out, errout = yield self.execute(["-c", code])
self.assertEqual(errcode, 0, out) assert errcode == 0, out
assert b"Redirecting (302)" in errout assert b"Redirecting (302)" in errout
assert b"Crawled (200)" in errout assert b"Crawled (200)" in errout
@ -89,7 +89,7 @@ class ShellTest(ProcessTest, SiteTest, unittest.TestCase):
url = self.url("/redirect-no-meta-refresh") url = self.url("/redirect-no-meta-refresh")
code = f"fetch('{url}', redirect=False)" code = f"fetch('{url}', redirect=False)"
errcode, out, errout = yield self.execute(["-c", code]) errcode, out, errout = yield self.execute(["-c", code])
self.assertEqual(errcode, 0, out) assert errcode == 0, out
assert b"Crawled (302)" in errout assert b"Crawled (302)" in errout
@defer.inlineCallbacks @defer.inlineCallbacks
@ -97,14 +97,14 @@ class ShellTest(ProcessTest, SiteTest, unittest.TestCase):
url = self.url("/text") url = self.url("/text")
code = f"fetch('{url}') or fetch(response.request.replace(method='POST'))" code = f"fetch('{url}') or fetch(response.request.replace(method='POST'))"
errcode, out, _ = yield self.execute(["-c", code]) errcode, out, _ = yield self.execute(["-c", code])
self.assertEqual(errcode, 0, out) assert errcode == 0, out
@defer.inlineCallbacks @defer.inlineCallbacks
def test_scrapy_import(self): def test_scrapy_import(self):
url = self.url("/text") url = self.url("/text")
code = f"fetch(scrapy.Request('{url}'))" code = f"fetch(scrapy.Request('{url}'))"
errcode, out, _ = yield self.execute(["-c", code]) errcode, out, _ = yield self.execute(["-c", code])
self.assertEqual(errcode, 0, out) assert errcode == 0, out
@defer.inlineCallbacks @defer.inlineCallbacks
def test_local_file(self): def test_local_file(self):
@ -118,8 +118,8 @@ class ShellTest(ProcessTest, SiteTest, unittest.TestCase):
errcode, out, err = yield self.execute( errcode, out, err = yield self.execute(
[filepath, "-c", "item"], check_code=False [filepath, "-c", "item"], check_code=False
) )
self.assertEqual(errcode, 1, out or err) assert errcode == 1, out or err
self.assertIn(b"No such file or directory", err) assert b"No such file or directory" in err
@defer.inlineCallbacks @defer.inlineCallbacks
def test_dns_failures(self): def test_dns_failures(self):
@ -127,8 +127,8 @@ class ShellTest(ProcessTest, SiteTest, unittest.TestCase):
raise unittest.SkipTest("Non-existing hosts are resolvable") raise unittest.SkipTest("Non-existing hosts are resolvable")
url = "www.somedomainthatdoesntexi.st" url = "www.somedomainthatdoesntexi.st"
errcode, out, err = yield self.execute([url, "-c", "item"], check_code=False) errcode, out, err = yield self.execute([url, "-c", "item"], check_code=False)
self.assertEqual(errcode, 1, out or err) assert errcode == 1, out or err
self.assertIn(b"DNS lookup failed", err) assert b"DNS lookup failed" in err
@defer.inlineCallbacks @defer.inlineCallbacks
def test_shell_fetch_async(self): def test_shell_fetch_async(self):
@ -137,10 +137,10 @@ class ShellTest(ProcessTest, SiteTest, unittest.TestCase):
code = f"fetch('{url}')" code = f"fetch('{url}')"
args = ["-c", code, "--set", f"TWISTED_REACTOR={reactor_path}"] args = ["-c", code, "--set", f"TWISTED_REACTOR={reactor_path}"]
_, _, err = yield self.execute(args, check_code=True) _, _, err = yield self.execute(args, check_code=True)
self.assertNotIn(b"RuntimeError: There is no current event loop in thread", err) assert b"RuntimeError: There is no current event loop in thread" not in err
class InteractiveShellTest(unittest.TestCase): class TestInteractiveShell:
def test_fetch(self): def test_fetch(self):
args = ( args = (
sys.executable, sys.executable,
@ -161,4 +161,4 @@ class InteractiveShellTest(unittest.TestCase):
p.sendeof() p.sendeof()
p.wait() p.wait()
logfile.seek(0) logfile.seek(0)
self.assertNotIn("Traceback", logfile.read().decode()) assert "Traceback" not in logfile.read().decode()

View File

@ -7,17 +7,14 @@ import scrapy
from tests.utils.testproc import ProcessTest from tests.utils.testproc import ProcessTest
class VersionTest(ProcessTest, unittest.TestCase): class TestVersionCommand(ProcessTest, unittest.TestCase):
command = "version" command = "version"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_output(self): def test_output(self):
encoding = sys.stdout.encoding or "utf-8" encoding = sys.stdout.encoding or "utf-8"
_, out, _ = yield self.execute([]) _, out, _ = yield self.execute([])
self.assertEqual( assert out.strip().decode(encoding) == f"Scrapy {scrapy.__version__}"
out.strip().decode(encoding),
f"Scrapy {scrapy.__version__}",
)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_verbose_output(self): def test_verbose_output(self):
@ -27,19 +24,16 @@ class VersionTest(ProcessTest, unittest.TestCase):
line.partition(":")[0].strip() line.partition(":")[0].strip()
for line in out.strip().decode(encoding).splitlines() for line in out.strip().decode(encoding).splitlines()
] ]
self.assertEqual( assert headers == [
headers, "Scrapy",
[ "lxml",
"Scrapy", "libxml2",
"lxml", "cssselect",
"libxml2", "parsel",
"cssselect", "w3lib",
"parsel", "Twisted",
"w3lib", "Python",
"Twisted", "pyOpenSSL",
"Python", "cryptography",
"pyOpenSSL", "Platform",
"cryptography", ]
"Platform",
],
)

View File

@ -14,7 +14,7 @@ from itertools import chain
from pathlib import Path from pathlib import Path
from shutil import copytree, rmtree from shutil import copytree, rmtree
from stat import S_IWRITE as ANYONE_WRITE_PERMISSION from stat import S_IWRITE as ANYONE_WRITE_PERMISSION
from tempfile import TemporaryFile, mkdtemp from tempfile import TemporaryDirectory, TemporaryFile, mkdtemp
from threading import Timer from threading import Timer
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from unittest import mock, skipIf from unittest import mock, skipIf
@ -35,8 +35,8 @@ if TYPE_CHECKING:
from collections.abc import Iterator from collections.abc import Iterator
class CommandSettings(unittest.TestCase): class TestCommandSettings:
def setUp(self): def setup_method(self):
self.command = ScrapyCommand() self.command = ScrapyCommand()
self.command.settings = Settings() self.command.settings = Settings()
self.parser = argparse.ArgumentParser( self.parser = argparse.ArgumentParser(
@ -50,10 +50,8 @@ class CommandSettings(unittest.TestCase):
args=["-s", f"FEEDS={feeds_json}", "spider.py"] args=["-s", f"FEEDS={feeds_json}", "spider.py"]
) )
self.command.process_options(args, opts) self.command.process_options(args, opts)
self.assertIsInstance( assert isinstance(self.command.settings["FEEDS"], scrapy.settings.BaseSettings)
self.command.settings["FEEDS"], scrapy.settings.BaseSettings assert dict(self.command.settings["FEEDS"]) == json.loads(feeds_json)
)
self.assertEqual(dict(self.command.settings["FEEDS"]), json.loads(feeds_json))
def test_help_formatter(self): def test_help_formatter(self):
formatter = ScrapyHelpFormatter(prog="scrapy") formatter = ScrapyHelpFormatter(prog="scrapy")
@ -64,17 +62,14 @@ class CommandSettings(unittest.TestCase):
"\n", "\n",
"Global Options:\n", "Global Options:\n",
] ]
self.assertEqual( assert formatter._join_parts(part_strings) == (
formatter._join_parts(part_strings), "Usage\n=====\n scrapy genspider [options] <name> <domain>\n\n\n"
( "Optional Arguments\n==================\n\n"
"Usage\n=====\n scrapy genspider [options] <name> <domain>\n\n\n" "Global Options\n--------------\n"
"Optional Arguments\n==================\n\n"
"Global Options\n--------------\n"
),
) )
class ProjectTest(unittest.TestCase): class TestProjectBase(unittest.TestCase):
project_name = "testproject" project_name = "testproject"
def setUp(self): def setUp(self):
@ -130,12 +125,12 @@ class ProjectTest(unittest.TestCase):
return None return None
class StartprojectTest(ProjectTest): class TestStartprojectCommand(TestProjectBase):
def test_startproject(self): def test_startproject(self):
p, out, err = self.proc("startproject", self.project_name) p, out, err = self.proc("startproject", self.project_name)
print(out) print(out)
print(err, file=sys.stderr) print(err, file=sys.stderr)
self.assertEqual(p.returncode, 0) assert p.returncode == 0
assert Path(self.proj_path, "scrapy.cfg").exists() assert Path(self.proj_path, "scrapy.cfg").exists()
assert Path(self.proj_path, "testproject").exists() assert Path(self.proj_path, "testproject").exists()
@ -145,13 +140,13 @@ class StartprojectTest(ProjectTest):
assert Path(self.proj_mod_path, "settings.py").exists() assert Path(self.proj_mod_path, "settings.py").exists()
assert Path(self.proj_mod_path, "spiders", "__init__.py").exists() assert Path(self.proj_mod_path, "spiders", "__init__.py").exists()
self.assertEqual(1, self.call("startproject", self.project_name)) assert self.call("startproject", self.project_name) == 1
self.assertEqual(1, self.call("startproject", "wrong---project---name")) assert self.call("startproject", "wrong---project---name") == 1
self.assertEqual(1, self.call("startproject", "sys")) assert self.call("startproject", "sys") == 1
def test_startproject_with_project_dir(self): def test_startproject_with_project_dir(self):
project_dir = mkdtemp() project_dir = mkdtemp()
self.assertEqual(0, self.call("startproject", self.project_name, project_dir)) assert self.call("startproject", self.project_name, project_dir) == 0
assert Path(project_dir, "scrapy.cfg").exists() assert Path(project_dir, "scrapy.cfg").exists()
assert Path(project_dir, "testproject").exists() assert Path(project_dir, "testproject").exists()
@ -161,20 +156,16 @@ class StartprojectTest(ProjectTest):
assert Path(project_dir, self.project_name, "settings.py").exists() assert Path(project_dir, self.project_name, "settings.py").exists()
assert Path(project_dir, self.project_name, "spiders", "__init__.py").exists() assert Path(project_dir, self.project_name, "spiders", "__init__.py").exists()
self.assertEqual( assert self.call("startproject", self.project_name, project_dir + "2") == 0
0, self.call("startproject", self.project_name, project_dir + "2")
)
self.assertEqual(1, self.call("startproject", self.project_name, project_dir)) assert self.call("startproject", self.project_name, project_dir) == 1
self.assertEqual( assert self.call("startproject", self.project_name + "2", project_dir) == 1
1, self.call("startproject", self.project_name + "2", project_dir) assert self.call("startproject", "wrong---project---name") == 1
) assert self.call("startproject", "sys") == 1
self.assertEqual(1, self.call("startproject", "wrong---project---name")) assert self.call("startproject") == 2
self.assertEqual(1, self.call("startproject", "sys")) assert (
self.assertEqual(2, self.call("startproject")) self.call("startproject", self.project_name, project_dir, "another_params")
self.assertEqual( == 2
2,
self.call("startproject", self.project_name, project_dir, "another_params"),
) )
def test_existing_project_dir(self): def test_existing_project_dir(self):
@ -186,7 +177,7 @@ class StartprojectTest(ProjectTest):
p, out, err = self.proc("startproject", project_name, cwd=project_dir) p, out, err = self.proc("startproject", project_name, cwd=project_dir)
print(out) print(out)
print(err, file=sys.stderr) print(err, file=sys.stderr)
self.assertEqual(p.returncode, 0) assert p.returncode == 0
assert Path(project_path, "scrapy.cfg").exists() assert Path(project_path, "scrapy.cfg").exists()
assert Path(project_path, project_name).exists() assert Path(project_path, project_name).exists()
@ -224,7 +215,7 @@ def get_permissions_dict(
return permissions_dict return permissions_dict
class StartprojectTemplatesTest(ProjectTest): class TestStartprojectTemplates(TestProjectBase):
maxDiff = None maxDiff = None
def setUp(self): def setUp(self):
@ -239,11 +230,10 @@ class StartprojectTemplatesTest(ProjectTest):
args = ["--set", f"TEMPLATES_DIR={self.tmpl}"] args = ["--set", f"TEMPLATES_DIR={self.tmpl}"]
p, out, err = self.proc("startproject", self.project_name, *args) p, out, err = self.proc("startproject", self.project_name, *args)
self.assertIn( assert (
f"New Scrapy project '{self.project_name}', using template directory", f"New Scrapy project '{self.project_name}', using template directory" in out
out,
) )
self.assertIn(self.tmpl_proj, out) assert self.tmpl_proj in out
assert Path(self.proj_path, "root_template").exists() assert Path(self.proj_path, "root_template").exists()
def test_startproject_permissions_from_writable(self): def test_startproject_permissions_from_writable(self):
@ -280,7 +270,7 @@ class StartprojectTemplatesTest(ProjectTest):
project_dir = Path(destination, project_name) project_dir = Path(destination, project_name)
actual_permissions = get_permissions_dict(project_dir) actual_permissions = get_permissions_dict(project_dir)
self.assertEqual(actual_permissions, expected_permissions) assert actual_permissions == expected_permissions
def test_startproject_permissions_from_read_only(self): def test_startproject_permissions_from_read_only(self):
"""Check that generated files have the right permissions when the """Check that generated files have the right permissions when the
@ -333,7 +323,7 @@ class StartprojectTemplatesTest(ProjectTest):
project_dir = Path(destination, project_name) project_dir = Path(destination, project_name)
actual_permissions = get_permissions_dict(project_dir) actual_permissions = get_permissions_dict(project_dir)
self.assertEqual(actual_permissions, expected_permissions) assert actual_permissions == expected_permissions
def test_startproject_permissions_unchanged_in_destination(self): def test_startproject_permissions_unchanged_in_destination(self):
"""Check that preexisting folders and files in the destination folder """Check that preexisting folders and files in the destination folder
@ -391,7 +381,7 @@ class StartprojectTemplatesTest(ProjectTest):
actual_permissions = get_permissions_dict(project_dir) actual_permissions = get_permissions_dict(project_dir)
self.assertEqual(actual_permissions, expected_permissions) assert actual_permissions == expected_permissions
def test_startproject_permissions_umask_022(self): def test_startproject_permissions_umask_022(self):
"""Check that generated files have the right permissions when the """Check that generated files have the right permissions when the
@ -435,10 +425,10 @@ class StartprojectTemplatesTest(ProjectTest):
project_dir = Path(destination, project_name) project_dir = Path(destination, project_name)
actual_permissions = get_permissions_dict(project_dir) actual_permissions = get_permissions_dict(project_dir)
self.assertEqual(actual_permissions, expected_permissions) assert actual_permissions == expected_permissions
class CommandTest(ProjectTest): class TestCommandBase(TestProjectBase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.call("startproject", self.project_name) self.call("startproject", self.project_name)
@ -446,13 +436,13 @@ class CommandTest(ProjectTest):
self.env["SCRAPY_SETTINGS_MODULE"] = f"{self.project_name}.settings" self.env["SCRAPY_SETTINGS_MODULE"] = f"{self.project_name}.settings"
class GenspiderCommandTest(CommandTest): class TestGenspiderCommand(TestCommandBase):
def test_arguments(self): def test_arguments(self):
# only pass one argument. spider script shouldn't be created # only pass one argument. spider script shouldn't be created
self.assertEqual(2, self.call("genspider", "test_name")) assert self.call("genspider", "test_name") == 2
assert not Path(self.proj_mod_path, "spiders", "test_name.py").exists() assert not Path(self.proj_mod_path, "spiders", "test_name.py").exists()
# pass two arguments <name> <domain>. spider script should be created # pass two arguments <name> <domain>. spider script should be created
self.assertEqual(0, self.call("genspider", "test_name", "test.com")) assert self.call("genspider", "test_name", "test.com") == 0
assert Path(self.proj_mod_path, "spiders", "test_name.py").exists() assert Path(self.proj_mod_path, "spiders", "test_name.py").exists()
def test_template(self, tplname="crawl"): def test_template(self, tplname="crawl"):
@ -460,20 +450,20 @@ class GenspiderCommandTest(CommandTest):
spname = "test_spider" spname = "test_spider"
spmodule = f"{self.project_name}.spiders.{spname}" spmodule = f"{self.project_name}.spiders.{spname}"
p, out, err = self.proc("genspider", spname, "test.com", *args) p, out, err = self.proc("genspider", spname, "test.com", *args)
self.assertIn( assert (
f"Created spider {spname!r} using template {tplname!r} in module:{os.linesep} {spmodule}", f"Created spider {spname!r} using template {tplname!r} in module:{os.linesep} {spmodule}"
out, in out
) )
self.assertTrue(Path(self.proj_mod_path, "spiders", "test_spider.py").exists()) assert Path(self.proj_mod_path, "spiders", "test_spider.py").exists()
modify_time_before = ( modify_time_before = (
Path(self.proj_mod_path, "spiders", "test_spider.py").stat().st_mtime Path(self.proj_mod_path, "spiders", "test_spider.py").stat().st_mtime
) )
p, out, err = self.proc("genspider", spname, "test.com", *args) p, out, err = self.proc("genspider", spname, "test.com", *args)
self.assertIn(f"Spider {spname!r} already exists in module", out) assert f"Spider {spname!r} already exists in module" in out
modify_time_after = ( modify_time_after = (
Path(self.proj_mod_path, "spiders", "test_spider.py").stat().st_mtime Path(self.proj_mod_path, "spiders", "test_spider.py").stat().st_mtime
) )
self.assertEqual(modify_time_after, modify_time_before) assert modify_time_after == modify_time_before
def test_template_basic(self): def test_template_basic(self):
self.test_template("basic") self.test_template("basic")
@ -485,14 +475,14 @@ class GenspiderCommandTest(CommandTest):
self.test_template("xmlfeed") self.test_template("xmlfeed")
def test_list(self): def test_list(self):
self.assertEqual(0, self.call("genspider", "--list")) assert self.call("genspider", "--list") == 0
def test_dump(self): def test_dump(self):
self.assertEqual(0, self.call("genspider", "--dump=basic")) assert self.call("genspider", "--dump=basic") == 0
self.assertEqual(0, self.call("genspider", "-d", "basic")) assert self.call("genspider", "-d", "basic") == 0
def test_same_name_as_project(self): def test_same_name_as_project(self):
self.assertEqual(2, self.call("genspider", self.project_name)) assert self.call("genspider", self.project_name) == 2
assert not Path( assert not Path(
self.proj_mod_path, "spiders", f"{self.project_name}.py" self.proj_mod_path, "spiders", f"{self.project_name}.py"
).exists() ).exists()
@ -500,7 +490,7 @@ class GenspiderCommandTest(CommandTest):
def test_same_filename_as_existing_spider(self, force=False): def test_same_filename_as_existing_spider(self, force=False):
file_name = "example" file_name = "example"
file_path = Path(self.proj_mod_path, "spiders", f"{file_name}.py") file_path = Path(self.proj_mod_path, "spiders", f"{file_name}.py")
self.assertEqual(0, self.call("genspider", file_name, "example.com")) assert self.call("genspider", file_name, "example.com") == 0
assert file_path.exists() assert file_path.exists()
# change name of spider but not its file name # change name of spider but not its file name
@ -515,39 +505,39 @@ class GenspiderCommandTest(CommandTest):
if force: if force:
p, out, err = self.proc("genspider", "--force", file_name, "example.com") p, out, err = self.proc("genspider", "--force", file_name, "example.com")
self.assertIn( assert (
f"Created spider {file_name!r} using template 'basic' in module", out f"Created spider {file_name!r} using template 'basic' in module" in out
) )
modify_time_after = file_path.stat().st_mtime modify_time_after = file_path.stat().st_mtime
self.assertNotEqual(modify_time_after, modify_time_before) assert modify_time_after != modify_time_before
file_contents_after = file_path.read_text(encoding="utf-8") file_contents_after = file_path.read_text(encoding="utf-8")
self.assertNotEqual(file_contents_after, file_contents_before) assert file_contents_after != file_contents_before
else: else:
p, out, err = self.proc("genspider", file_name, "example.com") p, out, err = self.proc("genspider", file_name, "example.com")
self.assertIn(f"{file_path.resolve()} already exists", out) assert f"{file_path.resolve()} already exists" in out
modify_time_after = file_path.stat().st_mtime modify_time_after = file_path.stat().st_mtime
self.assertEqual(modify_time_after, modify_time_before) assert modify_time_after == modify_time_before
file_contents_after = file_path.read_text(encoding="utf-8") file_contents_after = file_path.read_text(encoding="utf-8")
self.assertEqual(file_contents_after, file_contents_before) assert file_contents_after == file_contents_before
def test_same_filename_as_existing_spider_force(self): def test_same_filename_as_existing_spider_force(self):
self.test_same_filename_as_existing_spider(force=True) self.test_same_filename_as_existing_spider(force=True)
def test_url(self, url="test.com", domain="test.com"): def test_url(self, url="test.com", domain="test.com"):
self.assertEqual(0, self.call("genspider", "--force", "test_name", url)) assert self.call("genspider", "--force", "test_name", url) == 0
self.assertEqual( assert (
domain,
self.find_in_file( self.find_in_file(
Path(self.proj_mod_path, "spiders", "test_name.py"), Path(self.proj_mod_path, "spiders", "test_name.py"),
r"allowed_domains\s*=\s*\[['\"](.+)['\"]\]", r"allowed_domains\s*=\s*\[['\"](.+)['\"]\]",
).group(1), ).group(1)
== domain
) )
self.assertEqual( assert (
f"https://{domain}",
self.find_in_file( self.find_in_file(
Path(self.proj_mod_path, "spiders", "test_name.py"), Path(self.proj_mod_path, "spiders", "test_name.py"),
r"start_urls\s*=\s*\[['\"](.+)['\"]\]", r"start_urls\s*=\s*\[['\"](.+)['\"]\]",
).group(1), ).group(1)
== f"https://{domain}"
) )
def test_url_schema(self): def test_url_schema(self):
@ -556,15 +546,13 @@ class GenspiderCommandTest(CommandTest):
def test_template_start_urls( def test_template_start_urls(
self, url="test.com", expected="https://test.com", template="basic" self, url="test.com", expected="https://test.com", template="basic"
): ):
self.assertEqual( assert self.call("genspider", "-t", template, "--force", "test_name", url) == 0
0, self.call("genspider", "-t", template, "--force", "test_name", url) assert (
)
self.assertEqual(
expected,
self.find_in_file( self.find_in_file(
Path(self.proj_mod_path, "spiders", "test_name.py"), Path(self.proj_mod_path, "spiders", "test_name.py"),
r"start_urls\s*=\s*\[['\"](.+)['\"]\]", r"start_urls\s*=\s*\[['\"](.+)['\"]\]",
).group(1), ).group(1)
== expected
) )
def test_genspider_basic_start_urls(self): def test_genspider_basic_start_urls(self):
@ -611,7 +599,7 @@ class GenspiderCommandTest(CommandTest):
) )
class GenspiderStandaloneCommandTest(ProjectTest): class TestGenspiderStandaloneCommand(TestProjectBase):
def test_generate_standalone_spider(self): def test_generate_standalone_spider(self):
self.call("genspider", "example", "example.com") self.call("genspider", "example", "example.com")
assert Path(self.temp_path, "example.py").exists() assert Path(self.temp_path, "example.py").exists()
@ -620,7 +608,7 @@ class GenspiderStandaloneCommandTest(ProjectTest):
file_name = "example" file_name = "example"
file_path = Path(self.temp_path, file_name + ".py") file_path = Path(self.temp_path, file_name + ".py")
p, out, err = self.proc("genspider", file_name, "example.com") p, out, err = self.proc("genspider", file_name, "example.com")
self.assertIn(f"Created spider {file_name!r} using template 'basic' ", out) assert f"Created spider {file_name!r} using template 'basic' " in out
assert file_path.exists() assert file_path.exists()
modify_time_before = file_path.stat().st_mtime modify_time_before = file_path.stat().st_mtime
file_contents_before = file_path.read_text(encoding="utf-8") file_contents_before = file_path.read_text(encoding="utf-8")
@ -630,29 +618,29 @@ class GenspiderStandaloneCommandTest(ProjectTest):
p, out, err = self.proc( p, out, err = self.proc(
"genspider", "--force", "-t", "crawl", file_name, "example.com" "genspider", "--force", "-t", "crawl", file_name, "example.com"
) )
self.assertIn(f"Created spider {file_name!r} using template 'crawl' ", out) assert f"Created spider {file_name!r} using template 'crawl' " in out
modify_time_after = file_path.stat().st_mtime modify_time_after = file_path.stat().st_mtime
self.assertNotEqual(modify_time_after, modify_time_before) assert modify_time_after != modify_time_before
file_contents_after = file_path.read_text(encoding="utf-8") file_contents_after = file_path.read_text(encoding="utf-8")
self.assertNotEqual(file_contents_after, file_contents_before) assert file_contents_after != file_contents_before
else: else:
p, out, err = self.proc("genspider", file_name, "example.com") p, out, err = self.proc("genspider", file_name, "example.com")
self.assertIn( assert (
f"{Path(self.temp_path, file_name + '.py').resolve()} already exists", f"{Path(self.temp_path, file_name + '.py').resolve()} already exists"
out, in out
) )
modify_time_after = file_path.stat().st_mtime modify_time_after = file_path.stat().st_mtime
self.assertEqual(modify_time_after, modify_time_before) assert modify_time_after == modify_time_before
file_contents_after = file_path.read_text(encoding="utf-8") file_contents_after = file_path.read_text(encoding="utf-8")
self.assertEqual(file_contents_after, file_contents_before) assert file_contents_after == file_contents_before
def test_same_name_as_existing_file_force(self): def test_same_name_as_existing_file_force(self):
self.test_same_name_as_existing_file(force=True) self.test_same_name_as_existing_file(force=True)
class MiscCommandsTest(CommandTest): class TestMiscCommands(TestCommandBase):
def test_list(self): def test_list(self):
self.assertEqual(0, self.call("list")) assert self.call("list") == 0
def test_command_not_found(self): def test_command_not_found(self):
na_msg = """ na_msg = """
@ -670,10 +658,10 @@ Unknown command: abc
for cmdname, inproject, message in params: for cmdname, inproject, message in params:
with mock.patch("sys.stdout", new=StringIO()) as out: with mock.patch("sys.stdout", new=StringIO()) as out:
_print_unknown_command_msg(Settings(), cmdname, inproject) _print_unknown_command_msg(Settings(), cmdname, inproject)
self.assertEqual(out.getvalue().strip(), message.strip()) assert out.getvalue().strip() == message.strip()
class RunSpiderCommandTest(CommandTest): class TestRunSpiderCommand(TestCommandBase):
spider_filename = "myspider.py" spider_filename = "myspider.py"
debug_log_spider = """ debug_log_spider = """
@ -697,18 +685,14 @@ class BadSpider(scrapy.Spider):
""" """
@contextmanager @contextmanager
def _create_file(self, content, name=None) -> Iterator[str]: def _create_file(self, content: str, name: str | None = None) -> Iterator[str]:
tmpdir = Path(self.mktemp()) with TemporaryDirectory() as tmpdir:
tmpdir.mkdir() if name:
if name: fname = Path(tmpdir, name).resolve()
fname = (tmpdir / name).resolve() else:
else: fname = Path(tmpdir, self.spider_filename).resolve()
fname = (tmpdir / self.spider_filename).resolve() fname.write_text(content, encoding="utf-8")
fname.write_text(content, encoding="utf-8")
try:
yield str(fname) yield str(fname)
finally:
rmtree(tmpdir)
def runspider(self, code, name=None, args=()): def runspider(self, code, name=None, args=()):
with self._create_file(code, name) as fname: with self._create_file(code, name) as fname:
@ -720,29 +704,29 @@ class BadSpider(scrapy.Spider):
def test_runspider(self): def test_runspider(self):
log = self.get_log(self.debug_log_spider) log = self.get_log(self.debug_log_spider)
self.assertIn("DEBUG: It Works!", log) assert "DEBUG: It Works!" in log
self.assertIn("INFO: Spider opened", log) assert "INFO: Spider opened" in log
self.assertIn("INFO: Closing spider (finished)", log) assert "INFO: Closing spider (finished)" in log
self.assertIn("INFO: Spider closed (finished)", log) assert "INFO: Spider closed (finished)" in log
def test_run_fail_spider(self): def test_run_fail_spider(self):
proc, _, _ = self.runspider( proc, _, _ = self.runspider(
"import scrapy\n" + inspect.getsource(ExceptionSpider) "import scrapy\n" + inspect.getsource(ExceptionSpider)
) )
ret = proc.returncode ret = proc.returncode
self.assertNotEqual(ret, 0) assert ret != 0
def test_run_good_spider(self): def test_run_good_spider(self):
proc, _, _ = self.runspider( proc, _, _ = self.runspider(
"import scrapy\n" + inspect.getsource(NoRequestsSpider) "import scrapy\n" + inspect.getsource(NoRequestsSpider)
) )
ret = proc.returncode ret = proc.returncode
self.assertEqual(ret, 0) assert ret == 0
def test_runspider_log_level(self): def test_runspider_log_level(self):
log = self.get_log(self.debug_log_spider, args=("-s", "LOG_LEVEL=INFO")) log = self.get_log(self.debug_log_spider, args=("-s", "LOG_LEVEL=INFO"))
self.assertNotIn("DEBUG: It Works!", log) assert "DEBUG: It Works!" not in log
self.assertIn("INFO: Spider opened", log) assert "INFO: Spider opened" in log
def test_runspider_dnscache_disabled(self): def test_runspider_dnscache_disabled(self):
# see https://github.com/scrapy/scrapy/issues/2811 # see https://github.com/scrapy/scrapy/issues/2811
@ -761,36 +745,36 @@ class MySpider(scrapy.Spider):
return {'test': 'value'} return {'test': 'value'}
""" """
log = self.get_log(dnscache_spider, args=("-s", "DNSCACHE_ENABLED=False")) log = self.get_log(dnscache_spider, args=("-s", "DNSCACHE_ENABLED=False"))
self.assertNotIn("DNSLookupError", log) assert "DNSLookupError" not in log
self.assertIn("INFO: Spider opened", log) assert "INFO: Spider opened" in log
def test_runspider_log_short_names(self): def test_runspider_log_short_names(self):
log1 = self.get_log(self.debug_log_spider, args=("-s", "LOG_SHORT_NAMES=1")) log1 = self.get_log(self.debug_log_spider, args=("-s", "LOG_SHORT_NAMES=1"))
self.assertIn("[myspider] DEBUG: It Works!", log1) assert "[myspider] DEBUG: It Works!" in log1
self.assertIn("[scrapy]", log1) assert "[scrapy]" in log1
self.assertNotIn("[scrapy.core.engine]", log1) assert "[scrapy.core.engine]" not in log1
log2 = self.get_log(self.debug_log_spider, args=("-s", "LOG_SHORT_NAMES=0")) log2 = self.get_log(self.debug_log_spider, args=("-s", "LOG_SHORT_NAMES=0"))
self.assertIn("[myspider] DEBUG: It Works!", log2) assert "[myspider] DEBUG: It Works!" in log2
self.assertNotIn("[scrapy]", log2) assert "[scrapy]" not in log2
self.assertIn("[scrapy.core.engine]", log2) assert "[scrapy.core.engine]" in log2
def test_runspider_no_spider_found(self): def test_runspider_no_spider_found(self):
log = self.get_log("from scrapy.spiders import Spider\n") log = self.get_log("from scrapy.spiders import Spider\n")
self.assertIn("No spider found in file", log) assert "No spider found in file" in log
def test_runspider_file_not_found(self): def test_runspider_file_not_found(self):
_, _, log = self.proc("runspider", "some_non_existent_file") _, _, log = self.proc("runspider", "some_non_existent_file")
self.assertIn("File not found: some_non_existent_file", log) assert "File not found: some_non_existent_file" in log
def test_runspider_unable_to_load(self): def test_runspider_unable_to_load(self):
log = self.get_log("", name="myspider.txt") log = self.get_log("", name="myspider.txt")
self.assertIn("Unable to load", log) assert "Unable to load" in log
def test_start_requests_errors(self): def test_start_requests_errors(self):
log = self.get_log(self.badspider, name="badspider.py") log = self.get_log(self.badspider, name="badspider.py")
self.assertIn("start_requests", log) assert "start_requests" in log
self.assertIn("badspider.py", log) assert "badspider.py" in log
def test_asyncio_enabled_true(self): def test_asyncio_enabled_true(self):
log = self.get_log( log = self.get_log(
@ -800,14 +784,16 @@ class MySpider(scrapy.Spider):
"TWISTED_REACTOR=twisted.internet.asyncioreactor.AsyncioSelectorReactor", "TWISTED_REACTOR=twisted.internet.asyncioreactor.AsyncioSelectorReactor",
], ],
) )
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
def test_asyncio_enabled_default(self): def test_asyncio_enabled_default(self):
log = self.get_log(self.debug_log_spider, args=[]) log = self.get_log(self.debug_log_spider, args=[])
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
def test_asyncio_enabled_false(self): def test_asyncio_enabled_false(self):
@ -815,11 +801,10 @@ class MySpider(scrapy.Spider):
self.debug_log_spider, self.debug_log_spider,
args=["-s", "TWISTED_REACTOR=twisted.internet.selectreactor.SelectReactor"], args=["-s", "TWISTED_REACTOR=twisted.internet.selectreactor.SelectReactor"],
) )
self.assertIn( assert "Using reactor: twisted.internet.selectreactor.SelectReactor" in log
"Using reactor: twisted.internet.selectreactor.SelectReactor", log assert (
) "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
self.assertNotIn( not in log
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log
) )
@pytest.mark.requires_uvloop @pytest.mark.requires_uvloop
@ -833,7 +818,7 @@ class MySpider(scrapy.Spider):
"ASYNCIO_EVENT_LOOP=uvloop.Loop", "ASYNCIO_EVENT_LOOP=uvloop.Loop",
], ],
) )
self.assertIn("Using asyncio event loop: uvloop.Loop", log) assert "Using asyncio event loop: uvloop.Loop" in log
def test_custom_asyncio_loop_enabled_false(self): def test_custom_asyncio_loop_enabled_false(self):
log = self.get_log( log = self.get_log(
@ -849,9 +834,9 @@ class MySpider(scrapy.Spider):
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
else: else:
loop = asyncio.SelectorEventLoop() loop = asyncio.SelectorEventLoop()
self.assertIn( assert (
f"Using asyncio event loop: {loop.__module__}.{loop.__class__.__name__}", f"Using asyncio event loop: {loop.__module__}.{loop.__class__.__name__}"
log, in log
) )
def test_output(self): def test_output(self):
@ -867,9 +852,7 @@ class MySpider(scrapy.Spider):
""" """
args = ["-o", "example.json"] args = ["-o", "example.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert "[myspider] DEBUG: FEEDS: {'example.json': {'format': 'json'}}" in log
"[myspider] DEBUG: FEEDS: {'example.json': {'format': 'json'}}", log
)
def test_overwrite_output(self): def test_overwrite_output(self):
spider_code = """ spider_code = """
@ -890,13 +873,13 @@ class MySpider(scrapy.Spider):
Path(self.cwd, "example.json").write_text("not empty", encoding="utf-8") Path(self.cwd, "example.json").write_text("not empty", encoding="utf-8")
args = ["-O", "example.json"] args = ["-O", "example.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
'[myspider] DEBUG: FEEDS: {"example.json": {"format": "json", "overwrite": true}}', '[myspider] DEBUG: FEEDS: {"example.json": {"format": "json", "overwrite": true}}'
log, in log
) )
with Path(self.cwd, "example.json").open(encoding="utf-8") as f2: with Path(self.cwd, "example.json").open(encoding="utf-8") as f2:
first_line = f2.readline() first_line = f2.readline()
self.assertNotEqual(first_line, "not empty") assert first_line != "not empty"
def test_output_and_overwrite_output(self): def test_output_and_overwrite_output(self):
spider_code = """ spider_code = """
@ -910,8 +893,8 @@ class MySpider(scrapy.Spider):
""" """
args = ["-o", "example1.json", "-O", "example2.json"] args = ["-o", "example1.json", "-O", "example2.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
"error: Please use only one of -o/--output and -O/--overwrite-output", log "error: Please use only one of -o/--output and -O/--overwrite-output" in log
) )
def test_output_stdout(self): def test_output_stdout(self):
@ -927,7 +910,7 @@ class MySpider(scrapy.Spider):
""" """
args = ["-o", "-:json"] args = ["-o", "-:json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn("[myspider] DEBUG: FEEDS: {'stdout:': {'format': 'json'}}", log) assert "[myspider] DEBUG: FEEDS: {'stdout:': {'format': 'json'}}" in log
@skipIf(platform.system() == "Windows", reason="Linux only") @skipIf(platform.system() == "Windows", reason="Linux only")
def test_absolute_path_linux(self): def test_absolute_path_linux(self):
@ -946,16 +929,16 @@ class MySpider(scrapy.Spider):
args = ["-o", f"{temp_dir}/output1.json:json"] args = ["-o", f"{temp_dir}/output1.json:json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}/output1.json", f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}/output1.json"
log, in log
) )
args = ["-o", f"{temp_dir}/output2.json"] args = ["-o", f"{temp_dir}/output2.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}/output2.json", f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}/output2.json"
log, in log
) )
@skipIf(platform.system() != "Windows", reason="Windows only") @skipIf(platform.system() != "Windows", reason="Windows only")
@ -975,16 +958,16 @@ class MySpider(scrapy.Spider):
args = ["-o", f"{temp_dir}\\output1.json:json"] args = ["-o", f"{temp_dir}\\output1.json:json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}\\output1.json", f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}\\output1.json"
log, in log
) )
args = ["-o", f"{temp_dir}\\output2.json"] args = ["-o", f"{temp_dir}\\output2.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}\\output2.json", f"[scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: {temp_dir}\\output2.json"
log, in log
) )
def test_args_change_settings(self): def test_args_change_settings(self):
@ -1006,11 +989,11 @@ class MySpider(scrapy.Spider):
""" """
args = ["-a", "foo=42"] args = ["-a", "foo=42"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn("The value of FOO is 42", log) assert "The value of FOO is 42" in log
class WindowsRunSpiderCommandTest(RunSpiderCommandTest): class TestWindowsRunSpiderCommand(TestRunSpiderCommand):
spider_filename = "myspider.pyw" spider_filename = "myspider.pyw"
def setUp(self): def setUp(self):
@ -1020,24 +1003,24 @@ class WindowsRunSpiderCommandTest(RunSpiderCommandTest):
def test_start_requests_errors(self): def test_start_requests_errors(self):
log = self.get_log(self.badspider, name="badspider.pyw") log = self.get_log(self.badspider, name="badspider.pyw")
self.assertIn("start_requests", log) assert "start_requests" in log
self.assertIn("badspider.pyw", log) assert "badspider.pyw" in log
def test_runspider_unable_to_load(self): def test_runspider_unable_to_load(self):
raise unittest.SkipTest("Already Tested in 'RunSpiderCommandTest' ") raise unittest.SkipTest("Already Tested in 'RunSpiderCommandTest' ")
class BenchCommandTest(CommandTest): class TestBenchCommand(TestCommandBase):
def test_run(self): def test_run(self):
_, _, log = self.proc( _, _, log = self.proc(
"bench", "-s", "LOGSTATS_INTERVAL=0.001", "-s", "CLOSESPIDER_TIMEOUT=0.01" "bench", "-s", "LOGSTATS_INTERVAL=0.001", "-s", "CLOSESPIDER_TIMEOUT=0.01"
) )
self.assertIn("INFO: Crawled", log) assert "INFO: Crawled" in log
self.assertNotIn("Unhandled Error", log) assert "Unhandled Error" not in log
self.assertNotIn("log_count/ERROR", log) assert "log_count/ERROR" not in log
class ViewCommandTest(CommandTest): class TestViewCommand(TestCommandBase):
def test_methods(self): def test_methods(self):
command = view.Command() command = view.Command()
command.settings = Settings() command.settings = Settings()
@ -1048,13 +1031,11 @@ class ViewCommandTest(CommandTest):
conflict_handler="resolve", conflict_handler="resolve",
) )
command.add_options(parser) command.add_options(parser)
self.assertEqual(command.short_desc(), "Open URL in browser, as seen by Scrapy") assert command.short_desc() == "Open URL in browser, as seen by Scrapy"
self.assertIn( assert "URL using the Scrapy downloader and show its" in command.long_desc()
"URL using the Scrapy downloader and show its", command.long_desc()
)
class CrawlCommandTest(CommandTest): class TestCrawlCommand(TestCommandBase):
def crawl(self, code, args=()): def crawl(self, code, args=()):
Path(self.proj_mod_path, "spiders", "myspider.py").write_text( Path(self.proj_mod_path, "spiders", "myspider.py").write_text(
code, encoding="utf-8" code, encoding="utf-8"
@ -1077,7 +1058,7 @@ class MySpider(scrapy.Spider):
return [] return []
""" """
log = self.get_log(spider_code) log = self.get_log(spider_code)
self.assertIn("[myspider] DEBUG: It works!", log) assert "[myspider] DEBUG: It works!" in log
def test_output(self): def test_output(self):
spider_code = """ spider_code = """
@ -1092,9 +1073,7 @@ class MySpider(scrapy.Spider):
""" """
args = ["-o", "example.json"] args = ["-o", "example.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert "[myspider] DEBUG: FEEDS: {'example.json': {'format': 'json'}}" in log
"[myspider] DEBUG: FEEDS: {'example.json': {'format': 'json'}}", log
)
def test_overwrite_output(self): def test_overwrite_output(self):
spider_code = """ spider_code = """
@ -1115,13 +1094,13 @@ class MySpider(scrapy.Spider):
Path(self.cwd, "example.json").write_text("not empty", encoding="utf-8") Path(self.cwd, "example.json").write_text("not empty", encoding="utf-8")
args = ["-O", "example.json"] args = ["-O", "example.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
'[myspider] DEBUG: FEEDS: {"example.json": {"format": "json", "overwrite": true}}', '[myspider] DEBUG: FEEDS: {"example.json": {"format": "json", "overwrite": true}}'
log, in log
) )
with Path(self.cwd, "example.json").open(encoding="utf-8") as f2: with Path(self.cwd, "example.json").open(encoding="utf-8") as f2:
first_line = f2.readline() first_line = f2.readline()
self.assertNotEqual(first_line, "not empty") assert first_line != "not empty"
def test_output_and_overwrite_output(self): def test_output_and_overwrite_output(self):
spider_code = """ spider_code = """
@ -1135,12 +1114,12 @@ class MySpider(scrapy.Spider):
""" """
args = ["-o", "example1.json", "-O", "example2.json"] args = ["-o", "example1.json", "-O", "example2.json"]
log = self.get_log(spider_code, args=args) log = self.get_log(spider_code, args=args)
self.assertIn( assert (
"error: Please use only one of -o/--output and -O/--overwrite-output", log "error: Please use only one of -o/--output and -O/--overwrite-output" in log
) )
class HelpMessageTest(CommandTest): class TestHelpMessage(TestCommandBase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.commands = [ self.commands = [
@ -1163,30 +1142,30 @@ class HelpMessageTest(CommandTest):
def test_help_messages(self): def test_help_messages(self):
for command in self.commands: for command in self.commands:
_, out, _ = self.proc(command, "-h") _, out, _ = self.proc(command, "-h")
self.assertIn("Usage", out) assert "Usage" in out
class PopCommandNameTest(unittest.TestCase): class TestPopCommandName:
def test_valid_command(self): def test_valid_command(self):
argv = ["scrapy", "crawl", "my_spider"] argv = ["scrapy", "crawl", "my_spider"]
command = _pop_command_name(argv) command = _pop_command_name(argv)
self.assertEqual(command, "crawl") assert command == "crawl"
self.assertEqual(argv, ["scrapy", "my_spider"]) assert argv == ["scrapy", "my_spider"]
def test_no_command(self): def test_no_command(self):
argv = ["scrapy"] argv = ["scrapy"]
command = _pop_command_name(argv) command = _pop_command_name(argv)
self.assertIsNone(command) assert command is None
self.assertEqual(argv, ["scrapy"]) assert argv == ["scrapy"]
def test_option_before_command(self): def test_option_before_command(self):
argv = ["scrapy", "-h", "crawl"] argv = ["scrapy", "-h", "crawl"]
command = _pop_command_name(argv) command = _pop_command_name(argv)
self.assertEqual(command, "crawl") assert command == "crawl"
self.assertEqual(argv, ["scrapy", "-h"]) assert argv == ["scrapy", "-h"]
def test_option_after_command(self): def test_option_after_command(self):
argv = ["scrapy", "crawl", "-h"] argv = ["scrapy", "crawl", "-h"]
command = _pop_command_name(argv) command = _pop_command_name(argv)
self.assertEqual(command, "crawl") assert command == "crawl"
self.assertEqual(argv, ["scrapy", "-h"]) assert argv == ["scrapy", "-h"]

View File

@ -1,5 +1,6 @@
from unittest import TextTestResult from unittest import TextTestResult
import pytest
from twisted.internet import defer from twisted.internet import defer
from twisted.python import failure from twisted.python import failure
from twisted.trial import unittest from twisted.trial import unittest
@ -246,7 +247,7 @@ class InheritsDemoSpider(DemoSpider):
name = "inherits_demo_spider" name = "inherits_demo_spider"
class ContractsManagerTest(unittest.TestCase): class TestContractsManager(unittest.TestCase):
contracts = [ contracts = [
UrlContract, UrlContract,
CallbackKeywordArgumentsContract, CallbackKeywordArgumentsContract,
@ -263,34 +264,33 @@ class ContractsManagerTest(unittest.TestCase):
self.results = TextTestResult(stream=None, descriptions=False, verbosity=0) self.results = TextTestResult(stream=None, descriptions=False, verbosity=0)
def should_succeed(self): def should_succeed(self):
self.assertFalse(self.results.failures) assert not self.results.failures
self.assertFalse(self.results.errors) assert not self.results.errors
def should_fail(self): def should_fail(self):
self.assertTrue(self.results.failures) assert self.results.failures
self.assertFalse(self.results.errors) assert not self.results.errors
def should_error(self): def should_error(self):
self.assertTrue(self.results.errors) assert self.results.errors
def test_contracts(self): def test_contracts(self):
spider = DemoSpider() spider = DemoSpider()
# extract contracts correctly # extract contracts correctly
contracts = self.conman.extract_contracts(spider.returns_request) contracts = self.conman.extract_contracts(spider.returns_request)
self.assertEqual(len(contracts), 2) assert len(contracts) == 2
self.assertEqual( assert frozenset(type(x) for x in contracts) == frozenset(
frozenset(type(x) for x in contracts), [UrlContract, ReturnsContract]
frozenset([UrlContract, ReturnsContract]),
) )
# returns request for valid method # returns request for valid method
request = self.conman.from_method(spider.returns_request, self.results) request = self.conman.from_method(spider.returns_request, self.results)
self.assertNotEqual(request, None) assert request is not None
# no request for missing url # no request for missing url
request = self.conman.from_method(spider.parse_no_url, self.results) request = self.conman.from_method(spider.parse_no_url, self.results)
self.assertEqual(request, None) assert request is None
def test_cb_kwargs(self): def test_cb_kwargs(self):
spider = DemoSpider() spider = DemoSpider()
@ -298,35 +298,31 @@ class ContractsManagerTest(unittest.TestCase):
# extract contracts correctly # extract contracts correctly
contracts = self.conman.extract_contracts(spider.returns_request_cb_kwargs) contracts = self.conman.extract_contracts(spider.returns_request_cb_kwargs)
self.assertEqual(len(contracts), 3) assert len(contracts) == 3
self.assertEqual( assert frozenset(type(x) for x in contracts) == frozenset(
frozenset(type(x) for x in contracts), [UrlContract, CallbackKeywordArgumentsContract, ReturnsContract]
frozenset([UrlContract, CallbackKeywordArgumentsContract, ReturnsContract]),
) )
contracts = self.conman.extract_contracts(spider.returns_item_cb_kwargs) contracts = self.conman.extract_contracts(spider.returns_item_cb_kwargs)
self.assertEqual(len(contracts), 3) assert len(contracts) == 3
self.assertEqual( assert frozenset(type(x) for x in contracts) == frozenset(
frozenset(type(x) for x in contracts), [UrlContract, CallbackKeywordArgumentsContract, ReturnsContract]
frozenset([UrlContract, CallbackKeywordArgumentsContract, ReturnsContract]),
) )
contracts = self.conman.extract_contracts( contracts = self.conman.extract_contracts(
spider.returns_item_cb_kwargs_error_unexpected_keyword spider.returns_item_cb_kwargs_error_unexpected_keyword
) )
self.assertEqual(len(contracts), 3) assert len(contracts) == 3
self.assertEqual( assert frozenset(type(x) for x in contracts) == frozenset(
frozenset(type(x) for x in contracts), [UrlContract, CallbackKeywordArgumentsContract, ReturnsContract]
frozenset([UrlContract, CallbackKeywordArgumentsContract, ReturnsContract]),
) )
contracts = self.conman.extract_contracts( contracts = self.conman.extract_contracts(
spider.returns_item_cb_kwargs_error_missing_argument spider.returns_item_cb_kwargs_error_missing_argument
) )
self.assertEqual(len(contracts), 2) assert len(contracts) == 2
self.assertEqual( assert frozenset(type(x) for x in contracts) == frozenset(
frozenset(type(x) for x in contracts), [UrlContract, ReturnsContract]
frozenset([UrlContract, ReturnsContract]),
) )
# returns_request # returns_request
@ -360,17 +356,15 @@ class ContractsManagerTest(unittest.TestCase):
# extract contracts correctly # extract contracts correctly
contracts = self.conman.extract_contracts(spider.returns_request_meta) contracts = self.conman.extract_contracts(spider.returns_request_meta)
self.assertEqual(len(contracts), 3) assert len(contracts) == 3
self.assertEqual( assert frozenset(type(x) for x in contracts) == frozenset(
frozenset(type(x) for x in contracts), [UrlContract, MetadataContract, ReturnsContract]
frozenset([UrlContract, MetadataContract, ReturnsContract]),
) )
contracts = self.conman.extract_contracts(spider.returns_item_meta) contracts = self.conman.extract_contracts(spider.returns_item_meta)
self.assertEqual(len(contracts), 3) assert len(contracts) == 3
self.assertEqual( assert frozenset(type(x) for x in contracts) == frozenset(
frozenset(type(x) for x in contracts), [UrlContract, MetadataContract, ReturnsContract]
frozenset([UrlContract, MetadataContract, ReturnsContract]),
) )
response = ResponseMetaMock() response = ResponseMetaMock()
@ -505,8 +499,8 @@ class ContractsManagerTest(unittest.TestCase):
request = self.conman.from_method(spider.returns_request, self.results) request = self.conman.from_method(spider.returns_request, self.results)
request.errback(failure_mock) request.errback(failure_mock)
self.assertFalse(self.results.failures) assert not self.results.failures
self.assertTrue(self.results.errors) assert self.results.errors
@defer.inlineCallbacks @defer.inlineCallbacks
def test_same_url(self): def test_same_url(self):
@ -537,19 +531,19 @@ class ContractsManagerTest(unittest.TestCase):
crawler = get_crawler(TestSameUrlSpider) crawler = get_crawler(TestSameUrlSpider)
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(crawler.spider.visited, 2) assert crawler.spider.visited == 2
def test_form_contract(self): def test_form_contract(self):
spider = DemoSpider() spider = DemoSpider()
request = self.conman.from_method(spider.custom_form, self.results) request = self.conman.from_method(spider.custom_form, self.results)
self.assertEqual(request.method, "POST") assert request.method == "POST"
self.assertIsInstance(request, FormRequest) assert isinstance(request, FormRequest)
def test_inherited_contracts(self): def test_inherited_contracts(self):
spider = InheritsDemoSpider() spider = InheritsDemoSpider()
requests = self.conman.from_spider(spider, self.results) requests = self.conman.from_spider(spider, self.results)
self.assertTrue(requests) assert requests
class CustomFailContractPreProcess(Contract): class CustomFailContractPreProcess(Contract):
@ -566,8 +560,8 @@ class CustomFailContractPostProcess(Contract):
raise KeyboardInterrupt("Post-process exception") raise KeyboardInterrupt("Post-process exception")
class CustomContractPrePostProcess(unittest.TestCase): class TestCustomContractPrePostProcess:
def setUp(self): def setup_method(self):
self.results = TextTestResult(stream=None, descriptions=False, verbosity=0) self.results = TextTestResult(stream=None, descriptions=False, verbosity=0)
def test_pre_hook_keyboard_interrupt(self): def test_pre_hook_keyboard_interrupt(self):
@ -576,18 +570,13 @@ class CustomContractPrePostProcess(unittest.TestCase):
contract = CustomFailContractPreProcess(spider.returns_request) contract = CustomFailContractPreProcess(spider.returns_request)
conman = ContractsManager([contract]) conman = ContractsManager([contract])
try: request = conman.from_method(spider.returns_request, self.results)
request = conman.from_method(spider.returns_request, self.results) contract.add_pre_hook(request, self.results)
contract.add_pre_hook(request, self.results) with pytest.raises(KeyboardInterrupt, match="Pre-process exception"):
# Expect this to raise a KeyboardInterrupt
request.callback(response, **request.cb_kwargs) request.callback(response, **request.cb_kwargs)
except KeyboardInterrupt as e:
self.assertEqual(str(e), "Pre-process exception")
else:
self.fail("KeyboardInterrupt not raised")
self.assertFalse(self.results.failures) assert not self.results.failures
self.assertFalse(self.results.errors) assert not self.results.errors
def test_post_hook_keyboard_interrupt(self): def test_post_hook_keyboard_interrupt(self):
spider = DemoSpider() spider = DemoSpider()
@ -595,15 +584,10 @@ class CustomContractPrePostProcess(unittest.TestCase):
contract = CustomFailContractPostProcess(spider.returns_request) contract = CustomFailContractPostProcess(spider.returns_request)
conman = ContractsManager([contract]) conman = ContractsManager([contract])
try: request = conman.from_method(spider.returns_request, self.results)
request = conman.from_method(spider.returns_request, self.results) contract.add_post_hook(request, self.results)
contract.add_post_hook(request, self.results) with pytest.raises(KeyboardInterrupt, match="Post-process exception"):
# Expect this to raise a KeyboardInterrupt
request.callback(response, **request.cb_kwargs) request.callback(response, **request.cb_kwargs)
except KeyboardInterrupt as e:
self.assertEqual(str(e), "Post-process exception")
else:
self.fail("KeyboardInterrupt not raised")
self.assertFalse(self.results.failures) assert not self.results.failures
self.assertFalse(self.results.errors) assert not self.results.errors

View File

@ -30,16 +30,13 @@ from scrapy.utils.test import get_crawler
from tests.mockserver import PayloadResource, ssl_context_factory from tests.mockserver import PayloadResource, ssl_context_factory
class SlotTest(unittest.TestCase): class TestSlot:
def test_repr(self): def test_repr(self):
slot = Slot(concurrency=8, delay=0.1, randomize_delay=True) slot = Slot(concurrency=8, delay=0.1, randomize_delay=True)
self.assertEqual( assert repr(slot) == "Slot(concurrency=8, delay=0.10, randomize_delay=True)"
repr(slot),
"Slot(concurrency=8, delay=0.10, randomize_delay=True)",
)
class ContextFactoryBaseTestCase(unittest.TestCase): class TestContextFactoryBase(unittest.TestCase):
context_factory = None context_factory = None
def _listen(self, site): def _listen(self, site):
@ -90,7 +87,7 @@ class ContextFactoryBaseTestCase(unittest.TestCase):
return await maybe_deferred_to_future(d) return await maybe_deferred_to_future(d)
class ContextFactoryTestCase(ContextFactoryBaseTestCase): class TestContextFactory(TestContextFactoryBase):
@deferred_f_from_coro_f @deferred_f_from_coro_f
async def testPayload(self): async def testPayload(self):
s = "0123456789" * 10 s = "0123456789" * 10
@ -100,7 +97,7 @@ class ContextFactoryTestCase(ContextFactoryBaseTestCase):
body = await self.get_page( body = await self.get_page(
self.getURL("payload"), client_context_factory, body=s self.getURL("payload"), client_context_factory, body=s
) )
self.assertEqual(body, to_bytes(s)) assert body == to_bytes(s)
def test_override_getContext(self): def test_override_getContext(self):
class MyFactory(ScrapyClientContextFactory): class MyFactory(ScrapyClientContextFactory):
@ -112,14 +109,14 @@ class ContextFactoryTestCase(ContextFactoryBaseTestCase):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
MyFactory() MyFactory()
self.assertEqual(len(w), 1) assert len(w) == 1
self.assertIn( assert (
"Overriding ScrapyClientContextFactory.getContext() is deprecated", "Overriding ScrapyClientContextFactory.getContext() is deprecated"
str(w[0].message), in str(w[0].message)
) )
class ContextFactoryTLSMethodTestCase(ContextFactoryBaseTestCase): class TestContextFactoryTLSMethod(TestContextFactoryBase):
async def _assert_factory_works( async def _assert_factory_works(
self, client_context_factory: ScrapyClientContextFactory self, client_context_factory: ScrapyClientContextFactory
) -> None: ) -> None:
@ -127,7 +124,7 @@ class ContextFactoryTLSMethodTestCase(ContextFactoryBaseTestCase):
body = await self.get_page( body = await self.get_page(
self.getURL("payload"), client_context_factory, body=s self.getURL("payload"), client_context_factory, body=s
) )
self.assertEqual(body, to_bytes(s)) assert body == to_bytes(s)
@deferred_f_from_coro_f @deferred_f_from_coro_f
async def test_setting_default(self): async def test_setting_default(self):

View File

@ -55,7 +55,7 @@ from tests.spiders import (
) )
class CrawlTestCase(TestCase): class TestCrawl(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.mockserver = MockServer() cls.mockserver = MockServer()
@ -69,7 +69,7 @@ class CrawlTestCase(TestCase):
def test_follow_all(self): def test_follow_all(self):
crawler = get_crawler(FollowAllSpider) crawler = get_crawler(FollowAllSpider)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertEqual(len(crawler.spider.urls_visited), 11) # 10 + start_url assert len(crawler.spider.urls_visited) == 11 # 10 + start_url
@defer.inlineCallbacks @defer.inlineCallbacks
def test_fixed_delay(self): def test_fixed_delay(self):
@ -94,9 +94,7 @@ class CrawlTestCase(TestCase):
times = crawler.spider.times times = crawler.spider.times
total_time = times[-1] - times[0] total_time = times[-1] - times[0]
average = total_time / (len(times) - 1) average = total_time / (len(times) - 1)
self.assertTrue( assert average > delay * tolerance, f"download delay too small: {average}"
average > delay * tolerance, f"download delay too small: {average}"
)
# Ensure that the same test parameters would cause a failure if no # Ensure that the same test parameters would cause a failure if no
# download delay is set. Otherwise, it means we are using a combination # download delay is set. Otherwise, it means we are using a combination
@ -108,34 +106,32 @@ class CrawlTestCase(TestCase):
times = crawler.spider.times times = crawler.spider.times
total_time = times[-1] - times[0] total_time = times[-1] - times[0]
average = total_time / (len(times) - 1) average = total_time / (len(times) - 1)
self.assertFalse( assert average <= delay / tolerance, "test total or delay values are too small"
average > delay / tolerance, "test total or delay values are too small"
)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_timeout_success(self): def test_timeout_success(self):
crawler = get_crawler(DelaySpider) crawler = get_crawler(DelaySpider)
yield crawler.crawl(n=0.5, mockserver=self.mockserver) yield crawler.crawl(n=0.5, mockserver=self.mockserver)
self.assertTrue(crawler.spider.t1 > 0) assert crawler.spider.t1 > 0
self.assertTrue(crawler.spider.t2 > 0) assert crawler.spider.t2 > 0
self.assertTrue(crawler.spider.t2 > crawler.spider.t1) assert crawler.spider.t2 > crawler.spider.t1
@defer.inlineCallbacks @defer.inlineCallbacks
def test_timeout_failure(self): def test_timeout_failure(self):
crawler = get_crawler(DelaySpider, {"DOWNLOAD_TIMEOUT": 0.35}) crawler = get_crawler(DelaySpider, {"DOWNLOAD_TIMEOUT": 0.35})
yield crawler.crawl(n=0.5, mockserver=self.mockserver) yield crawler.crawl(n=0.5, mockserver=self.mockserver)
self.assertTrue(crawler.spider.t1 > 0) assert crawler.spider.t1 > 0
self.assertTrue(crawler.spider.t2 == 0) assert crawler.spider.t2 == 0
self.assertTrue(crawler.spider.t2_err > 0) assert crawler.spider.t2_err > 0
self.assertTrue(crawler.spider.t2_err > crawler.spider.t1) assert crawler.spider.t2_err > crawler.spider.t1
# server hangs after receiving response headers # server hangs after receiving response headers
crawler = get_crawler(DelaySpider, {"DOWNLOAD_TIMEOUT": 0.35}) crawler = get_crawler(DelaySpider, {"DOWNLOAD_TIMEOUT": 0.35})
yield crawler.crawl(n=0.5, b=1, mockserver=self.mockserver) yield crawler.crawl(n=0.5, b=1, mockserver=self.mockserver)
self.assertTrue(crawler.spider.t1 > 0) assert crawler.spider.t1 > 0
self.assertTrue(crawler.spider.t2 == 0) assert crawler.spider.t2 == 0
self.assertTrue(crawler.spider.t2_err > 0) assert crawler.spider.t2_err > 0
self.assertTrue(crawler.spider.t2_err > crawler.spider.t1) assert crawler.spider.t2_err > crawler.spider.t1
@defer.inlineCallbacks @defer.inlineCallbacks
def test_retry_503(self): def test_retry_503(self):
@ -173,10 +169,10 @@ class CrawlTestCase(TestCase):
crawler = get_crawler(BrokenStartRequestsSpider) crawler = get_crawler(BrokenStartRequestsSpider)
yield crawler.crawl(fail_before_yield=1, mockserver=self.mockserver) yield crawler.crawl(fail_before_yield=1, mockserver=self.mockserver)
self.assertEqual(len(log.records), 1) assert len(log.records) == 1
record = log.records[0] record = log.records[0]
self.assertIsNotNone(record.exc_info) assert record.exc_info is not None
self.assertIs(record.exc_info[0], ZeroDivisionError) assert record.exc_info[0] is ZeroDivisionError
@defer.inlineCallbacks @defer.inlineCallbacks
def test_start_requests_bug_yielding(self): def test_start_requests_bug_yielding(self):
@ -184,10 +180,10 @@ class CrawlTestCase(TestCase):
crawler = get_crawler(BrokenStartRequestsSpider) crawler = get_crawler(BrokenStartRequestsSpider)
yield crawler.crawl(fail_yielding=1, mockserver=self.mockserver) yield crawler.crawl(fail_yielding=1, mockserver=self.mockserver)
self.assertEqual(len(log.records), 1) assert len(log.records) == 1
record = log.records[0] record = log.records[0]
self.assertIsNotNone(record.exc_info) assert record.exc_info is not None
self.assertIs(record.exc_info[0], ZeroDivisionError) assert record.exc_info[0] is ZeroDivisionError
@defer.inlineCallbacks @defer.inlineCallbacks
def test_start_requests_items(self): def test_start_requests_items(self):
@ -195,7 +191,7 @@ class CrawlTestCase(TestCase):
crawler = get_crawler(StartRequestsItemSpider) crawler = get_crawler(StartRequestsItemSpider)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertEqual(len(log.records), 0) assert len(log.records) == 0
@defer.inlineCallbacks @defer.inlineCallbacks
def test_start_requests_unsupported_output(self): def test_start_requests_unsupported_output(self):
@ -203,23 +199,18 @@ class CrawlTestCase(TestCase):
crawler = get_crawler(StartRequestsGoodAndBadOutput) crawler = get_crawler(StartRequestsGoodAndBadOutput)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertEqual(len(log.records), 2) assert len(log.records) == 2
self.assertEqual( assert log.records[0].msg == (
log.records[0].msg, "Got 'data:,b' among start requests. Only requests and items "
( "are supported. It will be ignored."
"Got 'data:,b' among start requests. Only requests and items "
"are supported. It will be ignored."
),
) )
self.assertTrue( assert re.match(
re.match( (
( r"^Got <object object at 0x[0-9a-fA-F]+> among start "
r"^Got <object object at 0x[0-9a-fA-F]+> among start " r"requests\. Only requests and items are supported\. It "
r"requests\. Only requests and items are supported\. It " r"will be ignored\.$"
r"will be ignored\.$" ),
), log.records[1].msg,
log.records[1].msg,
)
) )
@defer.inlineCallbacks @defer.inlineCallbacks
@ -227,10 +218,9 @@ class CrawlTestCase(TestCase):
settings = {"CONCURRENT_REQUESTS": 1} settings = {"CONCURRENT_REQUESTS": 1}
crawler = get_crawler(BrokenStartRequestsSpider, settings) crawler = get_crawler(BrokenStartRequestsSpider, settings)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertTrue( assert crawler.spider.seedsseen.index(None) < crawler.spider.seedsseen.index(
crawler.spider.seedsseen.index(None) < crawler.spider.seedsseen.index(99), 99
crawler.spider.seedsseen, ), crawler.spider.seedsseen
)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_start_requests_dupes(self): def test_start_requests_dupes(self):
@ -239,7 +229,7 @@ class CrawlTestCase(TestCase):
yield crawler.crawl( yield crawler.crawl(
dont_filter=True, distinct_urls=2, dupe_factor=3, mockserver=self.mockserver dont_filter=True, distinct_urls=2, dupe_factor=3, mockserver=self.mockserver
) )
self.assertEqual(crawler.spider.visited, 6) assert crawler.spider.visited == 6
crawler = get_crawler(DuplicateStartRequestsSpider, settings) crawler = get_crawler(DuplicateStartRequestsSpider, settings)
yield crawler.crawl( yield crawler.crawl(
@ -248,7 +238,7 @@ class CrawlTestCase(TestCase):
dupe_factor=4, dupe_factor=4,
mockserver=self.mockserver, mockserver=self.mockserver,
) )
self.assertEqual(crawler.spider.visited, 3) assert crawler.spider.visited == 3
@defer.inlineCallbacks @defer.inlineCallbacks
def test_unbounded_response(self): def test_unbounded_response(self):
@ -282,7 +272,7 @@ with multiples lines
yield crawler.crawl( yield crawler.crawl(
self.mockserver.url(f"/raw?{query}"), mockserver=self.mockserver self.mockserver.url(f"/raw?{query}"), mockserver=self.mockserver
) )
self.assertEqual(str(log).count("Got response 200"), 1) assert str(log).count("Got response 200") == 1
@defer.inlineCallbacks @defer.inlineCallbacks
def test_retry_conn_lost(self): def test_retry_conn_lost(self):
@ -305,8 +295,8 @@ with multiples lines
self._assert_retried(log) self._assert_retried(log)
def _assert_retried(self, log): def _assert_retried(self, log):
self.assertEqual(str(log).count("Retrying"), 2) assert str(log).count("Retrying") == 2
self.assertEqual(str(log).count("Gave up retrying"), 1) assert str(log).count("Gave up retrying") == 1
@defer.inlineCallbacks @defer.inlineCallbacks
def test_referer_header(self): def test_referer_header(self):
@ -321,20 +311,20 @@ with multiples lines
crawler = get_crawler(SingleRequestSpider) crawler = get_crawler(SingleRequestSpider)
yield crawler.crawl(seed=req0, mockserver=self.mockserver) yield crawler.crawl(seed=req0, mockserver=self.mockserver)
# basic asserts in case of weird communication errors # basic asserts in case of weird communication errors
self.assertIn("responses", crawler.spider.meta) assert "responses" in crawler.spider.meta
self.assertNotIn("failures", crawler.spider.meta) assert "failures" not in crawler.spider.meta
# start requests doesn't set Referer header # start requests doesn't set Referer header
echo0 = json.loads(to_unicode(crawler.spider.meta["responses"][2].body)) echo0 = json.loads(to_unicode(crawler.spider.meta["responses"][2].body))
self.assertNotIn("Referer", echo0["headers"]) assert "Referer" not in echo0["headers"]
# following request sets Referer to start request url # following request sets Referer to start request url
echo1 = json.loads(to_unicode(crawler.spider.meta["responses"][1].body)) echo1 = json.loads(to_unicode(crawler.spider.meta["responses"][1].body))
self.assertEqual(echo1["headers"].get("Referer"), [req0.url]) assert echo1["headers"].get("Referer") == [req0.url]
# next request avoids Referer header # next request avoids Referer header
echo2 = json.loads(to_unicode(crawler.spider.meta["responses"][2].body)) echo2 = json.loads(to_unicode(crawler.spider.meta["responses"][2].body))
self.assertNotIn("Referer", echo2["headers"]) assert "Referer" not in echo2["headers"]
# last request explicitly sets a Referer header # last request explicitly sets a Referer header
echo3 = json.loads(to_unicode(crawler.spider.meta["responses"][3].body)) echo3 = json.loads(to_unicode(crawler.spider.meta["responses"][3].body))
self.assertEqual(echo3["headers"].get("Referer"), ["http://example.com"]) assert echo3["headers"].get("Referer") == ["http://example.com"]
@defer.inlineCallbacks @defer.inlineCallbacks
def test_engine_status(self): def test_engine_status(self):
@ -349,10 +339,10 @@ with multiples lines
yield crawler.crawl( yield crawler.crawl(
seed=self.mockserver.url("/"), callback_func=cb, mockserver=self.mockserver seed=self.mockserver.url("/"), callback_func=cb, mockserver=self.mockserver
) )
self.assertEqual(len(est), 1, est) assert len(est) == 1, est
s = dict(est[0]) s = dict(est[0])
self.assertEqual(s["engine.spider.name"], crawler.spider.name) assert s["engine.spider.name"] == crawler.spider.name
self.assertEqual(s["len(engine.scraper.slot.active)"], 1) assert s["len(engine.scraper.slot.active)"] == 1
@defer.inlineCallbacks @defer.inlineCallbacks
def test_format_engine_status(self): def test_format_engine_status(self):
@ -367,7 +357,7 @@ with multiples lines
yield crawler.crawl( yield crawler.crawl(
seed=self.mockserver.url("/"), callback_func=cb, mockserver=self.mockserver seed=self.mockserver.url("/"), callback_func=cb, mockserver=self.mockserver
) )
self.assertEqual(len(est), 1, est) assert len(est) == 1, est
est = est[0].split("\n")[2:-2] # remove header & footer est = est[0].split("\n")[2:-2] # remove header & footer
# convert to dict # convert to dict
est = [x.split(":") for x in est] est = [x.split(":") for x in est]
@ -376,8 +366,8 @@ with multiples lines
it = iter(est) it = iter(est)
s = dict(zip(it, it)) s = dict(zip(it, it))
self.assertEqual(s["engine.spider.name"], crawler.spider.name) assert s["engine.spider.name"] == crawler.spider.name
self.assertEqual(s["len(engine.scraper.slot.active)"], "1") assert s["len(engine.scraper.slot.active)"] == "1"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_graceful_crawl_error_handling(self): def test_graceful_crawl_error_handling(self):
@ -398,7 +388,7 @@ with multiples lines
crawler = get_crawler(FaultySpider) crawler = get_crawler(FaultySpider)
yield self.assertFailure(crawler.crawl(mockserver=self.mockserver), TestError) yield self.assertFailure(crawler.crawl(mockserver=self.mockserver), TestError)
self.assertFalse(crawler.crawling) assert not crawler.crawling
@defer.inlineCallbacks @defer.inlineCallbacks
def test_open_spider_error_on_faulty_pipeline(self): def test_open_spider_error_on_faulty_pipeline(self):
@ -414,7 +404,7 @@ with multiples lines
), ),
ZeroDivisionError, ZeroDivisionError,
) )
self.assertFalse(crawler.crawling) assert not crawler.crawling
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlerrunner_accepts_crawler(self): def test_crawlerrunner_accepts_crawler(self):
@ -426,7 +416,7 @@ with multiples lines
self.mockserver.url("/status?n=200"), self.mockserver.url("/status?n=200"),
mockserver=self.mockserver, mockserver=self.mockserver,
) )
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawl_multiple(self): def test_crawl_multiple(self):
@ -446,10 +436,10 @@ with multiples lines
yield runner.join() yield runner.join()
self._assert_retried(log) self._assert_retried(log)
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
class CrawlSpiderTestCase(TestCase): class TestCrawlSpider(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.mockserver = MockServer() cls.mockserver = MockServer()
@ -480,9 +470,9 @@ class CrawlSpiderTestCase(TestCase):
with LogCapture() as log: with LogCapture() as log:
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIn("[parse] status 200 (foo: None)", str(log)) assert "[parse] status 200 (foo: None)" in str(log)
self.assertIn("[parse] status 201 (foo: None)", str(log)) assert "[parse] status 201 (foo: None)" in str(log)
self.assertIn("[parse] status 202 (foo: bar)", str(log)) assert "[parse] status 202 (foo: bar)" in str(log)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_with_async_callback(self): def test_crawlspider_with_async_callback(self):
@ -490,9 +480,9 @@ class CrawlSpiderTestCase(TestCase):
with LogCapture() as log: with LogCapture() as log:
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIn("[parse_async] status 200 (foo: None)", str(log)) assert "[parse_async] status 200 (foo: None)" in str(log)
self.assertIn("[parse_async] status 201 (foo: None)", str(log)) assert "[parse_async] status 201 (foo: None)" in str(log)
self.assertIn("[parse_async] status 202 (foo: bar)", str(log)) assert "[parse_async] status 202 (foo: bar)" in str(log)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_with_async_generator_callback(self): def test_crawlspider_with_async_generator_callback(self):
@ -500,9 +490,9 @@ class CrawlSpiderTestCase(TestCase):
with LogCapture() as log: with LogCapture() as log:
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIn("[parse_async_gen] status 200 (foo: None)", str(log)) assert "[parse_async_gen] status 200 (foo: None)" in str(log)
self.assertIn("[parse_async_gen] status 201 (foo: None)", str(log)) assert "[parse_async_gen] status 201 (foo: None)" in str(log)
self.assertIn("[parse_async_gen] status 202 (foo: bar)", str(log)) assert "[parse_async_gen] status 202 (foo: bar)" in str(log)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_with_errback(self): def test_crawlspider_with_errback(self):
@ -510,12 +500,12 @@ class CrawlSpiderTestCase(TestCase):
with LogCapture() as log: with LogCapture() as log:
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIn("[parse] status 200 (foo: None)", str(log)) assert "[parse] status 200 (foo: None)" in str(log)
self.assertIn("[parse] status 201 (foo: None)", str(log)) assert "[parse] status 201 (foo: None)" in str(log)
self.assertIn("[parse] status 202 (foo: bar)", str(log)) assert "[parse] status 202 (foo: bar)" in str(log)
self.assertIn("[errback] status 404", str(log)) assert "[errback] status 404" in str(log)
self.assertIn("[errback] status 500", str(log)) assert "[errback] status 500" in str(log)
self.assertIn("[errback] status 501", str(log)) assert "[errback] status 501" in str(log)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawlspider_process_request_cb_kwargs(self): def test_crawlspider_process_request_cb_kwargs(self):
@ -523,9 +513,9 @@ class CrawlSpiderTestCase(TestCase):
with LogCapture() as log: with LogCapture() as log:
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIn("[parse] status 200 (foo: process_request)", str(log)) assert "[parse] status 200 (foo: process_request)" in str(log)
self.assertIn("[parse] status 201 (foo: process_request)", str(log)) assert "[parse] status 201 (foo: process_request)" in str(log)
self.assertIn("[parse] status 202 (foo: bar)", str(log)) assert "[parse] status 202 (foo: bar)" in str(log)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_parse(self): def test_async_def_parse(self):
@ -534,7 +524,7 @@ class CrawlSpiderTestCase(TestCase):
yield crawler.crawl( yield crawler.crawl(
self.mockserver.url("/status?n=200"), mockserver=self.mockserver self.mockserver.url("/status?n=200"), mockserver=self.mockserver
) )
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
@ -549,15 +539,15 @@ class CrawlSpiderTestCase(TestCase):
yield crawler.crawl( yield crawler.crawl(
self.mockserver.url("/status?n=200"), mockserver=self.mockserver self.mockserver.url("/status?n=200"), mockserver=self.mockserver
) )
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncio_parse_items_list(self): def test_async_def_asyncio_parse_items_list(self):
log, items, _ = yield self._run_spider(AsyncDefAsyncioReturnSpider) log, items, _ = yield self._run_spider(AsyncDefAsyncioReturnSpider)
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
self.assertIn({"id": 1}, items) assert {"id": 1} in items
self.assertIn({"id": 2}, items) assert {"id": 2} in items
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
@ -573,81 +563,81 @@ class CrawlSpiderTestCase(TestCase):
yield crawler.crawl( yield crawler.crawl(
self.mockserver.url("/status?n=200"), mockserver=self.mockserver self.mockserver.url("/status?n=200"), mockserver=self.mockserver
) )
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
self.assertIn({"foo": 42}, items) assert {"foo": 42} in items
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncgen_parse(self): def test_async_def_asyncgen_parse(self):
log, _, stats = yield self._run_spider(AsyncDefAsyncioGenSpider) log, _, stats = yield self._run_spider(AsyncDefAsyncioGenSpider)
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
itemcount = stats.get_value("item_scraped_count") itemcount = stats.get_value("item_scraped_count")
self.assertEqual(itemcount, 1) assert itemcount == 1
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncgen_parse_loop(self): def test_async_def_asyncgen_parse_loop(self):
log, items, stats = yield self._run_spider(AsyncDefAsyncioGenLoopSpider) log, items, stats = yield self._run_spider(AsyncDefAsyncioGenLoopSpider)
self.assertIn("Got response 200", str(log)) assert "Got response 200" in str(log)
itemcount = stats.get_value("item_scraped_count") itemcount = stats.get_value("item_scraped_count")
self.assertEqual(itemcount, 10) assert itemcount == 10
for i in range(10): for i in range(10):
self.assertIn({"foo": i}, items) assert {"foo": i} in items
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncgen_parse_exc(self): def test_async_def_asyncgen_parse_exc(self):
log, items, stats = yield self._run_spider(AsyncDefAsyncioGenExcSpider) log, items, stats = yield self._run_spider(AsyncDefAsyncioGenExcSpider)
log = str(log) log = str(log)
self.assertIn("Spider error processing", log) assert "Spider error processing" in log
self.assertIn("ValueError", log) assert "ValueError" in log
itemcount = stats.get_value("item_scraped_count") itemcount = stats.get_value("item_scraped_count")
self.assertEqual(itemcount, 7) assert itemcount == 7
for i in range(7): for i in range(7):
self.assertIn({"foo": i}, items) assert {"foo": i} in items
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncgen_parse_complex(self): def test_async_def_asyncgen_parse_complex(self):
_, items, stats = yield self._run_spider(AsyncDefAsyncioGenComplexSpider) _, items, stats = yield self._run_spider(AsyncDefAsyncioGenComplexSpider)
itemcount = stats.get_value("item_scraped_count") itemcount = stats.get_value("item_scraped_count")
self.assertEqual(itemcount, 156) assert itemcount == 156
# some random items # some random items
for i in [1, 4, 21, 22, 207, 311]: for i in [1, 4, 21, 22, 207, 311]:
self.assertIn({"index": i}, items) assert {"index": i} in items
for i in [10, 30, 122]: for i in [10, 30, 122]:
self.assertIn({"index2": i}, items) assert {"index2": i} in items
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_asyncio_parse_reqs_list(self): def test_async_def_asyncio_parse_reqs_list(self):
log, *_ = yield self._run_spider(AsyncDefAsyncioReqsReturnSpider) log, *_ = yield self._run_spider(AsyncDefAsyncioReqsReturnSpider)
for req_id in range(3): for req_id in range(3):
self.assertIn(f"Got response 200, req_id {req_id}", str(log)) assert f"Got response 200, req_id {req_id}" in str(log)
@pytest.mark.only_not_asyncio @pytest.mark.only_not_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_deferred_direct(self): def test_async_def_deferred_direct(self):
_, items, _ = yield self._run_spider(AsyncDefDeferredDirectSpider) _, items, _ = yield self._run_spider(AsyncDefDeferredDirectSpider)
self.assertEqual(items, [{"code": 200}]) assert items == [{"code": 200}]
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_deferred_wrapped(self): def test_async_def_deferred_wrapped(self):
log, items, _ = yield self._run_spider(AsyncDefDeferredWrappedSpider) log, items, _ = yield self._run_spider(AsyncDefDeferredWrappedSpider)
self.assertEqual(items, [{"code": 200}]) assert items == [{"code": 200}]
@defer.inlineCallbacks @defer.inlineCallbacks
def test_async_def_deferred_maybe_wrapped(self): def test_async_def_deferred_maybe_wrapped(self):
_, items, _ = yield self._run_spider(AsyncDefDeferredMaybeWrappedSpider) _, items, _ = yield self._run_spider(AsyncDefDeferredMaybeWrappedSpider)
self.assertEqual(items, [{"code": 200}]) assert items == [{"code": 200}]
@defer.inlineCallbacks @defer.inlineCallbacks
def test_response_ssl_certificate_none(self): def test_response_ssl_certificate_none(self):
crawler = get_crawler(SingleRequestSpider) crawler = get_crawler(SingleRequestSpider)
url = self.mockserver.url("/echo?body=test", is_secure=False) url = self.mockserver.url("/echo?body=test", is_secure=False)
yield crawler.crawl(seed=url, mockserver=self.mockserver) yield crawler.crawl(seed=url, mockserver=self.mockserver)
self.assertIsNone(crawler.spider.meta["responses"][0].certificate) assert crawler.spider.meta["responses"][0].certificate is None
@defer.inlineCallbacks @defer.inlineCallbacks
def test_response_ssl_certificate(self): def test_response_ssl_certificate(self):
@ -655,9 +645,9 @@ class CrawlSpiderTestCase(TestCase):
url = self.mockserver.url("/echo?body=test", is_secure=True) url = self.mockserver.url("/echo?body=test", is_secure=True)
yield crawler.crawl(seed=url, mockserver=self.mockserver) yield crawler.crawl(seed=url, mockserver=self.mockserver)
cert = crawler.spider.meta["responses"][0].certificate cert = crawler.spider.meta["responses"][0].certificate
self.assertIsInstance(cert, Certificate) assert isinstance(cert, Certificate)
self.assertEqual(cert.getSubject().commonName, b"localhost") assert cert.getSubject().commonName == b"localhost"
self.assertEqual(cert.getIssuer().commonName, b"localhost") assert cert.getIssuer().commonName == b"localhost"
@pytest.mark.xfail( @pytest.mark.xfail(
reason="Responses with no body return early and contain no certificate" reason="Responses with no body return early and contain no certificate"
@ -668,9 +658,9 @@ class CrawlSpiderTestCase(TestCase):
url = self.mockserver.url("/status?n=200", is_secure=True) url = self.mockserver.url("/status?n=200", is_secure=True)
yield crawler.crawl(seed=url, mockserver=self.mockserver) yield crawler.crawl(seed=url, mockserver=self.mockserver)
cert = crawler.spider.meta["responses"][0].certificate cert = crawler.spider.meta["responses"][0].certificate
self.assertIsInstance(cert, Certificate) assert isinstance(cert, Certificate)
self.assertEqual(cert.getSubject().commonName, b"localhost") assert cert.getSubject().commonName == b"localhost"
self.assertEqual(cert.getIssuer().commonName, b"localhost") assert cert.getIssuer().commonName == b"localhost"
@defer.inlineCallbacks @defer.inlineCallbacks
def test_dns_server_ip_address_none(self): def test_dns_server_ip_address_none(self):
@ -678,7 +668,7 @@ class CrawlSpiderTestCase(TestCase):
url = self.mockserver.url("/status?n=200") url = self.mockserver.url("/status?n=200")
yield crawler.crawl(seed=url, mockserver=self.mockserver) yield crawler.crawl(seed=url, mockserver=self.mockserver)
ip_address = crawler.spider.meta["responses"][0].ip_address ip_address = crawler.spider.meta["responses"][0].ip_address
self.assertIsNone(ip_address) assert ip_address is None
@defer.inlineCallbacks @defer.inlineCallbacks
def test_dns_server_ip_address(self): def test_dns_server_ip_address(self):
@ -687,61 +677,57 @@ class CrawlSpiderTestCase(TestCase):
expected_netloc, _ = urlparse(url).netloc.split(":") expected_netloc, _ = urlparse(url).netloc.split(":")
yield crawler.crawl(seed=url, mockserver=self.mockserver) yield crawler.crawl(seed=url, mockserver=self.mockserver)
ip_address = crawler.spider.meta["responses"][0].ip_address ip_address = crawler.spider.meta["responses"][0].ip_address
self.assertIsInstance(ip_address, IPv4Address) assert isinstance(ip_address, IPv4Address)
self.assertEqual(str(ip_address), gethostbyname(expected_netloc)) assert str(ip_address) == gethostbyname(expected_netloc)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_bytes_received_stop_download_callback(self): def test_bytes_received_stop_download_callback(self):
crawler = get_crawler(BytesReceivedCallbackSpider) crawler = get_crawler(BytesReceivedCallbackSpider)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIsNone(crawler.spider.meta.get("failure")) assert crawler.spider.meta.get("failure") is None
self.assertIsInstance(crawler.spider.meta["response"], Response) assert isinstance(crawler.spider.meta["response"], Response)
self.assertEqual( assert crawler.spider.meta["response"].body == crawler.spider.meta.get(
crawler.spider.meta["response"].body, "bytes_received"
crawler.spider.meta.get("bytes_received"),
) )
self.assertLess( assert (
len(crawler.spider.meta["response"].body), len(crawler.spider.meta["response"].body)
crawler.spider.full_response_length, < crawler.spider.full_response_length
) )
@defer.inlineCallbacks @defer.inlineCallbacks
def test_bytes_received_stop_download_errback(self): def test_bytes_received_stop_download_errback(self):
crawler = get_crawler(BytesReceivedErrbackSpider) crawler = get_crawler(BytesReceivedErrbackSpider)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIsNone(crawler.spider.meta.get("response")) assert crawler.spider.meta.get("response") is None
self.assertIsInstance(crawler.spider.meta["failure"], Failure) assert isinstance(crawler.spider.meta["failure"], Failure)
self.assertIsInstance(crawler.spider.meta["failure"].value, StopDownload) assert isinstance(crawler.spider.meta["failure"].value, StopDownload)
self.assertIsInstance(crawler.spider.meta["failure"].value.response, Response) assert isinstance(crawler.spider.meta["failure"].value.response, Response)
self.assertEqual( assert crawler.spider.meta[
crawler.spider.meta["failure"].value.response.body, "failure"
crawler.spider.meta.get("bytes_received"), ].value.response.body == crawler.spider.meta.get("bytes_received")
) assert (
self.assertLess( len(crawler.spider.meta["failure"].value.response.body)
len(crawler.spider.meta["failure"].value.response.body), < crawler.spider.full_response_length
crawler.spider.full_response_length,
) )
@defer.inlineCallbacks @defer.inlineCallbacks
def test_headers_received_stop_download_callback(self): def test_headers_received_stop_download_callback(self):
crawler = get_crawler(HeadersReceivedCallbackSpider) crawler = get_crawler(HeadersReceivedCallbackSpider)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIsNone(crawler.spider.meta.get("failure")) assert crawler.spider.meta.get("failure") is None
self.assertIsInstance(crawler.spider.meta["response"], Response) assert isinstance(crawler.spider.meta["response"], Response)
self.assertEqual( assert crawler.spider.meta["response"].headers == crawler.spider.meta.get(
crawler.spider.meta["response"].headers, "headers_received"
crawler.spider.meta.get("headers_received"),
) )
@defer.inlineCallbacks @defer.inlineCallbacks
def test_headers_received_stop_download_errback(self): def test_headers_received_stop_download_errback(self):
crawler = get_crawler(HeadersReceivedErrbackSpider) crawler = get_crawler(HeadersReceivedErrbackSpider)
yield crawler.crawl(mockserver=self.mockserver) yield crawler.crawl(mockserver=self.mockserver)
self.assertIsNone(crawler.spider.meta.get("response")) assert crawler.spider.meta.get("response") is None
self.assertIsInstance(crawler.spider.meta["failure"], Failure) assert isinstance(crawler.spider.meta["failure"], Failure)
self.assertIsInstance(crawler.spider.meta["failure"].value, StopDownload) assert isinstance(crawler.spider.meta["failure"].value, StopDownload)
self.assertIsInstance(crawler.spider.meta["failure"].value.response, Response) assert isinstance(crawler.spider.meta["failure"].value.response, Response)
self.assertEqual( assert crawler.spider.meta[
crawler.spider.meta["failure"].value.response.headers, "failure"
crawler.spider.meta.get("headers_received"), ].value.response.headers == crawler.spider.meta.get("headers_received")
)

View File

@ -39,13 +39,13 @@ def get_raw_crawler(spidercls=None, settings_dict=None):
return Crawler(spidercls or DefaultSpider, settings) return Crawler(spidercls or DefaultSpider, settings)
class BaseCrawlerTest(unittest.TestCase): class TestBaseCrawler(unittest.TestCase):
def assertOptionIsDefault(self, settings, key): def assertOptionIsDefault(self, settings, key):
self.assertIsInstance(settings, Settings) assert isinstance(settings, Settings)
self.assertEqual(settings[key], getattr(default_settings, key)) assert settings[key] == getattr(default_settings, key)
class CrawlerTestCase(BaseCrawlerTest): class TestCrawler(TestBaseCrawler):
def test_populate_spidercls_settings(self): def test_populate_spidercls_settings(self):
spider_settings = {"TEST1": "spider", "TEST2": "spider"} spider_settings = {"TEST1": "spider", "TEST2": "spider"}
project_settings = {**BASE_SETTINGS, "TEST1": "project", "TEST3": "project"} project_settings = {**BASE_SETTINGS, "TEST1": "project", "TEST3": "project"}
@ -58,16 +58,16 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = Crawler(CustomSettingsSpider, settings) crawler = Crawler(CustomSettingsSpider, settings)
crawler._apply_settings() crawler._apply_settings()
self.assertEqual(crawler.settings.get("TEST1"), "spider") assert crawler.settings.get("TEST1") == "spider"
self.assertEqual(crawler.settings.get("TEST2"), "spider") assert crawler.settings.get("TEST2") == "spider"
self.assertEqual(crawler.settings.get("TEST3"), "project") assert crawler.settings.get("TEST3") == "project"
self.assertFalse(settings.frozen) assert not settings.frozen
self.assertTrue(crawler.settings.frozen) assert crawler.settings.frozen
def test_crawler_accepts_dict(self): def test_crawler_accepts_dict(self):
crawler = get_crawler(DefaultSpider, {"foo": "bar"}) crawler = get_crawler(DefaultSpider, {"foo": "bar"})
self.assertEqual(crawler.settings["foo"], "bar") assert crawler.settings["foo"] == "bar"
self.assertOptionIsDefault(crawler.settings, "RETRY_ENABLED") self.assertOptionIsDefault(crawler.settings, "RETRY_ENABLED")
def test_crawler_accepts_None(self): def test_crawler_accepts_None(self):
@ -107,23 +107,23 @@ class CrawlerTestCase(BaseCrawlerTest):
}, },
} }
crawler = get_crawler(settings_dict=settings) crawler = get_crawler(settings_dict=settings)
self.assertEqual(len(TrackingAddon.instances), 1) assert len(TrackingAddon.instances) == 1
expected = TrackingAddon.instances[-1] expected = TrackingAddon.instances[-1]
addon = crawler.get_addon(TrackingAddon) addon = crawler.get_addon(TrackingAddon)
self.assertEqual(addon, expected) assert addon == expected
addon = crawler.get_addon(DefaultSpider) addon = crawler.get_addon(DefaultSpider)
self.assertIsNone(addon) assert addon is None
addon = crawler.get_addon(ParentAddon) addon = crawler.get_addon(ParentAddon)
self.assertEqual(addon, expected) assert addon == expected
class ChildAddon(TrackingAddon): class ChildAddon(TrackingAddon):
pass pass
addon = crawler.get_addon(ChildAddon) addon = crawler.get_addon(ChildAddon)
self.assertIsNone(addon) assert addon is None
@inlineCallbacks @inlineCallbacks
def test_get_downloader_middleware(self): def test_get_downloader_middleware(self):
@ -162,18 +162,18 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = TrackingDownloaderMiddleware MySpider.cls = TrackingDownloaderMiddleware
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(len(TrackingDownloaderMiddleware.instances), 1) assert len(TrackingDownloaderMiddleware.instances) == 1
self.assertEqual(MySpider.result, TrackingDownloaderMiddleware.instances[-1]) assert MySpider.result == TrackingDownloaderMiddleware.instances[-1]
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = DefaultSpider MySpider.cls = DefaultSpider
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ParentDownloaderMiddleware MySpider.cls = ParentDownloaderMiddleware
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(MySpider.result, TrackingDownloaderMiddleware.instances[-1]) assert MySpider.result == TrackingDownloaderMiddleware.instances[-1]
class ChildDownloaderMiddleware(TrackingDownloaderMiddleware): class ChildDownloaderMiddleware(TrackingDownloaderMiddleware):
pass pass
@ -181,7 +181,7 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ChildDownloaderMiddleware MySpider.cls = ChildDownloaderMiddleware
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
def test_get_downloader_middleware_not_crawling(self): def test_get_downloader_middleware_not_crawling(self):
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS) crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
@ -242,18 +242,18 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = TrackingExtension MySpider.cls = TrackingExtension
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(len(TrackingExtension.instances), 1) assert len(TrackingExtension.instances) == 1
self.assertEqual(MySpider.result, TrackingExtension.instances[-1]) assert MySpider.result == TrackingExtension.instances[-1]
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = DefaultSpider MySpider.cls = DefaultSpider
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ParentExtension MySpider.cls = ParentExtension
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(MySpider.result, TrackingExtension.instances[-1]) assert MySpider.result == TrackingExtension.instances[-1]
class ChildExtension(TrackingExtension): class ChildExtension(TrackingExtension):
pass pass
@ -261,7 +261,7 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ChildExtension MySpider.cls = ChildExtension
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
def test_get_extension_not_crawling(self): def test_get_extension_not_crawling(self):
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS) crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
@ -322,18 +322,18 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = TrackingItemPipeline MySpider.cls = TrackingItemPipeline
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(len(TrackingItemPipeline.instances), 1) assert len(TrackingItemPipeline.instances) == 1
self.assertEqual(MySpider.result, TrackingItemPipeline.instances[-1]) assert MySpider.result == TrackingItemPipeline.instances[-1]
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = DefaultSpider MySpider.cls = DefaultSpider
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ParentItemPipeline MySpider.cls = ParentItemPipeline
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(MySpider.result, TrackingItemPipeline.instances[-1]) assert MySpider.result == TrackingItemPipeline.instances[-1]
class ChildItemPipeline(TrackingItemPipeline): class ChildItemPipeline(TrackingItemPipeline):
pass pass
@ -341,7 +341,7 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ChildItemPipeline MySpider.cls = ChildItemPipeline
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
def test_get_item_pipeline_not_crawling(self): def test_get_item_pipeline_not_crawling(self):
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS) crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
@ -402,18 +402,18 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = TrackingSpiderMiddleware MySpider.cls = TrackingSpiderMiddleware
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(len(TrackingSpiderMiddleware.instances), 1) assert len(TrackingSpiderMiddleware.instances) == 1
self.assertEqual(MySpider.result, TrackingSpiderMiddleware.instances[-1]) assert MySpider.result == TrackingSpiderMiddleware.instances[-1]
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = DefaultSpider MySpider.cls = DefaultSpider
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ParentSpiderMiddleware MySpider.cls = ParentSpiderMiddleware
yield crawler.crawl() yield crawler.crawl()
self.assertEqual(MySpider.result, TrackingSpiderMiddleware.instances[-1]) assert MySpider.result == TrackingSpiderMiddleware.instances[-1]
class ChildSpiderMiddleware(TrackingSpiderMiddleware): class ChildSpiderMiddleware(TrackingSpiderMiddleware):
pass pass
@ -421,7 +421,7 @@ class CrawlerTestCase(BaseCrawlerTest):
crawler = get_raw_crawler(MySpider, settings) crawler = get_raw_crawler(MySpider, settings)
MySpider.cls = ChildSpiderMiddleware MySpider.cls = ChildSpiderMiddleware
yield crawler.crawl() yield crawler.crawl()
self.assertIsNone(MySpider.result) assert MySpider.result is None
def test_get_spider_middleware_not_crawling(self): def test_get_spider_middleware_not_crawling(self):
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS) crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
@ -446,7 +446,7 @@ class CrawlerTestCase(BaseCrawlerTest):
yield crawler.crawl() yield crawler.crawl()
class SpiderSettingsTestCase(unittest.TestCase): class TestSpiderSettings:
def test_spider_custom_settings(self): def test_spider_custom_settings(self):
class MySpider(scrapy.Spider): class MySpider(scrapy.Spider):
name = "spider" name = "spider"
@ -454,10 +454,10 @@ class SpiderSettingsTestCase(unittest.TestCase):
crawler = get_crawler(MySpider) crawler = get_crawler(MySpider)
enabled_exts = [e.__class__ for e in crawler.extensions.middlewares] enabled_exts = [e.__class__ for e in crawler.extensions.middlewares]
self.assertIn(AutoThrottle, enabled_exts) assert AutoThrottle in enabled_exts
class CrawlerLoggingTestCase(unittest.TestCase): class TestCrawlerLogging:
def test_no_root_handler_installed(self): def test_no_root_handler_installed(self):
handler = get_scrapy_root_handler() handler = get_scrapy_root_handler()
if handler is not None: if handler is not None:
@ -469,8 +469,8 @@ class CrawlerLoggingTestCase(unittest.TestCase):
get_crawler(MySpider) get_crawler(MySpider)
assert get_scrapy_root_handler() is None assert get_scrapy_root_handler() is None
def test_spider_custom_settings_log_level(self): def test_spider_custom_settings_log_level(self, tmp_path):
log_file = Path(self.mktemp()) log_file = Path(tmp_path, "log.txt")
log_file.write_text("previous message\n", encoding="utf-8") log_file.write_text("previous message\n", encoding="utf-8")
class MySpider(scrapy.Spider): class MySpider(scrapy.Spider):
@ -481,9 +481,9 @@ class CrawlerLoggingTestCase(unittest.TestCase):
} }
configure_logging() configure_logging()
self.assertEqual(get_scrapy_root_handler().level, logging.DEBUG) assert get_scrapy_root_handler().level == logging.DEBUG
crawler = get_crawler(MySpider) crawler = get_crawler(MySpider)
self.assertEqual(get_scrapy_root_handler().level, logging.INFO) assert get_scrapy_root_handler().level == logging.INFO
info_count = crawler.stats.get_value("log_count/INFO") info_count = crawler.stats.get_value("log_count/INFO")
logging.debug("debug message") logging.debug("debug message")
logging.info("info message") logging.info("info message")
@ -492,18 +492,18 @@ class CrawlerLoggingTestCase(unittest.TestCase):
logged = log_file.read_text(encoding="utf-8") logged = log_file.read_text(encoding="utf-8")
self.assertIn("previous message", logged) assert "previous message" in logged
self.assertNotIn("debug message", logged) assert "debug message" not in logged
self.assertIn("info message", logged) assert "info message" in logged
self.assertIn("warning message", logged) assert "warning message" in logged
self.assertIn("error message", logged) assert "error message" in logged
self.assertEqual(crawler.stats.get_value("log_count/ERROR"), 1) assert crawler.stats.get_value("log_count/ERROR") == 1
self.assertEqual(crawler.stats.get_value("log_count/WARNING"), 1) assert crawler.stats.get_value("log_count/WARNING") == 1
self.assertEqual(crawler.stats.get_value("log_count/INFO") - info_count, 1) assert crawler.stats.get_value("log_count/INFO") - info_count == 1
self.assertEqual(crawler.stats.get_value("log_count/DEBUG", 0), 0) assert crawler.stats.get_value("log_count/DEBUG", 0) == 0
def test_spider_custom_settings_log_append(self): def test_spider_custom_settings_log_append(self, tmp_path):
log_file = Path(self.mktemp()) log_file = Path(tmp_path, "log.txt")
log_file.write_text("previous message\n", encoding="utf-8") log_file.write_text("previous message\n", encoding="utf-8")
class MySpider(scrapy.Spider): class MySpider(scrapy.Spider):
@ -519,8 +519,8 @@ class CrawlerLoggingTestCase(unittest.TestCase):
logged = log_file.read_text(encoding="utf-8") logged = log_file.read_text(encoding="utf-8")
self.assertNotIn("previous message", logged) assert "previous message" not in logged
self.assertIn("debug message", logged) assert "debug message" in logged
class SpiderLoaderWithWrongInterface: class SpiderLoaderWithWrongInterface:
@ -532,7 +532,7 @@ class CustomSpiderLoader(SpiderLoader):
pass pass
class CrawlerRunnerTestCase(BaseCrawlerTest): class TestCrawlerRunner(TestBaseCrawler):
def test_spider_manager_verify_interface(self): def test_spider_manager_verify_interface(self):
settings = Settings( settings = Settings(
{ {
@ -544,7 +544,7 @@ class CrawlerRunnerTestCase(BaseCrawlerTest):
def test_crawler_runner_accepts_dict(self): def test_crawler_runner_accepts_dict(self):
runner = CrawlerRunner({"foo": "bar"}) runner = CrawlerRunner({"foo": "bar"})
self.assertEqual(runner.settings["foo"], "bar") assert runner.settings["foo"] == "bar"
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED") self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
def test_crawler_runner_accepts_None(self): def test_crawler_runner_accepts_None(self):
@ -552,10 +552,10 @@ class CrawlerRunnerTestCase(BaseCrawlerTest):
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED") self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
class CrawlerProcessTest(BaseCrawlerTest): class TestCrawlerProcess(TestBaseCrawler):
def test_crawler_process_accepts_dict(self): def test_crawler_process_accepts_dict(self):
runner = CrawlerProcess({"foo": "bar"}) runner = CrawlerProcess({"foo": "bar"})
self.assertEqual(runner.settings["foo"], "bar") assert runner.settings["foo"] == "bar"
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED") self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
def test_crawler_process_accepts_None(self): def test_crawler_process_accepts_None(self):
@ -579,7 +579,7 @@ class NoRequestsSpider(scrapy.Spider):
@pytest.mark.usefixtures("reactor_pytest") @pytest.mark.usefixtures("reactor_pytest")
class CrawlerRunnerHasSpider(unittest.TestCase): class TestCrawlerRunnerHasSpider(unittest.TestCase):
def _runner(self): def _runner(self):
return CrawlerRunner() return CrawlerRunner()
@ -587,14 +587,14 @@ class CrawlerRunnerHasSpider(unittest.TestCase):
def test_crawler_runner_bootstrap_successful(self): def test_crawler_runner_bootstrap_successful(self):
runner = self._runner() runner = self._runner()
yield runner.crawl(NoRequestsSpider) yield runner.crawl(NoRequestsSpider)
self.assertFalse(runner.bootstrap_failed) assert not runner.bootstrap_failed
@inlineCallbacks @inlineCallbacks
def test_crawler_runner_bootstrap_successful_for_several(self): def test_crawler_runner_bootstrap_successful_for_several(self):
runner = self._runner() runner = self._runner()
yield runner.crawl(NoRequestsSpider) yield runner.crawl(NoRequestsSpider)
yield runner.crawl(NoRequestsSpider) yield runner.crawl(NoRequestsSpider)
self.assertFalse(runner.bootstrap_failed) assert not runner.bootstrap_failed
@inlineCallbacks @inlineCallbacks
def test_crawler_runner_bootstrap_failed(self): def test_crawler_runner_bootstrap_failed(self):
@ -605,9 +605,9 @@ class CrawlerRunnerHasSpider(unittest.TestCase):
except ValueError: except ValueError:
pass pass
else: else:
self.fail("Exception should be raised from spider") pytest.fail("Exception should be raised from spider")
self.assertTrue(runner.bootstrap_failed) assert runner.bootstrap_failed
@inlineCallbacks @inlineCallbacks
def test_crawler_runner_bootstrap_failed_for_several(self): def test_crawler_runner_bootstrap_failed_for_several(self):
@ -618,11 +618,11 @@ class CrawlerRunnerHasSpider(unittest.TestCase):
except ValueError: except ValueError:
pass pass
else: else:
self.fail("Exception should be raised from spider") pytest.fail("Exception should be raised from spider")
yield runner.crawl(NoRequestsSpider) yield runner.crawl(NoRequestsSpider)
self.assertTrue(runner.bootstrap_failed) assert runner.bootstrap_failed
@inlineCallbacks @inlineCallbacks
def test_crawler_runner_asyncio_enabled_true(self): def test_crawler_runner_asyncio_enabled_true(self):
@ -664,31 +664,34 @@ class ScriptRunnerMixin:
return stderr.decode("utf-8") return stderr.decode("utf-8")
class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase): class TestCrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase):
script_dir = Path(__file__).parent.resolve() / "CrawlerProcess" script_dir = Path(__file__).parent.resolve() / "CrawlerProcess"
def test_simple(self): def test_simple(self):
log = self.run_script("simple.py") log = self.run_script("simple.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertNotIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
not in log
) )
def test_multi(self): def test_multi(self):
log = self.run_script("multi.py") log = self.run_script("multi.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertNotIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
not in log
) )
self.assertNotIn("ReactorAlreadyInstalledError", log) assert "ReactorAlreadyInstalledError" not in log
def test_reactor_default(self): def test_reactor_default(self):
log = self.run_script("reactor_default.py") log = self.run_script("reactor_default.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertNotIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
not in log
) )
self.assertNotIn("ReactorAlreadyInstalledError", log) assert "ReactorAlreadyInstalledError" not in log
def test_reactor_default_twisted_reactor_select(self): def test_reactor_default_twisted_reactor_select(self):
log = self.run_script("reactor_default_twisted_reactor_select.py") log = self.run_script("reactor_default_twisted_reactor_select.py")
@ -703,50 +706,46 @@ class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase):
# If that ever becomes the case on more platforms (i.e. if Linux # If that ever becomes the case on more platforms (i.e. if Linux
# also starts using the select reactor by default in a future # also starts using the select reactor by default in a future
# version of Twisted), then we will need to rethink this test. # version of Twisted), then we will need to rethink this test.
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
else: else:
self.assertNotIn("Spider closed (finished)", log) assert "Spider closed (finished)" not in log
self.assertIn( assert (
( "does not match the requested one "
"does not match the requested one " "(twisted.internet.selectreactor.SelectReactor)"
"(twisted.internet.selectreactor.SelectReactor)" ) in log
),
log,
)
def test_reactor_select(self): def test_reactor_select(self):
log = self.run_script("reactor_select.py") log = self.run_script("reactor_select.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertNotIn("ReactorAlreadyInstalledError", log) assert "ReactorAlreadyInstalledError" not in log
def test_reactor_select_twisted_reactor_select(self): def test_reactor_select_twisted_reactor_select(self):
log = self.run_script("reactor_select_twisted_reactor_select.py") log = self.run_script("reactor_select_twisted_reactor_select.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertNotIn("ReactorAlreadyInstalledError", log) assert "ReactorAlreadyInstalledError" not in log
def test_reactor_select_subclass_twisted_reactor_select(self): def test_reactor_select_subclass_twisted_reactor_select(self):
log = self.run_script("reactor_select_subclass_twisted_reactor_select.py") log = self.run_script("reactor_select_subclass_twisted_reactor_select.py")
self.assertNotIn("Spider closed (finished)", log) assert "Spider closed (finished)" not in log
self.assertIn( assert (
( "does not match the requested one "
"does not match the requested one " "(twisted.internet.selectreactor.SelectReactor)"
"(twisted.internet.selectreactor.SelectReactor)" ) in log
),
log,
)
def test_asyncio_enabled_no_reactor(self): def test_asyncio_enabled_no_reactor(self):
log = self.run_script("asyncio_enabled_no_reactor.py") log = self.run_script("asyncio_enabled_no_reactor.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
def test_asyncio_enabled_reactor(self): def test_asyncio_enabled_reactor(self):
log = self.run_script("asyncio_enabled_reactor.py") log = self.run_script("asyncio_enabled_reactor.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
@pytest.mark.skipif( @pytest.mark.skipif(
@ -755,129 +754,129 @@ class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase):
) )
def test_ipv6_default_name_resolver(self): def test_ipv6_default_name_resolver(self):
log = self.run_script("default_name_resolver.py") log = self.run_script("default_name_resolver.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"'downloader/exception_type_count/twisted.internet.error.DNSLookupError': 1,", "'downloader/exception_type_count/twisted.internet.error.DNSLookupError': 1,"
log, in log
) )
self.assertIn( assert (
"twisted.internet.error.DNSLookupError: DNS lookup failed: no results for hostname lookup: ::1.", "twisted.internet.error.DNSLookupError: DNS lookup failed: no results for hostname lookup: ::1."
log, in log
) )
def test_caching_hostname_resolver_ipv6(self): def test_caching_hostname_resolver_ipv6(self):
log = self.run_script("caching_hostname_resolver_ipv6.py") log = self.run_script("caching_hostname_resolver_ipv6.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertNotIn("twisted.internet.error.DNSLookupError", log) assert "twisted.internet.error.DNSLookupError" not in log
def test_caching_hostname_resolver_finite_execution(self): def test_caching_hostname_resolver_finite_execution(self):
with MockServer() as mock_server: with MockServer() as mock_server:
http_address = mock_server.http_address.replace("0.0.0.0", "127.0.0.1") http_address = mock_server.http_address.replace("0.0.0.0", "127.0.0.1")
log = self.run_script("caching_hostname_resolver.py", http_address) log = self.run_script("caching_hostname_resolver.py", http_address)
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertNotIn("ERROR: Error downloading", log) assert "ERROR: Error downloading" not in log
self.assertNotIn("TimeoutError", log) assert "TimeoutError" not in log
self.assertNotIn("twisted.internet.error.DNSLookupError", log) assert "twisted.internet.error.DNSLookupError" not in log
def test_twisted_reactor_select(self): def test_twisted_reactor_select(self):
log = self.run_script("twisted_reactor_select.py") log = self.run_script("twisted_reactor_select.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert "Using reactor: twisted.internet.selectreactor.SelectReactor" in log
"Using reactor: twisted.internet.selectreactor.SelectReactor", log
)
@pytest.mark.skipif( @pytest.mark.skipif(
platform.system() == "Windows", reason="PollReactor is not supported on Windows" platform.system() == "Windows", reason="PollReactor is not supported on Windows"
) )
def test_twisted_reactor_poll(self): def test_twisted_reactor_poll(self):
log = self.run_script("twisted_reactor_poll.py") log = self.run_script("twisted_reactor_poll.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn("Using reactor: twisted.internet.pollreactor.PollReactor", log) assert "Using reactor: twisted.internet.pollreactor.PollReactor" in log
def test_twisted_reactor_asyncio(self): def test_twisted_reactor_asyncio(self):
log = self.run_script("twisted_reactor_asyncio.py") log = self.run_script("twisted_reactor_asyncio.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
def test_twisted_reactor_asyncio_custom_settings(self): def test_twisted_reactor_asyncio_custom_settings(self):
log = self.run_script("twisted_reactor_custom_settings.py") log = self.run_script("twisted_reactor_custom_settings.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
def test_twisted_reactor_asyncio_custom_settings_same(self): def test_twisted_reactor_asyncio_custom_settings_same(self):
log = self.run_script("twisted_reactor_custom_settings_same.py") log = self.run_script("twisted_reactor_custom_settings_same.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
def test_twisted_reactor_asyncio_custom_settings_conflict(self): def test_twisted_reactor_asyncio_custom_settings_conflict(self):
log = self.run_script("twisted_reactor_custom_settings_conflict.py") log = self.run_script("twisted_reactor_custom_settings_conflict.py")
self.assertIn( assert "Using reactor: twisted.internet.selectreactor.SelectReactor" in log
"Using reactor: twisted.internet.selectreactor.SelectReactor", log assert (
) "(twisted.internet.selectreactor.SelectReactor) does not match the requested one"
self.assertIn( in log
"(twisted.internet.selectreactor.SelectReactor) does not match the requested one",
log,
) )
@pytest.mark.requires_uvloop @pytest.mark.requires_uvloop
def test_custom_loop_asyncio(self): def test_custom_loop_asyncio(self):
log = self.run_script("asyncio_custom_loop.py") log = self.run_script("asyncio_custom_loop.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
self.assertIn("Using asyncio event loop: uvloop.Loop", log) assert "Using asyncio event loop: uvloop.Loop" in log
@pytest.mark.requires_uvloop @pytest.mark.requires_uvloop
def test_custom_loop_asyncio_deferred_signal(self): def test_custom_loop_asyncio_deferred_signal(self):
log = self.run_script("asyncio_deferred_signal.py", "uvloop.Loop") log = self.run_script("asyncio_deferred_signal.py", "uvloop.Loop")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
self.assertIn("Using asyncio event loop: uvloop.Loop", log) assert "Using asyncio event loop: uvloop.Loop" in log
self.assertIn("async pipeline opened!", log) assert "async pipeline opened!" in log
@pytest.mark.requires_uvloop @pytest.mark.requires_uvloop
def test_asyncio_enabled_reactor_same_loop(self): def test_asyncio_enabled_reactor_same_loop(self):
log = self.run_script("asyncio_enabled_reactor_same_loop.py") log = self.run_script("asyncio_enabled_reactor_same_loop.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
self.assertIn("Using asyncio event loop: uvloop.Loop", log) assert "Using asyncio event loop: uvloop.Loop" in log
@pytest.mark.requires_uvloop @pytest.mark.requires_uvloop
def test_asyncio_enabled_reactor_different_loop(self): def test_asyncio_enabled_reactor_different_loop(self):
log = self.run_script("asyncio_enabled_reactor_different_loop.py") log = self.run_script("asyncio_enabled_reactor_different_loop.py")
self.assertNotIn("Spider closed (finished)", log) assert "Spider closed (finished)" not in log
self.assertIn( assert (
( "does not match the one specified in the ASYNCIO_EVENT_LOOP "
"does not match the one specified in the ASYNCIO_EVENT_LOOP " "setting (uvloop.Loop)"
"setting (uvloop.Loop)" ) in log
),
log,
)
def test_default_loop_asyncio_deferred_signal(self): def test_default_loop_asyncio_deferred_signal(self):
log = self.run_script("asyncio_deferred_signal.py") log = self.run_script("asyncio_deferred_signal.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn( assert (
"Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
in log
) )
self.assertNotIn("Using asyncio event loop: uvloop.Loop", log) assert "Using asyncio event loop: uvloop.Loop" not in log
self.assertIn("async pipeline opened!", log) assert "async pipeline opened!" in log
def test_args_change_settings(self): def test_args_change_settings(self):
log = self.run_script("args_settings.py") log = self.run_script("args_settings.py")
self.assertIn("Spider closed (finished)", log) assert "Spider closed (finished)" in log
self.assertIn("The value of FOO is 42", log) assert "The value of FOO is 42" in log
def test_shutdown_graceful(self): def test_shutdown_graceful(self):
sig = signal.SIGINT if sys.platform != "win32" else signal.SIGBREAK sig = signal.SIGINT if sys.platform != "win32" else signal.SIGBREAK
@ -910,23 +909,23 @@ class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase):
p.wait() p.wait()
class CrawlerRunnerSubprocess(ScriptRunnerMixin, unittest.TestCase): class TestCrawlerRunnerSubprocess(ScriptRunnerMixin):
script_dir = Path(__file__).parent.resolve() / "CrawlerRunner" script_dir = Path(__file__).parent.resolve() / "CrawlerRunner"
def test_response_ip_address(self): def test_response_ip_address(self):
log = self.run_script("ip_address.py") log = self.run_script("ip_address.py")
self.assertIn("INFO: Spider closed (finished)", log) assert "INFO: Spider closed (finished)" in log
self.assertIn("INFO: Host: not.a.real.domain", log) assert "INFO: Host: not.a.real.domain" in log
self.assertIn("INFO: Type: <class 'ipaddress.IPv4Address'>", log) assert "INFO: Type: <class 'ipaddress.IPv4Address'>" in log
self.assertIn("INFO: IP address: 127.0.0.1", log) assert "INFO: IP address: 127.0.0.1" in log
def test_change_default_reactor(self): def test_change_default_reactor(self):
log = self.run_script("change_reactor.py") log = self.run_script("change_reactor.py")
self.assertIn( assert (
"DEBUG: Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", "DEBUG: Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
log, in log
) )
self.assertIn("DEBUG: Using asyncio event loop", log) assert "DEBUG: Using asyncio event loop" in log
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -4,11 +4,12 @@ from configparser import ConfigParser
from importlib import import_module from importlib import import_module
from pathlib import Path from pathlib import Path
import pytest
from twisted import version as twisted_version from twisted import version as twisted_version
from twisted.trial import unittest from twisted.trial import unittest
class ScrapyUtilsTest(unittest.TestCase): class TestScrapyUtils:
def test_required_openssl_version(self): def test_required_openssl_version(self):
try: try:
module = import_module("OpenSSL") module = import_module("OpenSSL")
@ -27,7 +28,7 @@ class ScrapyUtilsTest(unittest.TestCase):
See https://github.com/scrapy/scrapy/pull/4814#issuecomment-706230011 See https://github.com/scrapy/scrapy/pull/4814#issuecomment-706230011
""" """
if not os.environ.get("_SCRAPY_PINNED", None): if not os.environ.get("_SCRAPY_PINNED", None):
self.skipTest("Not in a pinned environment") pytest.skip("Not in a pinned environment")
tox_config_file_path = Path(__file__).parent / ".." / "tox.ini" tox_config_file_path = Path(__file__).parent / ".." / "tox.ini"
config_parser = ConfigParser() config_parser = ConfigParser()
@ -36,4 +37,4 @@ class ScrapyUtilsTest(unittest.TestCase):
match = re.search(pattern, config_parser["pinned"]["deps"]) match = re.search(pattern, config_parser["pinned"]["deps"])
pinned_twisted_version_string = match[1] pinned_twisted_version_string = match[1]
self.assertEqual(twisted_version.short(), pinned_twisted_version_string) assert twisted_version.short() == pinned_twisted_version_string

View File

@ -76,7 +76,7 @@ class CrawlTestCase(TestCase):
for k, v in slots.items() for k, v in slots.items()
} }
self.assertTrue(max(list(error_delta.values())) < tolerance) assert max(list(error_delta.values())) < tolerance
def test_params(): def test_params():

View File

@ -2,7 +2,6 @@ import hashlib
import shutil import shutil
import sys import sys
import tempfile import tempfile
import unittest
from pathlib import Path from pathlib import Path
from warnings import catch_warnings from warnings import catch_warnings
@ -39,7 +38,7 @@ class DirectDupeFilter:
method = "n/a" method = "n/a"
class RFPDupeFilterTest(unittest.TestCase): class TestRFPDupeFilter:
def test_df_from_crawler_scheduler(self): def test_df_from_crawler_scheduler(self):
settings = { settings = {
"DUPEFILTER_DEBUG": True, "DUPEFILTER_DEBUG": True,
@ -47,8 +46,8 @@ class RFPDupeFilterTest(unittest.TestCase):
} }
crawler = get_crawler(settings_dict=settings) crawler = get_crawler(settings_dict=settings)
scheduler = Scheduler.from_crawler(crawler) scheduler = Scheduler.from_crawler(crawler)
self.assertTrue(scheduler.df.debug) assert scheduler.df.debug
self.assertEqual(scheduler.df.method, "from_crawler") assert scheduler.df.method == "from_crawler"
def test_df_direct_scheduler(self): def test_df_direct_scheduler(self):
settings = { settings = {
@ -56,7 +55,7 @@ class RFPDupeFilterTest(unittest.TestCase):
} }
crawler = get_crawler(settings_dict=settings) crawler = get_crawler(settings_dict=settings)
scheduler = Scheduler.from_crawler(crawler) scheduler = Scheduler.from_crawler(crawler)
self.assertEqual(scheduler.df.method, "n/a") assert scheduler.df.method == "n/a"
def test_filter(self): def test_filter(self):
dupefilter = _get_dupefilter() dupefilter = _get_dupefilter()
@ -256,16 +255,16 @@ class RFPDupeFilterTest(unittest.TestCase):
dupefilter.close("finished") dupefilter.close("finished")
class BaseDupeFilterTestCase(unittest.TestCase): class TestBaseDupeFilter:
def test_log_deprecation(self): def test_log_deprecation(self):
dupefilter = _get_dupefilter( dupefilter = _get_dupefilter(
settings={"DUPEFILTER_CLASS": BaseDupeFilter}, settings={"DUPEFILTER_CLASS": BaseDupeFilter},
) )
with catch_warnings(record=True) as warning_list: with catch_warnings(record=True) as warning_list:
dupefilter.log(None, None) dupefilter.log(None, None)
self.assertEqual(len(warning_list), 1) assert len(warning_list) == 1
self.assertEqual( assert (
str(warning_list[0].message), str(warning_list[0].message)
"Calling BaseDupeFilter.log() is deprecated.", == "Calling BaseDupeFilter.log() is deprecated."
) )
self.assertEqual(warning_list[0].category, ScrapyDeprecationWarning) assert warning_list[0].category == ScrapyDeprecationWarning

View File

@ -243,8 +243,9 @@ class CrawlerRun:
self.signals_caught[sig] = signalargs self.signals_caught[sig] = signalargs
class EngineTestBase(unittest.TestCase): class TestEngineBase(unittest.TestCase):
def _assert_visited_urls(self, run: CrawlerRun): @staticmethod
def _assert_visited_urls(run: CrawlerRun) -> None:
must_be_visited = [ must_be_visited = [
"/", "/",
"/redirect", "/redirect",
@ -259,8 +260,9 @@ class EngineTestBase(unittest.TestCase):
f"URLs not visited: {list(urls_expected - urls_visited)}" f"URLs not visited: {list(urls_expected - urls_visited)}"
) )
def _assert_scheduled_requests(self, run: CrawlerRun, count=None): @staticmethod
self.assertEqual(count, len(run.reqplug)) def _assert_scheduled_requests(run: CrawlerRun, count: int) -> None:
assert len(run.reqplug) == count
paths_expected = ["/item999.html", "/item2.html", "/item1.html"] paths_expected = ["/item999.html", "/item2.html", "/item1.html"]
@ -270,101 +272,104 @@ class EngineTestBase(unittest.TestCase):
scheduled_requests_count = len(run.reqplug) scheduled_requests_count = len(run.reqplug)
dropped_requests_count = len(run.reqdropped) dropped_requests_count = len(run.reqdropped)
responses_count = len(run.respplug) responses_count = len(run.respplug)
self.assertEqual( assert scheduled_requests_count == dropped_requests_count + responses_count
scheduled_requests_count, dropped_requests_count + responses_count assert len(run.reqreached) == responses_count
)
self.assertEqual(len(run.reqreached), responses_count)
def _assert_dropped_requests(self, run: CrawlerRun): @staticmethod
self.assertEqual(len(run.reqdropped), 1) def _assert_dropped_requests(run: CrawlerRun) -> None:
assert len(run.reqdropped) == 1
def _assert_downloaded_responses(self, run: CrawlerRun, count): @staticmethod
def _assert_downloaded_responses(run: CrawlerRun, count: int) -> None:
# response tests # response tests
self.assertEqual(count, len(run.respplug)) assert len(run.respplug) == count
self.assertEqual(count, len(run.reqreached)) assert len(run.reqreached) == count
for response, _ in run.respplug: for response, _ in run.respplug:
if run.getpath(response.url) == "/item999.html": if run.getpath(response.url) == "/item999.html":
self.assertEqual(404, response.status) assert response.status == 404
if run.getpath(response.url) == "/redirect": if run.getpath(response.url) == "/redirect":
self.assertEqual(302, response.status) assert response.status == 302
def _assert_items_error(self, run: CrawlerRun): @staticmethod
self.assertEqual(2, len(run.itemerror)) def _assert_items_error(run: CrawlerRun) -> None:
assert len(run.itemerror) == 2
for item, response, spider, failure in run.itemerror: for item, response, spider, failure in run.itemerror:
self.assertEqual(failure.value.__class__, ZeroDivisionError) assert failure.value.__class__ is ZeroDivisionError
self.assertEqual(spider, run.spider) assert spider == run.spider
self.assertEqual(item["url"], response.url) assert item["url"] == response.url
if "item1.html" in item["url"]: if "item1.html" in item["url"]:
self.assertEqual("Item 1 name", item["name"]) assert item["name"] == "Item 1 name"
self.assertEqual("100", item["price"]) assert item["price"] == "100"
if "item2.html" in item["url"]: if "item2.html" in item["url"]:
self.assertEqual("Item 2 name", item["name"]) assert item["name"] == "Item 2 name"
self.assertEqual("200", item["price"]) assert item["price"] == "200"
def _assert_scraped_items(self, run: CrawlerRun): @staticmethod
self.assertEqual(2, len(run.itemresp)) def _assert_scraped_items(run: CrawlerRun) -> None:
assert len(run.itemresp) == 2
for item, response in run.itemresp: for item, response in run.itemresp:
item = ItemAdapter(item) item = ItemAdapter(item)
self.assertEqual(item["url"], response.url) assert item["url"] == response.url
if "item1.html" in item["url"]: if "item1.html" in item["url"]:
self.assertEqual("Item 1 name", item["name"]) assert item["name"] == "Item 1 name"
self.assertEqual("100", item["price"]) assert item["price"] == "100"
if "item2.html" in item["url"]: if "item2.html" in item["url"]:
self.assertEqual("Item 2 name", item["name"]) assert item["name"] == "Item 2 name"
self.assertEqual("200", item["price"]) assert item["price"] == "200"
def _assert_headers_received(self, run: CrawlerRun): @staticmethod
def _assert_headers_received(run: CrawlerRun) -> None:
for headers in run.headers.values(): for headers in run.headers.values():
self.assertIn(b"Server", headers) assert b"Server" in headers
self.assertIn(b"TwistedWeb", headers[b"Server"]) assert b"TwistedWeb" in headers[b"Server"]
self.assertIn(b"Date", headers) assert b"Date" in headers
self.assertIn(b"Content-Type", headers) assert b"Content-Type" in headers
def _assert_bytes_received(self, run: CrawlerRun): @staticmethod
self.assertEqual(9, len(run.bytes)) def _assert_bytes_received(run: CrawlerRun) -> None:
assert len(run.bytes) == 9
for request, data in run.bytes.items(): for request, data in run.bytes.items():
joined_data = b"".join(data) joined_data = b"".join(data)
if run.getpath(request.url) == "/": if run.getpath(request.url) == "/":
self.assertEqual(joined_data, get_testdata("test_site", "index.html")) assert joined_data == get_testdata("test_site", "index.html")
elif run.getpath(request.url) == "/item1.html": elif run.getpath(request.url) == "/item1.html":
self.assertEqual(joined_data, get_testdata("test_site", "item1.html")) assert joined_data == get_testdata("test_site", "item1.html")
elif run.getpath(request.url) == "/item2.html": elif run.getpath(request.url) == "/item2.html":
self.assertEqual(joined_data, get_testdata("test_site", "item2.html")) assert joined_data == get_testdata("test_site", "item2.html")
elif run.getpath(request.url) == "/redirected": elif run.getpath(request.url) == "/redirected":
self.assertEqual(joined_data, b"Redirected here") assert joined_data == b"Redirected here"
elif run.getpath(request.url) == "/redirect": elif run.getpath(request.url) == "/redirect":
self.assertEqual( assert (
joined_data, joined_data == b"\n<html>\n"
b"\n<html>\n"
b" <head>\n" b" <head>\n"
b' <meta http-equiv="refresh" content="0;URL=/redirected">\n' b' <meta http-equiv="refresh" content="0;URL=/redirected">\n'
b" </head>\n" b" </head>\n"
b' <body bgcolor="#FFFFFF" text="#000000">\n' b' <body bgcolor="#FFFFFF" text="#000000">\n'
b' <a href="/redirected">click here</a>\n' b' <a href="/redirected">click here</a>\n'
b" </body>\n" b" </body>\n"
b"</html>\n", b"</html>\n"
) )
elif run.getpath(request.url) == "/tem999.html": elif run.getpath(request.url) == "/tem999.html":
self.assertEqual( assert (
joined_data, joined_data == b"\n<html>\n"
b"\n<html>\n"
b" <head><title>404 - No Such Resource</title></head>\n" b" <head><title>404 - No Such Resource</title></head>\n"
b" <body>\n" b" <body>\n"
b" <h1>No Such Resource</h1>\n" b" <h1>No Such Resource</h1>\n"
b" <p>File not found.</p>\n" b" <p>File not found.</p>\n"
b" </body>\n" b" </body>\n"
b"</html>\n", b"</html>\n"
) )
elif run.getpath(request.url) == "/numbers": elif run.getpath(request.url) == "/numbers":
# signal was fired multiple times # signal was fired multiple times
self.assertTrue(len(data) > 1) assert len(data) > 1
# bytes were received in order # bytes were received in order
numbers = [str(x).encode("utf8") for x in range(2**18)] numbers = [str(x).encode("utf8") for x in range(2**18)]
self.assertEqual(joined_data, b"".join(numbers)) assert joined_data == b"".join(numbers)
def _assert_signals_caught(self, run: CrawlerRun): @staticmethod
def _assert_signals_caught(run: CrawlerRun) -> None:
assert signals.engine_started in run.signals_caught assert signals.engine_started in run.signals_caught
assert signals.engine_stopped in run.signals_caught assert signals.engine_stopped in run.signals_caught
assert signals.spider_opened in run.signals_caught assert signals.spider_opened in run.signals_caught
@ -372,19 +377,14 @@ class EngineTestBase(unittest.TestCase):
assert signals.spider_closed in run.signals_caught assert signals.spider_closed in run.signals_caught
assert signals.headers_received in run.signals_caught assert signals.headers_received in run.signals_caught
self.assertEqual( assert {"spider": run.spider} == run.signals_caught[signals.spider_opened]
{"spider": run.spider}, run.signals_caught[signals.spider_opened] assert {"spider": run.spider} == run.signals_caught[signals.spider_idle]
) assert {"spider": run.spider, "reason": "finished"} == run.signals_caught[
self.assertEqual( signals.spider_closed
{"spider": run.spider}, run.signals_caught[signals.spider_idle] ]
)
self.assertEqual(
{"spider": run.spider, "reason": "finished"},
run.signals_caught[signals.spider_closed],
)
class EngineTest(EngineTestBase): class TestEngine(TestEngineBase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawler(self): def test_crawler(self):
for spider in ( for spider in (
@ -419,10 +419,9 @@ class EngineTest(EngineTestBase):
def test_crawler_change_close_reason_on_idle(self): def test_crawler_change_close_reason_on_idle(self):
run = CrawlerRun(ChangeCloseReasonSpider) run = CrawlerRun(ChangeCloseReasonSpider)
yield run.run() yield run.run()
self.assertEqual( assert {"spider": run.spider, "reason": "custom_reason"} == run.signals_caught[
{"spider": run.spider, "reason": "custom_reason"}, signals.spider_closed
run.signals_caught[signals.spider_closed], ]
)
@defer.inlineCallbacks @defer.inlineCallbacks
def test_close_downloader(self): def test_close_downloader(self):
@ -470,7 +469,7 @@ class EngineTest(EngineTestBase):
finally: finally:
timer.cancel() timer.cancel()
self.assertNotIn(b"Traceback", stderr) assert b"Traceback" not in stderr
def test_request_scheduled_signal(caplog): def test_request_scheduled_signal(caplog):

View File

@ -7,8 +7,8 @@ from tests.test_engine import (
CrawlerRun, CrawlerRun,
DataClassItemsSpider, DataClassItemsSpider,
DictItemsSpider, DictItemsSpider,
EngineTestBase,
MySpider, MySpider,
TestEngineBase,
) )
@ -18,7 +18,7 @@ class BytesReceivedCrawlerRun(CrawlerRun):
raise StopDownload(fail=False) raise StopDownload(fail=False)
class BytesReceivedEngineTest(EngineTestBase): class TestBytesReceivedEngine(TestEngineBase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawler(self): def test_crawler(self):
for spider in ( for spider in (
@ -61,14 +61,15 @@ class BytesReceivedEngineTest(EngineTestBase):
self._assert_headers_received(run) self._assert_headers_received(run)
self._assert_bytes_received(run) self._assert_bytes_received(run)
def _assert_bytes_received(self, run: CrawlerRun): @staticmethod
self.assertEqual(9, len(run.bytes)) def _assert_bytes_received(run: CrawlerRun) -> None:
assert len(run.bytes) == 9
for request, data in run.bytes.items(): for request, data in run.bytes.items():
joined_data = b"".join(data) joined_data = b"".join(data)
self.assertTrue(len(data) == 1) # signal was fired only once assert len(data) == 1 # signal was fired only once
if run.getpath(request.url) == "/numbers": if run.getpath(request.url) == "/numbers":
# Received bytes are not the complete response. The exact amount depends # Received bytes are not the complete response. The exact amount depends
# on the buffer size, which can vary, so we only check that the amount # on the buffer size, which can vary, so we only check that the amount
# of received bytes is strictly less than the full response. # of received bytes is strictly less than the full response.
numbers = [str(x).encode("utf8") for x in range(2**18)] numbers = [str(x).encode("utf8") for x in range(2**18)]
self.assertTrue(len(joined_data) < len(b"".join(numbers))) assert len(joined_data) < len(b"".join(numbers))

View File

@ -7,8 +7,8 @@ from tests.test_engine import (
CrawlerRun, CrawlerRun,
DataClassItemsSpider, DataClassItemsSpider,
DictItemsSpider, DictItemsSpider,
EngineTestBase,
MySpider, MySpider,
TestEngineBase,
) )
@ -18,7 +18,7 @@ class HeadersReceivedCrawlerRun(CrawlerRun):
raise StopDownload(fail=False) raise StopDownload(fail=False)
class HeadersReceivedEngineTest(EngineTestBase): class TestHeadersReceivedEngine(TestEngineBase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_crawler(self): def test_crawler(self):
for spider in ( for spider in (
@ -60,10 +60,12 @@ class HeadersReceivedEngineTest(EngineTestBase):
self._assert_bytes_received(run) self._assert_bytes_received(run)
self._assert_headers_received(run) self._assert_headers_received(run)
def _assert_bytes_received(self, run: CrawlerRun): @staticmethod
self.assertEqual(0, len(run.bytes)) def _assert_bytes_received(run: CrawlerRun) -> None:
assert len(run.bytes) == 0
def _assert_visited_urls(self, run: CrawlerRun): @staticmethod
def _assert_visited_urls(run: CrawlerRun) -> None:
must_be_visited = ["/", "/redirect", "/redirected"] must_be_visited = ["/", "/redirect", "/redirected"]
urls_visited = {rp[0].url for rp in run.respplug} urls_visited = {rp[0].url for rp in run.respplug}
urls_expected = {run.geturl(p) for p in must_be_visited} urls_expected = {run.geturl(p) for p in must_be_visited}

View File

@ -36,7 +36,7 @@ from tests.mockserver import (
PayloadResource, PayloadResource,
ssl_context_factory, ssl_context_factory,
) )
from tests.test_core_downloader import ContextFactoryBaseTestCase from tests.test_core_downloader import TestContextFactoryBase
def getPage(url, contextFactory=None, response_transform=None, *args, **kwargs): def getPage(url, contextFactory=None, response_transform=None, *args, **kwargs):
@ -63,7 +63,7 @@ def getPage(url, contextFactory=None, response_transform=None, *args, **kwargs):
@pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning") @pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning")
class ScrapyHTTPPageGetterTests(unittest.TestCase): class TestScrapyHTTPPageGetter:
def test_earlyHeaders(self): def test_earlyHeaders(self):
# basic test stolen from twisted HTTPageGetter # basic test stolen from twisted HTTPageGetter
factory = client.ScrapyHTTPClientFactory( factory = client.ScrapyHTTPClientFactory(
@ -177,9 +177,7 @@ class ScrapyHTTPPageGetterTests(unittest.TestCase):
protocol = client.ScrapyHTTPPageGetter() protocol = client.ScrapyHTTPPageGetter()
protocol.factory = factory protocol.factory = factory
protocol.makeConnection(transport) protocol.makeConnection(transport)
self.assertEqual( assert set(transport.value().splitlines()) == set(testvalue.splitlines())
set(transport.value().splitlines()), set(testvalue.splitlines())
)
return testvalue return testvalue
def test_non_standard_line_endings(self): def test_non_standard_line_endings(self):
@ -192,9 +190,7 @@ class ScrapyHTTPPageGetterTests(unittest.TestCase):
protocol.dataReceived(b"Hello: World\n") protocol.dataReceived(b"Hello: World\n")
protocol.dataReceived(b"Foo: Bar\n") protocol.dataReceived(b"Foo: Bar\n")
protocol.dataReceived(b"\n") protocol.dataReceived(b"\n")
self.assertEqual( assert protocol.headers == Headers({"Hello": ["World"], "Foo": ["Bar"]})
protocol.headers, Headers({"Hello": ["World"], "Foo": ["Bar"]})
)
class EncodingResource(resource.Resource): class EncodingResource(resource.Resource):
@ -207,7 +203,7 @@ class EncodingResource(resource.Resource):
@pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning") @pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning")
class WebClientTestCase(unittest.TestCase): class TestWebClient(unittest.TestCase):
def _listen(self, site): def _listen(self, site):
return reactor.listenTCP(0, site, interface="127.0.0.1") return reactor.listenTCP(0, site, interface="127.0.0.1")
@ -319,7 +315,7 @@ class WebClientTestCase(unittest.TestCase):
return getPage(self.getURL("notsuchfile")).addCallback(self._cbNoSuchFile) return getPage(self.getURL("notsuchfile")).addCallback(self._cbNoSuchFile)
def _cbNoSuchFile(self, pageData): def _cbNoSuchFile(self, pageData):
self.assertIn(b"404 - No Such Resource", pageData) assert b"404 - No Such Resource" in pageData
def testFactoryInfo(self): def testFactoryInfo(self):
url = self.getURL("file") url = self.getURL("file")
@ -329,20 +325,20 @@ class WebClientTestCase(unittest.TestCase):
return factory.deferred.addCallback(self._cbFactoryInfo, factory) return factory.deferred.addCallback(self._cbFactoryInfo, factory)
def _cbFactoryInfo(self, ignoredResult, factory): def _cbFactoryInfo(self, ignoredResult, factory):
self.assertEqual(factory.status, b"200") assert factory.status == b"200"
self.assertTrue(factory.version.startswith(b"HTTP/")) assert factory.version.startswith(b"HTTP/")
self.assertEqual(factory.message, b"OK") assert factory.message == b"OK"
self.assertEqual(factory.response_headers[b"content-length"], b"10") assert factory.response_headers[b"content-length"] == b"10"
def testRedirect(self): def testRedirect(self):
return getPage(self.getURL("redirect")).addCallback(self._cbRedirect) return getPage(self.getURL("redirect")).addCallback(self._cbRedirect)
def _cbRedirect(self, pageData): def _cbRedirect(self, pageData):
self.assertEqual( assert (
pageData, pageData
b'\n<html>\n <head>\n <meta http-equiv="refresh" content="0;URL=/file">\n' == b'\n<html>\n <head>\n <meta http-equiv="refresh" content="0;URL=/file">\n'
b' </head>\n <body bgcolor="#FFFFFF" text="#000000">\n ' b' </head>\n <body bgcolor="#FFFFFF" text="#000000">\n '
b'<a href="/file">click here</a>\n </body>\n</html>\n', b'<a href="/file">click here</a>\n </body>\n</html>\n'
) )
def test_encoding(self): def test_encoding(self):
@ -356,14 +352,12 @@ class WebClientTestCase(unittest.TestCase):
def _check_Encoding(self, response, original_body): def _check_Encoding(self, response, original_body):
content_encoding = to_unicode(response.headers[b"Content-Encoding"]) content_encoding = to_unicode(response.headers[b"Content-Encoding"])
self.assertEqual(content_encoding, EncodingResource.out_encoding) assert content_encoding == EncodingResource.out_encoding
self.assertEqual( assert response.body.decode(content_encoding) == to_unicode(original_body)
response.body.decode(content_encoding), to_unicode(original_body)
)
@pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning") @pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning")
class WebClientSSLTestCase(ContextFactoryBaseTestCase): class WebClientSSLTestCase(TestContextFactoryBase):
def testPayload(self): def testPayload(self):
s = "0123456789" * 10 s = "0123456789" * 10
return getPage(self.getURL("payload"), body=s).addCallback( return getPage(self.getURL("payload"), body=s).addCallback(