Merge pull request #733 from alexcepoi/contracts_tweaks

improvements to scrapy check/contracts
This commit is contained in:
Daniel Graña 2014-06-03 09:55:48 -03:00
commit 791889f51d
2 changed files with 81 additions and 33 deletions

View File

@ -1,22 +1,42 @@
from __future__ import print_function
import time
import sys
from collections import defaultdict
from functools import wraps
from unittest import TextTestRunner
from unittest import TextTestRunner, TextTestResult as _TextTestResult
from scrapy.command import ScrapyCommand
from scrapy.contracts import ContractsManager
from scrapy.utils.misc import load_object
from scrapy.utils.spider import iterate_spider_output
from scrapy.utils.conf import build_component_list
def _generate(cb):
""" create a callback which does not return anything """
@wraps(cb)
def wrapper(response):
output = cb(response)
output = list(iterate_spider_output(output))
return wrapper
class TextTestResult(_TextTestResult):
def printSummary(self, start, stop):
write = self.stream.write
writeln = self.stream.writeln
run = self.testsRun
plural = "s" if run != 1 else ""
writeln(self.separator2)
writeln("Ran %d contract%s in %.3fs" % (run, plural, stop - start))
writeln()
infos = []
if not self.wasSuccessful():
write("FAILED")
failed, errored = map(len, (self.failures, self.errors))
if failed:
infos.append("failures=%d" % failed)
if errored:
infos.append("errors=%d" % errored)
else:
write("OK")
if infos:
writeln(" (%s)" % (", ".join(infos),))
else:
write("\n")
class Command(ScrapyCommand):
@ -32,9 +52,9 @@ class Command(ScrapyCommand):
def add_options(self, parser):
ScrapyCommand.add_options(self, parser)
parser.add_option("-l", "--list", dest="list", action="store_true",
help="only list contracts, without checking them")
help="only list contracts, without checking them")
parser.add_option("-v", "--verbose", dest="verbose", default=1, action="count",
help="print all contract hooks")
help="print all contract hooks")
def run(self, args, opts):
# load contracts
@ -42,8 +62,9 @@ class Command(ScrapyCommand):
self.settings['SPIDER_CONTRACTS_BASE'],
self.settings['SPIDER_CONTRACTS'],
)
self.conman = ContractsManager([load_object(c) for c in contracts])
self.results = TextTestRunner(verbosity=opts.verbose)._makeResult()
conman = ContractsManager([load_object(c) for c in contracts])
runner = TextTestRunner(verbosity=opts.verbose)
result = TextTestResult(runner.stream, runner.descriptions, runner.verbosity)
# contract requests
contract_reqs = defaultdict(list)
@ -53,7 +74,7 @@ class Command(ScrapyCommand):
for spider in args or spiders.list():
spider = spiders.create(spider)
requests = self.get_requests(spider)
requests = self.get_requests(spider, conman, result)
if opts.list:
for req in requests:
@ -69,20 +90,23 @@ class Command(ScrapyCommand):
for method in sorted(methods):
print(' * %s' % method)
else:
start = time.time()
self.crawler_process.start()
self.results.printErrors()
self.exitcode = 0 if self.results.wasSuccessful() else 1
stop = time.time()
def get_requests(self, spider):
result.printErrors()
result.printSummary(start, stop)
self.exitcode = int(not result.wasSuccessful())
def get_requests(self, spider, conman, result):
requests = []
for key, value in vars(type(spider)).items():
if callable(value) and value.__doc__:
bound_method = value.__get__(spider, type(spider))
request = self.conman.from_method(bound_method, self.results)
request = conman.from_method(bound_method, result)
if request:
request.callback = _generate(request.callback)
requests.append(request)
return requests

View File

@ -48,28 +48,40 @@ class ContractsManager(object):
for contract in contracts:
request = contract.add_post_hook(request, results)
self._clean_req(request, method, results)
return request
def _clean_req(self, request, method, results):
""" stop the request from returning objects and records any errors """
cb = request.callback
@wraps(cb)
def cb_wrapper(response):
try:
output = cb(response)
output = list(iterate_spider_output(output))
except:
case = _create_testcase(method, 'callback')
results.addError(case, sys.exc_info())
def eb_wrapper(failure):
case = _create_testcase(method, 'errback')
exc_info = failure.value, failure.type, failure.getTracebackObject()
results.addError(case, exc_info)
request.callback = cb_wrapper
request.errback = eb_wrapper
class Contract(object):
""" Abstract class for contracts """
def __init__(self, method, *args):
self.testcase_pre = self.create_testcase(method, 'pre-hook')
self.testcase_post = self.create_testcase(method, 'post-hook')
self.testcase_pre = _create_testcase(method, '@%s pre-hook' % self.name)
self.testcase_post = _create_testcase(method, '@%s post-hook' % self.name)
self.args = args
def create_testcase(self, method, hook):
spider = method.__self__.name
class ContractTestCase(TestCase):
def __str__(_self):
return "[%s] %s (@%s %s)" % (spider, method.__name__, self.name, hook)
name = '%s_%s' % (spider, method.__name__)
setattr(ContractTestCase, name, lambda x: x)
return ContractTestCase(name)
def add_pre_hook(self, request, results):
if hasattr(self, 'pre_process'):
cb = request.callback
@ -119,3 +131,15 @@ class Contract(object):
def adjust_request_args(self, args):
return args
def _create_testcase(method, desc):
spider = method.__self__.name
class ContractTestCase(TestCase):
def __str__(_self):
return "[%s] %s (%s)" % (spider, method.__name__, desc)
name = '%s_%s' % (spider, method.__name__)
setattr(ContractTestCase, name, lambda x: x)
return ContractTestCase(name)