blob: 62c77a2e1e4ff3757aaffd71258a9fafc68caf08 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001from tcmessages import TeamcityServiceMessages
2import sys, traceback, datetime
3import unittest
4from tcunittest import strclass
5from tcunittest import TeamcityTestResult
6
7try:
8 from nose.util import isclass # backwards compat
9 from nose.config import Config
10 from nose.result import TextTestResult
11 from nose import SkipTest
12 from nose.plugins.errorclass import ErrorClassPlugin
13except (Exception, ):
14 e = sys.exc_info()[1]
15 raise NameError(
16 "Something went wrong, do you have nosetest installed? I got this error: %s" % e)
17
18class TeamcityPlugin(ErrorClassPlugin, TextTestResult, TeamcityTestResult):
19 """
20 TeamcityTest plugin for nose tests
21 """
22 name = "TeamcityPlugin"
23 enabled = True
24
25 def __init__(self, stream=sys.stderr, descriptions=None, verbosity=1,
26 config=None, errorClasses=None):
27 super(TeamcityPlugin, self).__init__()
28
29 if errorClasses is None:
30 errorClasses = {}
31
32 self.errorClasses = errorClasses
33 if config is None:
34 config = Config()
35 self.config = config
36 self.output = stream
37 self.messages = TeamcityServiceMessages(self.output,
38 prepend_linebreak=True)
39 self.messages.testMatrixEntered()
40 self.current_suite = None
41 TextTestResult.__init__(self, stream, descriptions, verbosity, config,
42 errorClasses)
43 TeamcityTestResult.__init__(self, stream)
44
45 def configure(self, options, conf):
46 if not self.can_configure:
47 return
48 self.conf = conf
49
50
51 def addError(self, test, err):
52 exctype, value, tb = err
53 err = self.formatErr(err)
54 if exctype == SkipTest:
55 self.messages.testIgnored(self.getTestName(test), message='Skip')
56 else:
57 self.messages.testError(self.getTestName(test), message='Error', details=err)
58
59 def formatErr(self, err):
60 exctype, value, tb = err
61 if isinstance(value, str):
62 value = exctype(value)
63 return ''.join(traceback.format_exception(exctype, value, tb))
64
65 def is_gen(self, test):
66 if hasattr(test, "test") and hasattr(test.test, "descriptor"):
67 if test.test.descriptor is not None:
68 return True
69 return False
70
71
72 def getTestName(self, test):
73 if hasattr(test, "error_context"):
74 return test.error_context
75 test_name_full = str(test)
76 if self.is_gen(test):
77 return test_name_full
78
79 ind_1 = test_name_full.rfind('(')
80 if ind_1 != -1:
81 return test_name_full[:ind_1]
82 ind = test_name_full.rfind('.')
83 if ind != -1:
84 return test_name_full[test_name_full.rfind(".") + 1:]
85 return test_name_full
86
87
88 def addFailure(self, test, err):
89 err = self.formatErr(err)
90
91 self.messages.testFailed(self.getTestName(test),
92 message='Failure', details=err)
93
94
95 def addSkip(self, test, reason):
96 self.messages.testIgnored(self.getTestName(test), message=reason)
97
98
99 def __getSuite(self, test):
100 if hasattr(test, "suite"):
101 suite = strclass(test.suite)
102 suite_location = test.suite.location
103 location = test.suite.abs_location
104 if hasattr(test, "lineno"):
105 location = location + ":" + str(test.lineno)
106 else:
107 location = location + ":" + str(test.test.lineno)
108 else:
109 suite = strclass(test.__class__)
110 suite_location = "python_uttestid://" + suite
111 try:
112 from nose_helper.util import func_lineno
113
114 if hasattr(test.test, "descriptor") and test.test.descriptor:
115 suite_location = "file://" + self.test_address(
116 test.test.descriptor)
117 location = suite_location + ":" + str(
118 func_lineno(test.test.descriptor))
119 else:
120 suite_location = "file://" + self.test_address(
121 test.test.test)
122 location = "file://" + self.test_address(
123 test.test.test) + ":" + str(func_lineno(test.test.test))
124 except:
125 test_id = test.id()
126 suite_id = test_id[:test_id.rfind(".")]
127 suite_location = "python_uttestid://" + str(suite_id)
128 location = "python_uttestid://" + str(test_id)
129 return (location, suite_location)
130
131
132 def test_address(self, test):
133 if hasattr(test, "address"):
134 return test.address()[0]
135 t = type(test)
136 file = None
137 import types, os
138
139 if (t == types.FunctionType or issubclass(t, type) or t == type
140 or isclass(test)):
141 module = getattr(test, '__module__', None)
142 if module is not None:
143 m = sys.modules[module]
144 file = getattr(m, '__file__', None)
145 if file is not None:
146 file = os.path.abspath(file)
147 if file.endswith("pyc"):
148 file = file[:-1]
149 return file
150 raise TypeError("I don't know what %s is (%s)" % (test, t))
151
152
153 def getSuiteName(self, test):
154 test_name_full = str(test)
155
156 if self.is_gen(test):
157 ind_1 = test_name_full.rfind('(')
158 if ind_1 != -1:
159 ind = test_name_full.rfind('.')
160 if ind != -1:
161 return test_name_full[:test_name_full.rfind(".")]
162
163 ind_1 = test_name_full.rfind('(')
164 if ind_1 != -1:
165 return test_name_full[ind_1 + 1: -1]
166 ind = test_name_full.rfind('.')
167 if ind != -1:
168 return test_name_full[:test_name_full.rfind(".")]
169 return test_name_full
170
171
172 def startTest(self, test):
173 location, suite_location = self.__getSuite(test)
174 suite = self.getSuiteName(test)
175 if suite != self.current_suite:
176 if self.current_suite:
177 self.messages.testSuiteFinished(self.current_suite)
178 self.current_suite = suite
179 self.messages.testSuiteStarted(self.current_suite,
180 location=suite_location)
181 setattr(test, "startTime", datetime.datetime.now())
182 self.messages.testStarted(self.getTestName(test), location=location)
183
184
185 def stopTest(self, test):
186 start = getattr(test, "startTime", datetime.datetime.now())
187 d = datetime.datetime.now() - start
188 duration = d.microseconds / 1000 + d.seconds * 1000 + d.days * 86400000
189 self.messages.testFinished(self.getTestName(test),
190 duration=int(duration))
191
192
193 def finalize(self, result):
194 if self.current_suite:
195 self.messages.testSuiteFinished(self.current_suite)
196 self.current_suite = None
197
198
199class TeamcityNoseRunner(unittest.TextTestRunner):
200 """Test runner that supports teamcity output
201 """
202
203 def __init__(self, stream=sys.stdout, descriptions=1, verbosity=1,
204 config=None):
205 if config is None:
206 config = Config()
207 self.config = config
208
209 unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
210
211
212 def _makeResult(self):
213 return TeamcityPlugin(self.stream,
214 self.descriptions,
215 self.verbosity,
216 self.config)
217
218 def run(self, test):
219 """Overrides to provide plugin hooks and defer all output to
220 the test result class.
221 """
222 #for 2.5 compat
223 plugins = self.config.plugins
224 plugins.configure(self.config.options, self.config)
225 plugins.begin()
226 wrapper = plugins.prepareTest(test)
227 if wrapper is not None:
228 test = wrapper
229
230 # plugins can decorate or capture the output stream
231 wrapped = self.config.plugins.setOutputStream(self.stream)
232 if wrapped is not None:
233 self.stream = wrapped
234
235 result = self._makeResult()
236 test(result)
237 result.endLastSuite()
238 plugins.finalize(result)
239
240 return result