##// 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 24 tests/.hypothesis
25 25 tests/hypothesis-generated
26 26 tests/annotated
27 tests/exceptions
27 28 tests/*.err
28 29 tests/htmlcov
29 30 build
@@ -46,6 +46,7 b''
46 46 from __future__ import absolute_import, print_function
47 47
48 48 import argparse
49 import collections
49 50 import difflib
50 51 import distutils.version as version
51 52 import errno
@@ -373,7 +374,7 b' def getparser():'
373 374 help="install and use chg wrapper in place of hg")
374 375 hgconf.add_argument("--compiler",
375 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 378 help='set the given config opt in the test hgrc')
378 379 hgconf.add_argument("-l", "--local", action="store_true",
379 380 help="shortcut for --with-hg=<testdir>/../hg, "
@@ -404,6 +405,8 b' def getparser():'
404 405 help="colorisation: always|auto|never (default: auto)")
405 406 reporting.add_argument("-c", "--cover", action="store_true",
406 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 410 reporting.add_argument("-H", "--htmlcov", action="store_true",
408 411 help="create an HTML report of the coverage of the files")
409 412 reporting.add_argument("--json", action="store_true",
@@ -2115,6 +2118,18 b' class TextTestRunner(unittest.TextTestRu'
2115 2118 os.environ['PYTHONHASHSEED'])
2116 2119 if self._runner.options.time:
2117 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 2133 self.stream.flush()
2119 2134
2120 2135 return result
@@ -2501,6 +2516,23 b' class TestRunner(object):'
2501 2516
2502 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 2536 vlog("# Using TESTDIR", self._testdir)
2505 2537 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2506 2538 vlog("# Using HGTMP", self._hgtmp)
@@ -2953,6 +2985,24 b' class TestRunner(object):'
2953 2985 print("WARNING: Did not find prerequisite tool: %s " %
2954 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 3006 if __name__ == '__main__':
2957 3007 runner = TestRunner()
2958 3008
General Comments 0
You need to be logged in to leave comments. Login now