Commit 05abd2ba authored by Stefan Behnel's avatar Stefan Behnel

Print run time stats in the test runner.

parent 3fa75854
...@@ -564,11 +564,34 @@ class ErrorWriter(object): ...@@ -564,11 +564,34 @@ class ErrorWriter(object):
pass # ignore, only to match file-like interface pass # ignore, only to match file-like interface
class Stats(object):
def __init__(self):
self.test_counts = defaultdict(int)
self.test_times = defaultdict(float)
def add_time(self, metric, t):
self.test_counts[metric] += 1
self.test_times[metric] += t
@contextmanager
def time(self, metric):
t = time.time()
yield
t = time.time() - t
self.add_time(metric, t)
def print_stats(self, out=sys.stderr):
lines = ['Times:']
for metric, t in sorted(self.test_times.items()):
lines.append("%-12s: %5.2f sec (%.3f / run)\n" % (metric, t, t / self.test_counts[metric]))
out.write(''.join(lines))
class TestBuilder(object): class TestBuilder(object):
def __init__(self, rootdir, workdir, selectors, exclude_selectors, options, def __init__(self, rootdir, workdir, selectors, exclude_selectors, options,
with_pyregr, languages, test_bugs, language_level, with_pyregr, languages, test_bugs, language_level,
common_utility_dir, pythran_dir=None, common_utility_dir, pythran_dir=None,
default_mode='run', default_mode='run', stats=None,
add_embedded_test=False): add_embedded_test=False):
self.rootdir = rootdir self.rootdir = rootdir
self.workdir = workdir self.workdir = workdir
...@@ -588,6 +611,7 @@ class TestBuilder(object): ...@@ -588,6 +611,7 @@ class TestBuilder(object):
self.common_utility_dir = common_utility_dir self.common_utility_dir = common_utility_dir
self.pythran_dir = pythran_dir self.pythran_dir = pythran_dir
self.default_mode = default_mode self.default_mode = default_mode
self.stats = stats
self.add_embedded_test = add_embedded_test self.add_embedded_test = add_embedded_test
def build_suite(self): def build_suite(self):
...@@ -646,7 +670,7 @@ class TestBuilder(object): ...@@ -646,7 +670,7 @@ class TestBuilder(object):
if ext == '.srctree': if ext == '.srctree':
if 'cpp' not in tags['tag'] or 'cpp' in self.languages: if 'cpp' not in tags['tag'] or 'cpp' in self.languages:
suite.addTest(EndToEndTest(filepath, workdir, self.cleanup_workdir)) suite.addTest(EndToEndTest(filepath, workdir, self.cleanup_workdir, stats=self.stats))
continue continue
# Choose the test suite. # Choose the test suite.
...@@ -676,7 +700,7 @@ class TestBuilder(object): ...@@ -676,7 +700,7 @@ class TestBuilder(object):
if pyver if pyver
] ]
if not min_py_ver or any(sys.version_info >= min_ver for min_ver in min_py_ver): if not min_py_ver or any(sys.version_info >= min_ver for min_ver in min_py_ver):
suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename), tags)) suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename), tags, stats=self.stats))
return suite return suite
...@@ -736,7 +760,8 @@ class TestBuilder(object): ...@@ -736,7 +760,8 @@ class TestBuilder(object):
warning_errors=warning_errors, warning_errors=warning_errors,
test_determinism=self.test_determinism, test_determinism=self.test_determinism,
common_utility_dir=self.common_utility_dir, common_utility_dir=self.common_utility_dir,
pythran_dir=pythran_dir) pythran_dir=pythran_dir,
stats=self.stats)
def skip_c(tags): def skip_c(tags):
...@@ -773,7 +798,7 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -773,7 +798,7 @@ class CythonCompileTestCase(unittest.TestCase):
cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False, cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False,
fork=True, language_level=2, warning_errors=False, fork=True, language_level=2, warning_errors=False,
test_determinism=False, test_determinism=False,
common_utility_dir=None, pythran_dir=None): common_utility_dir=None, pythran_dir=None, stats=None):
self.test_directory = test_directory self.test_directory = test_directory
self.tags = tags self.tags = tags
self.workdir = workdir self.workdir = workdir
...@@ -794,6 +819,7 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -794,6 +819,7 @@ class CythonCompileTestCase(unittest.TestCase):
self.test_determinism = test_determinism self.test_determinism = test_determinism
self.common_utility_dir = common_utility_dir self.common_utility_dir = common_utility_dir
self.pythran_dir = pythran_dir self.pythran_dir = pythran_dir
self.stats = stats
unittest.TestCase.__init__(self) unittest.TestCase.__init__(self)
def shortDescription(self): def shortDescription(self):
...@@ -1083,7 +1109,8 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -1083,7 +1109,8 @@ class CythonCompileTestCase(unittest.TestCase):
old_stderr = sys.stderr old_stderr = sys.stderr
try: try:
sys.stderr = ErrorWriter() sys.stderr = ErrorWriter()
self.run_cython(test_directory, module, workdir, incdir, annotate) with self.stats.time('cython'):
self.run_cython(test_directory, module, workdir, incdir, annotate)
errors, warnings = sys.stderr.getall() errors, warnings = sys.stderr.getall()
finally: finally:
sys.stderr = old_stderr sys.stderr = old_stderr
...@@ -1126,7 +1153,8 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -1126,7 +1153,8 @@ class CythonCompileTestCase(unittest.TestCase):
try: try:
with captured_fd(1) as get_stdout: with captured_fd(1) as get_stdout:
with captured_fd(2) as get_stderr: with captured_fd(2) as get_stderr:
so_path = self.run_distutils(test_directory, module, workdir, incdir) with self.stats.time('compile-%s' % self.language):
so_path = self.run_distutils(test_directory, module, workdir, incdir)
except Exception as exc: except Exception as exc:
if ('cerror' in self.tags['tag'] and if ('cerror' in self.tags['tag'] and
((get_stderr and get_stderr()) or ((get_stderr and get_stderr()) or
...@@ -1214,18 +1242,22 @@ class CythonRunTestCase(CythonCompileTestCase): ...@@ -1214,18 +1242,22 @@ class CythonRunTestCase(CythonCompileTestCase):
pass pass
def run_tests(self, result, ext_so_path): def run_tests(self, result, ext_so_path):
self.run_doctests(self.module, result, ext_so_path) with self.stats.time('run'):
self.run_doctests(self.module, result, ext_so_path)
def run_doctests(self, module_or_name, result, ext_so_path): def run_doctests(self, module_or_name, result, ext_so_path):
def run_test(result): def run_test(result):
if isinstance(module_or_name, basestring): if isinstance(module_or_name, basestring):
module = import_ext(module_or_name, ext_so_path) with self.stats.time('import'):
module = import_ext(module_or_name, ext_so_path)
else: else:
module = module_or_name module = module_or_name
tests = doctest.DocTestSuite(module) tests = doctest.DocTestSuite(module)
tests.run(result) with self.stats.time('run'):
tests.run(result)
run_forked_test(result, run_test, self.shortDescription(), self.fork) run_forked_test(result, run_test, self.shortDescription(), self.fork)
def run_forked_test(result, run_func, test_name, fork=True): def run_forked_test(result, run_func, test_name, fork=True):
if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'): if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'):
run_func(result) run_func(result)
...@@ -1301,11 +1333,13 @@ def run_forked_test(result, run_func, test_name, fork=True): ...@@ -1301,11 +1333,13 @@ def run_forked_test(result, run_func, test_name, fork=True):
except: except:
pass pass
class PureDoctestTestCase(unittest.TestCase): class PureDoctestTestCase(unittest.TestCase):
def __init__(self, module_name, module_path, tags): def __init__(self, module_name, module_path, tags, stats=None):
self.tags = tags self.tags = tags
self.module_name = module_name self.module_name = module_name
self.module_path = module_path self.module_path = module_path
self.stats = stats
unittest.TestCase.__init__(self, 'run') unittest.TestCase.__init__(self, 'run')
def shortDescription(self): def shortDescription(self):
...@@ -1320,9 +1354,11 @@ class PureDoctestTestCase(unittest.TestCase): ...@@ -1320,9 +1354,11 @@ class PureDoctestTestCase(unittest.TestCase):
self.setUp() self.setUp()
import imp import imp
m = imp.load_source(loaded_module_name, self.module_path) with self.stats.time('pyimport'):
m = imp.load_source(loaded_module_name, self.module_path)
try: try:
doctest.DocTestSuite(m).run(result) with self.stats.time('pyrun'):
doctest.DocTestSuite(m).run(result)
finally: finally:
del m del m
if loaded_module_name in sys.modules: if loaded_module_name in sys.modules:
...@@ -1336,22 +1372,20 @@ class PureDoctestTestCase(unittest.TestCase): ...@@ -1336,22 +1372,20 @@ class PureDoctestTestCase(unittest.TestCase):
except Exception: except Exception:
pass pass
try: if 'mypy' in self.tags['tag']:
from mypy import api as mypy_api try:
nomypy = False from mypy import api as mypy_api
except ImportError: except ImportError:
nomypy = True pass
if 'mypy' in self.tags['tag'] and not nomypy: else:
with self.stats.time('mypy'):
mypy_result = mypy_api.run(( mypy_result = mypy_api.run((
self.module_path, self.module_path,
'--ignore-missing-imports', '--ignore-missing-imports',
'--follow-imports', 'skip', '--follow-imports', 'skip',
)) ))
if mypy_result[2]:
if mypy_result[2]: self.fail(mypy_result[0])
import pdb; pdb.set_trace()
self.fail(mypy_result[0])
is_private_field = re.compile('^_[^_]').match is_private_field = re.compile('^_[^_]').match
...@@ -1420,7 +1454,8 @@ class CythonUnitTestCase(CythonRunTestCase): ...@@ -1420,7 +1454,8 @@ class CythonUnitTestCase(CythonRunTestCase):
return "compiling (%s) tests in %s" % (self.language, self.name) return "compiling (%s) tests in %s" % (self.language, self.name)
def run_tests(self, result, ext_so_path): def run_tests(self, result, ext_so_path):
module = import_ext(self.module, ext_so_path) with self.stats.time('import'):
module = import_ext(self.module, ext_so_path)
unittest.defaultTestLoader.loadTestsFromModule(module).run(result) unittest.defaultTestLoader.loadTestsFromModule(module).run(result)
...@@ -1452,10 +1487,12 @@ class CythonPyregrTestCase(CythonRunTestCase): ...@@ -1452,10 +1487,12 @@ class CythonPyregrTestCase(CythonRunTestCase):
suite.addTest(cls) suite.addTest(cls)
else: else:
suite.addTest(unittest.makeSuite(cls)) suite.addTest(unittest.makeSuite(cls))
suite.run(result) with self.stats.time('run'):
suite.run(result)
def _run_doctest(self, result, module): def _run_doctest(self, result, module):
self.run_doctests(module, result, None) with self.stats.time('run'):
self.run_doctests(module, result, None)
def run_tests(self, result, ext_so_path): def run_tests(self, result, ext_so_path):
try: try:
...@@ -1606,11 +1643,12 @@ class EndToEndTest(unittest.TestCase): ...@@ -1606,11 +1643,12 @@ class EndToEndTest(unittest.TestCase):
""" """
cython_root = os.path.dirname(os.path.abspath(__file__)) cython_root = os.path.dirname(os.path.abspath(__file__))
def __init__(self, treefile, workdir, cleanup_workdir=True): def __init__(self, treefile, workdir, cleanup_workdir=True, stats=None):
self.name = os.path.splitext(os.path.basename(treefile))[0] self.name = os.path.splitext(os.path.basename(treefile))[0]
self.treefile = treefile self.treefile = treefile
self.workdir = os.path.join(workdir, self.name) self.workdir = os.path.join(workdir, self.name)
self.cleanup_workdir = cleanup_workdir self.cleanup_workdir = cleanup_workdir
self.stats = stats
cython_syspath = [self.cython_root] cython_syspath = [self.cython_root]
for path in sys.path: for path in sys.path:
if path.startswith(self.cython_root) and path not in cython_syspath: if path.startswith(self.cython_root) and path not in cython_syspath:
...@@ -1658,12 +1696,13 @@ class EndToEndTest(unittest.TestCase): ...@@ -1658,12 +1696,13 @@ class EndToEndTest(unittest.TestCase):
env = dict(os.environ) env = dict(os.environ)
env['PYTHONPATH'] = self.cython_syspath + os.pathsep + (old_path or '') env['PYTHONPATH'] = self.cython_syspath + os.pathsep + (old_path or '')
for command in filter(None, commands.splitlines()): for command in filter(None, commands.splitlines()):
p = subprocess.Popen(command, with self.stats.time('endtoend'):
stderr=subprocess.PIPE, p = subprocess.Popen(command,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=True, stdout=subprocess.PIPE,
env=env) shell=True,
out, err = p.communicate() env=env)
out, err = p.communicate()
res = p.returncode res = p.returncode
if res != 0: if res != 0:
sys.stderr.write("%s\n%s\n%s\n" % ( sys.stderr.write("%s\n%s\n%s\n" % (
...@@ -1707,8 +1746,9 @@ class EmbedTest(unittest.TestCase): ...@@ -1707,8 +1746,9 @@ class EmbedTest(unittest.TestCase):
if sys.version_info[0] >=3 and CY3_DIR: if sys.version_info[0] >=3 and CY3_DIR:
cython = os.path.join(CY3_DIR, cython) cython = os.path.join(CY3_DIR, cython)
cython = os.path.abspath(os.path.join('..', '..', cython)) cython = os.path.abspath(os.path.join('..', '..', cython))
self.assertTrue(os.system( with self.stats.time('embed'):
"make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys.executable, cython, libdir)) == 0) self.assertTrue(os.system(
"make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys.executable, cython, libdir)) == 0)
try: try:
os.remove('make.output') os.remove('make.output')
except OSError: except OSError:
...@@ -2299,6 +2339,7 @@ def runtests(options, cmd_args, coverage=None): ...@@ -2299,6 +2339,7 @@ def runtests(options, cmd_args, coverage=None):
sys.stderr.write("\n") sys.stderr.write("\n")
test_suite = unittest.TestSuite() test_suite = unittest.TestSuite()
stats = Stats()
if options.unittests: if options.unittests:
collect_unittests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors, exclude_selectors) collect_unittests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors, exclude_selectors)
...@@ -2310,7 +2351,7 @@ def runtests(options, cmd_args, coverage=None): ...@@ -2310,7 +2351,7 @@ def runtests(options, cmd_args, coverage=None):
filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors, filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
options, options.pyregr, languages, test_bugs, options, options.pyregr, languages, test_bugs,
options.language_level, common_utility_dir, options.language_level, common_utility_dir,
options.pythran_dir, add_embedded_test=True) options.pythran_dir, add_embedded_test=True, stats=stats)
test_suite.addTest(filetests.build_suite()) test_suite.addTest(filetests.build_suite())
if options.examples and languages: if options.examples and languages:
for subdirectory in glob.glob(os.path.join(options.examples_dir, "*/")): for subdirectory in glob.glob(os.path.join(options.examples_dir, "*/")):
...@@ -2318,7 +2359,7 @@ def runtests(options, cmd_args, coverage=None): ...@@ -2318,7 +2359,7 @@ def runtests(options, cmd_args, coverage=None):
options, options.pyregr, languages, test_bugs, options, options.pyregr, languages, test_bugs,
options.language_level, common_utility_dir, options.language_level, common_utility_dir,
options.pythran_dir, options.pythran_dir,
default_mode='compile') default_mode='compile', stats=stats)
test_suite.addTest(filetests.build_suite()) test_suite.addTest(filetests.build_suite())
if options.system_pyregr and languages: if options.system_pyregr and languages:
...@@ -2328,7 +2369,7 @@ def runtests(options, cmd_args, coverage=None): ...@@ -2328,7 +2369,7 @@ def runtests(options, cmd_args, coverage=None):
if os.path.isdir(sys_pyregr_dir): if os.path.isdir(sys_pyregr_dir):
filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors, filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
options, True, languages, test_bugs, options, True, languages, test_bugs,
sys.version_info[0], common_utility_dir) sys.version_info[0], common_utility_dir, stats=stats)
sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir) sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir)
test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr')) test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr'))
...@@ -2393,6 +2434,8 @@ def runtests(options, cmd_args, coverage=None): ...@@ -2393,6 +2434,8 @@ def runtests(options, cmd_args, coverage=None):
if options.coverage_html: if options.coverage_html:
coverage.html_report(modules, directory="coverage-report-html") coverage.html_report(modules, directory="coverage-report-html")
stats.print_stats()
if missing_dep_excluder.tests_missing_deps: if missing_dep_excluder.tests_missing_deps:
sys.stderr.write("Following tests excluded because of missing dependencies on your system:\n") sys.stderr.write("Following tests excluded because of missing dependencies on your system:\n")
for test in missing_dep_excluder.tests_missing_deps: for test in missing_dep_excluder.tests_missing_deps:
......
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