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