Return non-zero exit code from scrapy commands in case of spider bootstrap errors

* method to detect spider creation in crawler

* correct method name

* method to know if crawlers has spiders

* we do not need to issue requests

* set exit code accordingly to spiders in crawlers

* more portable way to check ofr exceptions

* more clear way

* test cases for several spiders per crawler

* grammatically correct name for method

* method is private

* grammatically correct name for method

* method is private

* remove unused import

* correct order of imports

* changes mechanism of obtaining spider status from method to object member

* rename tests
This commit is contained in:
Vostretsov Nikita 2018-06-14 17:58:48 +03:00 committed by Mikhail Korobov
parent 667eb7151b
commit 72d0899bce
5 changed files with 83 additions and 1 deletions

View File

@ -56,3 +56,6 @@ class Command(ScrapyCommand):
self.crawler_process.crawl(spname, **opts.spargs)
self.crawler_process.start()
if self.crawler_process.bootstrap_failed:
self.exitcode = 1

View File

@ -87,3 +87,6 @@ class Command(ScrapyCommand):
self.crawler_process.crawl(spidercls, **opts.spargs)
self.crawler_process.start()
if self.crawler_process.bootstrap_failed:
self.exitcode = 1

View File

@ -137,6 +137,7 @@ class CrawlerRunner(object):
self.spider_loader = _get_spider_loader(settings)
self._crawlers = set()
self._active = set()
self.bootstrap_failed = False
@property
def spiders(self):
@ -178,6 +179,7 @@ class CrawlerRunner(object):
def _done(result):
self.crawlers.discard(crawler)
self._active.discard(d)
self.bootstrap_failed |= not getattr(crawler, 'spider', None)
return result
return d.addBoth(_done)

View File

@ -1,3 +1,4 @@
import inspect
import os
import sys
import subprocess
@ -17,6 +18,7 @@ from scrapy.utils.python import retry_on_eintr
from scrapy.utils.test import get_testenv
from scrapy.utils.testsite import SiteTest
from scrapy.utils.testproc import ProcessTest
from tests.test_crawler import ExceptionSpider, NoRequestsSpider
class ProjectTest(unittest.TestCase):
@ -220,6 +222,16 @@ class MySpider(scrapy.Spider):
self.assertIn("INFO: Closing spider (finished)", log)
self.assertIn("INFO: Spider closed (finished)", log)
def test_run_fail_spider(self):
proc = self.runspider("import scrapy\n" + inspect.getsource(ExceptionSpider))
ret = proc.returncode
self.assertNotEqual(ret, 0)
def test_run_good_spider(self):
proc = self.runspider("import scrapy\n" + inspect.getsource(NoRequestsSpider))
ret = proc.returncode
self.assertEqual(ret, 0)
def test_runspider_log_level(self):
log = self.get_log(self.debug_log_spider,
args=('-s', 'LOG_LEVEL=INFO'))

View File

@ -4,6 +4,9 @@ import tempfile
import warnings
import unittest
from twisted.internet import defer
import twisted.trial.unittest
import scrapy
from scrapy.crawler import Crawler, CrawlerRunner, CrawlerProcess
from scrapy.settings import Settings, default_settings
@ -11,9 +14,9 @@ from scrapy.spiderloader import SpiderLoader
from scrapy.utils.log import configure_logging, get_scrapy_root_handler
from scrapy.utils.spider import DefaultSpider
from scrapy.utils.misc import load_object
from scrapy.utils.test import get_crawler
from scrapy.extensions.throttle import AutoThrottle
class BaseCrawlerTest(unittest.TestCase):
def assertOptionIsDefault(self, settings, key):
@ -181,3 +184,62 @@ class CrawlerProcessTest(BaseCrawlerTest):
def test_crawler_process_accepts_None(self):
runner = CrawlerProcess()
self.assertOptionIsDefault(runner.settings, 'RETRY_ENABLED')
class ExceptionSpider(scrapy.Spider):
name = 'exception'
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
raise ValueError('Exception in from_crawler method')
class NoRequestsSpider(scrapy.Spider):
name = 'no_request'
def start_requests(self):
return []
class CrawlerRunnerHasSpider(twisted.trial.unittest.TestCase):
@defer.inlineCallbacks
def test_crawler_runner_bootstrap_successful(self):
runner = CrawlerRunner()
yield runner.crawl(NoRequestsSpider)
self.assertEqual(runner.bootstrap_failed, False)
@defer.inlineCallbacks
def test_crawler_runner_bootstrap_successful_for_several(self):
runner = CrawlerRunner()
yield runner.crawl(NoRequestsSpider)
yield runner.crawl(NoRequestsSpider)
self.assertEqual(runner.bootstrap_failed, False)
@defer.inlineCallbacks
def test_crawler_runner_bootstrap_failed(self):
runner = CrawlerRunner()
try:
yield runner.crawl(ExceptionSpider)
except ValueError:
pass
else:
self.fail('Exception should be raised from spider')
self.assertEqual(runner.bootstrap_failed, True)
@defer.inlineCallbacks
def test_crawler_runner_bootstrap_failed_for_several(self):
runner = CrawlerRunner()
try:
yield runner.crawl(ExceptionSpider)
except ValueError:
pass
else:
self.fail('Exception should be raised from spider')
yield runner.crawl(NoRequestsSpider)
self.assertEqual(runner.bootstrap_failed, True)