diff --git a/scrapy/tests/mockserver.py b/scrapy/tests/mockserver.py new file mode 100644 index 000000000..e3c8ed1d0 --- /dev/null +++ b/scrapy/tests/mockserver.py @@ -0,0 +1,83 @@ +import json, random, urllib +from time import time +from twisted.web.server import Site +from twisted.web.resource import Resource +from twisted.internet import reactor + + +_id = lambda x: x +_request_args = { + "args": _id, + "clientproto": _id, + "requestHeaders": lambda x: x._rawHeaders, + "responseHeaders": lambda x: x._rawHeaders, + "method": _id, + "path": _id, + "uri": _id, +} + +def encode_request(request): + """Encode request into a JSON-serializable type""" + d = {"time": time()} + for k, func in _request_args.iteritems(): + d[k] = func(getattr(request, k)) + return d + +def getarg(request, name, default=None, type=str): + if name in request.args: + return type(request.args[name][0]) + else: + return default + +class Follow(Resource): + + isLeaf = True + + def render(self, request): + total = getarg(request, "total", 100, type=int) + show = getarg(request, "show", 1, type=int) + order = getarg(request, "order", "desc") + n = getarg(request, "n", total, type=int) + if order == "rand": + nlist = [random.randint(1, total) for _ in range(show)] + else: # order == "desc" + nlist = range(n, max(n-show, 0), -1) + + s = """ """ + args = request.args.copy() + for nl in nlist: + args["n"] = [str(nl)] + argstr = urllib.urlencode(args, doseq=True) + s += "follow %d
" % (argstr, nl) + s += """""" + return s + +class Log(Resource): + + isLeaf = True + + def __init__(self, log): + self.log = log + + def render(self, request): + return json.dumps(self.log) + +class Root(Resource): + + def __init__(self): + Resource.__init__(self) + self.log = [] + self.putChild("follow", Follow()) + self.putChild("log", Log(self.log)) + + def getChild(self, request, name): + return self + + def render(self, request): + return 'Scrapy mock HTTP server\n' + + +root = Root() +factory = Site(root) +reactor.listenTCP(8998, factory) +reactor.run() diff --git a/scrapy/tests/test_crawl.py b/scrapy/tests/test_crawl.py new file mode 100644 index 000000000..d705d27ba --- /dev/null +++ b/scrapy/tests/test_crawl.py @@ -0,0 +1,60 @@ +import sys, time +from twisted.internet import defer +from twisted.trial.unittest import TestCase, SkipTest +from subprocess import Popen +from scrapy.spider import BaseSpider +from scrapy.http import Request +from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor +from scrapy.utils.test import get_crawler + +class FollowAllSpider(BaseSpider): + + name = 'follow' + start_urls = ["http://localhost:8998/follow?total=10&show=5&order=rand"] + link_extractor = SgmlLinkExtractor() + + def __init__(self): + self.urls_visited = [] + self.times = [] + + def parse(self, response): + self.urls_visited.append(response.url) + self.times.append(time.time()) + for link in self.link_extractor.extract_links(response): + yield Request(link.url, callback=self.parse) + +def docrawl(spider, settings=None): + crawler = get_crawler(settings) + crawler.configure() + crawler.crawl(spider) + return crawler.start() + +class CrawlTestCase(TestCase): + + def setUp(self): + self.proc = Popen([sys.executable, '-m', 'scrapy.tests.mockserver']) + time.sleep(0.2) + + def tearDown(self): + self.proc.kill() + self.proc.wait() + time.sleep(0.2) + + @defer.inlineCallbacks + def test_follow_all(self): + spider = FollowAllSpider() + yield docrawl(spider) + self.assertEqual(len(spider.urls_visited), 11) # 10 + start_url + + @defer.inlineCallbacks + def test_delay(self): + # FIXME: this test fails because Scrapy leaves the reactor dirty with + # callLater calls when download delays are used. This test should be + # enabled after this bug is fixed. + raise SkipTest("disabled due to a reactor leak in the scrapy downloader") + + spider = FollowAllSpider() + yield docrawl(spider) + t = spider.times[0] + for y in spider.times[1:]: + self.assertTrue(y-t > 0.5, "download delay too small: %s" % (y-t))