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.server import site_utils 11from autotest_lib.server.cros.dynamic_suite import job_status 12from autotest_lib.server.cros.dynamic_suite import reporting_utils 13from autotest_lib.server.cros.dynamic_suite import tools 14 15 16class TestBug(object): 17 """ 18 Wrap up all information needed to make an intelligent report about an 19 issue. Each TestBug has a search marker associated with it that can be 20 used to find similar reports. 21 """ 22 23 def __init__(self, build, chrome_version, suite, result): 24 """ 25 @param build: The build type, of the form <board>/<milestone>-<release>. 26 eg: x86-mario-release/R25-4321.0.0 27 @param chrome_version: The chrome version associated with the build. 28 eg: 28.0.1498.1 29 @param suite: The name of the suite that this test run is a part of. 30 @param result: The status of the job associated with this issue. 31 This contains the status, job id, test name, hostname 32 and reason for issue. 33 """ 34 self.build = build 35 self.chrome_version = chrome_version 36 self.suite = suite 37 self.name = tools.get_test_name(build, suite, result.test_name) 38 self.reason = result.reason 39 # The result_owner is used to find results and logs. 40 self.result_owner = result.owner 41 self.hostname = result.hostname 42 self.job_id = result.id 43 44 # Aborts, server/client job failures or a test failure without a 45 # reason field need lab attention. Lab bugs for the aborted case 46 # are disabled till crbug.com/188217 is resolved. 47 self.lab_error = job_status.is_for_infrastructure_fail(result) 48 49 # The owner is who the bug is assigned to. 50 self.owner = '' 51 self.cc = [] 52 self.components = [] 53 54 if result.is_warn(): 55 self.labels = ['Test-Warning'] 56 self.status = 'Warning' 57 else: 58 self.labels = [] 59 self.status = 'Failure' 60 61 62 def title(self): 63 """Combines information about this bug into a title string.""" 64 return '[%s] %s %s on %s' % (self.suite, self.name, 65 self.status, self.build) 66 67 68 def summary(self): 69 """Combines information about this bug into a summary string.""" 70 71 links = self._get_links_for_failure() 72 template = ('This report is automatically generated to track the ' 73 'following %(status)s:\n' 74 'Test: %(test)s.\n' 75 'Suite: %(suite)s.\n' 76 'Chrome Version: %(chrome_version)s.\n' 77 'Build: %(build)s.\n\nReason:\n%(reason)s.\n' 78 'build artifacts: %(build_artifacts)s.\n' 79 'results log: %(results_log)s.\n' 80 'status log: %(status_log)s.\n' 81 'job link: %(job)s.\n\n' 82 'You may want to check the test history: ' 83 '%(test_history_url)s\n' 84 'You may also want to check the test retry dashboard in ' 85 'case this is a flakey test: %(retry_url)s\n') 86 87 specifics = { 88 'status': self.status, 89 'test': self.name, 90 'suite': self.suite, 91 'build': self.build, 92 'chrome_version': self.chrome_version, 93 'reason': self.reason, 94 'build_artifacts': links.artifacts, 95 'results_log': links.results, 96 'status_log': links.status_log, 97 'job': links.job, 98 'test_history_url': links.test_history_url, 99 'retry_url': links.retry_url, 100 } 101 102 return template % specifics 103 104 105 # TO-DO(shuqianz) Fix the dedupe failing issue because reason contains 106 # special characters after 107 # https://bugs.chromium.org/p/monorail/issues/detail?id=806 being fixed. 108 def search_marker(self): 109 """Return an Anchor that we can use to dedupe this exact bug.""" 110 board = '' 111 try: 112 board = site_utils.ParseBuildName(self.build)[0] 113 except site_utils.ParseBuildNameException as e: 114 logging.error(str(e)) 115 116 # Substitute the board name for a placeholder. We try both build and 117 # release board name variants. 118 reason = self.reason 119 if board: 120 for b in (board, board.replace('_', '-')): 121 reason = reason.replace(b, 'BOARD_PLACEHOLDER') 122 123 return "%s{%s,%s,%s}" % ('Test%s' % self.status, self.suite, 124 self.name, reason) 125 126 127 def _get_links_for_failure(self): 128 """Returns a named tuple of links related to this failure.""" 129 links = collections.namedtuple('links', ('results,' 130 'status_log,' 131 'artifacts,' 132 'job,' 133 'test_history_url,' 134 'retry_url')) 135 return links(reporting_utils.link_result_logs( 136 self.job_id, self.result_owner, self.hostname), 137 reporting_utils.link_status_log( 138 self.job_id, self.result_owner, self.hostname), 139 reporting_utils.link_build_artifacts(self.build), 140 reporting_utils.link_job(self.job_id), 141 reporting_utils.link_test_history(self.name), 142 reporting_utils.link_retry_url(self.name)) 143 144 145ReportResult = collections.namedtuple('ReportResult', ['bug_id', 'update_count']) 146 147 148def send_email(bug, bug_template): 149 """Send email to the owner and cc's to notify the TestBug. 150 151 @param bug: TestBug instance. 152 @param bug_template: A template dictionary specifying the default bug 153 filing options for failures in this suite. 154 """ 155 # TODO(ayatane): Deprecated, no time to untangle imports to delete right now. 156 pass 157