From a43be0b7948d2596edcd9fe7258e81d6e31a70c8 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Tue, 3 Nov 2009 14:56:00 -0500 Subject: Initial import --- machineout.py | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 machineout.py (limited to 'machineout.py') diff --git a/machineout.py b/machineout.py new file mode 100644 index 0000000..c074e30 --- /dev/null +++ b/machineout.py @@ -0,0 +1,119 @@ + +""" +Formats nose output into format easily parsable by machine. + +It is intended to be use to integrate nose with your IDE such as Vim. + +@author: Max Ischenko +""" + +import re +import os +import os.path +import traceback +from nose.plugins import Plugin + +__all__ = ['NoseMachineReadableOutput'] + +try: + import doctest + doctest_fname = re.sub('\.py.?$', '.py', doctest.__file__) + del doctest +except ImportError: + doctest_fname = None + +class dummystream: + def write(self, *arg): + pass + def writeln(self, *arg): + pass + +def is_doctest_traceback(fname): + return fname == doctest_fname + +class PluginError(Exception): + def __repr__(self): + s = super(PluginError, self).__repr__() + return s + "\nReport bugs to ischenko@gmail.com." + +class NoseMachineReadableOutput(Plugin): + + """ + Output errors and failures in a machine-readable way. + """ + + name = 'machineout' + + doctest_failure_re = re.compile('File "([^"]+)", line (\d+), in ([^\n]+)\n(.+)', + re.DOTALL) + + def __init__(self): + super(NoseMachineReadableOutput, self).__init__() + self.basepath = os.getcwd() + + def add_options(self, parser, env): + super(NoseMachineReadableOutput, self).add_options(parser, env) + parser.add_option("--machine-output", action="store_true", + dest="machine_output", + default=False, + help="Reports test results in easily parsable format.") + + def configure(self, options, conf): + super(NoseMachineReadableOutput, self).configure(options, conf) + self.enabled = options.machine_output + + def addSkip(self, test): + pass + + def addDeprecated(self, test): + pass + + def addError(self, test, err, capt): + self.addFormatted('error', err) + + def addFormatted(self, etype, err): + exctype, value, tb = err + fulltb = traceback.extract_tb(tb) + fname, lineno, funname, msg = fulltb[-1] + # explicit support for doctests is needed + if is_doctest_traceback(fname): + # doctest traceback includes pre-formatted error message + # which we parse (in a very crude way). + n = value.args[0].rindex('-'*20) + formatted_msg = value.args[0][n+20+1:] + m = self.doctest_failure_re.match(formatted_msg) + if not m: + raise RuntimeError("Can't parse doctest output: %r" % value.args[0]) + fname, lineno, funname, msg = m.groups() + if '.' in funname: # strip module package name, if any + funname = funname.split('.')[-1] + lineno = int(lineno) + lines = msg.split('\n') + msg0 = lines[0] + else: + lines = traceback.format_exception_only(exctype, value) + lines = [line.strip('\n') for line in lines] + msg0 = lines[0] + fname = self.format_testfname(fname) + prefix = "%s:%d" % (fname, lineno) + self.stream.writeln("%s: In %s" % (fname, funname)) + self.stream.writeln("%s: %s: %s" % (prefix, etype, msg0)) + if len(lines) > 1: + pad = ' '*(len(etype)+1) + for line in lines[1:]: + self.stream.writeln("%s: %s %s" % (prefix, pad, line)) + + def format_testfname(self, fname): + "Strips common path segments if any." + if fname.startswith(self.basepath): + return fname[len(self.basepath)+1:] + return fname + + def addFailure(self, test, err, capt, tb_info): + self.addFormatted('fail', err) + + def setOutputStream(self, stream): + # grab for own use + self.stream = stream + # return dummy stream to supress normal output + return dummystream() -- cgit v1.2.3