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')) + glob(
join(subdir, 
'*.csh'))):
 
  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