| 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 return self.completed and not self.FailedTests() |
| 172 |
| 161 def ProcessLine(self, line): | 173 def ProcessLine(self, line): |
| 162 """This is called once with each line of the test log.""" | 174 """This is called once with each line of the test log.""" |
| 163 | 175 |
| 164 # Track line number for error messages. | 176 # Track line number for error messages. |
| 165 self._line_number += 1 | 177 self._line_number += 1 |
| 166 | 178 |
| 167 # Note: When sharding, the number of disabled and flaky tests will be read | 179 # 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 | 180 # multiple times, so this will only show the most recent values (but they |
| 169 # should all be the same anyway). | 181 # should all be the same anyway). |
| 170 | 182 |
| 183 # Is it a line listing the master name? |
| 184 if not self.master_name: |
| 185 results = self._master_name_re.search(line) |
| 186 if results: |
| 187 self.master_name = results.group(1) |
| 188 |
| 189 # Is it a line declaring all tests passed? |
| 190 results = self._test_passed.search(line) |
| 191 if results: |
| 192 self.completed = True |
| 193 self._current_test = '' |
| 194 return |
| 195 |
| 171 # Is it a line reporting disabled tests? | 196 # Is it a line reporting disabled tests? |
| 172 results = self._disabled.search(line) | 197 results = self._disabled.search(line) |
| 173 if results: | 198 if results: |
| 174 try: | 199 try: |
| 175 disabled = int(results.group(1)) | 200 disabled = int(results.group(1)) |
| 176 except ValueError: | 201 except ValueError: |
| 177 disabled = 0 | 202 disabled = 0 |
| 178 if disabled > 0 and isinstance(self._disabled_tests, int): | 203 if disabled > 0 and isinstance(self._disabled_tests, int): |
| 179 self._disabled_tests = disabled | 204 self._disabled_tests = disabled |
| 180 else: | 205 else: |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 303 if results: | 328 if results: |
| 304 test_name = results.group(1) | 329 test_name = results.group(1) |
| 305 status = self._StatusOfTest(test_name) | 330 status = self._StatusOfTest(test_name) |
| 306 if status in ('not known', 'OK'): | 331 if status in ('not known', 'OK'): |
| 307 self._test_status[test_name] = ( | 332 self._test_status[test_name] = ( |
| 308 'failed', ['Unknown error, see stdio log.']) | 333 'failed', ['Unknown error, see stdio log.']) |
| 309 else: | 334 else: |
| 310 self._parsing_failures = False | 335 self._parsing_failures = False |
| 311 elif line.startswith('Failing tests:'): | 336 elif line.startswith('Failing tests:'): |
| 312 self._parsing_failures = True | 337 self._parsing_failures = True |
| OLD | NEW |