summaryrefslogtreecommitdiff
path: root/machineout.py
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2009-11-03 14:56:00 -0500
committerMike Crute <mcrute@gmail.com>2009-11-03 14:56:00 -0500
commita43be0b7948d2596edcd9fe7258e81d6e31a70c8 (patch)
treefa9982ab4ba488599eb3cb61e7ea9993912c5eef /machineout.py
downloadnose-machineout-a43be0b7948d2596edcd9fe7258e81d6e31a70c8.tar.bz2
nose-machineout-a43be0b7948d2596edcd9fe7258e81d6e31a70c8.tar.xz
nose-machineout-a43be0b7948d2596edcd9fe7258e81d6e31a70c8.zip
Initial import
Diffstat (limited to 'machineout.py')
-rw-r--r--machineout.py119
1 files changed, 119 insertions, 0 deletions
diff --git a/machineout.py b/machineout.py
new file mode 100644
index 0000000..c074e30
--- /dev/null
+++ b/machineout.py
@@ -0,0 +1,119 @@
1
2"""
3Formats nose output into format easily parsable by machine.
4
5It is intended to be use to integrate nose with your IDE such as Vim.
6
7@author: Max Ischenko <ischenko@gmail.com>
8"""
9
10import re
11import os
12import os.path
13import traceback
14from nose.plugins import Plugin
15
16__all__ = ['NoseMachineReadableOutput']
17
18try:
19 import doctest
20 doctest_fname = re.sub('\.py.?$', '.py', doctest.__file__)
21 del doctest
22except ImportError:
23 doctest_fname = None
24
25class dummystream:
26 def write(self, *arg):
27 pass
28 def writeln(self, *arg):
29 pass
30
31def is_doctest_traceback(fname):
32 return fname == doctest_fname
33
34class PluginError(Exception):
35 def __repr__(self):
36 s = super(PluginError, self).__repr__()
37 return s + "\nReport bugs to ischenko@gmail.com."
38
39class NoseMachineReadableOutput(Plugin):
40
41 """
42 Output errors and failures in a machine-readable way.
43 """
44
45 name = 'machineout'
46
47 doctest_failure_re = re.compile('File "([^"]+)", line (\d+), in ([^\n]+)\n(.+)',
48 re.DOTALL)
49
50 def __init__(self):
51 super(NoseMachineReadableOutput, self).__init__()
52 self.basepath = os.getcwd()
53
54 def add_options(self, parser, env):
55 super(NoseMachineReadableOutput, self).add_options(parser, env)
56 parser.add_option("--machine-output", action="store_true",
57 dest="machine_output",
58 default=False,
59 help="Reports test results in easily parsable format.")
60
61 def configure(self, options, conf):
62 super(NoseMachineReadableOutput, self).configure(options, conf)
63 self.enabled = options.machine_output
64
65 def addSkip(self, test):
66 pass
67
68 def addDeprecated(self, test):
69 pass
70
71 def addError(self, test, err, capt):
72 self.addFormatted('error', err)
73
74 def addFormatted(self, etype, err):
75 exctype, value, tb = err
76 fulltb = traceback.extract_tb(tb)
77 fname, lineno, funname, msg = fulltb[-1]
78 # explicit support for doctests is needed
79 if is_doctest_traceback(fname):
80 # doctest traceback includes pre-formatted error message
81 # which we parse (in a very crude way).
82 n = value.args[0].rindex('-'*20)
83 formatted_msg = value.args[0][n+20+1:]
84 m = self.doctest_failure_re.match(formatted_msg)
85 if not m:
86 raise RuntimeError("Can't parse doctest output: %r" % value.args[0])
87 fname, lineno, funname, msg = m.groups()
88 if '.' in funname: # strip module package name, if any
89 funname = funname.split('.')[-1]
90 lineno = int(lineno)
91 lines = msg.split('\n')
92 msg0 = lines[0]
93 else:
94 lines = traceback.format_exception_only(exctype, value)
95 lines = [line.strip('\n') for line in lines]
96 msg0 = lines[0]
97 fname = self.format_testfname(fname)
98 prefix = "%s:%d" % (fname, lineno)
99 self.stream.writeln("%s: In %s" % (fname, funname))
100 self.stream.writeln("%s: %s: %s" % (prefix, etype, msg0))
101 if len(lines) > 1:
102 pad = ' '*(len(etype)+1)
103 for line in lines[1:]:
104 self.stream.writeln("%s: %s %s" % (prefix, pad, line))
105
106 def format_testfname(self, fname):
107 "Strips common path segments if any."
108 if fname.startswith(self.basepath):
109 return fname[len(self.basepath)+1:]
110 return fname
111
112 def addFailure(self, test, err, capt, tb_info):
113 self.addFormatted('fail', err)
114
115 def setOutputStream(self, stream):
116 # grab for own use
117 self.stream = stream
118 # return dummy stream to supress normal output
119 return dummystream()