1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
"""
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 <ischenko@gmail.com>
"""
import re
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 flush(self):
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):
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):
self.addFormatted('fail', err)
def setOutputStream(self, stream):
# grab for own use
self.stream = stream
# return dummy stream to supress normal output
return dummystream()
|