Commit bb7cf8fb authored by Jason Madden's avatar Jason Madden Committed by GitHub

Merge pull request #1313 from gevent/issue1296

Refactoring to run tests from an install
parents 9560b486 5a252e03
......@@ -24,6 +24,7 @@ from _setuputils import DEFINE_MACROS
from _setuputils import glob_many
from _setuputils import dep_abspath
from _setuputils import RUNNING_ON_CI
from _setuputils import RUNNING_FROM_CHECKOUT
from _setuputils import cythonize1
......@@ -51,14 +52,19 @@ ares_configure_command = ' '.join([
def configure_ares(bext, ext):
print("Embedding c-ares", bext, ext)
bdir = os.path.join(bext.build_temp, 'c-ares')
ext.include_dirs.insert(0, bdir)
print("Inserted ", bdir, "in include dirs", ext.include_dirs)
if not os.path.isdir(bdir):
os.makedirs(bdir)
if WIN:
shutil.copy("deps\\c-ares\\ares_build.h.dist", os.path.join(bdir, "ares_build.h"))
src = "deps\\c-ares\\ares_build.h.dist"
dest = os.path.join(bdir, "ares_build.h")
print("Copying %r to %r" % (src, dest))
shutil.copy(src, dest)
return
cwd = os.getcwd()
......@@ -87,7 +93,8 @@ ARES = Extension(name='gevent.resolver.cares',
depends=glob_many('src/gevent/resolver/dnshelper.c',
'src/gevent/resolver/cares_*.[ch]'))
ARES.optional = not RUNNING_ON_CI
ares_required = RUNNING_ON_CI and RUNNING_FROM_CHECKOUT
ARES.optional = not ares_required
if CARES_EMBED:
......@@ -108,5 +115,6 @@ if CARES_EMBED:
else:
ARES.libraries.append('cares')
ARES.define_macros += [('HAVE_NETDB_H', '')]
ARES.configure = lambda bext, ext: print("c-ares not embedded, not configuring", bext, ext)
ARES = cythonize1(ARES)
......@@ -15,6 +15,7 @@ from glob import glob
from setuptools import Extension as _Extension
from setuptools.command.build_ext import build_ext
THIS_DIR = os.path.dirname(__file__)
## Exported configurations
......@@ -24,7 +25,7 @@ WIN = sys.platform.startswith('win')
RUNNING_ON_TRAVIS = os.environ.get('TRAVIS')
RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR')
RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR
RUNNING_FROM_CHECKOUT = os.path.isdir(os.path.join(THIS_DIR, ".git"))
LIBRARIES = []
......@@ -37,8 +38,6 @@ if WIN:
### File handling
THIS_DIR = os.path.dirname(__file__)
def quoted_abspath(*segments):
return '"' + os.path.abspath(os.path.join(*segments)) + '"'
......@@ -78,7 +77,7 @@ def _parse_environ(key):
value = value.lower().strip()
if value in ('1', 'true', 'on', 'yes'):
return True
elif value in ('0', 'false', 'off', 'no'):
if value in ('0', 'false', 'off', 'no'):
return False
raise ValueError('Environment variable %r has invalid value %r. '
'Please set it to 1, 0 or an empty string' % (key, value))
......
......@@ -138,7 +138,7 @@ cache:
- '%LOCALAPPDATA%\pip\Cache'
build_script:
- "%PYEXE% -m pip install -U --upgrade-strategy=eager -e .[test,events,dnspython]"
- "%PYEXE% -m pip install -U --upgrade-strategy=eager .[test,events,dnspython]"
test_script:
# Run the project tests
......
......@@ -26,8 +26,6 @@ Cooperative ``subprocess`` module.
from __future__ import absolute_import, print_function
# Can we split this up to make it cleaner? See https://github.com/gevent/gevent/issues/748
# pylint: disable=too-many-lines
# Import magic
# pylint: disable=undefined-all-variable,undefined-variable
# Most of this we inherit from the standard lib
# pylint: disable=bare-except,too-many-locals,too-many-statements,attribute-defined-outside-init
# pylint: disable=too-many-branches,too-many-instance-attributes
......@@ -51,7 +49,7 @@ from gevent._compat import fspath
from gevent._compat import fsencode
from gevent._util import _NONE
from gevent._util import copy_globals
from gevent.fileobject import FileObject
from gevent.greenlet import Greenlet, joinall
spawn = Greenlet.spawn
import subprocess as __subprocess__
......@@ -274,7 +272,7 @@ def check_call(*popenargs, **kwargs):
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise CalledProcessError(retcode, cmd)
raise CalledProcessError(retcode, cmd) # pylint:disable=undefined-variable
return 0
def check_output(*popenargs, **kwargs):
......@@ -297,10 +295,10 @@ def check_output(*popenargs, **kwargs):
To capture standard error in the result, use ``stderr=STDOUT``::
>>> check_output(["/bin/sh", "-c",
>>> print(check_output(["/bin/sh", "-c",
... "ls -l non_existent_file ; exit 0"],
... stderr=STDOUT)
'ls: non_existent_file: No such file or directory\n'
... stderr=STDOUT).decode('ascii').strip())
ls: non_existent_file: No such file or directory
There is an additional optional argument, "input", allowing you to
pass a string to the subprocess's stdin. If you use this argument
......@@ -346,6 +344,7 @@ def check_output(*popenargs, **kwargs):
raise
retcode = process.poll()
if retcode:
# pylint:disable=undefined-variable
raise CalledProcessError(retcode, process.args, output=output)
return output
......@@ -396,6 +395,13 @@ else:
_set_inheritable = lambda i, v: True
def FileObject(*args):
# Defer importing FileObject until we need it
# to allow it to be configured more easily.
from gevent.fileobject import FileObject as _FileObject
globals()['FileObject'] = _FileObject
return _FileObject(*args)
class Popen(object):
"""
The underlying process creation and management in this module is
......@@ -516,6 +522,7 @@ class Popen(object):
# Validate the combinations of text and universal_newlines
if (text is not None and universal_newlines is not None
and bool(universal_newlines) != bool(text)):
# pylint:disable=undefined-variable
raise SubprocessError('Cannot disambiguate when both text '
'and universal_newlines are supplied but '
'different. Pass one or the other.')
......@@ -566,6 +573,7 @@ class Popen(object):
# Python 3, so it's actually a unicode str
self._communicate_empty_value = ''
if p2cwrite != -1:
if PY3 and text_mode:
# Under Python 3, if we left on the 'b' we'd get different results
......@@ -814,6 +822,7 @@ class Popen(object):
"""Construct and return tuple with IO objects:
p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
"""
# pylint:disable=undefined-variable
if stdin is None and stdout is None and stderr is None:
return (-1, -1, -1, -1, -1, -1)
......@@ -896,12 +905,14 @@ class Popen(object):
def _make_inheritable(self, handle):
"""Return a duplicate of handle, which is inheritable"""
# pylint:disable=undefined-variable
return DuplicateHandle(GetCurrentProcess(),
handle, GetCurrentProcess(), 0, 1,
DUPLICATE_SAME_ACCESS)
def _find_w9xpopen(self):
"""Find and return absolute path to w9xpopen.exe"""
# pylint:disable=undefined-variable
w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)),
"w9xpopen.exe")
if not os.path.exists(w9xpopen):
......@@ -938,7 +949,7 @@ class Popen(object):
errread, errwrite,
unused_restore_signals, unused_start_new_session):
"""Execute program (MS Windows version)"""
# pylint:disable=undefined-variable
assert not pass_fds, "pass_fds not supported on Windows."
if not isinstance(args, string_types):
......@@ -1053,6 +1064,7 @@ class Popen(object):
"""Check if child process has terminated. Returns returncode
attribute.
"""
# pylint:disable=undefined-variable
if self.returncode is None:
if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
self.returncode = GetExitCodeProcess(self._handle)
......@@ -1067,6 +1079,7 @@ class Popen(object):
# XXX unlink
def _blocking_wait(self):
# pylint:disable=undefined-variable
WaitForSingleObject(self._handle, INFINITE)
self.returncode = GetExitCodeProcess(self._handle)
return self.returncode
......@@ -1098,6 +1111,7 @@ class Popen(object):
def terminate(self):
"""Terminates the process
"""
# pylint:disable=undefined-variable
# Don't terminate a process that we know has already died.
if self.returncode is not None:
return
......@@ -1169,7 +1183,7 @@ class Popen(object):
pass
elif stderr == PIPE:
errread, errwrite = self.pipe_cloexec()
elif stderr == STDOUT:
elif stderr == STDOUT: # pylint:disable=undefined-variable
if c2pwrite != -1:
errwrite = c2pwrite
else: # child's stdout is not set, use parent's stdout
......@@ -1599,6 +1613,7 @@ class CompletedProcess(object):
def check_returncode(self):
"""Raise CalledProcessError if the exit code is non-zero."""
if self.returncode:
# pylint:disable=undefined-variable
raise _with_stdout_stderr(CalledProcessError(self.returncode, self.args, self.stdout), self.stderr)
......@@ -1667,6 +1682,7 @@ def run(*popenargs, **kwargs):
raise
retcode = process.poll()
if check and retcode:
# pylint:disable=undefined-variable
raise _with_stdout_stderr(CalledProcessError(retcode, process.args, stdout), stderr)
return CompletedProcess(process.args, retcode, stdout, stderr)
......@@ -406,9 +406,6 @@ if LIBUV:
'test_httpservers.CGIHTTPServerTestCase.test_invaliduri',
'test_httpservers.CGIHTTPServerTestCase.test_issue19435',
# Sometimes raises LoopExit on CPython
'test_socket.BufferIOTest.testRecvFromIntoArray',
# Unexpected timeouts sometimes
'test_smtplib.TooLongLineTests.testLineTooLong',
'test_smtplib.GeneralTests.testTimeoutValue',
......@@ -463,6 +460,10 @@ if LIBUV:
# This test winds up hanging a long time.
# Inserting GCs doesn't fix it.
'test_ssl.ThreadedTests.test_handshake_timeout',
# These sometimes raise LoopExit, for no apparent reason.
'test_socket.BufferIOTest.testRecvFromIntoBytearray',
'test_socket.BufferIOTest.testRecvFromIntoArray',
]
if PY3:
......
......@@ -75,11 +75,13 @@ def run_many(tests,
total = 0
failed = {}
passed = {}
total_cases = [0]
total_skipped = [0]
NWORKERS = min(len(tests), NWORKERS) or 1
pool = ThreadPool(NWORKERS)
util.BUFFER_OUTPUT = NWORKERS > 1
util.BUFFER_OUTPUT = NWORKERS > 1 or quiet
def run_one(cmd, **kwargs):
kwargs['quiet'] = quiet
......@@ -90,6 +92,8 @@ def run_many(tests,
failed[result.name] = [cmd, kwargs]
else:
passed[result.name] = True
total_cases[0] += result.run_count
total_skipped[0] += result.skipped_count
results = []
......@@ -143,7 +147,8 @@ def run_many(tests,
except KeyboardInterrupt:
pool.terminate()
report(total, failed, passed, exit=False, took=time.time() - start,
configured_failing_tests=configured_failing_tests)
configured_failing_tests=configured_failing_tests,
total_cases=total_cases[0], total_skipped=total_skipped[0])
log('(partial results)\n')
raise
except:
......@@ -153,7 +158,8 @@ def run_many(tests,
reap_all()
report(total, failed, passed, took=time.time() - start,
configured_failing_tests=configured_failing_tests)
configured_failing_tests=configured_failing_tests,
total_cases=total_cases[0], total_skipped=total_skipped[0])
def discover(
tests=None, ignore_files=None,
......@@ -194,7 +200,7 @@ def discover(
tests = sorted(tests)
to_process = []
to_import = []
for filename in tests:
module_name = os.path.splitext(filename)[0]
......@@ -210,11 +216,7 @@ def discover(
contents = f.read()
if b'TESTRUNNER' in contents: # test__monkey_patching.py
# XXX: Rework this to avoid importing.
module = importlib.import_module(qualified_name)
for cmd, options in module.TESTRUNNER():
if remove_options(cmd)[-1] in ignore:
continue
to_process.append((cmd, options))
to_import.append(qualified_name)
else:
cmd = [sys.executable, '-u']
if PYPY and PY2:
......@@ -232,6 +234,15 @@ def discover(
to_process.append((cmd, options))
os.chdir(olddir)
# When we actually execute, do so from the original directory,
# this helps find setup.py
for qualified_name in to_import:
module = importlib.import_module(qualified_name)
for cmd, options in module.TESTRUNNER():
if remove_options(cmd)[-1] in ignore:
continue
to_process.append((cmd, options))
return to_process
......@@ -275,8 +286,9 @@ def format_seconds(seconds):
def report(total, failed, passed, exit=True, took=None,
configured_failing_tests=()):
# pylint:disable=redefined-builtin,too-many-branches
configured_failing_tests=(),
total_cases=0, total_skipped=0):
# pylint:disable=redefined-builtin,too-many-branches,too-many-locals
runtimelog = util.runtimelog
if runtimelog:
log('\nLongest-running tests:')
......@@ -319,7 +331,13 @@ def report(total, failed, passed, exit=True, took=None,
log('\n%s/%s unexpected failures', len(failed_unexpected), total, color='error')
print_list(failed_unexpected)
else:
log('\n%s tests passed%s', total, took)
log(
'\nRan %s tests%s in %s files%s',
total_cases,
util._colorize('skipped', " (skipped=%d)" % total_skipped) if total_skipped else '',
total,
took,
)
if exit:
if failed_unexpected:
......
from __future__ import print_function, absolute_import, division
import re
import sys
import os
from . import six
......@@ -220,10 +222,14 @@ def start(command, quiet=False, **kwargs):
class RunResult(object):
def __init__(self, code, output=None, name=None):
def __init__(self, code,
output=None, name=None,
run_count=0, skipped_count=0):
self.code = code
self.output = output
self.name = name
self.run_count = run_count
self.skipped_count = skipped_count
def __bool__(self):
......@@ -236,29 +242,50 @@ class RunResult(object):
def _should_show_warning_output(out):
if b'Warning' in out:
if 'Warning' in out:
# Strip out some patterns we specifically do not
# care about.
# from test.support for monkey-patched tests
out = out.replace(b'Warning -- reap_children', b'NADA')
out = out.replace(b"Warning -- threading_cleanup", b'NADA')
out = out.replace('Warning -- reap_children', 'NADA')
out = out.replace("Warning -- threading_cleanup", 'NADA')
# The below *could* be done with sophisticated enough warning
# filters passed to the children
# collections.abc is the new home; setuptools uses the old one,
# as does dnspython
out = out.replace(b"DeprecationWarning: Using or importing the ABCs", b'NADA')
out = out.replace("DeprecationWarning: Using or importing the ABCs", 'NADA')
# libuv poor timer resolution
out = out.replace(b'UserWarning: libuv only supports', b'NADA')
out = out.replace('UserWarning: libuv only supports', 'NADA')
# Packages on Python 2
out = out.replace(b'ImportWarning: Not importing directory', b'NADA')
return b'Warning' in out
out = out.replace('ImportWarning: Not importing directory', 'NADA')
return 'Warning' in out
output_lock = threading.Lock()
def run(command, **kwargs):
def _find_test_status(took, out):
status = '[took %.1fs%s]'
skipped = ''
run_count = 0
skipped_count = 0
if out:
m = re.search(r"Ran (\d+) tests in", out)
if m:
result = out[m.start():m.end()]
status = status.replace('took', result)
run_count = int(out[m.start(1):m.end(1)])
m = re.search(r' \(skipped=(\d+)\)$', out)
if m:
skipped = _colorize('skipped', out[m.start():m.end()])
skipped_count = int(out[m.start(1):m.end(1)])
status = status % (took, skipped)
if took > 10:
status = _colorize('slow-test', status)
return status, run_count, skipped_count
def run(command, **kwargs): # pylint:disable=too-many-locals
buffer_output = kwargs.pop('buffer_output', BUFFER_OUTPUT)
quiet = kwargs.pop('quiet', QUIET)
verbose = not quiet
......@@ -282,21 +309,27 @@ def run(command, **kwargs):
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)):
out = out.strip().decode('utf-8', 'ignore')
if out:
out = ' ' + out.replace('\n', '\n ')
out = out.rstrip()
out += '\n'
log('| %s\n%s', name, out)
status, run_count, skipped_count = _find_test_status(took, out)
if result:
log('! %s [code %s] [took %.1fs]', name, result, took, color='error')
log('! %s [code %s] %s', name, result, status, color='error')
elif not nested:
log('- %s [took %.1fs]', name, took)
log('- %s %s', name, status)
if took >= MIN_RUNTIME:
runtimelog.append((-took, name))
return RunResult(result, out, name)
return RunResult(result, out, name, run_count, skipped_count)
class NoSetupPyFound(Exception):
"Raised by find_setup_py_above"
def find_setup_py_above(a_file):
"Return the directory containing setup.py somewhere above *a_file*"
......@@ -305,34 +338,68 @@ def find_setup_py_above(a_file):
prev, root = root, os.path.dirname(root)
if root == prev:
# Let's avoid infinite loops at root
raise AssertionError('could not find my setup.py')
raise NoSetupPyFound('could not find my setup.py above %r' % (a_file,))
return root
def search_for_setup_py(a_file=None, a_module_name=None, a_class=None, climb_cwd=True):
if a_file is not None:
try:
return find_setup_py_above(a_file)
except NoSetupPyFound:
pass
class TestServer(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
if a_class is not None:
try:
return find_setup_py_above(sys.modules[a_class.__module__].__file__)
except NoSetupPyFound:
pass
if a_module_name is not None:
try:
return find_setup_py_above(sys.modules[a_module_name].__file__)
except NoSetupPyFound:
pass
if climb_cwd:
return find_setup_py_above("./dne")
raise NoSetupPyFound("After checking %r" % (locals(),))
class ExampleMixin(object):
"Something that uses the examples/ directory"
def find_setup_py(self):
"Return the directory containing setup.py"
return find_setup_py_above(__file__)
# XXX: We need to extend this if we want it to be useful
# for other packages; our __file__ won't work for them.
# We can look at the CWD, and we can look at the __file__ of the
# sys.modules[type(self).__module__].
return search_for_setup_py(
a_file=__file__,
a_class=type(self)
)
@property
def cwd(self):
root = self.find_setup_py()
try:
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
def start(self):
kwargs = self.start_kwargs or {}
return start([sys.executable, '-u', self.server] + self.args, cwd=self.cwd, **kwargs)
try:
kwargs = self.start_kwargs or {}
return start([sys.executable, '-u', self.server] + self.args, cwd=self.cwd, **kwargs)
except NoSetupPyFound as e:
raise unittest.SkipTest("Unable to locate file/dir to run: %s" % (e,))
def running_server(self):
from contextlib import contextmanager
......
......@@ -7,25 +7,34 @@ import atexit
from gevent.testing import util
TIMEOUT = 120
# XXX: Generalize this so other packages can use it.
setup_py = util.find_setup_py_above(__file__)
greentest = os.path.join(setup_py, 'src', 'greentest')
TIMEOUT = 120
directory = '%s.%s' % sys.version_info[:2]
full_directory = '%s.%s.%s' % sys.version_info[:3]
if hasattr(sys, 'pypy_version_info'):
directory += 'pypy'
full_directory += 'pypy'
def find_stdlib_tests():
setup_py = util.search_for_setup_py(a_file=__file__)
greentest = os.path.join(setup_py, 'src', 'greentest')
directory = '%s.%s' % sys.version_info[:2]
full_directory = '%s.%s.%s' % sys.version_info[:3]
if hasattr(sys, 'pypy_version_info'):
directory += 'pypy'
full_directory += 'pypy'
directory = os.path.join(greentest, directory)
full_directory = os.path.join(greentest, full_directory)
directory = os.path.join(greentest, directory)
full_directory = os.path.join(greentest, full_directory)
return directory, full_directory
version = '%s.%s.%s' % sys.version_info[:3]
if sys.version_info[3] == 'alpha':
version += 'a%s' % sys.version_info[4]
elif sys.version_info[3] == 'beta':
version += 'b%s' % sys.version_info[4]
def get_python_version():
version = '%s.%s.%s' % sys.version_info[:3]
if sys.version_info[3] == 'alpha':
version += 'a%s' % sys.version_info[4]
elif sys.version_info[3] == 'beta':
version += 'b%s' % sys.version_info[4]
return version
def get_absolute_pythonpath():
paths = [os.path.abspath(p) for p in os.environ.get('PYTHONPATH', '').split(os.pathsep)]
......@@ -33,18 +42,31 @@ def get_absolute_pythonpath():
def TESTRUNNER(tests=None):
if not os.path.exists(directory):
util.log('WARNING: No test directory found at %s', directory)
try:
test_dir, version_test_dir = find_stdlib_tests()
except util.NoSetupPyFound as e:
util.log("WARNING: No setup.py and src/greentest found: %r", e,
color="suboptimal-behaviour")
return
if not os.path.exists(test_dir):
util.log('WARNING: No test directory found at %s', test_dir,
color="suboptimal-behaviour")
return
with open(os.path.join(directory, 'version')) as f:
with open(os.path.join(test_dir, 'version')) as f:
preferred_version = f.read().strip()
if preferred_version != version:
util.log('WARNING: The tests in %s/ are from version %s and your Python is %s', directory, preferred_version, version)
version_tests = glob.glob('%s/test_*.py' % full_directory)
running_version = get_python_version()
if preferred_version != running_version:
util.log('WARNING: The tests in %s/ are from version %s and your Python is %s',
test_dir, preferred_version, running_version,
color="suboptimal-behaviour")
version_tests = glob.glob('%s/test_*.py' % version_test_dir)
version_tests = sorted(version_tests)
if not tests:
tests = glob.glob('%s/test_*.py' % directory)
tests = glob.glob('%s/test_*.py' % test_dir)
tests = sorted(tests)
PYTHONPATH = (os.getcwd() + os.pathsep + get_absolute_pythonpath()).rstrip(':')
......@@ -53,7 +75,7 @@ def TESTRUNNER(tests=None):
version_tests = [os.path.basename(x) for x in version_tests]
options = {
'cwd': directory,
'cwd': test_dir,
'timeout': TIMEOUT,
'setenv': {
'PYTHONPATH': PYTHONPATH,
......@@ -73,11 +95,11 @@ def TESTRUNNER(tests=None):
basic_args = [sys.executable, '-u', '-W', 'ignore', '-m' 'gevent.testing.monkey_test']
for filename in tests:
if filename in version_tests:
util.log("Overriding %s from %s with file from %s", filename, directory, full_directory)
util.log("Overriding %s from %s with file from %s", filename, test_dir, version_test_dir)
continue
yield basic_args + [filename], options.copy()
options['cwd'] = full_directory
options['cwd'] = version_test_dir
for filename in version_tests:
yield basic_args + [filename], options.copy()
......
......@@ -7,11 +7,7 @@ import re
import sys
import unittest
import gevent
from gevent import socket
from gevent.testing import walk_modules
from gevent.testing import sysinfo
from gevent.testing import util
# Ignore tracebacks: ZeroDivisionError
......@@ -39,19 +35,16 @@ class RENormalizingOutputChecker(doctest.OutputChecker):
return doctest.OutputChecker.check_output(self, want, got, optionflags)
FORBIDDEN_MODULES = set()
if sysinfo.WIN:
FORBIDDEN_MODULES |= {
# Uses commands only found on posix
'gevent.subprocess',
}
class Modules(object):
def __init__(self, allowed_modules):
from gevent.testing import walk_modules
self.allowed_modules = allowed_modules
self.modules = set()
for path, module in walk_modules():
for path, module in walk_modules(recursive=True):
self.add_module(module, path)
......@@ -71,19 +64,36 @@ class Modules(object):
return iter(self.modules)
def main():
def main(): # pylint:disable=too-many-locals
cwd = os.getcwd()
# Use pure_python to get the correct module source and docstrings
os.environ['PURE_PYTHON'] = '1'
import gevent
from gevent import socket
from gevent.testing import util
from gevent.testing import sysinfo
if sysinfo.WIN:
FORBIDDEN_MODULES.update({
# Uses commands only found on posix
'gevent.subprocess',
})
try:
allowed_modules = sys.argv[1:]
sys.path.append('.')
os.chdir(util.find_setup_py_above(__file__))
globs = {'myfunction': myfunction, 'gevent': gevent, 'socket': socket}
globs = {
'myfunction': myfunction,
'gevent': gevent,
'socket': socket,
}
modules = Modules(allowed_modules)
modules.add_module('setup', 'setup.py')
if not modules:
sys.exit('No modules found matching %s' % ' '.join(allowed_modules))
......@@ -91,8 +101,9 @@ def main():
checker = RENormalizingOutputChecker((
# Normalize subprocess.py: BSD ls is in the example, gnu ls outputs
# 'cannot access'
(re.compile('cannot access non_existent_file: No such file or directory'),
'non_existent_file: No such file or directory'),
(re.compile(
"ls: cannot access 'non_existent_file': No such file or directory"),
"ls: non_existent_file: No such file or directory"),
# Python 3 bytes add a "b".
(re.compile(r'b(".*?")'), r"\1"),
(re.compile(r"b('.*?')"), r"\1"),
......@@ -106,11 +117,12 @@ def main():
if re.search(br'^\s*>>> ', contents, re.M):
s = doctest.DocTestSuite(m, extraglobs=globs, checker=checker)
test_count = len(s._tests)
print('%s (from %s): %s tests' % (m, path, test_count))
util.log('%s (from %s): %s tests', m, path, test_count)
suite.addTest(s)
modules_count += 1
tests_count += test_count
print('Total: %s tests in %s modules' % (tests_count, modules_count))
util.log('Total: %s tests in %s modules', tests_count, modules_count)
# TODO: Pass this off to unittest.main()
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
......
......@@ -2,23 +2,34 @@ import sys
import os
import glob
import time
import unittest
import gevent.testing as greentest
from gevent.testing import util
this_dir = os.path.dirname(__file__)
cwd = '../../examples/'
ignore = [
'wsgiserver.py',
'wsgiserver_ssl.py',
'webproxy.py',
'webpy.py',
'unixsocket_server.py',
'unixsocket_client.py',
'psycopg2_pool.py',
'geventsendfile.py',
]
ignore += [x[14:] for x in glob.glob('test__example_*.py')]
def _find_files_to_ignore():
old_dir = os.getcwd()
try:
os.chdir(this_dir)
result = [
'wsgiserver.py',
'wsgiserver_ssl.py',
'webproxy.py',
'webpy.py',
'unixsocket_server.py',
'unixsocket_client.py',
'psycopg2_pool.py',
'geventsendfile.py',
]
result += [x[14:] for x in glob.glob('test__example_*.py')]
finally:
os.chdir(old_dir)
return result
default_time_range = (2, 4)
time_ranges = {
......@@ -26,7 +37,7 @@ time_ranges = {
'processes.py': (0, 4)
}
class _AbstractTestMixin(object):
class _AbstractTestMixin(util.ExampleMixin):
time_range = (2, 4)
filename = None
......@@ -35,7 +46,7 @@ class _AbstractTestMixin(object):
min_time, max_time = self.time_range
if util.run([sys.executable, '-u', self.filename],
timeout=max_time,
cwd=cwd,
cwd=self.cwd,
quiet=True,
buffer_output=True,
nested=True,
......@@ -45,17 +56,32 @@ class _AbstractTestMixin(object):
took = time.time() - start
self.assertGreaterEqual(took, min_time)
for filename in glob.glob(cwd + '/*.py'):
bn = os.path.basename(filename)
if bn in ignore:
continue
tc = type('Test_' + bn,
(_AbstractTestMixin, greentest.TestCase),
{
'filename': bn,
'time_range': time_ranges.get(bn, _AbstractTestMixin.time_range)
})
locals()[tc.__name__] = tc
def _build_test_classes():
result = {}
try:
example_dir = util.ExampleMixin().cwd
except unittest.SkipTest:
util.log("WARNING: No examples dir found", color='suboptimal-behaviour')
return result
ignore = _find_files_to_ignore()
for filename in glob.glob(example_dir + '/*.py'):
bn = os.path.basename(filename)
if bn in ignore:
continue
tc = type(
'Test_' + bn,
(_AbstractTestMixin, greentest.TestCase),
{
'filename': bn,
'time_range': time_ranges.get(bn, _AbstractTestMixin.time_range)
}
)
result[tc.__name__] = tc
return result
for k, v in _build_test_classes().items():
locals()[k] = v
if __name__ == '__main__':
greentest.main()
......@@ -45,7 +45,7 @@ import time
import unittest
import weakref
import lock_tests
from gevent.tests import lock_tests
# A trivial mutable counter.
......
......@@ -19,7 +19,7 @@ try:
except ImportError:
_testcapi = None
import lock_tests # gevent: use local copy
from gevent.tests import lock_tests # gevent: use local copy
# A trivial mutable counter.
class Counter(object):
......
......@@ -24,7 +24,7 @@ except:
# some lib_pypy/_testcapimodule.o file is truncated
_testcapi = None
import lock_tests # gevent: use local copy
from gevent.tests import lock_tests # gevent: use local copy
# A trivial mutable counter.
class Counter(object):
......
......@@ -17,7 +17,7 @@ import weakref
import os
import subprocess
import lock_tests # gevent: use our local copy
from gevent.tests import lock_tests # gevent: use our local copy
from test import support
......
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