Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import re | 6 import re |
| 7 | 7 |
| 8 | 8 |
| 9 class GTestLogParser(object): | 9 class GTestLogParser(object): |
| 10 """This helper class process GTest test output.""" | 10 """This helper class process GTest test output.""" |
| 11 | 11 |
| 12 def __init__(self): | 12 def __init__(self): |
| 13 # State tracking for log parsing | 13 # State tracking for log parsing |
| 14 self.completed = False | |
| 14 self._current_test = '' | 15 self._current_test = '' |
| 15 self._failure_description = [] | 16 self._failure_description = [] |
| 16 self._current_suppression_hash = '' | 17 self._current_suppression_hash = '' |
| 17 self._current_suppression = [] | 18 self._current_suppression = [] |
| 18 self._parsing_failures = False | 19 self._parsing_failures = False |
| 19 | 20 |
| 20 # Line number currently being processed. | 21 # Line number currently being processed. |
| 21 self._line_number = 0 | 22 self._line_number = 0 |
| 22 | 23 |
| 23 # List of parsing errors, as human-readable strings. | 24 # List of parsing errors, as human-readable strings. |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 37 # '%s disabled' or '%s flaky' on the waterfall display. | 38 # '%s disabled' or '%s flaky' on the waterfall display. |
| 38 self._disabled_tests = 0 | 39 self._disabled_tests = 0 |
| 39 self._flaky_tests = 0 | 40 self._flaky_tests = 0 |
| 40 | 41 |
| 41 # Regular expressions for parsing GTest logs. Test names look like | 42 # Regular expressions for parsing GTest logs. Test names look like |
| 42 # SomeTestCase.SomeTest | 43 # SomeTestCase.SomeTest |
| 43 # SomeName/SomeTestCase.SomeTest/1 | 44 # SomeName/SomeTestCase.SomeTest/1 |
| 44 # This regexp also matches SomeName.SomeTest/1, which should be harmless. | 45 # This regexp also matches SomeName.SomeTest/1, which should be harmless. |
| 45 test_name_regexp = r'((\w+/)?\w+\.\w+(/\d+)?)' | 46 test_name_regexp = r'((\w+/)?\w+\.\w+(/\d+)?)' |
| 46 | 47 |
| 48 self._master_name_re = re.compile('\[Running for master: "([^"]*)"') | |
| 49 self.master_name = '' | |
| 50 | |
| 47 self._test_name = re.compile(test_name_regexp) | 51 self._test_name = re.compile(test_name_regexp) |
| 48 self._test_start = re.compile('\[\s+RUN\s+\] ' + test_name_regexp) | 52 self._test_start = re.compile('\[\s+RUN\s+\] ' + test_name_regexp) |
| 49 self._test_ok = re.compile('\[\s+OK\s+\] ' + test_name_regexp) | 53 self._test_ok = re.compile('\[\s+OK\s+\] ' + test_name_regexp) |
| 50 self._test_fail = re.compile('\[\s+FAILED\s+\] ' + test_name_regexp) | 54 self._test_fail = re.compile('\[\s+FAILED\s+\] ' + test_name_regexp) |
| 55 self._test_passed = re.compile('\[\s+PASSED\s+\] \d+ tests?.') | |
| 51 self._test_timeout = re.compile( | 56 self._test_timeout = re.compile( |
| 52 'Test timeout \([0-9]+ ms\) exceeded for ' + test_name_regexp) | 57 'Test timeout \([0-9]+ ms\) exceeded for ' + test_name_regexp) |
| 53 self._disabled = re.compile(' YOU HAVE (\d+) DISABLED TEST') | 58 self._disabled = re.compile(' YOU HAVE (\d+) DISABLED TEST') |
| 54 self._flaky = re.compile(' YOU HAVE (\d+) FLAKY TEST') | 59 self._flaky = re.compile(' YOU HAVE (\d+) FLAKY TEST') |
| 55 | 60 |
| 56 self._suppression_start = re.compile( | 61 self._suppression_start = re.compile( |
| 57 'Suppression \(error hash=#([0-9A-F]+)#\):') | 62 'Suppression \(error hash=#([0-9A-F]+)#\):') |
| 58 self._suppression_end = re.compile('^}\s*$') | 63 self._suppression_end = re.compile('^}\s*$') |
| 59 | 64 |
| 60 self._retry_message = re.compile('RETRYING FAILED TESTS:') | 65 self._retry_message = re.compile('RETRYING FAILED TESTS:') |
| 61 self.retrying_failed = False | 66 self.retrying_failed = False |
| 62 | 67 |
| 68 def GetCurrentTest(self): | |
| 69 return self._current_test | |
| 70 | |
| 63 def _StatusOfTest(self, test): | 71 def _StatusOfTest(self, test): |
| 64 """Returns the status code for the given test, or 'not known'.""" | 72 """Returns the status code for the given test, or 'not known'.""" |
| 65 test_status = self._test_status.get(test, ('not known', [])) | 73 test_status = self._test_status.get(test, ('not known', [])) |
| 66 return test_status[0] | 74 return test_status[0] |
| 67 | 75 |
| 68 def _TestsByStatus(self, status, include_fails, include_flaky): | 76 def _TestsByStatus(self, status, include_fails, include_flaky): |
| 69 """Returns list of tests with the given status. | 77 """Returns list of tests with the given status. |
| 70 | 78 |
| 71 Args: | 79 Args: |
| 72 include_fails: If False, tests containing 'FAILS_' anywhere in their | 80 include_fails: If False, tests containing 'FAILS_' anywhere in their |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 151 """Returns list of suppression hashes found in the log.""" | 159 """Returns list of suppression hashes found in the log.""" |
| 152 return self._suppressions.keys() | 160 return self._suppressions.keys() |
| 153 | 161 |
| 154 def Suppression(self, suppression_hash): | 162 def Suppression(self, suppression_hash): |
| 155 """Returns a list containing the suppression for a given hash. | 163 """Returns a list containing the suppression for a given hash. |
| 156 | 164 |
| 157 If the suppression hash doesn't exist, returns []. | 165 If the suppression hash doesn't exist, returns []. |
| 158 """ | 166 """ |
| 159 return self._suppressions.get(suppression_hash, []) | 167 return self._suppressions.get(suppression_hash, []) |
| 160 | 168 |
| 169 def CompletedWithoutFailure(self): | |
| 170 """Returns True if all tests completed and no tests failed unexpectedly.""" | |
| 171 | |
|
cmp
2012/06/28 19:14:16
nit: remove empty line
| |
| 172 if not self.completed: | |
| 173 return False | |
| 174 | |
| 175 # don't count FAILS and FLAKY here | |
| 176 if self.FailedTests(): | |
| 177 return False | |
| 178 | |
| 179 return True | |
|
cmp
2012/06/28 19:14:16
let's condense lines 172-179 to one line:
return
| |
| 180 | |
| 161 def ProcessLine(self, line): | 181 def ProcessLine(self, line): |
| 162 """This is called once with each line of the test log.""" | 182 """This is called once with each line of the test log.""" |
| 163 | 183 |
| 164 # Track line number for error messages. | 184 # Track line number for error messages. |
| 165 self._line_number += 1 | 185 self._line_number += 1 |
| 166 | 186 |
| 167 # Note: When sharding, the number of disabled and flaky tests will be read | 187 # Note: When sharding, the number of disabled and flaky tests will be read |
| 168 # multiple times, so this will only show the most recent values (but they | 188 # multiple times, so this will only show the most recent values (but they |
| 169 # should all be the same anyway). | 189 # should all be the same anyway). |
| 170 | 190 |
| 191 # Is it a line listing the master name? | |
| 192 if not self.master_name: | |
| 193 results = self._master_name_re.search(line) | |
| 194 if results: | |
| 195 self.master_name = results.group(1) | |
| 196 | |
| 197 # Is it a line declaring all tests passed? | |
| 198 results = self._test_passed.search(line) | |
| 199 if results: | |
| 200 self.completed = True | |
| 201 self._current_test = '' | |
| 202 return | |
| 203 | |
| 171 # Is it a line reporting disabled tests? | 204 # Is it a line reporting disabled tests? |
| 172 results = self._disabled.search(line) | 205 results = self._disabled.search(line) |
| 173 if results: | 206 if results: |
| 174 try: | 207 try: |
| 175 disabled = int(results.group(1)) | 208 disabled = int(results.group(1)) |
| 176 except ValueError: | 209 except ValueError: |
| 177 disabled = 0 | 210 disabled = 0 |
| 178 if disabled > 0 and isinstance(self._disabled_tests, int): | 211 if disabled > 0 and isinstance(self._disabled_tests, int): |
| 179 self._disabled_tests = disabled | 212 self._disabled_tests = disabled |
| 180 else: | 213 else: |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 303 if results: | 336 if results: |
| 304 test_name = results.group(1) | 337 test_name = results.group(1) |
| 305 status = self._StatusOfTest(test_name) | 338 status = self._StatusOfTest(test_name) |
| 306 if status in ('not known', 'OK'): | 339 if status in ('not known', 'OK'): |
| 307 self._test_status[test_name] = ( | 340 self._test_status[test_name] = ( |
| 308 'failed', ['Unknown error, see stdio log.']) | 341 'failed', ['Unknown error, see stdio log.']) |
| 309 else: | 342 else: |
| 310 self._parsing_failures = False | 343 self._parsing_failures = False |
| 311 elif line.startswith('Failing tests:'): | 344 elif line.startswith('Failing tests:'): |
| 312 self._parsing_failures = True | 345 self._parsing_failures = True |
| OLD | NEW |