| #!/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() |