Jenkins: lously lapic timer set incorrectly for launcher
[akaros.git] / tools / jenkins / utils / test_reporter.py
1 #!/usr/bin/python
2 """Parses AKAROS output to detect tests and report on them.
3 Arguments:
4         [0]: Path to file containing AKAROS output.
5         [1]: Path to directory where to save test reports.
6         [2]: IDs of tests suites to look for.
7 """
8 import markup
9 import re
10 import sys
11
12
13
14 class TestSuite() :
15         """Represents a test suite (collection of test cases) and has the ability of
16         printing itself plus the test cases into XML markup.
17         """
18         def __init__(self, name, class_name) :
19                 """The tests will be reported as belonging to 'name.class_name', so you 
20                 can represent two levels of hierarchy this way.
21                 """
22                 self.name = name
23                 self.class_name = class_name
24                 self.test_cases = []
25                 self.errors_num = 0
26                 self.failures_num = 0
27                 self.skipped_num = 0
28
29         def add_test_case(self, name, status, el_time=None, failure_msg=None) :
30                 """Adds a test case to the suite.
31                 """
32                 test_case = TestCase(self, name, status, el_time, failure_msg)
33                 self.test_cases.append(test_case)
34
35                 if status == 'DISABLED' :
36                         self.skipped_num += 1
37                 elif status == 'FAILED' :
38                         self.failures_num += 1
39
40         def generate_markup(self) :
41                 """Generates and returns a string containing the representation of the
42                 suite and the testcases in XML XUnit format.
43
44                 Returns:
45                         String containing the representation.
46                 """
47                 report = markup.page( mode='xml' )
48                 report.init( encoding='UTF-8' )
49
50                 report.testsuite.open(name='akaros_tests', tests=len(self.test_cases), \
51                                       errors=self.errors_num, \
52                                       failures=self.failures_num, \
53                                       skip=self.skipped_num)
54                 for test_case in self.test_cases:
55                         test_case.generate_markup(report)
56                 report.testsuite.close()
57
58                 return report
59
60 class TestCase() :
61         """Represents a test case, and has ability to print it into a markup page.
62         """
63         def __init__(self, suite, name, status, el_time=None, failure_msg=None) :
64                 self.suite = suite
65                 self.name = name
66                 self.status = status
67                 if self.status in ['PASSED', 'FAILED'] :
68                         self.el_time = el_time
69                 if self.status == 'FAILED' :
70                         self.failure_msg = failure_msg
71
72         def generate_markup(self, report) :
73                 """Generates XML markup representing the test case, and in XUnit format
74                 in the given markup.page file.
75                 """
76                 full_name = self.suite.name + '.' + self.suite.class_name
77
78                 if self.status in ['PASSED', 'FAILED'] :
79                         report.testcase.open(classname=full_name, name=self.name, \
80                                              time=self.el_time)
81                 else :
82                         report.testcase.open(classname=full_name, name=self.name)
83
84                 if self.status == 'DISABLED' :
85                         report.skipped.open(type='DISABLED', message='Disabled')
86                         report.skipped.close()
87                 elif self.status == 'FAILED' :
88                         report.failure.open(type='FAILED', message=self.failure_msg)
89                         report.failure.close()
90
91                 report.testcase.close()
92
93
94
95 class TestParser() :
96         """This class is a helper for parsing the output from test suite groups
97         ran inside AKAROS.
98
99         Tests must be printed on to a file (specified by test_output_path) with the
100         following format:
101         <-- BEGIN_{test_suite_name}_{test_class_name}_TESTS -->
102                 (PASSED|FAILED|DISABLED) [{test_case_name}]({test_et}s)? {failure_msg}?
103                 (PASSED|FAILED|DISABLED) [{test_case_name}]({test_et}s)? {failure_msg}?
104                 ...
105         <-- END_{test_suite_name}_{test_class_name}_TESTS -->
106
107         For example:
108         <-- BEGIN_KERNEL_PB_TESTS -->
109                 PASSED   [test_easy_to_pass](1.000s)
110                 FAILED   [test_will_fail](0.01s)   This test should do X and Y.
111                 DISABLED [test_useless]
112                 ...
113         <-- END_KERNEL_PB_TESTS -->
114         """
115
116         def __init__(self, test_output_path, test_suite_name, test_class_name) :
117                 self.test_output = open(test_output_path, 'r')
118                 self.regex_test_start = \
119                     re.compile('^\s*<--\s*BEGIN_%s_%s_TESTS\s*-->\s*$' \
120                                % (test_suite_name, test_class_name))
121                 self.regex_test_end = \
122                     re.compile('^\s*<--\s*END_%s_%s_TESTS\s*-->\s*$' \
123                                % (test_suite_name, test_class_name))
124                 self.test_suite_name = test_suite_name
125                 self.test_class_name = test_class_name
126
127                 # Prepare for reading.
128                 self.__advance_to_beginning_of_tests()
129
130         def __advance_to_beginning_of_tests(self) :
131                 beginning_reached = False
132                 while not beginning_reached :
133                         line = self.test_output.readline()
134                         if (re.match(self.regex_test_start, line)) :
135                                 beginning_reached = True
136                         elif (len(line) == 0) :
137                                 exc_msg = 'Could not find tests for {0}_{1}.'
138                                 exc_msg = exc_msg.format(self.test_suite_name, \
139                                                          self.test_class_name)
140                                 raise Exception(exc_msg)
141
142         def __extract_test_result(self, line) :
143                 regex = r'^\s*([A-Z]+)\s*.*$'
144                 matchRes = re.match(regex, line)
145                 return matchRes.group(1)
146
147         def __extract_test_name(self, line) :
148                 regex = r'^\s*(?:[A-Z]+)\s*\[([a-zA-Z_-]+)\].*$'
149                 matchRes = re.match(regex, line)
150                 return matchRes.group(1)
151
152         def __extract_test_elapsed_time(self, line) :
153                 regex= r'^\s*(?:PASSED|FAILED)\s*\[(?:[a-zA-Z_-]+)\]\(([0-9\.]+)s\).*$'
154                 matchRes = re.match(regex, line)
155                 return matchRes.group(1)
156
157         def __extract_test_fail_msg(self, line) :
158                 regex = r'^\s*FAILED\s*\[(?:[a-zA-Z_-]+)\](?:\(.*?\))\s+(.*)$'
159                 matchRes = re.match(regex, line)
160                 return matchRes.group(1)
161
162         def __next_test(self) :
163                 """Parses the next test from the test output file.
164                 Returns:
165                         First, True if there was a next test and we had not reached the end.
166                         Second, a String with the name of the test.
167                         Third, result of the test (PASSED, FAILED, DISABLED).
168                         Fourth, time elapsed in seconds, with 3 decimals.
169                         Fifth, message of a failed test.
170                 """
171                 # Look for test.
172                 line = ''
173                 while len(line) < 8 :
174                         line = self.test_output.readline()
175                         if (len(line) == 0) : # EOF
176                                 return False, '', '', ''
177
178                 if (re.match(self.regex_test_end, line)) :
179                         return False, '', '', 0, ''
180                 else :
181                         name = self.__extract_test_name(line)
182                         res = self.__extract_test_result(line)
183                         time = self.__extract_test_elapsed_time(line) \
184                                if res in ['FAILED', 'PASSED'] else None
185                         msg = self.__extract_test_fail_msg(line) if res == 'FAILED' \
186                               else None
187                         
188                         return True, name, res, time, msg
189
190         def __cleanup(self) :
191                 self.test_output.close()
192
193         def parse_test_suite(self) :
194                 test_suite = TestSuite(self.test_suite_name, self.test_class_name)
195
196                 end_not_reached = True
197                 while end_not_reached :
198                         end_not_reached, test_name, test_res, test_et, fail_msg \
199                             = self.__next_test()
200                         if end_not_reached :
201                                 test_suite.add_test_case(test_name, test_res, test_et, fail_msg)
202                 
203                 self.__cleanup()
204                 
205                 return test_suite
206
207
208 def extract_tests(path):
209         regex = r'^\s*<--\s*BEGIN_(.+_.+)_TESTS\s*-->\s*$'
210         f = open(path, 'r')
211         tests = []
212         for line in f:
213                 matchRes = re.match(regex, line)
214                 if matchRes:
215                         tests.append(matchRes.group(1))
216         return tests
217
218 def save_report(dir, filename, report) :
219         filepath = dir + '/' + filename + '_TESTS.xml'
220         report_file = open(filepath, 'w+')
221         report_file.write(report)
222         report_file.flush()
223         report_file.close()
224
225
226 def main() :
227         akaros_output_file_path = sys.argv[1]
228         test_output_dir = sys.argv[2]
229         tests = extract_tests(akaros_output_file_path)
230
231         # Parse, process, and save the test results
232         for test in tests :
233                 suite_name, class_name = test.split('_')
234                 test_suite = TestParser(akaros_output_file_path, \
235                                         suite_name, class_name).parse_test_suite()
236                 test_report_str = test_suite.generate_markup().__str__()
237                 save_report(test_output_dir, test, test_report_str)
238
239 main()