1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import collections 6import logging 7 8import common 9 10from autotest_lib.client.common_lib import global_config 11from autotest_lib.server import site_utils 12from autotest_lib.server.cros.dynamic_suite import job_status 13from autotest_lib.server.cros.dynamic_suite import reporting_utils 14from autotest_lib.server.cros.dynamic_suite import tools 15from autotest_lib.site_utils import gmail_lib 16 17try: 18 from chromite.lib import metrics 19except ImportError: 20 metrics = site_utils.metrics_mock 21 22 23EMAIL_CREDS_FILE = global_config.global_config.get_config_value( 24 'NOTIFICATIONS', 'gmail_api_credentials_test_failure', default=None) 25 26 27class TestBug(object): 28 """ 29 Wrap up all information needed to make an intelligent report about an 30 issue. Each TestBug has a search marker associated with it that can be 31 used to find similar reports. 32 """ 33 34 def __init__(self, build, chrome_version, suite, result): 35 """ 36 @param build: The build type, of the form <board>/<milestone>-<release>. 37 eg: x86-mario-release/R25-4321.0.0 38 @param chrome_version: The chrome version associated with the build. 39 eg: 28.0.1498.1 40 @param suite: The name of the suite that this test run is a part of. 41 @param result: The status of the job associated with this issue. 42 This contains the status, job id, test name, hostname 43 and reason for issue. 44 """ 45 self.build = build 46 self.chrome_version = chrome_version 47 self.suite = suite 48 self.name = tools.get_test_name(build, suite, result.test_name) 49 self.reason = result.reason 50 # The result_owner is used to find results and logs. 51 self.result_owner = result.owner 52 self.hostname = result.hostname 53 self.job_id = result.id 54 55 # Aborts, server/client job failures or a test failure without a 56 # reason field need lab attention. Lab bugs for the aborted case 57 # are disabled till crbug.com/188217 is resolved. 58 self.lab_error = job_status.is_for_infrastructure_fail(result) 59 60 # The owner is who the bug is assigned to. 61 self.owner = '' 62 self.cc = [] 63 self.components = [] 64 65 if result.is_warn(): 66 self.labels = ['Test-Warning'] 67 self.status = 'Warning' 68 else: 69 self.labels = [] 70 self.status = 'Failure' 71 72 73 def title(self): 74 """Combines information about this bug into a title string.""" 75 return '[%s] %s %s on %s' % (self.suite, self.name, 76 self.status, self.build) 77 78 79 def summary(self): 80 """Combines information about this bug into a summary string.""" 81 82 links = self._get_links_for_failure() 83 template = ('This report is automatically generated to track the ' 84 'following %(status)s:\n' 85 'Test: %(test)s.\n' 86 'Suite: %(suite)s.\n' 87 'Chrome Version: %(chrome_version)s.\n' 88 'Build: %(build)s.\n\nReason:\n%(reason)s.\n' 89 'build artifacts: %(build_artifacts)s.\n' 90 'results log: %(results_log)s.\n' 91 'status log: %(status_log)s.\n' 92 'job link: %(job)s.\n\n' 93 'You may want to check the test history: ' 94 '%(test_history_url)s\n' 95 'You may also want to check the test retry dashboard in ' 96 'case this is a flakey test: %(retry_url)s\n') 97 98 specifics = { 99 'status': self.status, 100 'test': self.name, 101 'suite': self.suite, 102 'build': self.build, 103 'chrome_version': self.chrome_version, 104 'reason': self.reason, 105 'build_artifacts': links.artifacts, 106 'results_log': links.results, 107 'status_log': links.status_log, 108 'job': links.job, 109 'test_history_url': links.test_history_url, 110 'retry_url': links.retry_url, 111 } 112 113 return template % specifics 114 115 116 # TO-DO(shuqianz) Fix the dedupe failing issue because reason contains 117 # special characters after 118 # https://bugs.chromium.org/p/monorail/issues/detail?id=806 being fixed. 119 def search_marker(self): 120 """Return an Anchor that we can use to dedupe this exact bug.""" 121 board = '' 122 try: 123 board = site_utils.ParseBuildName(self.build)[0] 124 except site_utils.ParseBuildNameException as e: 125 logging.error(str(e)) 126 127 # Substitute the board name for a placeholder. We try both build and 128 # release board name variants. 129 reason = self.reason 130 if board: 131 for b in (board, board.replace('_', '-')): 132 reason = reason.replace(b, 'BOARD_PLACEHOLDER') 133 134 return "%s{%s,%s,%s}" % ('Test%s' % self.status, self.suite, 135 self.name, reason) 136 137 138 def _get_links_for_failure(self): 139 """Returns a named tuple of links related to this failure.""" 140 links = collections.namedtuple('links', ('results,' 141 'status_log,' 142 'artifacts,' 143 'job,' 144 'test_history_url,' 145 'retry_url')) 146 return links(reporting_utils.link_result_logs( 147 self.job_id, self.result_owner, self.hostname), 148 reporting_utils.link_status_log( 149 self.job_id, self.result_owner, self.hostname), 150 reporting_utils.link_build_artifacts(self.build), 151 reporting_utils.link_job(self.job_id), 152 reporting_utils.link_test_history(self.name), 153 reporting_utils.link_retry_url(self.name)) 154 155 156ReportResult = collections.namedtuple('ReportResult', ['bug_id', 'update_count']) 157 158 159def send_email(bug, bug_template): 160 """Send email to the owner and cc's to notify the TestBug. 161 162 @param bug: TestBug instance. 163 @param bug_template: A template dictionary specifying the default bug 164 filing options for failures in this suite. 165 """ 166 to_set = set(bug.cc) if bug.cc else set() 167 if bug.owner: 168 to_set.add(bug.owner) 169 if bug_template.get('cc'): 170 to_set = to_set.union(bug_template.get('cc')) 171 if bug_template.get('owner'): 172 to_set.add(bug_template.get('owner')) 173 recipients = ', '.join(to_set) 174 if not recipients: 175 logging.warning('No owner/cc found. Will skip sending a mail.') 176 return 177 success = False 178 try: 179 gmail_lib.send_email( 180 recipients, bug.title(), bug.summary(), retry=False, 181 creds_path=site_utils.get_creds_abspath(EMAIL_CREDS_FILE)) 182 success = True 183 finally: 184 (metrics.Counter('chromeos/autotest/errors/send_bug_email') 185 .increment(fields={'success': success})) 186