| 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 |