Commit c34abdc0 authored by Jason Madden's avatar Jason Madden

Let tests run without having gevent installed, just on pythonpath. Fixes #1409

parent c0c91a5d
......@@ -117,7 +117,10 @@ class BaseServer(object):
# XXX: FIXME: Subclasses rely on the presence or absence of the
# `socket` attribute to determine whether we are open/should be opened.
# Instead, have it be None.
self.pool = None
# XXX: In general, the state management here is confusing. Lots of stuff is
# deferred until the various ``set_`` methods are called, and it's not documented
# when it's safe to call those
self.pool = None # can be set from ``spawn``; overrides self.full()
try:
self.set_listener(listener)
self.set_spawn(spawn)
......@@ -250,9 +253,9 @@ class BaseServer(object):
self.delay = min(self.max_delay, self.delay * 2)
break
def full(self):
# copied from self.pool
# pylint: disable=method-hidden
def full(self): # pylint: disable=method-hidden
# If a Pool is given for to ``set_spawn`` (the *spawn* argument
# of the constructor) it will replace this method.
return False
def __repr__(self):
......
......@@ -101,6 +101,7 @@ def log(message, *args, **kwargs):
:keyword str color: One of the values from _colorscheme
"""
with output_lock: # pylint:disable=not-context-manager
color = kwargs.pop('color', 'normal')
try:
if args:
......@@ -192,6 +193,8 @@ def kill(popen):
IGNORED_GEVENT_ENV_KEYS = {
'GEVENTTEST_QUIET',
'GEVENT_DEBUG',
'GEVENTSETUP_EV_VERIFY',
'GEVENTSETUP_EMBED',
}
# A set of (name, value) pairs we ignore for printing purposes.
......@@ -236,10 +239,7 @@ def start(command, quiet=False, **kwargs):
if preexec_fn is not None:
setenv['DO_NOT_SETPGRP'] = '1'
if setenv:
if env:
env = env.copy()
else:
env = os.environ.copy()
env = env.copy() if env else os.environ.copy()
env.update(setenv)
if not quiet:
......@@ -272,16 +272,22 @@ class RunResult(object):
command,
run_kwargs,
code,
output=None, name=None,
output=None, # type: str
error=None, # type: str
name=None,
run_count=0, skipped_count=0):
self.command = command
self.run_kwargs = run_kwargs
self.code = code
self.output = output
self.error = error
self.name = name
self.run_count = run_count
self.skipped_count = skipped_count
@property
def output_lines(self):
return self.output.splitlines()
def __bool__(self):
return not bool(self.code)
......@@ -291,6 +297,27 @@ class RunResult(object):
def __int__(self):
return self.code
def __repr__(self):
return (
"RunResult of: %r\n"
"Code: %s\n"
"kwargs: %r\n"
"Output:\n"
"----\n"
"%s"
"----\n"
"Error:\n"
"----\n"
"%s"
"----\n"
) % (
self.command,
self.code,
self.run_kwargs,
self.output,
self.error
)
def _should_show_warning_output(out):
if 'Warning' in out:
......@@ -310,6 +337,8 @@ def _should_show_warning_output(out):
out = out.replace('UserWarning: libuv only supports', 'NADA')
# Packages on Python 2
out = out.replace('ImportWarning: Not importing directory', 'NADA')
# Testing that U mode does the same thing
out = out.replace("DeprecationWarning: 'U' mode is deprecated", 'NADA')
return 'Warning' in out
output_lock = threading.Lock()
......@@ -337,7 +366,11 @@ def _find_test_status(took, out):
def run(command, **kwargs): # pylint:disable=too-many-locals
"Execute *command*, returning a `RunResult`"
"""
Execute *command*, returning a `RunResult`.
This blocks until *command* finishes or until it times out.
"""
buffer_output = kwargs.pop('buffer_output', BUFFER_OUTPUT)
quiet = kwargs.pop('quiet', QUIET)
verbose = not quiet
......@@ -348,6 +381,7 @@ def run(command, **kwargs): # pylint:disable=too-many-locals
kwargs['stdout'] = subprocess.PIPE
popen = start(command, quiet=nested, **kwargs)
name = popen.name
try:
time_start = time.time()
out, err = popen.communicate()
......@@ -358,14 +392,13 @@ def run(command, **kwargs): # pylint:disable=too-many-locals
result = popen.poll()
finally:
kill(popen)
assert not err
with output_lock: # pylint:disable=not-context-manager
failed = bool(result)
if out:
out = out.strip()
out = out if isinstance(out, str) else out.decode('utf-8', 'ignore')
if out and (failed or verbose or _should_show_warning_output(out)):
if out:
out = ' ' + out.replace('\n', '\n ')
out = out.rstrip()
out += '\n'
......@@ -377,8 +410,13 @@ def run(command, **kwargs): # pylint:disable=too-many-locals
log('- %s %s', name, status)
if took >= MIN_RUNTIME:
runtimelog.append((-took, name))
return RunResult(command, kwargs, result,
output=out, name=name, run_count=run_count, skipped_count=skipped_count)
return RunResult(
command, kwargs, result,
output=out, error=err,
name=name,
run_count=run_count,
skipped_count=skipped_count
)
class NoSetupPyFound(Exception):
......@@ -453,8 +491,33 @@ def find_stdlib_tests():
return directory, full_directory
def absolute_pythonpath():
"""
Return the PYTHONPATH environment variable (if set) with each
entry being an absolute path. If not set, returns None.
"""
if 'PYTHONPATH' not in os.environ:
return None
path = os.environ['PYTHONPATH']
path = [os.path.abspath(p) for p in path.split(os.path.pathsep)]
return os.path.pathsep.join(path)
class ExampleMixin(object):
"Something that uses the examples/ directory"
"""
Something that uses the ``examples/`` directory
from the root of the gevent distribution.
The `cwd` property is set to the root of the gevent distribution.
"""
#: Arguments to pass to the example file.
example_args = []
before_delay = 3
after_delay = 0.5
#: Path of the example Python file, relative to `cwd`
example = None # subclasses define this to be the path to the server.py
#: Keyword arguments to pass to the start or run method.
start_kwargs = None
def find_setup_py(self):
"Return the directory containing setup.py"
......@@ -469,31 +532,64 @@ class ExampleMixin(object):
root = self.find_setup_py()
except NoSetupPyFound as e:
raise unittest.SkipTest("Unable to locate file/dir to run: %s" % (e,))
return os.path.join(root, 'examples')
class TestServer(ExampleMixin,
unittest.TestCase):
args = []
before_delay = 3
after_delay = 0.5
popen = None
server = None # subclasses define this to be the path to the server.py
start_kwargs = None
@property
def setenv(self):
"""
Returns a dictionary of environment variables to set for the
child in addition to (or replacing) the ones already in the
environment.
Since the child is run in `cwd`, relative paths in ``PYTHONPATH``
need to be converted to absolute paths.
"""
abs_pythonpath = absolute_pythonpath()
return {'PYTHONPATH': abs_pythonpath} if abs_pythonpath else None
def _start(self, meth):
if getattr(self, 'args', None):
raise AssertionError("Invalid test", self, self.args)
if getattr(self, 'server', None):
raise AssertionError("Invalid test", self, self.server)
def start(self):
try:
kwargs = self.start_kwargs or {}
return start([sys.executable, '-u', self.server] + self.args, cwd=self.cwd, **kwargs)
# These could be or are properties that can raise
server = self.example
server_dir = self.cwd
except NoSetupPyFound as e:
raise unittest.SkipTest("Unable to locate file/dir to run: %s" % (e,))
kwargs = self.start_kwargs or {}
setenv = self.setenv
if setenv:
if 'setenv' in kwargs:
kwargs['setenv'].update(setenv)
else:
kwargs['setenv'] = setenv
return meth(
[sys.executable, '-W', 'ignore', '-u', server] + self.example_args,
cwd=server_dir,
**kwargs
)
def start_example(self):
return self._start(meth=start)
def run_example(self):# run() is a unittest method.
return self._start(meth=run)
class TestServer(ExampleMixin,
unittest.TestCase):
popen = None
def running_server(self):
from contextlib import contextmanager
@contextmanager
def running_server():
with self.start() as popen:
with self.start_example() as popen:
self.popen = popen
self.before()
yield
......@@ -507,12 +603,18 @@ class TestServer(ExampleMixin,
def before(self):
if self.before_delay is not None:
time.sleep(self.before_delay)
assert self.popen.poll() is None, '%s died with code %s' % (self.server, self.popen.poll(), )
self.assertIsNone(self.popen.poll(),
'%s died with code %s' % (
self.example, self.popen.poll(),
))
def after(self):
if self.after_delay is not None:
time.sleep(self.after_delay)
assert self.popen.poll() is None, '%s died with code %s' % (self.server, self.popen.poll(), )
self.assertIsNone(self.popen.poll(),
'%s died with code %s' % (
self.example, self.popen.poll(),
))
def _run_all_tests(self):
ran = False
......
......@@ -6,7 +6,7 @@ from gevent.testing import util
from gevent.testing import params
class Test(util.TestServer):
server = 'echoserver.py'
example = 'echoserver.py'
def _run_all_tests(self):
def test_client(message):
......
......@@ -13,9 +13,9 @@ from gevent.testing import util
@greentest.skipOnLibuvOnCIOnPyPy("Timing issues sometimes lead to connection refused")
class Test(util.TestServer):
server = 'portforwarder.py'
example = 'portforwarder.py'
# [listen on, forward to]
args = ['127.0.0.1:10011', '127.0.0.1:10012']
example_args = ['127.0.0.1:10011', '127.0.0.1:10012']
if greentest.WIN:
from subprocess import CREATE_NEW_PROCESS_GROUP
......@@ -40,7 +40,7 @@ class Test(util.TestServer):
break
log.append(data)
server = StreamServer(self.args[1], handle)
server = StreamServer(self.example_args[1], handle)
server.start()
try:
conn = socket.create_connection(('127.0.0.1', 10011))
......
from gevent import monkey
monkey.patch_all(subprocess=True)
import sys
from gevent.server import DatagramServer
from gevent.testing.util import run
from gevent.testing import util
from gevent.testing import main
class Test_udp_client(util.TestServer):
start_kwargs = {'timeout': 10}
example = 'udp_client.py'
example_args = ['Test_udp_client']
def test(self):
log = []
......@@ -20,8 +23,7 @@ class Test_udp_client(util.TestServer):
server = DatagramServer('127.0.0.1:9001', handle)
server.start()
try:
run([sys.executable, '-W', 'ignore', '-u', 'udp_client.py', 'Test_udp_client'],
timeout=10, cwd=self.cwd)
self.run_example()
finally:
server.close()
self.assertEqual(log, [b'Test_udp_client'])
......
......@@ -5,7 +5,7 @@ from gevent.testing import main
class Test(util.TestServer):
server = 'udp_server.py'
example = 'udp_server.py'
def _run_all_tests(self):
sock = socket.socket(type=socket.SOCK_DGRAM)
......
......@@ -9,7 +9,7 @@ from . import test__example_wsgiserver
@greentest.skipOnCI("Timing issues sometimes lead to a connection refused")
@greentest.skipWithoutExternalNetwork("Tries to reach google.com")
class Test_webproxy(test__example_wsgiserver.Test_wsgiserver):
server = 'webproxy.py'
example = 'webproxy.py'
def _run_all_tests(self):
status, data = self.read('/')
......
......@@ -16,7 +16,7 @@ from gevent.testing import params
@greentest.skipOnCI("Timing issues sometimes lead to a connection refused")
class Test_wsgiserver(util.TestServer):
server = 'wsgiserver.py'
example = 'wsgiserver.py'
URL = 'http://%s:8088' % (params.DEFAULT_LOCAL_HOST_ADDR,)
PORT = 8088
not_found_message = b'<h1>Not Found</h1>'
......
......@@ -9,7 +9,7 @@ from . import test__example_wsgiserver
@greentest.skipOnCI("Timing issues sometimes lead to a connection refused")
class Test_wsgiserver_ssl(test__example_wsgiserver.Test_wsgiserver):
server = 'wsgiserver_ssl.py'
example = 'wsgiserver_ssl.py'
URL = 'https://%s:8443' % (params.DEFAULT_LOCAL_HOST_ADDR,)
PORT = 8443
_use_ssl = True
......
......@@ -11,7 +11,6 @@ most commonly the resource will be ``network``. You can use this technique to sp
non-existant resources for things that should never be tested.
"""
import re
import sys
import os
import glob
import time
......@@ -45,12 +44,12 @@ time_ranges = {
class _AbstractTestMixin(util.ExampleMixin):
time_range = default_time_range
filename = None
example = None
def _check_resources(self):
from gevent.testing import resources
with open(os.path.join(self.cwd, self.filename), 'r') as f:
with open(os.path.join(self.cwd, self.example), 'r') as f:
contents = f.read()
pattern = re.compile('^# gevent-test-requires-resource: (.*)$', re.MULTILINE)
......@@ -64,14 +63,15 @@ class _AbstractTestMixin(util.ExampleMixin):
start = time.time()
min_time, max_time = self.time_range
if not util.run([sys.executable, '-u', self.filename],
timeout=max_time,
cwd=self.cwd,
quiet=True,
buffer_output=True,
nested=True,
setenv={'GEVENT_DEBUG': 'error'}):
self.fail("Failed example: " + self.filename)
self.start_kwargs = {
'timeout': max_time,
'quiet': True,
'buffer_output': True,
'nested': True,
'setenv': {'GEVENT_DEBUG': 'error'}
}
if not self.run_example():
self.fail("Failed example: " + self.example)
else:
took = time.time() - start
self.assertGreaterEqual(took, min_time)
......@@ -94,7 +94,7 @@ def _build_test_classes():
'Test_' + bn,
(_AbstractTestMixin, greentest.TestCase),
{
'filename': bn,
'example': bn,
'time_range': time_ranges.get(bn, _AbstractTestMixin.time_range)
}
)
......
......@@ -13,15 +13,15 @@ import os
import os.path
import sys
from subprocess import Popen
from subprocess import PIPE
from gevent import testing as greentest
from gevent.testing.util import absolute_pythonpath
from gevent.testing.util import run
class TestRun(greentest.TestCase):
maxDiff = None
def setUp(self):
self.abs_pythonpath = absolute_pythonpath() # before we cd
self.cwd = os.getcwd()
os.chdir(os.path.dirname(__file__))
......@@ -31,29 +31,42 @@ class TestRun(greentest.TestCase):
def _run(self, script, module=False):
env = os.environ.copy()
env['PYTHONWARNINGS'] = 'ignore'
if self.abs_pythonpath:
env['PYTHONPATH'] = self.abs_pythonpath
run_kwargs = dict(
buffer_output=True,
quiet=True,
nested=True,
env=env,
timeout=10,
)
args = [sys.executable, '-m', 'gevent.monkey']
if module:
args.append('--module')
args += [script, 'patched']
p = Popen(args, stdout=PIPE, stderr=PIPE, env=env)
monkey_out, monkey_err = p.communicate()
self.assertEqual(0, p.returncode, (p.returncode, monkey_out, monkey_err))
monkey_result = run(
args,
**run_kwargs
)
self.assertTrue(monkey_result)
if module:
args = [sys.executable, "-m", script, 'stdlib']
else:
args = [sys.executable, script, 'stdlib']
p = Popen(args, stdout=PIPE, stderr=PIPE)
std_out, std_err = p.communicate()
self.assertEqual(0, p.returncode, (p.returncode, std_out, std_err))
monkey_out_lines = monkey_out.decode("utf-8").splitlines()
std_out_lines = std_out.decode('utf-8').splitlines()
std_result = run(
args,
**run_kwargs
)
self.assertTrue(std_result)
monkey_out_lines = monkey_result.output_lines
std_out_lines = std_result.output_lines
self.assertEqual(monkey_out_lines, std_out_lines)
self.assertEqual(monkey_err, std_err)
self.assertEqual(monkey_result.error, std_result.error)
return monkey_out_lines, monkey_err
return monkey_out_lines, monkey_result.error
def test_run_simple(self):
self._run(os.path.join('monkey_package', 'script.py'))
......@@ -61,8 +74,8 @@ class TestRun(greentest.TestCase):
def _run_package(self, module):
lines, _ = self._run('monkey_package', module=module)
self.assertTrue(lines[0].endswith('__main__.py'), lines[0])
self.assertEqual(lines[1], '__main__')
self.assertTrue(lines[0].endswith(u'__main__.py'), lines[0])
self.assertEqual(lines[1].strip(), u'__main__')
def test_run_package(self):
# Run a __main__ inside a package, even without specifying -m
......@@ -75,10 +88,10 @@ class TestRun(greentest.TestCase):
def test_issue_302(self):
lines, _ = self._run(os.path.join('monkey_package', 'issue302monkey.py'))
self.assertEqual(lines[0], 'True')
lines[1] = lines[1].replace('\\', '/') # windows path
self.assertEqual(lines[1], 'monkey_package/issue302monkey.py')
self.assertEqual(lines[2], 'True', lines)
self.assertEqual(lines[0].strip(), u'True')
lines[1] = lines[1].replace(u'\\', u'/') # windows path
self.assertEqual(lines[1].strip(), u'monkey_package/issue302monkey.py')
self.assertEqual(lines[2].strip(), u'True', lines)
# These three tests all sometimes fail on Py2 on CI, writing
# to stderr:
......
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