Commit 15992102 authored by Jason Madden's avatar Jason Madden

Merge pull request #575 from gevent/travis-container

Use the TravisCI container infrastructure and enable linters
parents dedb08cf c24f612e
[pep8] [pep8]
ignore=E702,E265,E402,E731,E266 ignore=E702,E265,E402,E731,E266,E261,W503
max_line_length=160 max_line_length=160
exclude=.git,build,2.6,2.7,2.7pypy,3.3,test_support.py,test_queue.py,patched_tests_setup.py,test_threading_2.py,lock_tests.py,_sslgte279.py exclude=.tox,.git,build,2.6,2.7,2.7pypy,3.3,test_support.py,test_queue.py,patched_tests_setup.py,test_threading_2.py,lock_tests.py,_sslgte279.py
language: python language: python
sudo: false
python: python:
- "2.6" - 2.6
- "2.7" - 2.7
- "3.3" - pypy
- "3.4" - 3.3
- "pypy" - 3.4
env:
- LINT=true
- LINT=false
install:
# First install a newer pip so that it can use the wheel cache
# (only needed until travis upgrades pip to 7.x)
- travis_retry pip install -U pip
# Then start installing our deps. Note that use of --build-options / --global-options / --install-options
# disables the cache.
- travis_retry pip install -U tox cython greenlet pep8 pyflakes
script: script:
- if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then NWORKERS=4 PYTHON=pypy make travis_pypy; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' && $LINT == true ]]; then python setup.py develop && make travis_test_linters; elif [[ $LINT == false && $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then python setup.py develop && make toxtest; elif [[ $LINT == false ]]; then python setup.py develop && make fulltoxtest; fi
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy' ]]; then NWORKERS=4 PYTHON=python$TRAVIS_PYTHON_VERSION make travis_cpython; fi
notifications: notifications:
email: false email: false
# cache: pip seems not to work
cache:
directories:
- $HOME/.cache/pip
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
...@@ -35,7 +35,7 @@ doc: ...@@ -35,7 +35,7 @@ doc:
cd doc && PYTHONPATH=.. make html cd doc && PYTHONPATH=.. make html
whitespace: whitespace:
! find . -not -path "./.git/*" -not -path "./build/*" -not -path "./libev/*" -not -path "./c-ares/*" -not -path "./doc/_build/*" -not -path "./doc/mytheme/static/*" -type f | xargs egrep -l " $$" ! find . -not -path "./.tox/*" -not -path "*/__pycache__/*" -not -path "*.so" -not -path "*.pyc" -not -path "./.git/*" -not -path "./build/*" -not -path "./libev/*" -not -path "./gevent/libev/*" -not -path "./gevent.egg-info/*" -not -path "./dist/*" -not -path "./.DS_Store" -not -path "./c-ares/*" -not -path "./gevent/gevent.*.[ch]" -not -path "./gevent/core.pyx" -not -path "./doc/_build/*" -not -path "./doc/mytheme/static/*" -type f | xargs egrep -l " $$"
pep8: pep8:
${PYTHON} `which pep8` . ${PYTHON} `which pep8` .
...@@ -43,13 +43,12 @@ pep8: ...@@ -43,13 +43,12 @@ pep8:
pyflakes: pyflakes:
${PYTHON} util/pyflakes.py ${PYTHON} util/pyflakes.py
lint: whitespace pep8 pyflakes lint: whitespace pyflakes pep8
travistest: travistest:
which ${PYTHON} which ${PYTHON}
${PYTHON} --version ${PYTHON} --version
cd greenlet-* && ${PYTHON} setup.py install -q
${PYTHON} -c 'import greenlet; print(greenlet, greenlet.__version__)' ${PYTHON} -c 'import greenlet; print(greenlet, greenlet.__version__)'
${PYTHON} setup.py install ${PYTHON} setup.py install
...@@ -67,11 +66,13 @@ fulltoxtest: ...@@ -67,11 +66,13 @@ fulltoxtest:
cd greentest && GEVENT_RESOLVER=ares GEVENTARES_SERVERS=8.8.8.8 python testrunner.py --config ../known_failures.py --ignore tests_that_dont_use_resolver.txt cd greentest && GEVENT_RESOLVER=ares GEVENTARES_SERVERS=8.8.8.8 python testrunner.py --config ../known_failures.py --ignore tests_that_dont_use_resolver.txt
cd greentest && GEVENT_FILE=thread python testrunner.py --config ../known_failures.py `grep -l subprocess test_*.py` cd greentest && GEVENT_FILE=thread python testrunner.py --config ../known_failures.py `grep -l subprocess test_*.py`
leaktest:
GEVENTSETUP_EV_VERIFY=3 GEVENTTEST_LEAKCHECK=1 make travistest
bench: bench:
${PYTHON} greentest/bench_sendall.py ${PYTHON} greentest/bench_sendall.py
travis_pypy: travis_pypy:
# no need to repeat linters here
which ${PYTHON} which ${PYTHON}
${PYTHON} --version ${PYTHON} --version
${PYTHON} setup.py install ${PYTHON} setup.py install
...@@ -79,24 +80,13 @@ travis_pypy: ...@@ -79,24 +80,13 @@ travis_pypy:
cd greentest && ${PYTHON} testrunner.py --config ../known_failures.py cd greentest && ${PYTHON} testrunner.py --config ../known_failures.py
travis_cpython: travis_cpython:
sudo add-apt-repository -y ppa:chris-lea/cython pip install cython greenlet
# somehow travis changed something and python2.6 and python3.3 no longer accessible anymore
sudo add-apt-repository -y ppa:fkrull/deadsnakes
sudo apt-get -qq -y update
sudo -E apt-get -qq -y install ${PYTHON} ${PYTHON}-dev
sudo apt-get -qq -y install cython
cython --version
pip install -q --download . greenlet
unzip -q greenlet-*.zip
sudo -E make travistest
sudo -E apt-get install ${PYTHON}-dbg make travistest
sudo -E PYTHON=${PYTHON}-dbg GEVENTSETUP_EV_VERIFY=3 make travistest travis_test_linters:
make lint
make leaktest
.PHONY: clean all doc pep8 whitespace pyflakes lint travistest travis .PHONY: clean all doc pep8 whitespace pyflakes lint travistest travis
...@@ -40,12 +40,24 @@ To install the latest development version: ...@@ -40,12 +40,24 @@ To install the latest development version:
running tests running tests
------------- -------------
There are a few different ways to run the tests. To simply run the
tests on one version of Python during development, try this:
python setup.py build python setup.py build
cd greentest cd greentest
PYTHONPATH=.. python testrunner.py --config ../known_failures.py PYTHONPATH=.. python testrunner.py --config ../known_failures.py
Before submitting a pull request, it's a good idea to run the tests
across all supported versions of Python, and to check the code quality
using pep8 and pyflakes. This is what is done on Travis CI. Locally it
can be done using tox:
pip install tox
tox
.. _gevent: http://www.gevent.org .. _gevent: http://www.gevent.org
.. _greenlet: http://pypi.python.org/pypi/greenlet .. _greenlet: http://pypi.python.org/pypi/greenlet
...@@ -61,4 +73,3 @@ running tests ...@@ -61,4 +73,3 @@ running tests
.. _mailing list: http://groups.google.com/group/gevent .. _mailing list: http://groups.google.com/group/gevent
.. _blog: http://blog.gevent.org .. _blog: http://blog.gevent.org
.. _twitter (@gevent): http://twitter.com/gevent .. _twitter (@gevent): http://twitter.com/gevent
...@@ -298,7 +298,7 @@ class socket(object): ...@@ -298,7 +298,7 @@ class socket(object):
while True: while True:
data_sent += self.send(_get_memory(data, data_sent), flags, timeout=timeleft) data_sent += self.send(_get_memory(data, data_sent), flags, timeout=timeleft)
if data_sent >= len(data): if data_sent >= len(data):
break return
timeleft = end - time.time() timeleft = end - time.time()
if timeleft <= 0: if timeleft <= 0:
raise timeout('timed out') raise timeout('timed out')
...@@ -398,6 +398,7 @@ else: ...@@ -398,6 +398,7 @@ else:
if hasattr(__socket__, 'ssl'): if hasattr(__socket__, 'ssl'):
from gevent.hub import PYGTE279 from gevent.hub import PYGTE279
def ssl(sock, keyfile=None, certfile=None): def ssl(sock, keyfile=None, certfile=None):
# deprecated in 2.7.9 but still present # deprecated in 2.7.9 but still present
if PYGTE279: if PYGTE279:
......
...@@ -127,6 +127,7 @@ class BaseServer(object): ...@@ -127,6 +127,7 @@ class BaseServer(object):
def do_handle(self, *args): def do_handle(self, *args):
spawn = self._spawn spawn = self._spawn
handle = self._handle handle = self._handle
def _close_when_done(*args): def _close_when_done(*args):
try: try:
return handle(*args) return handle(*args)
......
...@@ -1058,6 +1058,7 @@ class child(watcher): ...@@ -1058,6 +1058,7 @@ class child(watcher):
def rstatus(self, value): def rstatus(self, value):
self._watcher.rstatus = value self._watcher.rstatus = value
class stat(watcher): class stat(watcher):
_watcher_start = libev.ev_stat_start _watcher_start = libev.ev_stat_start
_watcher_stop = libev.ev_stat_stop _watcher_stop = libev.ev_stat_stop
...@@ -1106,6 +1107,7 @@ class stat(watcher): ...@@ -1106,6 +1107,7 @@ class stat(watcher):
def interval(self): def interval(self):
return self._watcher.interval return self._watcher.interval
def _syserr_cb(msg): def _syserr_cb(msg):
try: try:
msg = ffi.string(msg) msg = ffi.string(msg)
......
...@@ -41,7 +41,7 @@ if PYPY: ...@@ -41,7 +41,7 @@ if PYPY:
# otherwise we get a new DummyThread, which cannot be joined. # otherwise we get a new DummyThread, which cannot be joined.
# Fixes tests in test_threading_2 # Fixes tests in test_threading_2
if _get_ident() not in __threading__._active and len(__threading__._active) == 1: if _get_ident() not in __threading__._active and len(__threading__._active) == 1:
k,v = __threading__._active.items()[0] k, v = __threading__._active.items()[0]
del __threading__._active[k] del __threading__._active[k]
__threading__._active[_get_ident()] = v __threading__._active[_get_ident()] = v
......
...@@ -85,9 +85,33 @@ def wrap_timeout(timeout, method): ...@@ -85,9 +85,33 @@ def wrap_timeout(timeout, method):
def wrap_refcount(method): def wrap_refcount(method):
if gettotalrefcount is None: if not os.getenv('GEVENTTEST_LEAKCHECK'):
return method return method
# Some builtin things that we ignore
IGNORED_TYPES = (tuple, dict)
def type_hist():
import collections
d = collections.defaultdict(int)
for x in gc.get_objects():
k = type(x)
if k in IGNORED_TYPES:
continue
d[k] += 1
return d
def report_diff(a, b):
diff_lines = []
for k, v in sorted(a.items()):
if b[k] != v:
diff_lines.append("%s: %s != %s" % (k, v, b[k]))
if not diff_lines:
return None
diff = '\n'.join(diff_lines)
return diff
@wraps(method) @wraps(method)
def wrapped(self, *args, **kwargs): def wrapped(self, *args, **kwargs):
gc.collect() gc.collect()
...@@ -98,16 +122,29 @@ def wrap_refcount(method): ...@@ -98,16 +122,29 @@ def wrap_refcount(method):
gc.disable() gc.disable()
try: try:
while True: while True:
d = gettotalrefcount()
# Grab current snapshot
hist_before = type_hist()
d = sum(hist_before.values())
self.setUp() self.setUp()
method(self, *args, **kwargs) method(self, *args, **kwargs)
self.tearDown() self.tearDown()
# Grab post snapshot
if 'urlparse' in sys.modules: if 'urlparse' in sys.modules:
sys.modules['urlparse'].clear_cache() sys.modules['urlparse'].clear_cache()
if 'urllib.parse' in sys.modules: if 'urllib.parse' in sys.modules:
sys.modules['urllib.parse'].clear_cache() sys.modules['urllib.parse'].clear_cache()
d = gettotalrefcount() - d hist_after = type_hist()
d = sum(hist_after.values()) - d
deltas.append(d) deltas.append(d)
# Reset and check for cycles
gc.collect()
if gc.garbage:
raise AssertionError("Generated uncollectable garbage")
# the following configurations are classified as "no leak" # the following configurations are classified as "no leak"
# [0, 0] # [0, 0]
# [x, 0, 0] # [x, 0, 0]
...@@ -122,7 +159,8 @@ def wrap_refcount(method): ...@@ -122,7 +159,8 @@ def wrap_refcount(method):
elif len(deltas) >= 4 and sum(deltas[-4:]) == 0: elif len(deltas) >= 4 and sum(deltas[-4:]) == 0:
break break
elif len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]: elif len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]:
raise AssertionError('refcount increased by %r' % (deltas, )) diff = report_diff(hist_before, hist_after)
raise AssertionError('refcount increased by %r\n%s' % (deltas, diff))
# OK, we don't know for sure yet. Let's search for more # OK, we don't know for sure yet. Let's search for more
if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2: if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2:
# this is suspicious, so give a few more runs # this is suspicious, so give a few more runs
...@@ -130,7 +168,7 @@ def wrap_refcount(method): ...@@ -130,7 +168,7 @@ def wrap_refcount(method):
else: else:
limit = 7 limit = 7
if len(deltas) >= limit: if len(deltas) >= limit:
raise AssertionError('refcount increased by %r' % (deltas, )) raise AssertionError('refcount increased by %r\n%s' % (deltas, report_diff(hist_before, hist_after)))
finally: finally:
gc.enable() gc.enable()
self.skipTearDown = True self.skipTearDown = True
......
...@@ -10,6 +10,7 @@ import util ...@@ -10,6 +10,7 @@ import util
import ssl import ssl
class Test_wsgiserver(util.TestServer): class Test_wsgiserver(util.TestServer):
server = 'wsgiserver.py' server = 'wsgiserver.py'
URL = 'http://127.0.0.1:8088' URL = 'http://127.0.0.1:8088'
......
...@@ -10,7 +10,6 @@ except ImportError as ex: ...@@ -10,7 +10,6 @@ except ImportError as ex:
sys.exit(0) sys.exit(0)
class TestPickle(greentest.TestCase): class TestPickle(greentest.TestCase):
# Issue 104: ares.ares_host_result unpickleable # Issue 104: ares.ares_host_result unpickleable
...@@ -24,7 +23,7 @@ class TestPickle(greentest.TestCase): ...@@ -24,7 +23,7 @@ class TestPickle(greentest.TestCase):
for i in range(0, pickle.HIGHEST_PROTOCOL): for i in range(0, pickle.HIGHEST_PROTOCOL):
def make_test(j): def make_test(j):
return lambda self: self._test(j) return lambda self: self._test(j)
setattr(TestPickle, 'test' + str(i), make_test(i) ) setattr(TestPickle, 'test' + str(i), make_test(i))
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -25,7 +25,6 @@ class Test(greentest.TestCase): ...@@ -25,7 +25,6 @@ class Test(greentest.TestCase):
def test(self): def test(self):
server = backdoor.BackdoorServer(('127.0.0.1', 0)) server = backdoor.BackdoorServer(('127.0.0.1', 0))
server.start()
def connect(): def connect():
conn = create_connection(('127.0.0.1', server.server_port)) conn = create_connection(('127.0.0.1', server.server_port))
...@@ -34,9 +33,12 @@ class Test(greentest.TestCase): ...@@ -34,9 +33,12 @@ class Test(greentest.TestCase):
line = conn.makefile().readline() line = conn.makefile().readline()
assert line.strip() == '4', repr(line) assert line.strip() == '4', repr(line)
jobs = [gevent.spawn(connect) for _ in xrange(10)] server.start()
gevent.joinall(jobs) try:
server.close() jobs = [gevent.spawn(connect) for _ in xrange(10)]
gevent.joinall(jobs, raise_error=True)
finally:
server.close()
#self.assertEqual(conn.recv(1), '') #self.assertEqual(conn.recv(1), '')
def test_quit(self): def test_quit(self):
......
...@@ -266,6 +266,7 @@ class TestStuff(greentest.TestCase): ...@@ -266,6 +266,7 @@ class TestStuff(greentest.TestCase):
self.assertEqual(e.get(), 1) self.assertEqual(e.get(), 1)
def test_wait_error(self): def test_wait_error(self):
def x(): def x():
sleep(DELAY) sleep(DELAY)
return 1 return 1
...@@ -293,6 +294,7 @@ class TestStuff(greentest.TestCase): ...@@ -293,6 +294,7 @@ class TestStuff(greentest.TestCase):
# works. # works.
def raises_but_ignored(): def raises_but_ignored():
raise ExpectedError("count") raise ExpectedError("count")
def sleep_forever(): def sleep_forever():
while True: while True:
sleep(0.1) sleep(0.1)
......
...@@ -1000,7 +1000,6 @@ class ChunkedInputTests(TestCase): ...@@ -1000,7 +1000,6 @@ class ChunkedInputTests(TestCase):
self.assert_error(IOError, 'unexpected end of file while parsing chunked data') self.assert_error(IOError, 'unexpected end of file while parsing chunked data')
class Expect100ContinueTests(TestCase): class Expect100ContinueTests(TestCase):
validator = None validator = None
......
...@@ -25,6 +25,7 @@ are not leaked by the hub. ...@@ -25,6 +25,7 @@ are not leaked by the hub.
from __future__ import print_function from __future__ import print_function
from _socket import socket from _socket import socket
class Socket(socket): class Socket(socket):
"Something we can have a weakref to" "Something we can have a weakref to"
......
...@@ -33,6 +33,7 @@ class SimpleStreamServer(StreamServer): ...@@ -33,6 +33,7 @@ class SimpleStreamServer(StreamServer):
finally: finally:
fd.close() fd.close()
class Settings: class Settings:
ServerClass = StreamServer ServerClass = StreamServer
ServerSubClass = SimpleStreamServer ServerSubClass = SimpleStreamServer
......
...@@ -51,11 +51,12 @@ class TestTCP(greentest.TestCase): ...@@ -51,11 +51,12 @@ class TestTCP(greentest.TestCase):
self.port = listener.getsockname()[1] self.port = listener.getsockname()[1]
def cleanup(self): def cleanup(self):
try: if hasattr(self, 'listener'):
self.listener.close() try:
except: self.listener.close()
pass except:
del self.listener pass
del self.listener
def create_connection(self): def create_connection(self):
sock = socket.socket() sock = socket.socket()
...@@ -146,20 +147,25 @@ class TestTCP(greentest.TestCase): ...@@ -146,20 +147,25 @@ class TestTCP(greentest.TestCase):
if sys.platform != 'win32': if sys.platform != 'win32':
def test_sendall_timeout(self): def test_sendall_timeout(self):
# Travis-CI container infrastructure is configured with
# large socket buffers, at least 2MB, as-of Jun 3, 2015,
# so we must be sure to send more data than that.
data_sent = b'hello' * 1000000
client_sock = [] client_sock = []
acceptor = Thread(target=lambda: client_sock.append(self.listener.accept())) acceptor = Thread(target=lambda: client_sock.append(self.listener.accept()))
client = self.create_connection() client = self.create_connection()
time.sleep(0.1) time.sleep(0.1)
assert client_sock assert client_sock
client.settimeout(0.1) client.settimeout(0.1)
data_sent = b'h' * 1000000
start = time.time() start = time.time()
self.assertRaises(self.TIMEOUT_ERROR, client.sendall, data_sent) try:
took = time.time() - start self.assertRaises(self.TIMEOUT_ERROR, client.sendall, data_sent)
assert 0.1 - 0.01 <= took <= 0.1 + 0.1, took took = time.time() - start
acceptor.join() assert 0.1 - 0.01 <= took <= 0.1 + 0.1, took
client.close() finally:
client_sock[0][0].close() acceptor.join()
client.close()
client_sock[0][0].close()
def test_makefile(self): def test_makefile(self):
......
...@@ -105,7 +105,7 @@ def compare_relaxed(a, b): ...@@ -105,7 +105,7 @@ def compare_relaxed(a, b):
a_segments = a.count(':') a_segments = a.count(':')
b_segments = b.count(':') b_segments = b.count(':')
if a_segments and b_segments: if a_segments and b_segments:
if a_segments == b_segments and a_segments in (4,5,6,7): if a_segments == b_segments and a_segments in (4, 5, 6, 7):
return True return True
if a.rstrip(':').startswith(b.rstrip(':')) or b.rstrip(':').startswith(a.rstrip(':')): if a.rstrip(':').startswith(b.rstrip(':')) or b.rstrip(':').startswith(a.rstrip(':')):
return True return True
...@@ -245,8 +245,7 @@ class TestCase(greentest.TestCase): ...@@ -245,8 +245,7 @@ class TestCase(greentest.TestCase):
# because it calls assertSequenceEqual, which highlights the exact # because it calls assertSequenceEqual, which highlights the exact
# difference in the tuple # difference in the tuple
msg = format_call(func, args) msg = format_call(func, args)
self.assertEqual((msg,gevent_result), (msg,real_result)) self.assertEqual((msg, gevent_result), (msg, real_result))
class TestTypeError(TestCase): class TestTypeError(TestCase):
......
[tox] [tox]
envlist = envlist =
py26,py27,pypy,py33,py34 py26,py27,pypy,py33,py34,lint
[testenv] [testenv]
deps = deps =
greenlet greenlet
cython
whitelist_externals = whitelist_externals =
* *
commands = commands =
...@@ -17,3 +18,27 @@ commands = ...@@ -17,3 +18,27 @@ commands =
[testenv:pypy] [testenv:pypy]
deps = deps =
[testenv:lint]
basepython =
python2.7
deps =
{[testenv]deps}
pep8
pyflakes
commands =
make lint
[testenv:travis-lint]
basepython =
python2.7
deps =
{[testenv:lint]deps}
commands =
make travis_test_linters
[testenv:leak]
basepython =
python2.7
commonds =
make leaktest
...@@ -15,6 +15,7 @@ gevent/subprocess.py:\d+: undefined name ...@@ -15,6 +15,7 @@ gevent/subprocess.py:\d+: undefined name
gevent/_?ssl[23]?.py:\d+: undefined name gevent/_?ssl[23]?.py:\d+: undefined name
gevent/__init__.py:\d+:.*imported but unused gevent/__init__.py:\d+:.*imported but unused
gevent/__init__.py:\d+: redefinition of unused 'signal' from line gevent/__init__.py:\d+: redefinition of unused 'signal' from line
gevent/__init__.py:\d+: redefinition of unused 'socket' from line
gevent/coros.py:\d+: 'from gevent.lock import *' used; unable to detect undefined names gevent/coros.py:\d+: 'from gevent.lock import *' used; unable to detect undefined names
gevent/coros.py:\d+: '__all__' imported but unused gevent/coros.py:\d+: '__all__' imported but unused
gevent/hub.py:\d+: 'reraise' imported but unused gevent/hub.py:\d+: 'reraise' imported but unused
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment