tests/run-tests.py
changeset 35190 bd8875b6473c
parent 35189 073843b5e353
child 35193 f12747de13ea
--- a/tests/run-tests.py	Mon Nov 20 21:26:11 2017 -0800
+++ b/tests/run-tests.py	Mon Nov 20 23:02:32 2017 -0800
@@ -46,6 +46,7 @@
 from __future__ import absolute_import, print_function
 
 import argparse
+import collections
 import difflib
 import distutils.version as version
 import errno
@@ -373,7 +374,7 @@
         help="install and use chg wrapper in place of hg")
     hgconf.add_argument("--compiler",
         help="compiler to build with")
-    hgconf.add_argument('--extra-config-opt', action="append",
+    hgconf.add_argument('--extra-config-opt', action="append", default=[],
         help='set the given config opt in the test hgrc')
     hgconf.add_argument("-l", "--local", action="store_true",
         help="shortcut for --with-hg=<testdir>/../hg, "
@@ -404,6 +405,8 @@
         help="colorisation: always|auto|never (default: auto)")
     reporting.add_argument("-c", "--cover", action="store_true",
         help="print a test coverage report")
+    reporting.add_argument('--exceptions', action='store_true',
+        help='log all exceptions and generate an exception report')
     reporting.add_argument("-H", "--htmlcov", action="store_true",
         help="create an HTML report of the coverage of the files")
     reporting.add_argument("--json", action="store_true",
@@ -2115,6 +2118,18 @@
                     os.environ['PYTHONHASHSEED'])
             if self._runner.options.time:
                 self.printtimes(result.times)
+
+            if self._runner.options.exceptions:
+                exceptions = aggregateexceptions(
+                    os.path.join(self._runner._outputdir, b'exceptions'))
+                total = sum(exceptions.values())
+
+                self.stream.writeln('Exceptions Report:')
+                self.stream.writeln('%d total from %d frames' %
+                                    (total, len(exceptions)))
+                for (frame, line, exc), count in exceptions.most_common():
+                    self.stream.writeln('%d\t%s: %s' % (count, frame, exc))
+
             self.stream.flush()
 
         return result
@@ -2501,6 +2516,23 @@
 
         self._coveragefile = os.path.join(self._testdir, b'.coverage')
 
+        if self.options.exceptions:
+            exceptionsdir = os.path.join(self._outputdir, b'exceptions')
+            try:
+                os.makedirs(exceptionsdir)
+            except OSError as e:
+                if e.errno != errno.EEXIST:
+                    raise
+
+            # Remove all existing exception reports.
+            for f in os.listdir(exceptionsdir):
+                os.unlink(os.path.join(exceptionsdir, f))
+
+            osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
+            logexceptions = os.path.join(self._testdir, b'logexceptions.py')
+            self.options.extra_config_opt.append(
+                'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
+
         vlog("# Using TESTDIR", self._testdir)
         vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
         vlog("# Using HGTMP", self._hgtmp)
@@ -2953,6 +2985,24 @@
                 print("WARNING: Did not find prerequisite tool: %s " %
                       p.decode("utf-8"))
 
+def aggregateexceptions(path):
+    exceptions = collections.Counter()
+
+    for f in os.listdir(path):
+        with open(os.path.join(path, f), 'rb') as fh:
+            data = fh.read().split(b'\0')
+            if len(data) != 4:
+                continue
+
+            exc, mainframe, hgframe, hgline = data
+            exc = exc.decode('utf-8')
+            mainframe = mainframe.decode('utf-8')
+            hgframe = hgframe.decode('utf-8')
+            hgline = hgline.decode('utf-8')
+            exceptions[(hgframe, hgline, exc)] += 1
+
+    return exceptions
+
 if __name__ == '__main__':
     runner = TestRunner()