Skip to content

Commit

Permalink
Complete Azure Pipelines CI setup
Browse files Browse the repository at this point in the history
  • Loading branch information
Gallaecio committed Jul 2, 2020
1 parent 6e58da1 commit 3199048
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 29 deletions.
18 changes: 6 additions & 12 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
# Python package
# Create and test a Python package on multiple Python versions.
# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/python


variables:
TOXENV: py
pool:
vmImage: 'windows-2019'
vmImage: 'windows-latest'
strategy:
matrix:
Python35:
python.version: '3.5'
TOXENV: py35
TOXENV: windows-pinned
Python36:
python.version: '3.6'
TOXENV: py36
Python37:
python.version: '3.7'
TOXENV: py37

Python38:
python.version: '3.8'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
displayName: 'Use Python $(python.version)'

- script: |
pip install -U tox twine wheel codecov
tox
Expand Down
15 changes: 14 additions & 1 deletion tests/CrawlerRunner/ip_address.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from urllib.parse import urlparse

from twisted.internet import reactor
from twisted.names.client import createResolver
from twisted.names import cache, hosts as hostsModule, resolve
from twisted.names.client import Resolver
from twisted.python.runtime import platform

from scrapy import Spider, Request
from scrapy.crawler import CrawlerRunner
Expand All @@ -10,6 +12,17 @@
from tests.mockserver import MockServer, MockDNSServer


# https://stackoverflow.com/a/32784190
def createResolver(servers=None, resolvconf=None, hosts=None):
if hosts is None:
hosts = (b'/etc/hosts' if platform.getType() == 'posix'
else r'c:\windows\hosts')
theResolver = Resolver(resolvconf, servers)
hostResolver = hostsModule.Resolver(hosts)
L = [hostResolver, cache.CacheResolver(), theResolver]
return resolve.ResolverChain(L)


class LocalhostSpider(Spider):
name = "localhost_spider"

Expand Down
5 changes: 2 additions & 3 deletions tests/mockserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,8 @@ class MockDNSServer:
def __enter__(self):
self.proc = Popen([sys.executable, '-u', '-m', 'tests.mockserver', '-t', 'dns'],
stdout=PIPE, env=get_testenv())
host, port = self.proc.stdout.readline().strip().decode('ascii').split(":")
self.host = host
self.port = int(port)
self.host = '127.0.0.1'
self.port = int(self.proc.stdout.readline().strip().decode('ascii').split(":")[1])
return self

def __exit__(self, exc_type, exc_value, traceback):
Expand Down
5 changes: 5 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import optparse
import os
import platform
import subprocess
import sys
import tempfile
Expand All @@ -10,6 +11,7 @@
from shutil import rmtree, copytree
from tempfile import mkdtemp
from threading import Timer
from unittest import skipIf

from twisted.trial import unittest

Expand Down Expand Up @@ -319,6 +321,9 @@ def start_requests(self):
self.assertIn("start_requests", log)
self.assertIn("badspider.py", log)

# https://twistedmatrix.com/trac/ticket/9766
@skipIf(platform.system() == 'Windows' and sys.version_info >= (3, 8),
"the asyncio reactor is broken on Windows when running Python ≥ 3.8")
def test_asyncio_enabled_true(self):
log = self.get_log(self.debug_log_spider, args=[
'-s', 'TWISTED_REACTOR=twisted.internet.asyncioreactor.AsyncioSelectorReactor'
Expand Down
13 changes: 13 additions & 0 deletions tests/test_crawler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
import sys
import warnings
from unittest import skipIf

from pytest import raises, mark
from testfixtures import LogCapture
Expand Down Expand Up @@ -252,6 +253,9 @@ def test_crawler_runner_asyncio_enabled_true(self):
})

