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"
26 PY2 = sys.version_info < (3, 0, 0)
33 __author__ =
"Tamas Gal"
34 __credits__ =
"Brian Beyer"
36 __email__ =
"tgal@km3net.de"
37 __status__ =
"Development"
39 TESTS_DIR = sys.argv[1]
40 JUNIT_XML =
'out/junit_{}.xml'.format(os.path.basename(TESTS_DIR))
42 if hasattr(sys.stdout,
'isatty')
and sys.stdout.isatty():
49 INFO, OK, FAIL, RST, BOLD = (
'', ) * 5
54 n_tests = len(test_results)
55 n_failed_tests = sum(1
for r
in test_results.values()
if r[0] > 0)
56 total_time = sum(r[1]
for r
in test_results.values())
61 "Total number of tests: {}\n{}"
62 "Failed tests: {}{}\n"
63 "Elapsed time: {:.1f}s\n".format(
64 INFO, RST, n_tests, BOLD + FAIL
if n_failed_tests > 0
else OK,
65 n_failed_tests, RST, total_time))
69 if n_failed_tests > 0:
77 """Generate XML file according to JUnit specs"""
79 for test_script, (exit_code, t, stdout, stderr)
in test_results.items():
85 test_case.add_error_info(
'non-zero exit-code: %d' % exit_code)
86 test_cases.append(test_case)
87 test_suite =
TestSuite(
"Jpp Test Suite", test_cases)
88 with
open(JUNIT_XML,
'w')
as f:
89 TestSuite.to_file(f, [test_suite])
93 """Prints the STDOUT and STDERR of failing test scripts"""
95 "Captured output of failing tests\n"
96 "================================\n{}".format(INFO, RST))
97 for test_script, (exit_code, t, stdout, stderr)
in test_results.items():
99 print(
"{}\n{}\n".format(test_script, len(test_script) *
'-'))
100 print(
'{}stdout:{}\n{}\n{}stderr:{}\n{}'.format(
101 OK + BOLD, RST, stdout.decode(
'utf-8'), FAIL + BOLD, RST,
102 stderr.decode(
'utf-8')))
106 """Runs each script in the tests directory and returns the results.
111 The path to the test dir, containing the test scripts (`*.sh`).
115 dict: key = script path, value = (exit_code, elapsed_time, stdout, stderr)
120 for subdir
in sorted(glob(
join(tests_dir,
'*'))):
121 component_group = basename(subdir)
122 print(
"\n{}{}\n{}{}".format(INFO, component_group,
123 len(component_group) *
'=', RST))
124 for test_script
in sorted(glob(
join(subdir,
'*.sh'))):
125 print(
"+ {}".format(test_script), end=
' => ')
128 proc = Popen(test_script, stdout=PIPE, stderr=PIPE)
129 out, err = proc.communicate()
130 exit_code = proc.wait()
131 delta_t = time() - start_time
132 test_results[test_script] = (exit_code, delta_t, out, err)
133 print(
" ({:.2f} s) ".format(delta_t), end=
'')
136 print(
"{}FAILED (exit code {}){}".format(FAIL, exit_code, RST))
139 print(
"{}OK{}".format(OK, RST))
147 If not already unicode, decode it.
150 if isinstance(var, unicode):
152 elif isinstance(var, str):
154 ret = var.decode(encoding)
167 Can handle unicode strings or binary strings if their encoding is provided.
188 raise Exception(
'test_cases must be a list of test cases')
203 Builds the XML document for the JUnit test suite.
204 Produces clean unicode strings and decodes non-unicode with the help of encoding.
205 @param encoding: Used to decode encoded strings.
206 @return: XML document with unicode string elements
210 test_suite_attributes = dict()
211 test_suite_attributes[
'name'] =
decode(self.
name, encoding)
212 if any(c.assertions
for c
in self.
test_cases):
213 test_suite_attributes[
'assertions'] = \
214 str(sum([int(c.assertions)
for c
in self.
test_cases if c.assertions]))
215 test_suite_attributes[
'disabled'] = \
216 str(len([c
for c
in self.
test_cases if not c.is_enabled]))
217 test_suite_attributes[
'failures'] = \
218 str(len([c
for c
in self.
test_cases if c.is_failure()]))
219 test_suite_attributes[
'errors'] = \
220 str(len([c
for c
in self.
test_cases if c.is_error()]))
221 test_suite_attributes[
'skipped'] = \
222 str(len([c
for c
in self.
test_cases if c.is_skipped()]))
223 test_suite_attributes[
'time'] = \
224 str(sum(c.elapsed_sec
for c
in self.
test_cases if c.elapsed_sec))
225 test_suite_attributes[
'tests'] = str(len(self.
test_cases))
228 test_suite_attributes[
'hostname'] =
decode(self.
hostname, encoding)
230 test_suite_attributes[
'id'] =
decode(self.
id, encoding)
232 test_suite_attributes[
'package'] =
decode(self.
package, encoding)
234 test_suite_attributes[
'timestamp'] =
decode(
237 test_suite_attributes[
'file'] =
decode(self.
file, encoding)
239 test_suite_attributes[
'log'] =
decode(self.
log, encoding)
241 test_suite_attributes[
'url'] =
decode(self.
url, encoding)
243 xml_element = ET.Element(
"testsuite", test_suite_attributes)
247 props_element = ET.SubElement(xml_element,
"properties")
248 for k, v
in self.properties.items():
250 'name':
decode(k, encoding),
251 'value':
decode(v, encoding)
253 ET.SubElement(props_element,
"property", attrs)
257 stdout_element = ET.SubElement(xml_element,
"system-out")
262 stderr_element = ET.SubElement(xml_element,
"system-err")
267 test_case_attributes = dict()
268 test_case_attributes[
'name'] =
decode(case.name, encoding)
271 test_case_attributes[
'assertions'] =
"%d" % case.assertions
273 test_case_attributes[
'time'] =
"%f" % case.elapsed_sec
275 test_case_attributes[
'timestamp'] =
decode(
276 case.timestamp, encoding)
278 test_case_attributes[
'classname'] =
decode(
279 case.classname, encoding)
281 test_case_attributes[
'status'] =
decode(case.status, encoding)
283 test_case_attributes[
'class'] =
decode(case.category, encoding)
285 test_case_attributes[
'file'] =
decode(case.file, encoding)
287 test_case_attributes[
'line'] =
decode(case.line, encoding)
289 test_case_attributes[
'log'] =
decode(case.log, encoding)
291 test_case_attributes[
'url'] =
decode(case.url, encoding)
293 test_case_element = ET.SubElement(xml_element,
"testcase",
294 test_case_attributes)
297 if case.is_failure():
298 attrs = {
'type':
'failure'}
299 if case.failure_message:
300 attrs[
'message'] =
decode(case.failure_message, encoding)
301 if case.failure_type:
302 attrs[
'type'] =
decode(case.failure_type, encoding)
303 failure_element = ET.Element(
"failure", attrs)
304 if case.failure_output:
305 failure_element.text =
decode(case.failure_output,
307 test_case_element.append(failure_element)
311 attrs = {
'type':
'error'}
312 if case.error_message:
313 attrs[
'message'] =
decode(case.error_message, encoding)
315 attrs[
'type'] =
decode(case.error_type, encoding)
316 error_element = ET.Element(
"error", attrs)
317 if case.error_output:
318 error_element.text =
decode(case.error_output, encoding)
319 test_case_element.append(error_element)
322 if case.is_skipped():
323 attrs = {
'type':
'skipped'}
324 if case.skipped_message:
325 attrs[
'message'] =
decode(case.skipped_message, encoding)
326 skipped_element = ET.Element(
"skipped", attrs)
327 if case.skipped_output:
328 skipped_element.text =
decode(case.skipped_output,
330 test_case_element.append(skipped_element)
334 stdout_element = ET.Element(
"system-out")
335 stdout_element.text =
decode(case.stdout, encoding)
336 test_case_element.append(stdout_element)
340 stderr_element = ET.Element(
"system-err")
341 stderr_element.text =
decode(case.stderr, encoding)
342 test_case_element.append(stderr_element)
349 Returns the string representation of the JUnit XML document.
350 @param encoding: The encoding of the input.
351 @return: unicode string
357 raise Exception(
'test_suites must be a list of test suites')
359 xml_element = ET.Element(
"testsuites")
360 attributes = defaultdict(int)
361 for ts
in test_suites:
362 ts_xml = ts.build_xml_doc(encoding=encoding)
363 for key
in [
'failures',
'errors',
'tests',
'disabled']:
364 attributes[key] += int(ts_xml.get(key, 0))
366 attributes[key] += float(ts_xml.get(key, 0))
367 xml_element.append(ts_xml)
368 for key, value
in attributes.items():
369 xml_element.set(key, str(value))
371 xml_string = ET.tostring(xml_element, encoding=encoding)
373 xml_string = TestSuite._clean_illegal_xml_chars(
374 xml_string.decode(encoding
or 'utf-8'))
379 xml_string = xml_string.encode(encoding
or 'utf-8')
380 xml_string = xml.dom.minidom.parseString(xml_string)
382 xml_string = xml_string.toprettyxml(encoding=encoding)
384 xml_string = xml_string.decode(encoding)
389 def to_file(file_descriptor, test_suites, prettyprint=True, encoding=None):
391 Writes the JUnit XML document to a file.
393 xml_string = TestSuite.to_xml_string(test_suites,
394 prettyprint=prettyprint,
397 file_descriptor.write(xml_string)
402 Removes any illegal unicode characters from the given XML string.
403 @see: http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python
406 illegal_unichrs = [(0x00, 0x08), (0x0B, 0x1F), (0x7F, 0x84),
407 (0x86, 0x9F), (0xD800, 0xDFFF), (0xFDD0, 0xFDDF),
408 (0xFFFE, 0xFFFF), (0x1FFFE, 0x1FFFF),
409 (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
410 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF),
411 (0x6FFFE, 0x6FFFF), (0x7FFFE, 0x7FFFF),
412 (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
413 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF),
414 (0xCFFFE, 0xCFFFF), (0xDFFFE, 0xDFFFF),
415 (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
416 (0x10FFFE, 0x10FFFF)]
420 for (low, high)
in illegal_unichrs
if low < sys.maxunicode
423 illegal_xml_re = re.compile(
'[%s]' %
''.
join(illegal_ranges))
424 return illegal_xml_re.sub(
'', string_to_clean)
428 """A JUnit test case with a result and possibly some stdout or stderr"""
469 """Adds an error message, output, or both to the test case"""
478 """Adds a failure message, output, or both to the test case"""
487 """Adds a skipped message, output, or both to the test case"""
494 """returns true if this test case is a failure"""
498 """returns true if this test case is an error"""
502 """returns true if this test case has been skipped"""
506 if __name__ ==
'__main__':
T * open(const std::string &file_name)
Open file.
def print_captured_output
def _clean_illegal_xml_chars