blob: 5fd9b254e7e46b6aeff8676038cf7cb69bfd1145 [file] [log] [blame]
#!/usr/bin/python
"""Parses AKAROS output to detect tests and report on them.
Arguments:
[0]: Path to file containing AKAROS output.
[1]: Path to directory where to save test reports.
[2]: IDs of tests suites to look for.
"""
import markup
import re
import sys
class TestSuite() :
"""Represents a test suite (collection of test cases) and has the ability of
printing itself plus the test cases into XML markup.
"""
def __init__(self, name, class_name) :
"""The tests will be reported as belonging to 'name.class_name', so you
can represent two levels of hierarchy this way.
"""
self.name = name
self.class_name = class_name
self.test_cases = []
self.errors_num = 0
self.failures_num = 0
self.skipped_num = 0
def add_test_case(self, name, status, el_time=None, failure_msg=None) :
"""Adds a test case to the suite.
"""
test_case = TestCase(self, name, status, el_time, failure_msg)
self.test_cases.append(test_case)
if status == 'DISABLED' :
self.skipped_num += 1
elif status == 'FAILED' :
self.failures_num += 1
def generate_markup(self) :
"""Generates and returns a string containing the representation of the
suite and the testcases in XML XUnit format.
Returns:
String containing the representation.
"""
report = markup.page( mode='xml' )
report.init( encoding='UTF-8' )
report.testsuite.open(name='akaros_tests', tests=len(self.test_cases), \
errors=self.errors_num, \
failures=self.failures_num, \
skip=self.skipped_num)
for test_case in self.test_cases:
test_case.generate_markup(report)
report.testsuite.close()
return report
class TestCase() :
"""Represents a test case, and has ability to print it into a markup page.
"""
def __init__(self, suite, name, status, el_time=None, failure_msg=None) :
self.suite = suite
self.name = name
self.status = status
if self.status in ['PASSED', 'FAILED'] :
self.el_time = el_time
if self.status == 'FAILED' :
self.failure_msg = failure_msg
def generate_markup(self, report) :
"""Generates XML markup representing the test case, and in XUnit format
in the given markup.page file.
"""
full_name = self.suite.name + '.' + self.suite.class_name
if self.status in ['PASSED', 'FAILED'] :
report.testcase.open(classname=full_name, name=self.name, \
time=self.el_time)
else :
report.testcase.open(classname=full_name, name=self.name)
if self.status == 'DISABLED' :
report.skipped.open(type='DISABLED', message='Disabled')
report.skipped.close()
elif self.status == 'FAILED' :
report.failure.open(type='FAILED', message=self.failure_msg)
report.failure.close()
report.testcase.close()
class TestParser() :
"""This class is a helper for parsing the output from test suite groups
ran inside AKAROS.
Tests must be printed on to a file (specified by test_output_path) with the
following format:
<-- BEGIN_{test_suite_name}_{test_class_name}_TESTS -->
(PASSED|FAILED|DISABLED) [{test_case_name}]({test_et}s)? {failure_msg}?
(PASSED|FAILED|DISABLED) [{test_case_name}]({test_et}s)? {failure_msg}?
...
<-- END_{test_suite_name}_{test_class_name}_TESTS -->
For example:
<-- BEGIN_KERNEL_PB_TESTS -->
PASSED [test_easy_to_pass](1.000s)
FAILED [test_will_fail](0.01s) This test should do X and Y.
DISABLED [test_useless]
...
<-- END_KERNEL_PB_TESTS -->
"""
def __init__(self, test_output_path, test_suite_name, test_class_name) :
self.test_output = open(test_output_path, 'r')
self.regex_test_start = \
re.compile('^\s*<--\s*BEGIN_%s_%s_TESTS\s*-->\s*$' \
% (test_suite_name, test_class_name))
self.regex_test_end = \
re.compile('^\s*<--\s*END_%s_%s_TESTS\s*-->\s*$' \
% (test_suite_name, test_class_name))
self.test_suite_name = test_suite_name
self.test_class_name = test_class_name
# Prepare for reading.
self.__advance_to_beginning_of_tests()
def __advance_to_beginning_of_tests(self) :
beginning_reached = False
while not beginning_reached :
line = self.test_output.readline()
if (re.match(self.regex_test_start, line)) :
beginning_reached = True
elif (len(line) == 0) :
exc_msg = 'Could not find tests for {0}_{1}.'
exc_msg = exc_msg.format(self.test_suite_name, \
self.test_class_name)
raise Exception(exc_msg)
def __extract_test_result(self, line) :
regex = r'^\s*([A-Z]+)\s*.*$'
matchRes = re.match(regex, line)
return matchRes.group(1)
def __extract_test_name(self, line) :
regex = r'^\s*(?:[A-Z]+)\s*\[([a-zA-Z_-]+)\].*$'
matchRes = re.match(regex, line)
return matchRes.group(1)
def __extract_test_elapsed_time(self, line) :
regex= r'^\s*(?:PASSED|FAILED)\s*\[(?:[a-zA-Z_-]+)\]\(([0-9\.]+)s\).*$'
matchRes = re.match(regex, line)
return matchRes.group(1)
def __extract_test_fail_msg(self, line) :
regex = r'^\s*FAILED\s*\[(?:[a-zA-Z_-]+)\](?:\(.*?\))\s+(.*)$'
matchRes = re.match(regex, line)
return matchRes.group(1)
def __next_test(self) :
"""Parses the next test from the test output file.
Returns:
First, True if there was a next test and we had not reached the end.
Second, a String with the name of the test.
Third, result of the test (PASSED, FAILED, DISABLED).
Fourth, time elapsed in seconds, with 3 decimals.
Fifth, message of a failed test.
"""
# Look for test.
line = ''
while len(line) < 8 :
line = self.test_output.readline()
if (len(line) == 0) : # EOF
return False, '', '', ''
if (re.match(self.regex_test_end, line)) :
return False, '', '', 0, ''
else :
name = self.__extract_test_name(line)
res = self.__extract_test_result(line)
time = self.__extract_test_elapsed_time(line) \
if res in ['FAILED', 'PASSED'] else None
msg = self.__extract_test_fail_msg(line) if res == 'FAILED' \
else None
return True, name, res, time, msg
def __cleanup(self) :
self.test_output.close()
def parse_test_suite(self) :
test_suite = TestSuite(self.test_suite_name, self.test_class_name)
end_not_reached = True
while end_not_reached :
end_not_reached, test_name, test_res, test_et, fail_msg \
= self.__next_test()
if end_not_reached :
test_suite.add_test_case(test_name, test_res, test_et, fail_msg)
self.__cleanup()
return test_suite
def extract_tests(path):
regex = r'^\s*<--\s*BEGIN_(.+_.+)_TESTS\s*-->\s*$'
f = open(path, 'r')
tests = []
for line in f:
matchRes = re.match(regex, line)
if matchRes:
tests.append(matchRes.group(1))
return tests
def save_report(dir, filename, report) :
filepath = dir + '/' + filename + '_TESTS.xml'
report_file = open(filepath, 'w+')
report_file.write(report)
report_file.flush()
report_file.close()
def main() :
akaros_output_file_path = sys.argv[1]
test_output_dir = sys.argv[2]
tests = extract_tests(akaros_output_file_path)
# Parse, process, and save the test results
for test in tests :
suite_name, class_name = test.split('_')
test_suite = TestParser(akaros_output_file_path, \
suite_name, class_name).parse_test_suite()
test_report_str = test_suite.generate_markup().__str__()
save_report(test_output_dir, test, test_report_str)
main()