@defer.inlineCallbacks
# https://twistedmatrix.com/trac/ticket/9766
@skipIf(platform.system() == 'Windows' and sys.version_info >= (3, 8),
"the asyncio reactor is broken on Windows when running Python ≥ 3.8")
def test_crawler_process_asyncio_enabled_true(self):
with LogCapture(level=logging.DEBUG) as log:
if self.reactor_pytest == 'asyncio':
Expand Down Expand Up @@ -293,11 +297,17 @@ def test_simple(self):
self.assertIn('Spider closed (finished)', log)
self.assertNotIn("Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log)

# https://twistedmatrix.com/trac/ticket/9766
@skipIf(platform.system() == 'Windows' and sys.version_info >= (3, 8),
"the asyncio reactor is broken on Windows when running Python ≥ 3.8")
def test_asyncio_enabled_no_reactor(self):
log = self.run_script('asyncio_enabled_no_reactor.py')
self.assertIn('Spider closed (finished)', log)
self.assertIn("Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log)

# https://twistedmatrix.com/trac/ticket/9766
@skipIf(platform.system() == 'Windows' and sys.version_info >= (3, 8),
"the asyncio reactor is broken on Windows when running Python ≥ 3.8")
def test_asyncio_enabled_reactor(self):
log = self.run_script('asyncio_enabled_reactor.py')
self.assertIn('Spider closed (finished)', log)
Expand Down Expand Up @@ -327,6 +337,9 @@ def test_reactor_poll(self):
self.assertIn("Spider closed (finished)", log)
self.assertIn("Using reactor: twisted.internet.pollreactor.PollReactor", log)

# https://twistedmatrix.com/trac/ticket/9766
@skipIf(platform.system() == 'Windows' and sys.version_info >= (3, 8),
"the asyncio reactor is broken on Windows when running Python ≥ 3.8")
def test_reactor_asyncio(self):
log = self.run_script("twisted_reactor_asyncio.py")
self.assertIn("Spider closed (finished)", log)
Expand Down
8 changes: 7 additions & 1 deletion tests/test_feedexport.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,15 @@ def _random_temp_filename(self):
def run_and_export(self, spider_cls, settings):
""" Run spider with specified settings; return exported data. """

def path_to_url(path):
return urljoin('file:', pathname2url(str(path)))

def printf_escape(string):
return string.replace('%', '%%')

FEEDS = settings.get('FEEDS') or {}
settings['FEEDS'] = {
urljoin('file:', pathname2url(str(file_path))): feed
printf_escape(path_to_url(file_path)): feed
for file_path, feed in FEEDS.items()
}

Expand Down
3 changes: 3 additions & 0 deletions tests/test_proxy_connect.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
import platform
import re
import sys
from subprocess import Popen, PIPE
Expand Down Expand Up @@ -59,6 +60,8 @@ def _wrong_credentials(proxy_url):

@skipIf(sys.version_info < (3, 5, 4),
"requires mitmproxy < 3.0.0, which these tests do not support")
@skipIf(platform.system() == 'Windows' and sys.version_info < (3, 7),
"mitmproxy does not support Windows when running Python < 3.7")
class ProxyConnectTestCase(TestCase):

def setUp(self):
Expand Down
23 changes: 15 additions & 8 deletions tests/test_spiderloader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@
module_dir = os.path.dirname(os.path.abspath(__file__))


def _copytree(source, target):
try:
shutil.copytree(source, target)
except shutil.Error:
pass


class SpiderLoaderTest(unittest.TestCase):

def setUp(self):
orig_spiders_dir = os.path.join(module_dir, 'test_spiders')
self.tmpdir = tempfile.mkdtemp()
self.spiders_dir = os.path.join(self.tmpdir, 'test_spiders_xxx')
shutil.copytree(orig_spiders_dir, self.spiders_dir)
_copytree(orig_spiders_dir, self.spiders_dir)
sys.path.append(self.tmpdir)
settings = Settings({'SPIDER_MODULES': ['test_spiders_xxx']})
self.spider_loader = SpiderLoader.from_settings(settings)
Expand Down Expand Up @@ -124,7 +131,7 @@ def setUp(self):
self.tmpdir = self.mktemp()
os.mkdir(self.tmpdir)
self.spiders_dir = os.path.join(self.tmpdir, 'test_spiders_xxx')
shutil.copytree(orig_spiders_dir, self.spiders_dir)
_copytree(orig_spiders_dir, self.spiders_dir)
sys.path.append(self.tmpdir)
self.settings = Settings({'SPIDER_MODULES': ['test_spiders_xxx']})

Expand All @@ -134,8 +141,8 @@ def tearDown(self):

def test_dupename_warning(self):
# copy 1 spider module so as to have duplicate spider name
shutil.copyfile(os.path.join(self.tmpdir, 'test_spiders_xxx/spider3.py'),
os.path.join(self.tmpdir, 'test_spiders_xxx/spider3dupe.py'))
shutil.copyfile(os.path.join(self.tmpdir, 'test_spiders_xxx', 'spider3.py'),
os.path.join(self.tmpdir, 'test_spiders_xxx', 'spider3dupe.py'))

with warnings.catch_warnings(record=True) as w:
spider_loader = SpiderLoader.from_settings(self.settings)
Expand All @@ -156,10 +163,10 @@ def test_dupename_warning(self):
def test_multiple_dupename_warning(self):
# copy 2 spider modules so as to have duplicate spider name
# This should issue 2 warning, 1 for each duplicate spider name
shutil.copyfile(os.path.join(self.tmpdir, 'test_spiders_xxx/spider1.py'),
os.path.join(self.tmpdir, 'test_spiders_xxx/spider1dupe.py'))
shutil.copyfile(os.path.join(self.tmpdir, 'test_spiders_xxx/spider2.py'),
os.path.join(self.tmpdir, 'test_spiders_xxx/spider2dupe.py'))
shutil.copyfile(os.path.join(self.tmpdir, 'test_spiders_xxx', 'spider1.py'),
os.path.join(self.tmpdir, 'test_spiders_xxx', 'spider1dupe.py'))
shutil.copyfile(os.path.join(self.tmpdir, 'test_spiders_xxx', 'spider2.py'),
os.path.join(self.tmpdir, 'test_spiders_xxx', 'spider2dupe.py'))

with warnings.catch_warnings(record=True) as w:
spider_loader = SpiderLoader.from_settings(self.settings)
Expand Down
7 changes: 6 additions & 1 deletion tests/test_utils_asyncio.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from unittest import TestCase
import platform
import sys
from unittest import skipIf, TestCase

from pytest import mark

Expand All @@ -12,6 +14,9 @@ def test_is_asyncio_reactor_installed(self):
# the result should depend only on the pytest --reactor argument
self.assertEqual(is_asyncio_reactor_installed(), self.reactor_pytest == 'asyncio')

# https://twistedmatrix.com/trac/ticket/9766
@skipIf(platform.system() == 'Windows' and sys.version_info >= (3, 8),
"the asyncio reactor is broken on Windows when running Python ≥ 3.8")
def test_install_asyncio_reactor(self):
# this should do nothing
install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
18 changes: 15 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,12 @@ basepython = pypy3
commands =
py.test {posargs:--durations=10 docs scrapy tests}

[testenv:pinned]
basepython = python3
[pinned]
deps =
-ctests/constraints.txt
cryptography==2.0
cssselect==0.9.1
itemadapter==0.1.0
lxml==3.5.0
parsel==1.5.0
Protego==0.1.15
PyDispatcher==2.0.5
Expand All @@ -85,6 +83,20 @@ deps =
botocore==1.3.23
Pillow==3.4.2

[testenv:pinned]
basepython = python3
deps =
{[pinned]deps}
lxml==3.5.0

[testenv:windows-pinned]
basepython = python3
deps =
{[pinned]deps}
# First lxml version that includes a Windows wheel for Python 3.5, so we do
# not need to build lxml from sources in a CI Windows job:
lxml==3.8.0

[testenv:extra-deps]
deps =
{[testenv]deps}
Expand Down

0 comments on commit 3199048

Please sign in to comment.