1 | """ |
---|
2 | Test Result |
---|
3 | ----------- |
---|
4 | |
---|
5 | Provides a TextTestResult that extends unittest._TextTestResult to |
---|
6 | provide support for error classes (such as the builtin skip and |
---|
7 | deprecated classes), and hooks for plugins to take over or extend |
---|
8 | reporting. |
---|
9 | """ |
---|
10 | |
---|
11 | import logging |
---|
12 | from unittest import _TextTestResult |
---|
13 | from nose.config import Config |
---|
14 | from nose.util import isclass, ln as _ln # backwards compat |
---|
15 | |
---|
16 | log = logging.getLogger('nose.result') |
---|
17 | |
---|
18 | |
---|
19 | def _exception_detail(exc): |
---|
20 | # this is what stdlib module traceback does |
---|
21 | try: |
---|
22 | return str(exc) |
---|
23 | except: |
---|
24 | return '<unprintable %s object>' % type(exc).__name__ |
---|
25 | |
---|
26 | |
---|
27 | class TextTestResult(_TextTestResult): |
---|
28 | """Text test result that extends unittest's default test result |
---|
29 | support for a configurable set of errorClasses (eg, Skip, |
---|
30 | Deprecated, TODO) that extend the errors/failures/success triad. |
---|
31 | """ |
---|
32 | def __init__(self, stream, descriptions, verbosity, config=None, |
---|
33 | errorClasses=None): |
---|
34 | if errorClasses is None: |
---|
35 | errorClasses = {} |
---|
36 | self.errorClasses = errorClasses |
---|
37 | if config is None: |
---|
38 | config = Config() |
---|
39 | self.config = config |
---|
40 | _TextTestResult.__init__(self, stream, descriptions, verbosity) |
---|
41 | |
---|
42 | def addError(self, test, err): |
---|
43 | """Overrides normal addError to add support for |
---|
44 | errorClasses. If the exception is a registered class, the |
---|
45 | error will be added to the list for that class, not errors. |
---|
46 | """ |
---|
47 | stream = getattr(self, 'stream', None) |
---|
48 | ec, ev, tb = err |
---|
49 | try: |
---|
50 | exc_info = self._exc_info_to_string(err, test) |
---|
51 | except TypeError: |
---|
52 | # 2.3 compat |
---|
53 | exc_info = self._exc_info_to_string(err) |
---|
54 | for cls, (storage, label, isfail) in self.errorClasses.items(): |
---|
55 | if isclass(ec) and issubclass(ec, cls): |
---|
56 | if isfail: |
---|
57 | test.passed = False |
---|
58 | storage.append((test, exc_info)) |
---|
59 | # Might get patched into a streamless result |
---|
60 | if stream is not None: |
---|
61 | if self.showAll: |
---|
62 | message = [label] |
---|
63 | detail = _exception_detail(err[1]) |
---|
64 | if detail: |
---|
65 | message.append(detail) |
---|
66 | stream.writeln(": ".join(message)) |
---|
67 | elif self.dots: |
---|
68 | stream.write(label[:1]) |
---|
69 | return |
---|
70 | self.errors.append((test, exc_info)) |
---|
71 | test.passed = False |
---|
72 | if stream is not None: |
---|
73 | if self.showAll: |
---|
74 | self.stream.writeln('ERROR') |
---|
75 | elif self.dots: |
---|
76 | stream.write('E') |
---|
77 | |
---|
78 | def printErrors(self): |
---|
79 | """Overrides to print all errorClasses errors as well. |
---|
80 | """ |
---|
81 | _TextTestResult.printErrors(self) |
---|
82 | for cls in self.errorClasses.keys(): |
---|
83 | storage, label, isfail = self.errorClasses[cls] |
---|
84 | if isfail: |
---|
85 | self.printErrorList(label, storage) |
---|
86 | # Might get patched into a result with no config |
---|
87 | if hasattr(self, 'config'): |
---|
88 | self.config.plugins.report(self.stream) |
---|
89 | |
---|
90 | def printSummary(self, start, stop): |
---|
91 | """Called by the test runner to print the final summary of test |
---|
92 | run results. |
---|
93 | """ |
---|
94 | write = self.stream.write |
---|
95 | writeln = self.stream.writeln |
---|
96 | taken = float(stop - start) |
---|
97 | run = self.testsRun |
---|
98 | plural = run != 1 and "s" or "" |
---|
99 | |
---|
100 | writeln(self.separator2) |
---|
101 | writeln("Ran %s test%s in %.3fs" % (run, plural, taken)) |
---|
102 | writeln() |
---|
103 | |
---|
104 | summary = {} |
---|
105 | eckeys = self.errorClasses.keys() |
---|
106 | eckeys.sort() |
---|
107 | for cls in eckeys: |
---|
108 | storage, label, isfail = self.errorClasses[cls] |
---|
109 | count = len(storage) |
---|
110 | if not count: |
---|
111 | continue |
---|
112 | summary[label] = count |
---|
113 | if len(self.failures): |
---|
114 | summary['failures'] = len(self.failures) |
---|
115 | if len(self.errors): |
---|
116 | summary['errors'] = len(self.errors) |
---|
117 | |
---|
118 | if not self.wasSuccessful(): |
---|
119 | write("FAILED") |
---|
120 | else: |
---|
121 | write("OK") |
---|
122 | items = summary.items() |
---|
123 | if items: |
---|
124 | items.sort() |
---|
125 | write(" (") |
---|
126 | write(", ".join(["%s=%s" % (label, count) for |
---|
127 | label, count in items])) |
---|
128 | writeln(")") |
---|
129 | else: |
---|
130 | writeln() |
---|
131 | |
---|
132 | def wasSuccessful(self): |
---|
133 | """Overrides to check that there are no errors in errorClasses |
---|
134 | lists that are marked as errors and should cause a run to |
---|
135 | fail. |
---|
136 | """ |
---|
137 | if self.errors or self.failures: |
---|
138 | return False |
---|
139 | for cls in self.errorClasses.keys(): |
---|
140 | storage, label, isfail = self.errorClasses[cls] |
---|
141 | if not isfail: |
---|
142 | continue |
---|
143 | if storage: |
---|
144 | return False |
---|
145 | return True |
---|
146 | |
---|
147 | def _addError(self, test, err): |
---|
148 | try: |
---|
149 | exc_info = self._exc_info_to_string(err, test) |
---|
150 | except TypeError: |
---|
151 | # 2.3: does not take test arg |
---|
152 | exc_info = self._exc_info_to_string(err) |
---|
153 | self.errors.append((test, exc_info)) |
---|
154 | if self.showAll: |
---|
155 | self.stream.write('ERROR') |
---|
156 | elif self.dots: |
---|
157 | self.stream.write('E') |
---|
158 | |
---|
159 | def _exc_info_to_string(self, err, test=None): |
---|
160 | # 2.3/2.4 -- 2.4 passes test, 2.3 does not |
---|
161 | try: |
---|
162 | return _TextTestResult._exc_info_to_string(self, err, test) |
---|
163 | except TypeError: |
---|
164 | # 2.3: does not take test arg |
---|
165 | return _TextTestResult._exc_info_to_string(self, err) |
---|
166 | |
---|
167 | |
---|
168 | def ln(*arg, **kw): |
---|
169 | from warnings import warn |
---|
170 | warn("ln() has moved to nose.util from nose.result and will be removed " |
---|
171 | "from nose.result in a future release. Please update your imports ", |
---|
172 | DeprecationWarning) |
---|
173 | return _ln(*arg, **kw) |
---|
174 | |
---|
175 | |
---|