Commit 751c3d46 authored by Stefan Behnel's avatar Stefan Behnel

Show slowest N tests in test stats per metric.

parent 109d3369
...@@ -7,6 +7,7 @@ import os ...@@ -7,6 +7,7 @@ import os
import sys import sys
import re import re
import gc import gc
import heapq
import locale import locale
import shutil import shutil
import time import time
...@@ -565,20 +566,26 @@ class ErrorWriter(object): ...@@ -565,20 +566,26 @@ class ErrorWriter(object):
class Stats(object): class Stats(object):
TOP_N = 8
def __init__(self): def __init__(self):
self.test_counts = defaultdict(int) self.test_counts = defaultdict(int)
self.test_times = defaultdict(float) self.test_times = defaultdict(float)
self.top_tests = defaultdict(list)
def add_time(self, metric, t): def add_time(self, name, language, metric, t):
self.test_counts[metric] += 1 self.test_counts[metric] += 1
self.test_times[metric] += t self.test_times[metric] += t
top = self.top_tests[metric]
push = heapq.heappushpop if len(top) >= self.TOP_N else heapq.heappush
push(top, (t, name, language))
@contextmanager @contextmanager
def time(self, metric): def time(self, name, language, metric):
t = time.time() t = time.time()
yield yield
t = time.time() - t t = time.time() - t
self.add_time(metric, t) self.add_time(name, language, metric, t)
def print_stats(self, out=sys.stderr): def print_stats(self, out=sys.stderr):
if not self.test_times: if not self.test_times:
...@@ -586,7 +593,10 @@ class Stats(object): ...@@ -586,7 +593,10 @@ class Stats(object):
lines = ['Times:\n'] lines = ['Times:\n']
for metric, t in sorted(self.test_times.items()): for metric, t in sorted(self.test_times.items()):
count = self.test_counts[metric] count = self.test_counts[metric]
lines.append("%-12s: %8.2f sec (%4d, %6.3f / run)\n" % (metric, t, count, t / count)) top = self.top_tests[metric]
lines.append("%-12s: %8.2f sec (%4d, %6.3f / run) - slowest: %s\n" % (
metric, t, count, t / count,
', '.join("'{2}:{1}' ({0:.2f}s)".format(*item) for item in heapq.nlargest(self.TOP_N, top))))
out.write(''.join(lines)) out.write(''.join(lines))
...@@ -1112,7 +1122,7 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -1112,7 +1122,7 @@ class CythonCompileTestCase(unittest.TestCase):
old_stderr = sys.stderr old_stderr = sys.stderr
try: try:
sys.stderr = ErrorWriter() sys.stderr = ErrorWriter()
with self.stats.time('cython'): with self.stats.time(self.name, self.language, 'cython'):
self.run_cython(test_directory, module, workdir, incdir, annotate) self.run_cython(test_directory, module, workdir, incdir, annotate)
errors, warnings = sys.stderr.getall() errors, warnings = sys.stderr.getall()
finally: finally:
...@@ -1156,7 +1166,7 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -1156,7 +1166,7 @@ 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:
with self.stats.time('compile-%s' % self.language): with self.stats.time(self.name, self.language, 'compile-%s' % self.language):
so_path = self.run_distutils(test_directory, module, workdir, incdir) 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
...@@ -1245,18 +1255,18 @@ class CythonRunTestCase(CythonCompileTestCase): ...@@ -1245,18 +1255,18 @@ class CythonRunTestCase(CythonCompileTestCase):
pass pass
def run_tests(self, result, ext_so_path): def run_tests(self, result, ext_so_path):
with self.stats.time('run'): with self.stats.time(self.name, self.language, 'run'):
self.run_doctests(self.module, result, ext_so_path) 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):
with self.stats.time('import'): with self.stats.time(self.name, self.language, 'import'):
module = import_ext(module_or_name, ext_so_path) 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)
with self.stats.time('run'): with self.stats.time(self.name, self.language, 'run'):
tests.run(result) tests.run(result)
run_forked_test(result, run_test, self.shortDescription(), self.fork) run_forked_test(result, run_test, self.shortDescription(), self.fork)
...@@ -1340,7 +1350,7 @@ def run_forked_test(result, run_func, test_name, fork=True): ...@@ -1340,7 +1350,7 @@ def run_forked_test(result, run_func, test_name, fork=True):
class PureDoctestTestCase(unittest.TestCase): class PureDoctestTestCase(unittest.TestCase):
def __init__(self, module_name, module_path, tags, stats=None): def __init__(self, module_name, module_path, tags, stats=None):
self.tags = tags self.tags = tags
self.module_name = module_name self.module_name = self.name = module_name
self.module_path = module_path self.module_path = module_path
self.stats = stats self.stats = stats
unittest.TestCase.__init__(self, 'run') unittest.TestCase.__init__(self, 'run')
...@@ -1357,10 +1367,10 @@ class PureDoctestTestCase(unittest.TestCase): ...@@ -1357,10 +1367,10 @@ class PureDoctestTestCase(unittest.TestCase):
self.setUp() self.setUp()
import imp import imp
with self.stats.time('pyimport'): with self.stats.time(self.name, 'py', 'pyimport'):
m = imp.load_source(loaded_module_name, self.module_path) m = imp.load_source(loaded_module_name, self.module_path)
try: try:
with self.stats.time('pyrun'): with self.stats.time(self.name, 'py', 'pyrun'):
doctest.DocTestSuite(m).run(result) doctest.DocTestSuite(m).run(result)
finally: finally:
del m del m
...@@ -1381,7 +1391,7 @@ class PureDoctestTestCase(unittest.TestCase): ...@@ -1381,7 +1391,7 @@ class PureDoctestTestCase(unittest.TestCase):
except ImportError: except ImportError:
pass pass
else: else:
with self.stats.time('mypy'): with self.stats.time(self.name, 'py', 'mypy'):
mypy_result = mypy_api.run(( mypy_result = mypy_api.run((
self.module_path, self.module_path,
'--ignore-missing-imports', '--ignore-missing-imports',
...@@ -1457,7 +1467,7 @@ class CythonUnitTestCase(CythonRunTestCase): ...@@ -1457,7 +1467,7 @@ 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):
with self.stats.time('import'): with self.stats.time(self.name, self.language, 'import'):
module = import_ext(self.module, ext_so_path) module = import_ext(self.module, ext_so_path)
unittest.defaultTestLoader.loadTestsFromModule(module).run(result) unittest.defaultTestLoader.loadTestsFromModule(module).run(result)
...@@ -1490,11 +1500,11 @@ class CythonPyregrTestCase(CythonRunTestCase): ...@@ -1490,11 +1500,11 @@ class CythonPyregrTestCase(CythonRunTestCase):
suite.addTest(cls) suite.addTest(cls)
else: else:
suite.addTest(unittest.makeSuite(cls)) suite.addTest(unittest.makeSuite(cls))
with self.stats.time('run'): with self.stats.time(self.name, self.language, 'run'):
suite.run(result) suite.run(result)
def _run_doctest(self, result, module): def _run_doctest(self, result, module):
with self.stats.time('run'): with self.stats.time(self.name, self.language, 'run'):
self.run_doctests(module, result, None) self.run_doctests(module, result, None)
def run_tests(self, result, ext_so_path): def run_tests(self, result, ext_so_path):
...@@ -1699,7 +1709,7 @@ class EndToEndTest(unittest.TestCase): ...@@ -1699,7 +1709,7 @@ 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()):
with self.stats.time('endtoend'): with self.stats.time(self.name, 'c', 'endtoend'):
p = subprocess.Popen(command, p = subprocess.Popen(command,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
......
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