##// END OF EJS Templates
run-tests: mechanism to report exceptions during test execution...
Gregory Szorc -
r35191:bd8875b6 default
parent child Browse files
Show More
@@ -0,0 +1,73 b''
1 # logexceptions.py - Write files containing info about Mercurial exceptions
2 #
3 # Copyright 2017 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 from __future__ import absolute_import
9
10 import inspect
11 import os
12 import sys
13 import traceback
14 import uuid
15
16 from mercurial import (
17 dispatch,
18 extensions,
19 )
20
21 def handleexception(orig, ui):
22 res = orig(ui)
23
24 if not ui.environ.get(b'HGEXCEPTIONSDIR'):
25 return res
26
27 dest = os.path.join(ui.environ[b'HGEXCEPTIONSDIR'],
28 str(uuid.uuid4()).encode('ascii'))
29
30 exc_type, exc_value, exc_tb = sys.exc_info()
31
32 stack = []
33 tb = exc_tb
34 while tb:
35 stack.append(tb)
36 tb = tb.tb_next
37 stack.reverse()
38
39 hgframe = 'unknown'
40 hgline = 'unknown'
41
42 # Find the first Mercurial frame in the stack.
43 for tb in stack:
44 mod = inspect.getmodule(tb)
45 if not mod.__name__.startswith(('hg', 'mercurial')):
46 continue
47
48 frame = tb.tb_frame
49
50 try:
51 with open(inspect.getsourcefile(tb), 'r') as fh:
52 hgline = fh.readlines()[frame.f_lineno - 1].strip()
53 except (IndexError, OSError):
54 pass
55
56 hgframe = '%s:%d' % (frame.f_code.co_filename, frame.f_lineno)
57 break
58
59 primary = traceback.extract_tb(exc_tb)[-1]
60 primaryframe = '%s:%d' % (primary.filename, primary.lineno)
61
62 with open(dest, 'wb') as fh:
63 parts = [
64 str(exc_value),
65 primaryframe,
66 hgframe,
67 hgline,
68 ]
69 fh.write(b'\0'.join(p.encode('utf-8', 'replace') for p in parts))
70
71 def extsetup(ui):
72 extensions.wrapfunction(dispatch, 'handlecommandexception',
73 handleexception)
@@ -24,6 +24,7 b' tests/.testtimes*'
24 tests/.hypothesis
24 tests/.hypothesis
25 tests/hypothesis-generated
25 tests/hypothesis-generated
26 tests/annotated
26 tests/annotated
27 tests/exceptions
27 tests/*.err
28 tests/*.err
28 tests/htmlcov
29 tests/htmlcov
29 build
30 build
@@ -46,6 +46,7 b''
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import argparse
48 import argparse
49 import collections
49 import difflib
50 import difflib
50 import distutils.version as version
51 import distutils.version as version
51 import errno
52 import errno
@@ -373,7 +374,7 b' def getparser():'
373 help="install and use chg wrapper in place of hg")
374 help="install and use chg wrapper in place of hg")
374 hgconf.add_argument("--compiler",
375 hgconf.add_argument("--compiler",
375 help="compiler to build with")
376 help="compiler to build with")
376 hgconf.add_argument('--extra-config-opt', action="append",
377 hgconf.add_argument('--extra-config-opt', action="append", default=[],
377 help='set the given config opt in the test hgrc')
378 help='set the given config opt in the test hgrc')
378 hgconf.add_argument("-l", "--local", action="store_true",
379 hgconf.add_argument("-l", "--local", action="store_true",
379 help="shortcut for --with-hg=<testdir>/../hg, "
380 help="shortcut for --with-hg=<testdir>/../hg, "
@@ -404,6 +405,8 b' def getparser():'
404 help="colorisation: always|auto|never (default: auto)")
405 help="colorisation: always|auto|never (default: auto)")
405 reporting.add_argument("-c", "--cover", action="store_true",
406 reporting.add_argument("-c", "--cover", action="store_true",
406 help="print a test coverage report")
407 help="print a test coverage report")
408 reporting.add_argument('--exceptions', action='store_true',
409 help='log all exceptions and generate an exception report')
407 reporting.add_argument("-H", "--htmlcov", action="store_true",
410 reporting.add_argument("-H", "--htmlcov", action="store_true",
408 help="create an HTML report of the coverage of the files")
411 help="create an HTML report of the coverage of the files")
409 reporting.add_argument("--json", action="store_true",
412 reporting.add_argument("--json", action="store_true",
@@ -2115,6 +2118,18 b' class TextTestRunner(unittest.TextTestRu'
2115 os.environ['PYTHONHASHSEED'])
2118 os.environ['PYTHONHASHSEED'])
2116 if self._runner.options.time:
2119 if self._runner.options.time:
2117 self.printtimes(result.times)
2120 self.printtimes(result.times)
2121
2122 if self._runner.options.exceptions:
2123 exceptions = aggregateexceptions(
2124 os.path.join(self._runner._outputdir, b'exceptions'))
2125 total = sum(exceptions.values())
2126
2127 self.stream.writeln('Exceptions Report:')
2128 self.stream.writeln('%d total from %d frames' %
2129 (total, len(exceptions)))
2130 for (frame, line, exc), count in exceptions.most_common():
2131 self.stream.writeln('%d\t%s: %s' % (count, frame, exc))
2132
2118 self.stream.flush()
2133 self.stream.flush()
2119
2134
2120 return result
2135 return result
@@ -2501,6 +2516,23 b' class TestRunner(object):'
2501
2516
2502 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2517 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2503
2518
2519 if self.options.exceptions:
2520 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2521 try:
2522 os.makedirs(exceptionsdir)
2523 except OSError as e:
2524 if e.errno != errno.EEXIST:
2525 raise
2526
2527 # Remove all existing exception reports.
2528 for f in os.listdir(exceptionsdir):
2529 os.unlink(os.path.join(exceptionsdir, f))
2530
2531 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2532 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2533 self.options.extra_config_opt.append(
2534 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2535
2504 vlog("# Using TESTDIR", self._testdir)
2536 vlog("# Using TESTDIR", self._testdir)
2505 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2537 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2506 vlog("# Using HGTMP", self._hgtmp)
2538 vlog("# Using HGTMP", self._hgtmp)
@@ -2953,6 +2985,24 b' class TestRunner(object):'
2953 print("WARNING: Did not find prerequisite tool: %s " %
2985 print("WARNING: Did not find prerequisite tool: %s " %
2954 p.decode("utf-8"))
2986 p.decode("utf-8"))
2955
2987
2988 def aggregateexceptions(path):
2989 exceptions = collections.Counter()
2990
2991 for f in os.listdir(path):
2992 with open(os.path.join(path, f), 'rb') as fh:
2993 data = fh.read().split(b'\0')
2994 if len(data) != 4:
2995 continue
2996
2997 exc, mainframe, hgframe, hgline = data
2998 exc = exc.decode('utf-8')
2999 mainframe = mainframe.decode('utf-8')
3000 hgframe = hgframe.decode('utf-8')
3001 hgline = hgline.decode('utf-8')
3002 exceptions[(hgframe, hgline, exc)] += 1
3003
3004 return exceptions
3005
2956 if __name__ == '__main__':
3006 if __name__ == '__main__':
2957 runner = TestRunner()
3007 runner = TestRunner()
2958
3008
General Comments 0
You need to be logged in to leave comments. Login now