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 import codecs
37 import os
38 import sys
39 import string
40 from xml.dom.minidom import parse, parseString
41 from xml.dom import Node as DomNode
42
43 import roslib.rosenv
44
45
47
48
50 self.type = type
51 self.text = text
52
55 return u'<error type="%s"><![CDATA[%s]]></error>'%(self.type, self.text)
56
57
60 return u'<failure type="%s"><![CDATA[%s]]></failure>'%(self.type, self.text)
61
62
63
65
67 self.name = name
68 self.failures = []
69 self.errors = []
70 self.time = 0.0
71 self.classname = ''
72
73
75 return not self.errors and not self.failures
76
77 passed = property(_passed)
78
79
81 if self.failures:
82 tmpl = "[%s][FAILURE]"%self.name
83 tmpl = tmpl + '-'*(80-len(tmpl))
84 tmpl = tmpl+"\n%s\n"+'-'*80+"\n\n"
85 return '\n'.join(tmpl%x.text for x in self.failures)
86 return ''
87
89 if self.errors:
90 tmpl = "[%s][ERROR]"%self.name
91 tmpl = tmpl + '-'*(80-len(tmpl))
92 tmpl = tmpl+"\n%s\n"+'-'*80+"\n\n"
93 return '\n'.join(tmpl%x.text for x in self.errors)
94 return ''
95
97 if self.passed:
98 return "[%s][passed]\n"%self.name
99 else:
100 return self._failure_description()+\
101 self._error_description()
102
103 description = property(_description)
104
106 self.failures.append(failure)
107
109 self.errors.append(error)
110
112 return u' <testcase classname="%s" name="%s" time="%s">\n'%(self.classname, self.name, self.time)+\
113 '\n '.join([f.xml() for f in self.failures])+\
114 '\n '.join([e.xml() for e in self.errors])+\
115 ' </testcase>'
116
118 __slots__ = ['name', 'num_errors', 'num_failures', 'num_tests', \
119 'test_case_results', 'system_out', 'system_err', 'time']
120 - def __init__(self, name, num_errors, num_failures, num_tests):
129
130
131
141
142
143
146
147
149 return u'<?xml version="1.0" encoding="utf-8"?>'+\
150 '<testsuite name="%s" tests="%s" errors="%s" failures="%s" time="%s">'%\
151 (self.name, self.num_tests, self.num_errors, self.num_failures, self.time)+\
152 '\n'.join([tc.xml() for tc in self.test_case_results])+\
153 ' <system-out><![CDATA[%s]]></system-out>'%self.system_out+\
154 ' <system-err><![CDATA[%s]]></system-err>'%self.system_err+\
155 '</testsuite>'
156
158 return reduce(lambda x, y: x + y, [c.data for c in tag.childNodes if c.nodeType in [DomNode.TEXT_NODE, DomNode.CDATA_SECTION_NODE]], "").strip()
159
161 nodes = [n for n in test_suite.childNodes \
162 if n.nodeType == DomNode.ELEMENT_NODE]
163 for node in nodes:
164 name = node.tagName
165 if name == 'testsuite':
166
167 _load_suite_results(test_suite_name, node, result)
168 elif name == 'system-out':
169 if _text(node):
170 system_out = "[%s] stdout"%test_suite_name + "-"*(71-len(test_suite_name))
171 system_out += '\n'+_text(node)
172 result.system_out += system_out
173 elif name == 'system-err':
174 if _text(node):
175 system_err = "[%s] stderr"%test_suite_name + "-"*(71-len(test_suite_name))
176 system_err += '\n'+_text(node)
177 result.system_err += system_err
178 elif name == 'testcase':
179 name = node.getAttribute('name') or 'unknown'
180 classname = node.getAttribute('classname') or 'unknown'
181
182
183
184 if '__main__.' in classname:
185 classname = classname[classname.find('__main__.')+9:]
186 if classname == 'rostest.rostest.RosTest':
187 classname = 'rostest'
188 elif not classname.startswith(result.name):
189 classname = "%s.%s"%(result.name,classname)
190
191 time = node.getAttribute('time') or 0.0
192 tc_result = TestCaseResult("%s/%s"%(test_suite_name,name))
193 tc_result.classname = classname
194 tc_result.time = time
195 result.add_test_case_result(tc_result)
196 for d in [n for n in node.childNodes \
197 if n.nodeType == DomNode.ELEMENT_NODE]:
198
199
200 if d.tagName == 'failure':
201 message = d.getAttribute('message') or ''
202 text = _text(d) or message
203 x = TestFailure(d.getAttribute('type') or '', text)
204 tc_result.add_failure(x)
205 elif d.tagName == 'error':
206 message = d.getAttribute('message') or ''
207 text = _text(d) or message
208 x = TestError(d.getAttribute('type') or '', text)
209 tc_result.add_error(x)
210
211
212
213
214 import re
215 RE_XML_ILLEGAL = u'([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])' + \
216 u'|' + \
217 u'([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])' % \
218 (unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
219 unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
220 unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff))
221 _safe_xml_regex = re.compile(RE_XML_ILLEGAL)
222
223
225 f = None
226 try:
227
228
229 if not os.path.isfile(test_file):
230 raise Exception("test file does not exist")
231 try:
232 f = codecs.open(test_file, "r", "utf-8" )
233 x = f.read()
234 except:
235 if f is not None:
236 f.close()
237 f = codecs.open(test_file, "r", "iso8859-1" )
238 x = f.read()
239
240 for match in _safe_xml_regex.finditer(x):
241 x = x[:match.start()] + "?" + x[match.end():]
242 return x.encode("utf-8")
243 finally:
244 if f is not None:
245 f.close()
246
247
248
249
250
251 -def read(test_file, test_name):
252 try:
253 xml_str = _read_file_safe_xml(test_file)
254 if not xml_str.strip():
255 print "WARN: test result file is empty [%s]"%(test_file)
256 return Result(test_name, 0, 0, 0)
257 test_suite = parseString(xml_str).getElementsByTagName('testsuite')
258 except Exception as e:
259 print >> sys.stderr, str(e)
260 print "WARN: cannot read test result file [%s]: %s"%(test_file, str(e))
261 return Result(test_name, 0, 0, 0)
262 if not test_suite:
263 print "WARN: test result file [%s] contains no results"%test_file
264 return Result(test_name, 0, 0, 0)
265 test_suite = test_suite[0]
266 vals = [test_suite.getAttribute(attr) for attr in ['errors', 'failures', 'tests']]
267 vals = [v or 0 for v in vals]
268 err, fail, tests = [string.atoi(val) for val in vals]
269
270 result = Result(test_name, err, fail, tests)
271 result.time = test_suite.getAttribute('time') or 0.0
272
273
274
275
276 test_file_base = os.path.basename(os.path.dirname(test_file))
277 fname = os.path.basename(test_file)
278 if fname.startswith('TEST-'):
279 fname = fname[5:]
280 if fname.endswith('.xml'):
281 fname = fname[:-4]
282 test_file_base = "%s.%s"%(test_file_base, fname)
283 _load_suite_results(test_file_base, test_suite, result)
284 return result
285
287 """
288 Read in the test_results and aggregate into a single Result object
289 @param filter: list of packages that should be processed
290 @type filter: [str]
291 @return: aggregated result
292 @rtype: L{Result}
293 """
294 dir_ = roslib.rosenv.get_test_results_dir()
295 root_result = Result('ros', 0, 0, 0)
296 if not os.path.exists(dir_):
297 return root_result
298 for d in os.listdir(dir_):
299 if filter and not d in filter:
300 continue
301 subdir = os.path.join(dir_, d)
302 if os.path.isdir(subdir):
303 for file in os.listdir(subdir):
304 if file.endswith('.xml'):
305 file = os.path.join(subdir, file)
306 result = read(file, os.path.basename(subdir))
307 root_result.accumulate(result)
308 return root_result
309