| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Generates annotated output. |
| |
| TODO(stip): Move the gtest_utils gtest parser selection code from runtest.py |
| to here. |
| TODO(stip): Move the perf dashboard code from runtest.py to here. |
| """ |
| |
| import re |
| |
| from slave import slave_utils |
| |
| |
| # Status codes that can be returned by the evaluateCommand method. |
| # From buildbot.status.builder. |
| # Duplicated from performance_log_processor to break dependency. |
| SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY = range(6) |
| |
| |
| def getText(result, observer, name): |
| """Generate a text summary for the waterfall. |
| |
| Updates the waterfall with any unusual test output, with a link to logs of |
| failed test steps. |
| """ |
| GTEST_DASHBOARD_BASE = ('https://test-results.appspot.com' |
| '/dashboards/flakiness_dashboard.html') |
| |
| # TODO(xusydoc): unify this with gtest reporting below so getText() is |
| # less confusing |
| if hasattr(observer, 'PerformanceSummary'): |
| basic_info = [name] |
| summary_text = ['<div class="BuildResultInfo">'] |
| summary_text.extend(observer.PerformanceSummary()) |
| summary_text.append('</div>') |
| return basic_info + summary_text |
| |
| # basic_info is an array of lines to display on the waterfall. |
| basic_info = [name] |
| |
| disabled = observer.DisabledTests() |
| if disabled: |
| basic_info.append('%s disabled' % str(disabled)) |
| |
| flaky = observer.FlakyTests() |
| if flaky: |
| basic_info.append('%s flaky' % str(flaky)) |
| |
| failed_test_count = len(observer.FailedTests()) |
| if failed_test_count == 0: |
| if result == SUCCESS: |
| return basic_info |
| elif result == WARNINGS: |
| return basic_info + ['warnings'] |
| |
| if observer.RunningTests(): |
| basic_info += ['did not complete'] |
| |
| # TODO(xusydoc): see if 'crashed or hung' should be tracked by RunningTests(). |
| if failed_test_count: |
| failure_text = ['failed %d' % failed_test_count] |
| if observer.master_name: |
| # Include the link to the flakiness dashboard. |
| failure_text.append('<div class="BuildResultInfo">') |
| failure_text.append('<a href="%s#testType=%s' |
| '&tests=%s">' % (GTEST_DASHBOARD_BASE, |
| name, |
| ','.join(observer.FailedTests()))) |
| failure_text.append('Flakiness dashboard') |
| failure_text.append('</a>') |
| failure_text.append('</div>') |
| else: |
| failure_text = ['crashed or hung'] |
| return basic_info + failure_text |
| |
| |
| def annotate(test_name, result, log_processor, perf_dashboard_id=None): |
| """Given a test result and tracker, update the waterfall with test results.""" |
| |
| # Always print raw exit code of the subprocess. This is very helpful |
| # for debugging, especially when one gets the "crashed or hung" message |
| # with no output (exit code can have some clues, especially on Windows). |
| print 'exit code (as seen by runtest.py): %d' % result |
| |
| get_text_result = SUCCESS |
| |
| for failure in sorted(log_processor.FailedTests()): |
| clean_test_name = re.sub(r'[^\w\.\-]', '_', failure) |
| slave_utils.WriteLogLines(clean_test_name, |
| log_processor.FailureDescription(failure)) |
| for report_hash in sorted(log_processor.MemoryToolReportHashes()): |
| slave_utils.WriteLogLines(report_hash, |
| log_processor.MemoryToolReport(report_hash)) |
| |
| if log_processor.ParsingErrors(): |
| # Generate a log file containing the list of errors. |
| slave_utils.WriteLogLines('log parsing error(s)', |
| log_processor.ParsingErrors()) |
| |
| log_processor.ClearParsingErrors() |
| |
| if hasattr(log_processor, 'evaluateCommand'): |
| parser_result = log_processor.evaluateCommand('command') |
| if parser_result > result: |
| result = parser_result |
| |
| if result == SUCCESS: |
| if (len(log_processor.ParsingErrors()) or |
| len(log_processor.FailedTests()) or |
| len(log_processor.MemoryToolReportHashes())): |
| print '@@@STEP_WARNINGS@@@' |
| get_text_result = WARNINGS |
| elif result == slave_utils.WARNING_EXIT_CODE: |
| print '@@@STEP_WARNINGS@@@' |
| get_text_result = WARNINGS |
| else: |
| print '@@@STEP_FAILURE@@@' |
| get_text_result = FAILURE |
| |
| for desc in getText(get_text_result, log_processor, test_name): |
| print '@@@STEP_TEXT@%s@@@' % desc |
| |
| if hasattr(log_processor, 'PerformanceLogs'): |
| if not perf_dashboard_id: |
| raise Exception('runtest.py error: perf step specified but' |
| 'no test_id in factory_properties!') |
| for logname, log in log_processor.PerformanceLogs().iteritems(): |
| lines = [str(l).rstrip() for l in log] |
| slave_utils.WriteLogLines(logname, lines, perf=perf_dashboard_id) |