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