diff options
author | Mike Crute <mcrute@gmail.com> | 2009-11-03 14:56:00 -0500 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2009-11-03 14:56:00 -0500 |
commit | a43be0b7948d2596edcd9fe7258e81d6e31a70c8 (patch) | |
tree | fa9982ab4ba488599eb3cb61e7ea9993912c5eef /machineout.py | |
download | nose-machineout-a43be0b7948d2596edcd9fe7258e81d6e31a70c8.tar.bz2 nose-machineout-a43be0b7948d2596edcd9fe7258e81d6e31a70c8.tar.xz nose-machineout-a43be0b7948d2596edcd9fe7258e81d6e31a70c8.zip |
Initial import
Diffstat (limited to 'machineout.py')
-rw-r--r-- | machineout.py | 119 |
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 | """ | ||
3 | Formats nose output into format easily parsable by machine. | ||
4 | |||
5 | It is intended to be use to integrate nose with your IDE such as Vim. | ||
6 | |||
7 | @author: Max Ischenko <ischenko@gmail.com> | ||
8 | """ | ||
9 | |||
10 | import re | ||
11 | import os | ||
12 | import os.path | ||
13 | import traceback | ||
14 | from nose.plugins import Plugin | ||
15 | |||
16 | __all__ = ['NoseMachineReadableOutput'] | ||
17 | |||
18 | try: | ||
19 | import doctest | ||
20 | doctest_fname = re.sub('\.py.?$', '.py', doctest.__file__) | ||
21 | del doctest | ||
22 | except ImportError: | ||
23 | doctest_fname = None | ||
24 | |||
25 | class dummystream: | ||
26 | def write(self, *arg): | ||
27 | pass | ||
28 | def writeln(self, *arg): | ||
29 | pass | ||
30 | |||
31 | def is_doctest_traceback(fname): | ||
32 | return fname == doctest_fname | ||
33 | |||
34 | class PluginError(Exception): | ||
35 | def __repr__(self): | ||
36 | s = super(PluginError, self).__repr__() | ||
37 | return s + "\nReport bugs to ischenko@gmail.com." | ||
38 | |||
39 | class 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() | ||