OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 | 5 |
| 6 import json |
6 import logging | 7 import logging |
| 8 import os |
| 9 import time |
| 10 import traceback |
| 11 |
| 12 import constants |
7 | 13 |
8 | 14 |
9 # Language values match constants in Sponge protocol buffer (sponge.proto). | 15 # Language values match constants in Sponge protocol buffer (sponge.proto). |
10 JAVA = 5 | 16 JAVA = 5 |
11 PYTHON = 7 | 17 PYTHON = 7 |
12 | 18 |
13 | 19 |
14 class BaseTestResult(object): | 20 class BaseTestResult(object): |
15 """A single result from a unit test.""" | 21 """A single result from a unit test.""" |
16 | 22 |
(...skipping 11 matching lines...) Expand all Loading... |
28 dur: Duration of the test run in milliseconds. | 34 dur: Duration of the test run in milliseconds. |
29 lang: Language of the test (JAVA or PYTHON). | 35 lang: Language of the test (JAVA or PYTHON). |
30 log: An optional string listing any errors. | 36 log: An optional string listing any errors. |
31 error: A tuple of a short error message and a longer version used by Sponge | 37 error: A tuple of a short error message and a longer version used by Sponge |
32 if test resulted in a fail or error. An empty tuple implies a pass. | 38 if test resulted in a fail or error. An empty tuple implies a pass. |
33 """ | 39 """ |
34 | 40 |
35 def __init__(self, full_name, start_date, dur, lang, log='', error=()): | 41 def __init__(self, full_name, start_date, dur, lang, log='', error=()): |
36 BaseTestResult.__init__(self, full_name, log) | 42 BaseTestResult.__init__(self, full_name, log) |
37 name_pieces = full_name.rsplit('#') | 43 name_pieces = full_name.rsplit('#') |
38 if len(name_pieces) > 0: | 44 if len(name_pieces) > 1: |
39 self.test_name = name_pieces[1] | 45 self.test_name = name_pieces[1] |
40 self.class_name = name_pieces[0] | 46 self.class_name = name_pieces[0] |
41 else: | 47 else: |
42 self.class_name = full_name | 48 self.class_name = full_name |
43 self.test_name = full_name | 49 self.test_name = full_name |
44 self.start_date = start_date | 50 self.start_date = start_date |
45 self.dur = dur | 51 self.dur = dur |
46 self.error = error | 52 self.error = error |
47 self.lang = lang | 53 self.lang = lang |
48 | 54 |
49 | 55 |
50 class TestResults(object): | 56 class TestResults(object): |
51 """Results of a test run.""" | 57 """Results of a test run.""" |
52 | 58 |
53 def __init__(self): | 59 def __init__(self): |
54 self.ok = [] | 60 self.ok = [] |
55 self.failed = [] | 61 self.failed = [] |
56 self.crashed = [] | 62 self.crashed = [] |
57 self.unknown = [] | 63 self.unknown = [] |
58 self.disabled = [] | |
59 self.unexpected_pass = [] | |
60 self.timed_out = False | 64 self.timed_out = False |
61 self.overall_fail = False | 65 self.overall_fail = False |
62 | 66 |
63 @staticmethod | 67 @staticmethod |
64 def FromRun(ok=None, failed=None, crashed=None, timed_out=False, | 68 def FromRun(ok=None, failed=None, crashed=None, timed_out=False, |
65 overall_fail=False): | 69 overall_fail=False): |
66 ret = TestResults() | 70 ret = TestResults() |
67 ret.ok = ok or [] | 71 ret.ok = ok or [] |
68 ret.failed = failed or [] | 72 ret.failed = failed or [] |
69 ret.crashed = crashed or [] | 73 ret.crashed = crashed or [] |
70 ret.timed_out = timed_out | 74 ret.timed_out = timed_out |
71 ret.overall_fail = overall_fail | 75 ret.overall_fail = overall_fail |
72 return ret | 76 return ret |
73 | 77 |
74 @staticmethod | 78 @staticmethod |
75 def FromTestResults(results): | 79 def FromTestResults(results): |
76 """Combines a list of results in a single TestResults object.""" | 80 """Combines a list of results in a single TestResults object.""" |
77 ret = TestResults() | 81 ret = TestResults() |
78 for t in results: | 82 for t in results: |
79 ret.ok += t.ok | 83 ret.ok += t.ok |
80 ret.failed += t.failed | 84 ret.failed += t.failed |
81 ret.crashed += t.crashed | 85 ret.crashed += t.crashed |
82 ret.unknown += t.unknown | 86 ret.unknown += t.unknown |
83 ret.disabled += t.disabled | |
84 ret.unexpected_pass += t.unexpected_pass | |
85 if t.timed_out: | 87 if t.timed_out: |
86 ret.timed_out = True | 88 ret.timed_out = True |
87 if t.overall_fail: | 89 if t.overall_fail: |
88 ret.overall_fail = True | 90 ret.overall_fail = True |
89 return ret | 91 return ret |
90 | 92 |
| 93 @staticmethod |
| 94 def FromPythonException(test_name, start_date_ms, exc_info): |
| 95 """Constructs a TestResults with exception information for the given test. |
| 96 |
| 97 Args: |
| 98 test_name: name of the test which raised an exception. |
| 99 start_date_ms: the starting time for the test. |
| 100 exc_info: exception info, ostensibly from sys.exc_info(). |
| 101 |
| 102 Returns: |
| 103 A TestResults object with a SingleTestResult in the failed list. |
| 104 """ |
| 105 exc_type, exc_value, exc_traceback = exc_info |
| 106 trace_info = ''.join(traceback.format_exception(exc_type, exc_value, |
| 107 exc_traceback)) |
| 108 log_msg = 'Exception:\n' + trace_info |
| 109 duration_ms = (int(time.time()) * 1000) - start_date_ms |
| 110 |
| 111 exc_result = SingleTestResult( |
| 112 full_name='PythonWrapper#' + test_name, |
| 113 start_date=start_date_ms, |
| 114 dur=duration_ms, |
| 115 lang=PYTHON, |
| 116 log=log_msg, |
| 117 error=(str(exc_type), log_msg)) |
| 118 |
| 119 results = TestResults() |
| 120 results.failed.append(exc_result) |
| 121 return results |
| 122 |
91 def _Log(self, sorted_list): | 123 def _Log(self, sorted_list): |
92 for t in sorted_list: | 124 for t in sorted_list: |
93 logging.critical(t.name) | 125 logging.critical(t.name) |
94 if t.log: | 126 if t.log: |
95 logging.critical(t.log) | 127 logging.critical(t.log) |
96 | 128 |
97 def GetAllBroken(self): | 129 def GetAllBroken(self): |
98 """Returns the all broken tests including failed, crashed, unknown.""" | 130 """Returns the all broken tests including failed, crashed, unknown.""" |
99 return self.failed + self.crashed + self.unknown | 131 return self.failed + self.crashed + self.unknown |
100 | 132 |
101 def LogFull(self): | 133 def LogFull(self, test_group, test_suite): |
102 """Output all broken tests or 'passed' if none broken""" | 134 """Output broken test logs, summarize in a log file and the test output.""" |
| 135 # Output all broken tests or 'passed' if none broken. |
103 logging.critical('*' * 80) | 136 logging.critical('*' * 80) |
104 logging.critical('Final result') | 137 logging.critical('Final result') |
105 if self.failed: | 138 if self.failed: |
106 logging.critical('Failed:') | 139 logging.critical('Failed:') |
107 self._Log(sorted(self.failed)) | 140 self._Log(sorted(self.failed)) |
108 if self.crashed: | 141 if self.crashed: |
109 logging.critical('Crashed:') | 142 logging.critical('Crashed:') |
110 self._Log(sorted(self.crashed)) | 143 self._Log(sorted(self.crashed)) |
111 if self.unknown: | 144 if self.unknown: |
112 logging.critical('Unknown:') | 145 logging.critical('Unknown:') |
113 self._Log(sorted(self.unknown)) | 146 self._Log(sorted(self.unknown)) |
114 if not self.GetAllBroken(): | 147 if not self.GetAllBroken(): |
115 logging.critical('Passed') | 148 logging.critical('Passed') |
116 logging.critical('*' * 80) | 149 logging.critical('*' * 80) |
117 | 150 |
| 151 # Summarize in a log file, if tests are running on bots. |
| 152 if test_group and test_suite and os.environ.get('BUILDBOT_BUILDERNAME'): |
| 153 log_file_path = os.path.join(constants.CHROME_DIR, 'out', |
| 154 'Release', 'test_logs') |
| 155 if not os.path.exists(log_file_path): |
| 156 os.mkdir(log_file_path) |
| 157 full_file_name = os.path.join(log_file_path, test_group) |
| 158 if not os.path.exists(full_file_name): |
| 159 with open(full_file_name, 'w') as log_file: |
| 160 print >> log_file, '\n%s results for %s build %s:' % ( |
| 161 test_group, os.environ.get('BUILDBOT_BUILDERNAME'), |
| 162 os.environ.get('BUILDBOT_BUILDNUMBER')) |
| 163 log_contents = [' %s result : %d tests ran' % (test_suite, |
| 164 len(self.ok) + |
| 165 len(self.failed) + |
| 166 len(self.crashed) + |
| 167 len(self.unknown))] |
| 168 content_pairs = [('passed', len(self.ok)), ('failed', len(self.failed)), |
| 169 ('crashed', len(self.crashed))] |
| 170 for (result, count) in content_pairs: |
| 171 if count: |
| 172 log_contents.append(', %d tests %s' % (count, result)) |
| 173 with open(full_file_name, 'a') as log_file: |
| 174 print >> log_file, ''.join(log_contents) |
| 175 content = {'test_group': test_group, |
| 176 'ok': [t.name for t in self.ok], |
| 177 'failed': [t.name for t in self.failed], |
| 178 'crashed': [t.name for t in self.failed], |
| 179 'unknown': [t.name for t in self.unknown],} |
| 180 with open(os.path.join(log_file_path, 'results.json'), 'a') as json_file: |
| 181 print >> json_file, json.dumps(content) |
| 182 |
118 # Summarize in the test output. | 183 # Summarize in the test output. |
119 summary_string = 'Summary:\n' | 184 summary_string = 'Summary:\n' |
120 summary_string += 'RAN=%d\n' % (len(self.ok) + len(self.failed) + | 185 summary_string += 'RAN=%d\n' % (len(self.ok) + len(self.failed) + |
121 len(self.crashed) + len(self.unknown)) | 186 len(self.crashed) + len(self.unknown)) |
122 summary_string += 'PASSED=%d\n' % (len(self.ok)) | 187 summary_string += 'PASSED=%d\n' % (len(self.ok)) |
123 summary_string += 'FAILED=%d %s\n' % (len(self.failed), | 188 summary_string += 'FAILED=%d %s\n' % (len(self.failed), |
124 [t.name for t in self.failed]) | 189 [t.name for t in self.failed]) |
125 summary_string += 'CRASHED=%d %s\n' % (len(self.crashed), | 190 summary_string += 'CRASHED=%d %s\n' % (len(self.crashed), |
126 [t.name for t in self.crashed]) | 191 [t.name for t in self.crashed]) |
127 summary_string += 'UNKNOWN=%d %s\n' % (len(self.unknown), | 192 summary_string += 'UNKNOWN=%d %s\n' % (len(self.unknown), |
128 [t.name for t in self.unknown]) | 193 [t.name for t in self.unknown]) |
129 logging.critical(summary_string) | 194 logging.critical(summary_string) |
130 return summary_string | 195 return summary_string |
OLD | NEW |