5 This script traverses the tests directory and reports a summary.
8 from __future__
import print_function
9 from collections
import defaultdict
10 from glob
import glob, iglob
12 from os.path
import join, basename
13 from subprocess
import Popen, PIPE
17 import xml.etree.ElementTree
as ET
18 import xml.dom.minidom
20 if not len(sys.argv) == 2:
21 print(
"Usage: run_tests.py PATH_TO_TESTS")
24 os.environ[
"TEST_DEBUG"] =
"1"
31 __author__ =
"Tamas Gal"
32 __credits__ =
"Brian Beyer"
34 __email__ =
"tgal@km3net.de"
35 __status__ =
"Development"
37 TESTS_DIR = sys.argv[1]
38 JUNIT_XML =
'out/junit_{}.xml'.format(os.path.basename(TESTS_DIR))
40 if hasattr(sys.stdout,
'isatty')
and sys.stdout.isatty():
47 INFO, OK, FAIL, RST, BOLD = (
'', ) * 5
52 n_tests = len(test_results)
53 n_failed_tests = sum(1
for r
in test_results.values()
if r[0] > 0)
54 total_time = sum(r[1]
for r
in test_results.values())
59 "Total number of tests: {}\n{}"
60 "Failed tests: {}{}\n"
61 "Elapsed time: {:.1f}s\n".format(
62 INFO, RST, n_tests, BOLD + FAIL
if n_failed_tests > 0
else OK,
63 n_failed_tests, RST, total_time))
67 if n_failed_tests > 0:
75 """Generate XML file according to JUnit specs"""
77 for test_script, (exit_code, t, stdout, stderr)
in test_results.items():
83 test_case.add_error_info(
'non-zero exit-code: %d' % exit_code)
84 test_cases.append(test_case)
85 test_suite =
TestSuite(
"Jpp Test Suite", test_cases)
86 with
open(JUNIT_XML,
'w')
as f:
87 TestSuite.to_file(f, [test_suite])
91 """Prints the STDOUT and STDERR of failing test scripts"""
93 "Captured output of failing tests\n"
94 "================================\n{}".format(INFO, RST))
95 for test_script, (exit_code, t, stdout, stderr)
in test_results.items():
97 print(
"{}\n{}\n".format(test_script, len(test_script) *
'-'))
98 print(
'{}stdout:{}\n{}\n{}stderr:{}\n{}'.format(
99 OK + BOLD, RST, stdout, FAIL + BOLD, RST, stderr))
103 return obj.decode(
'utf-8').encode(
'ascii',
'ignore').decode(
'ascii')
107 """Runs each script in the tests directory and returns the results.
112 The path to the test dir, containing the test scripts (`*.sh`).
116 dict: key = script path, value = (exit_code, elapsed_time, stdout, stderr)
121 for subdir
in sorted(glob(
join(tests_dir,
'*'))):
122 component_group = basename(subdir)
123 print(
"\n{}{}\n{}{}".format(INFO, component_group,
124 len(component_group) *
'=', RST))
125 for test_script
in sorted(glob(
join(subdir,
'*.sh'))):
126 print(
"+ {}".format(test_script), end=
' => ')
129 proc = Popen(test_script, stdout=PIPE, stderr=PIPE)
130 out, err = [
safe_str(t)
for t
in proc.communicate()]
131 exit_code = proc.wait()
132 delta_t = time() - start_time
133 test_results[test_script] = (exit_code, delta_t, out, err)
134 print(
" ({:.2f} s) ".format(delta_t), end=
'')
137 print(
"{}FAILED (exit code {}){}".format(FAIL, exit_code, RST))
140 print(
"{}OK{}".format(OK, RST))
149 Can handle unicode strings or binary strings if their encoding is provided.
170 raise Exception(
'test_cases must be a list of test cases')
185 Builds the XML document for the JUnit test suite.
186 Produces clean unicode strings and decodes non-unicode with the help of encoding.
187 @param encoding: Used to decode encoded strings.
188 @return: XML document with unicode string elements
192 test_suite_attributes = dict()
193 test_suite_attributes[
'name'] = self.
name
194 if any(c.assertions
for c
in self.
test_cases):
195 test_suite_attributes[
'assertions'] = \
196 str(sum([int(c.assertions)
for c
in self.
test_cases if c.assertions]))
197 test_suite_attributes[
'disabled'] = \
198 str(len([c
for c
in self.
test_cases if not c.is_enabled]))
199 test_suite_attributes[
'failures'] = \
200 str(len([c
for c
in self.
test_cases if c.is_failure()]))
201 test_suite_attributes[
'errors'] = \
202 str(len([c
for c
in self.
test_cases if c.is_error()]))
203 test_suite_attributes[
'skipped'] = \
204 str(len([c
for c
in self.
test_cases if c.is_skipped()]))
205 test_suite_attributes[
'time'] = \
206 str(sum(c.elapsed_sec
for c
in self.
test_cases if c.elapsed_sec))
207 test_suite_attributes[
'tests'] = str(len(self.
test_cases))
210 test_suite_attributes[
'hostname'] = self.
hostname
212 test_suite_attributes[
'id'] = self.
id
214 test_suite_attributes[
'package'] = self.
package
216 test_suite_attributes[
'timestamp'] = self.
timestamp
218 test_suite_attributes[
'file'] = self.
file
220 test_suite_attributes[
'log'] = self.
log
222 test_suite_attributes[
'url'] = self.
url
224 xml_element = ET.Element(
"testsuite", test_suite_attributes)
228 props_element = ET.SubElement(xml_element,
"properties")
229 for k, v
in self.properties.items():
234 ET.SubElement(props_element,
"property", attrs)
238 stdout_element = ET.SubElement(xml_element,
"system-out")
239 stdout_element.text = self.
stdout
243 stderr_element = ET.SubElement(xml_element,
"system-err")
244 stderr_element.text = self.
stderr
248 test_case_attributes = dict()
249 test_case_attributes[
'name'] = case.name
252 test_case_attributes[
'assertions'] =
"%d" % case.assertions
254 test_case_attributes[
'time'] =
"%f" % case.elapsed_sec
256 test_case_attributes[
'timestamp'] = case.timestamp
258 test_case_attributes[
'classname'] = case.classname
260 test_case_attributes[
'status'] = case.status
262 test_case_attributes[
'class'] = case.category
264 test_case_attributes[
'file'] = case.file
266 test_case_attributes[
'line'] = case.line
268 test_case_attributes[
'log'] = case.log
270 test_case_attributes[
'url'] = case.url
272 test_case_element = ET.SubElement(xml_element,
"testcase",
273 test_case_attributes)
276 if case.is_failure():
277 attrs = {
'type':
'failure'}
278 if case.failure_message:
279 attrs[
'message'] = case.failure_message
280 if case.failure_type:
281 attrs[
'type'] = case.failure_type
282 failure_element = ET.Element(
"failure", attrs)
283 if case.failure_output:
284 failure_element.text = case.failure_output
285 test_case_element.append(failure_element)
289 attrs = {
'type':
'error'}
290 if case.error_message:
291 attrs[
'message'] = case.error_message
293 attrs[
'type'] = case.error_type
294 error_element = ET.Element(
"error", attrs)
295 if case.error_output:
296 error_element.text = case.error_output
297 test_case_element.append(error_element)
300 if case.is_skipped():
301 attrs = {
'type':
'skipped'}
302 if case.skipped_message:
303 attrs[
'message'] = case.skipped_message
304 skipped_element = ET.Element(
"skipped", attrs)
305 if case.skipped_output:
306 skipped_element.text = case.skipped_output
307 test_case_element.append(skipped_element)
311 stdout_element = ET.Element(
"system-out")
312 stdout_element.text = case.stdout
313 test_case_element.append(stdout_element)
317 stderr_element = ET.Element(
"system-err")
318 stderr_element.text = case.stderr
319 test_case_element.append(stderr_element)
326 Returns the string representation of the JUnit XML document.
327 @param encoding: The encoding of the input.
328 @return: unicode string
334 raise Exception(
'test_suites must be a list of test suites')
336 xml_element = ET.Element(
"testsuites")
337 attributes = defaultdict(int)
338 for ts
in test_suites:
339 ts_xml = ts.build_xml_doc(encoding=encoding)
340 for key
in [
'failures',
'errors',
'tests',
'disabled']:
341 attributes[key] += int(ts_xml.get(key, 0))
343 attributes[key] += float(ts_xml.get(key, 0))
344 xml_element.append(ts_xml)
345 for key, value
in attributes.items():
346 xml_element.set(key, str(value))
348 xml_string = ET.tostring(xml_element, encoding=encoding)
350 xml_string = TestSuite._clean_illegal_xml_chars(
351 xml_string.decode(encoding
or 'utf-8'))
356 xml_string = xml_string.encode(encoding
or 'utf-8')
357 xml_string = xml.dom.minidom.parseString(xml_string)
359 xml_string = xml_string.toprettyxml(encoding=encoding)
361 xml_string = xml_string.decode(encoding)
366 def to_file(file_descriptor, test_suites, prettyprint=True, encoding=None):
368 Writes the JUnit XML document to a file.
370 xml_string = TestSuite.to_xml_string(test_suites,
371 prettyprint=prettyprint,
374 file_descriptor.write(xml_string)
379 Removes any illegal unicode characters from the given XML string.
380 @see: http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python
383 illegal_unichrs = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84),
384 (0x86, 0x9F), (0xD800, 0xDFFF), (0xFDD0, 0xFDDF),
385 (0xFFFE, 0xFFFF), (0x1FFFE, 0x1FFFF),
386 (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
387 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF),
388 (0x6FFFE, 0x6FFFF), (0x7FFFE, 0x7FFFF),
389 (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
390 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF),
391 (0xCFFFE, 0xCFFFF), (0xDFFFE, 0xDFFFF),
392 (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
393 (0x10FFFE, 0x10FFFF)]
397 for (low, high)
in illegal_unichrs
if low < sys.maxunicode
400 illegal_xml_re = re.compile(
'[%s]' %
''.
join(illegal_ranges))
401 return illegal_xml_re.sub(
'', string_to_clean)
405 """A JUnit test case with a result and possibly some stdout or stderr"""
446 """Adds an error message, output, or both to the test case"""
455 """Adds a failure message, output, or both to the test case"""
464 """Adds a skipped message, output, or both to the test case"""
471 """returns true if this test case is a failure"""
475 """returns true if this test case is an error"""
479 """returns true if this test case has been skipped"""
483 if __name__ ==
'__main__':
T * open(const std::string &file_name)
Open file.
def print_captured_output
def _clean_illegal_xml_chars