blob: b326eb89bd080a4d50cb43b2e3dc85d756f2c966 [file] [log] [blame]
Chris Masone24b80f12012-02-14 14:18:01 -08001#!/usr/bin/python
2#
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Fang Deng5a43be62014-05-07 17:17:04 -07007
Chris Masone24b80f12012-02-14 14:18:01 -08008"""Tool for running suites of tests and waiting for completion.
9
Fang Deng5a43be62014-05-07 17:17:04 -070010The desired test suite will be scheduled with autotest. By default,
Chris Masone24b80f12012-02-14 14:18:01 -080011this tool will block until the job is complete, printing a summary
12at the end. Error conditions result in exceptions.
13
14This is intended for use only with Chrome OS test suits that leverage the
15dynamic suite infrastructure in server/cros/dynamic_suite.py.
Fang Deng5a43be62014-05-07 17:17:04 -070016
17This script exits with one of the following codes:
180 - OK: Suite finished successfully
191 - ERROR: Test(s) failed, or hits its own timeout
Fang Dengaeab6172014-05-07 17:17:04 -0700202 - WARNING: Test(s) raised a warning or passed on retry, none failed/timed out.
Fang Deng5a43be62014-05-07 17:17:04 -0700213 - INFRA_FAILURE: Infrastructure related issues, e.g.
22 * Lab is down
23 * Too many duts (defined as a constant) in repair failed status
24 * Suite job issues, like bug in dynamic suite,
25 user aborted the suite, lose a drone/all devservers/rpc server,
26 0 tests ran, etc.
Fang Deng95af42f2014-09-12 14:16:11 -070027 * provision failed
28 TODO(fdeng): crbug.com/413918, reexamine treating all provision
29 failures as INFRA failures.
Fang Deng5a43be62014-05-07 17:17:04 -0700304 - SUITE_TIMEOUT: Suite timed out, some tests ran,
31 none failed by the time the suite job was aborted. This will cover,
32 but not limited to, the following cases:
33 * A devserver failure that manifests as a timeout
34 * No DUTs available midway through a suite
35 * Provision/Reset/Cleanup took longer time than expected for new image
36 * A regression in scheduler tick time.
Fang Deng6197da32014-09-25 10:18:48 -0700375- BOARD_NOT_AVAILABLE: If there is no host for the requested board/pool.
386- INVALID_OPTIONS: If options are not valid.
Chris Masone24b80f12012-02-14 14:18:01 -080039"""
40
Fang Deng5a43be62014-05-07 17:17:04 -070041
Prashanth B923ca262014-03-14 12:36:29 -070042import datetime as datetime_base
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -070043import ast, getpass, json, logging, optparse, os, re, sys, time
Chris Masonecfa7efc2012-09-06 16:00:07 -070044from datetime import datetime
45
Chris Masone24b80f12012-02-14 14:18:01 -080046import common
Shuqian Zhao2fecacd2015-08-05 22:56:30 -070047from autotest_lib.client.common_lib import control_data
Fang Deng5a43be62014-05-07 17:17:04 -070048from autotest_lib.client.common_lib import error
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080049from autotest_lib.client.common_lib import global_config, enum
50from autotest_lib.client.common_lib import priorities
Dan Shidfea3682014-08-10 23:38:40 -070051from autotest_lib.client.common_lib import time_utils
Gabe Black1e1c41b2015-02-04 23:55:15 -080052from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Prashanth B6285f6a2014-05-08 18:01:27 -070053from autotest_lib.client.common_lib.cros import retry
Prashanth B923ca262014-03-14 12:36:29 -070054from autotest_lib.frontend.afe.json_rpc import proxy
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080055from autotest_lib.server import utils
Dan Shi36cfd832014-10-10 13:38:51 -070056from autotest_lib.server.cros import provision
Chris Masone44e4d6c2012-08-15 14:25:53 -070057from autotest_lib.server.cros.dynamic_suite import constants
Chris Masoneb4935552012-08-14 12:05:54 -070058from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Prashanth B923ca262014-03-14 12:36:29 -070059from autotest_lib.server.cros.dynamic_suite import reporting_utils
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -070060from autotest_lib.server.cros.dynamic_suite import tools
Prashanth B923ca262014-03-14 12:36:29 -070061from autotest_lib.site_utils import diagnosis_utils
MK Ryu977a9752014-10-21 11:58:09 -070062from autotest_lib.site_utils import job_overhead
63
Chris Masone24b80f12012-02-14 14:18:01 -080064
Chris Masone1120cdf2012-02-27 17:35:07 -080065CONFIG = global_config.global_config
66
Simran Basi7203d4e2015-02-03 15:50:18 -080067WMATRIX_RETRY_URL = CONFIG.get_config_value('BUG_REPORTING',
68 'wmatrix_retry_url')
69
Simran Basi22aa9fe2012-12-07 16:37:09 -080070# Return code that will be sent back to autotest_rpc_server.py
Fang Deng5a43be62014-05-07 17:17:04 -070071RETURN_CODES = enum.Enum(
Fang Dengfb4a9492014-09-18 17:52:06 -070072 'OK', 'ERROR', 'WARNING', 'INFRA_FAILURE', 'SUITE_TIMEOUT',
Fang Deng6197da32014-09-25 10:18:48 -070073 'BOARD_NOT_AVAILABLE', 'INVALID_OPTIONS')
Fang Deng5a43be62014-05-07 17:17:04 -070074# The severity of return code. If multiple codes
75# apply, the script should always return the severest one.
76# E.g. if we have a test failure and the suite also timed out,
77# we should return 'ERROR'.
78SEVERITY = {RETURN_CODES.OK: 0,
79 RETURN_CODES.WARNING: 1,
Fang Deng95af42f2014-09-12 14:16:11 -070080 RETURN_CODES.SUITE_TIMEOUT: 2,
81 RETURN_CODES.INFRA_FAILURE: 3,
Fang Deng6197da32014-09-25 10:18:48 -070082 RETURN_CODES.ERROR: 4}
Simran Basibf6ebc92016-05-27 15:35:05 -070083ANDROID_BUILD_REGEX = r'.+/.+/P?([0-9]+|LATEST)'
Fang Deng5a43be62014-05-07 17:17:04 -070084
85
86def get_worse_code(code1, code2):
Fang Dengaeab6172014-05-07 17:17:04 -070087 """Compare the severity of two codes and return the worse code.
Fang Deng5a43be62014-05-07 17:17:04 -070088
89 @param code1: An enum value of RETURN_CODES
90 @param code2: An enum value of RETURN_CODES
91
Fang Dengaeab6172014-05-07 17:17:04 -070092 @returns: the more severe one between code1 and code2.
Fang Deng5a43be62014-05-07 17:17:04 -070093
94 """
Fang Dengaeab6172014-05-07 17:17:04 -070095 return code1 if SEVERITY[code1] >= SEVERITY[code2] else code2
Simran Basi22aa9fe2012-12-07 16:37:09 -080096
Chris Masonedfa0beba2012-03-19 11:41:47 -070097
Chris Masone24b80f12012-02-14 14:18:01 -080098def parse_options():
Aviv Keshet1480c4a2013-03-21 16:38:31 -070099 #pylint: disable-msg=C0111
Zdenek Behan77290c32012-06-26 17:39:47 +0200100 usage = "usage: %prog [options]"
Chris Masone24b80f12012-02-14 14:18:01 -0800101 parser = optparse.OptionParser(usage=usage)
102 parser.add_option("-b", "--board", dest="board")
103 parser.add_option("-i", "--build", dest="build")
Prashanth Balasubramanian673016d2014-11-04 10:40:48 -0800104 parser.add_option("-w", "--web", dest="web", default=None,
105 help="Address of a webserver to receive suite requests.")
Dan Shi0723bf52015-06-24 10:52:38 -0700106 parser.add_option('--firmware_rw_build', dest='firmware_rw_build',
107 default=None,
Dan Shi36cfd832014-10-10 13:38:51 -0700108 help='Firmware build to be installed in dut RW firmware.')
109 parser.add_option('--firmware_ro_build', dest='firmware_ro_build',
110 default=None,
111 help='Firmware build to be installed in dut RO firmware.')
112 parser.add_option('--test_source_build', dest='test_source_build',
113 default=None,
114 help=('Build that contains the test code, '
115 'e.g., it can be the value of `--build`, '
Dan Shi0723bf52015-06-24 10:52:38 -0700116 '`--firmware_rw_build` or `--firmware_ro_build` '
Dan Shi36cfd832014-10-10 13:38:51 -0700117 'arguments. Default is None, that is, use the test '
118 'code from `--build` (CrOS image)'))
Chris Masone359c0fd2012-03-13 15:18:59 -0700119 # This should just be a boolean flag, but the autotest "proxy" code
120 # can't handle flags that don't take arguments.
Alex Millerab33ddb2012-10-03 12:56:02 -0700121 parser.add_option("-n", "--no_wait", dest="no_wait", default="False",
122 help='Must pass "True" or "False" if used.')
Alex Miller0032e932013-10-23 12:52:58 -0700123 # If you really want no pool, --pool="" will do it. USE WITH CARE.
124 parser.add_option("-p", "--pool", dest="pool", default="suites")
Chris Masone24b80f12012-02-14 14:18:01 -0800125 parser.add_option("-s", "--suite_name", dest="name")
Fang Dengfb4a9492014-09-18 17:52:06 -0700126 parser.add_option("-a", "--afe_timeout_mins", type="int",
127 dest="afe_timeout_mins", default=30)
128 parser.add_option("-t", "--timeout_mins", type="int",
129 dest="timeout_mins", default=1440)
Simran Basi441fbc12015-01-23 12:28:54 -0800130 parser.add_option("-x", "--max_runtime_mins", type="int",
131 dest="max_runtime_mins", default=1440)
Fang Dengfb4a9492014-09-18 17:52:06 -0700132 parser.add_option("-d", "--delay_sec", type="int",
133 dest="delay_sec", default=10)
Chris Masone986459e2012-04-11 11:36:48 -0700134 parser.add_option("-m", "--mock_job_id", dest="mock_job_id",
Aviv Keshetdb321de2015-04-10 19:09:58 -0700135 help="Attach to existing job id for already running "
136 "suite, and creates report.")
137 # NOTE(akeshet): This looks similar to --no_wait, but behaves differently.
138 # --no_wait is passed in to the suite rpc itself and affects the suite,
139 # while this does not.
140 parser.add_option("-c", "--create_and_return", dest="create_and_return",
141 action="store_true",
142 help="Create the suite and print the job id, then "
143 "finish immediately.")
Alex Miller05a2fff2012-09-10 10:14:34 -0700144 parser.add_option("-u", "--num", dest="num", type="int", default=None,
Chris Masone8906ab12012-07-23 15:37:56 -0700145 help="Run on at most NUM machines.")
Alex Millerf43d0eb2012-10-01 13:43:13 -0700146 # Same boolean flag issue applies here.
Alex Millerab33ddb2012-10-03 12:56:02 -0700147 parser.add_option("-f", "--file_bugs", dest="file_bugs", default='False',
148 help='File bugs on test failures. Must pass "True" or '
149 '"False" if used.')
Dan Shia02181f2013-01-29 14:03:32 -0800150 parser.add_option("-l", "--bypass_labstatus", dest="bypass_labstatus",
151 action="store_true", help='Bypass lab status check.')
Alex Miller88762a82013-09-04 15:41:28 -0700152 # We allow either a number or a string for the priority. This way, if you
153 # know what you're doing, one can specify a custom priority level between
154 # other levels.
155 parser.add_option("-r", "--priority", dest="priority",
156 default=priorities.Priority.DEFAULT,
Aviv Keshetb0cb7532016-02-16 10:19:55 -0800157 action="store",
158 help="Priority of suite. Either numerical value, or "
159 "one of (" + ", ".join(priorities.Priority.names)
160 + ").")
Fang Deng058860c2014-05-15 15:41:50 -0700161 parser.add_option('--retry', dest='retry', default='False',
162 action='store', help='Enable test retry. '
163 'Must pass "True" or "False" if used.')
Fang Deng443f1952015-01-02 14:51:49 -0800164 parser.add_option('--max_retries', dest='max_retries', default=None,
165 type='int', action='store', help='Maximum retries'
166 'allowed at suite level. No limit if not specified.')
Dan Shi8de6d1b2014-06-12 09:10:37 -0700167 parser.add_option('--minimum_duts', dest='minimum_duts', type=int,
168 default=0, action='store',
Fang Dengcbc01212014-11-25 16:09:46 -0800169 help='Check that the pool has at least such many '
170 'healthy machines, otherwise suite will not run. '
171 'Default to 0.')
172 parser.add_option('--suite_min_duts', dest='suite_min_duts', type=int,
173 default=0, action='store',
174 help='Preferred minimum number of machines. Scheduler '
175 'will prioritize on getting such many machines for '
176 'the suite when it is competing with another suite '
177 'that has a higher priority but already got minimum '
178 'machines it needs. Default to 0.')
Aviv Keshet7cd12312013-07-25 10:25:55 -0700179 parser.add_option("--suite_args", dest="suite_args",
180 default=None, action="store",
181 help="Argument string for suite control file.")
Simran Basi1e10e922015-04-16 15:09:56 -0700182 parser.add_option('--offload_failures_only', dest='offload_failures_only',
183 action='store', default='False',
184 help='Only enable gs_offloading for failed tests. '
185 'Successful tests will be deleted. Must pass "True"'
186 ' or "False" if used.')
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -0700187 parser.add_option('--use_suite_attr', dest='use_suite_attr',
188 action='store_true', default=False,
189 help='Advanced. Run the suite based on ATTRIBUTES of '
190 'control files, rather than SUITE.')
Shuqian Zhao2fecacd2015-08-05 22:56:30 -0700191 parser.add_option('--json_dump', dest='json_dump', action='store_true',
192 default=False,
193 help='Dump the output of run_suite to stdout.')
Simran Basi5ace6f22016-01-06 17:30:44 -0800194 parser.add_option('--run_prod_code', dest='run_prod_code',
195 action='store_true', default=False,
196 help='Run the test code that lives in prod aka the test '
197 'code currently on the lab servers.')
Dan Shi059261a2016-02-22 12:06:37 -0800198 parser.add_option('--delay_minutes', type=int, default=0,
199 help=('Delay the creation of test jobs for a given '
200 'number of minutes. This argument can be used to '
201 'force provision jobs being delayed, which helps '
202 'to distribute loads across devservers.'))
Ningning Xiad9649172016-04-18 11:40:59 -0700203 parser.add_option('--skip_duts_check', dest='skip_duts_check', action='store_true',
204 default=False,
205 help='If True, skip minimum available DUTs check')
Chris Masone24b80f12012-02-14 14:18:01 -0800206 options, args = parser.parse_args()
207 return parser, options, args
208
209
Fang Deng0454e632014-04-07 15:39:47 -0700210def verify_options_and_args(options, args):
Fang Dengdd20e452014-04-07 15:39:47 -0700211 """Verify the validity of options and args.
212
Fang Dengdd20e452014-04-07 15:39:47 -0700213 @param options: The parsed options to verify.
214 @param args: The parsed args to verify.
215
216 @returns: True if verification passes, False otherwise.
217
218 """
Fang Deng6865aab2015-02-20 14:49:47 -0800219 if args:
220 print 'Unknown arguments: ' + str(args)
221 return False
222
223 if options.mock_job_id and (
224 not options.build or not options.name or not options.board):
225 print ('When using -m, need to specify build, board and suite '
226 'name which you have used for creating the original job')
227 return False
228 else:
Fang Dengdd20e452014-04-07 15:39:47 -0700229 if not options.build:
230 print 'Need to specify which build to use'
231 return False
232 if not options.board:
233 print 'Need to specify board'
234 return False
235 if not options.name:
236 print 'Need to specify suite name'
237 return False
238 if options.num is not None and options.num < 1:
239 print 'Number of machines must be more than 0, if specified.'
240 return False
241 if options.no_wait != 'True' and options.no_wait != 'False':
242 print 'Please specify "True" or "False" for --no_wait.'
243 return False
244 if options.file_bugs != 'True' and options.file_bugs != 'False':
245 print 'Please specify "True" or "False" for --file_bugs.'
246 return False
Fang Deng058860c2014-05-15 15:41:50 -0700247 if options.retry != 'True' and options.retry != 'False':
248 print 'Please specify "True" or "False" for --retry'
249 return False
Fang Deng443f1952015-01-02 14:51:49 -0800250 if options.retry == 'False' and options.max_retries is not None:
251 print 'max_retries can only be used with --retry=True'
252 return False
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -0700253 if options.use_suite_attr and options.suite_args is not None:
254 print ('The new suite control file cannot parse the suite_args: %s.'
255 'Please not specify any suite_args here.' % options.suite_args)
256 return False
Fang Deng058860c2014-05-15 15:41:50 -0700257 if options.no_wait == 'True' and options.retry == 'True':
258 print 'Test retry is not available when using --no_wait=True'
Dan Shi36cfd832014-10-10 13:38:51 -0700259 # Default to use the test code in CrOS build.
260 if not options.test_source_build and options.build:
261 options.test_source_build = options.build
Fang Dengdd20e452014-04-07 15:39:47 -0700262 return True
263
264
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -0700265def change_options_for_suite_attr(options):
266 """Change options to be prepared to run the suite_attr_wrapper.
267
268 If specify 'use_suite_attr' from the cmd line, it indicates to run the
269 new style suite control file, suite_attr_wrapper. Then, change the
270 options.suite_name to 'suite_attr_wrapper', change the options.suite_args to
271 include the arguments needed by suite_attr_wrapper.
272
273 @param options: The verified options.
274
275 @returns: The changed options.
276
277 """
278 # Convert the suite_name to attribute boolean expression.
279 if type(options.name) is str:
280 attr_filter_val = 'suite:%s' % options.name
281 else:
282 attr_filter_val = ' or '.join(['suite:%s' % x for x in options.name])
283
284 # change the suite_args to be a dict of arguments for suite_attr_wrapper
285 # if suite_args is not None, store the values in 'other_args' of the dict
286 args_dict = {}
287 args_dict['attr_filter'] = attr_filter_val
288 options.suite_args = str(args_dict)
289 options.name = 'suite_attr_wrapper'
290
291 return options
292
293
Chris Masone24b80f12012-02-14 14:18:01 -0800294def get_pretty_status(status):
Aviv Keshet1480c4a2013-03-21 16:38:31 -0700295 """
296 Converts a status string into a pretty-for-printing string.
297
298 @param status: Status to convert.
299
300 @return: Returns pretty string.
301 GOOD -> [ PASSED ]
302 TEST_NA -> [ INFO ]
303 other -> [ FAILED ]
304 """
Chris Masone24b80f12012-02-14 14:18:01 -0800305 if status == 'GOOD':
306 return '[ PASSED ]'
Chris Masone8906ab12012-07-23 15:37:56 -0700307 elif status == 'TEST_NA':
308 return '[ INFO ]'
Chris Masone24b80f12012-02-14 14:18:01 -0800309 return '[ FAILED ]'
310
Fang Dengdd20e452014-04-07 15:39:47 -0700311
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -0700312def get_original_suite_name(suite_name, suite_args):
313 """Get the original suite name when running suite_attr_wrapper.
314
315 @param suite_name: the name of the suite launched in afe. When it is
316 suite_attr_wrapper, the suite that actually running is
317 specified in the suite_args.
318 @param suite_args: the parsed option which contains the original suite name.
319
320 @returns: the original suite name.
321
322 """
323 if suite_name == 'suite_attr_wrapper':
324 attrs = ast.literal_eval(suite_args).get('attr_filter', '')
325 suite_list = ([x[6:] for x in re.split('[() ]', attrs)
326 if x and x.startswith('suite:')])
327 return suite_list[0] if suite_list else suite_name
328 return suite_name
329
330
Aviv Keshet9afee5e2014-10-09 16:33:09 -0700331def GetBuildbotStepLink(anchor_text, url):
332 """Generate a buildbot formatted link.
333
334 @param anchor_text The link text.
335 @param url The url to link to.
336 """
Ningning Xia80256e22016-04-05 15:52:32 -0700337 new_anchor_text = anchor_text.replace('@', '-AT-')
338 return '@@@STEP_LINK@%s@%s@@@' % (new_anchor_text, url)
Aviv Keshet9afee5e2014-10-09 16:33:09 -0700339
Chris Masone24b80f12012-02-14 14:18:01 -0800340
Craig Harrison25eb0f32012-08-23 16:48:49 -0700341class LogLink(object):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700342 """Information needed to record a link in the logs.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700343
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700344 Depending on context and the information provided at
345 construction time, the link may point to either to log files for
346 a job, or to a bug filed for a failure in the job.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700347
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700348 @var anchor The link text.
349 @var url The link url.
350 @var bug_id Id of a bug to link to, or None.
351 """
352
353 _BUG_URL_PREFIX = CONFIG.get_config_value('BUG_REPORTING',
354 'tracker_url')
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700355 _URL_PATTERN = CONFIG.get_config_value('CROS',
356 'log_url_pattern', type=str)
357
Kevin Cheng2bdd3722016-03-24 21:30:52 -0700358 # A list of tests that don't get retried so skip the dashboard.
359 _SKIP_RETRY_DASHBOARD = ['provision']
360
Ningning Xiabd911bd2016-04-19 14:06:03 -0700361 _BUG_LINK_PREFIX = 'Auto-Bug'
362 _LOG_LINK_PREFIX = 'Test-Logs'
363
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700364
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700365 @classmethod
366 def get_bug_link(cls, bug_id):
367 """Generate a bug link for the given bug_id.
368
369 @param bug_id: The id of the bug.
370 @return: A link, eg: https://crbug.com/<bug_id>.
371 """
372 return '%s%s' % (cls._BUG_URL_PREFIX, bug_id)
373
374
Fang Dengaeab6172014-05-07 17:17:04 -0700375 def __init__(self, anchor, server, job_string, bug_info=None, reason=None,
Simran Basi7203d4e2015-02-03 15:50:18 -0800376 retry_count=0, testname=None):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700377 """Initialize the LogLink by generating the log URL.
378
379 @param anchor The link text.
Alex Millerc7a59522013-10-30 15:18:57 -0700380 @param server The hostname of the server this suite ran on.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700381 @param job_string The job whose logs we'd like to link to.
382 @param bug_info Info about the bug, if one was filed.
Fang Deng53c6ff52014-02-24 17:51:24 -0800383 @param reason A string representing the reason of failure if any.
Fang Dengaeab6172014-05-07 17:17:04 -0700384 @param retry_count How many times the test has been retried.
Simran Basi7203d4e2015-02-03 15:50:18 -0800385 @param testname Optional Arg that supplies the testname.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700386 """
387 self.anchor = anchor
Alex Millerc7a59522013-10-30 15:18:57 -0700388 self.url = self._URL_PATTERN % (server, job_string)
Fang Deng53c6ff52014-02-24 17:51:24 -0800389 self.reason = reason
Fang Dengaeab6172014-05-07 17:17:04 -0700390 self.retry_count = retry_count
Simran Basi7203d4e2015-02-03 15:50:18 -0800391 self.testname = testname
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700392 if bug_info:
393 self.bug_id, self.bug_count = bug_info
394 else:
395 self.bug_id = None
396 self.bug_count = None
Craig Harrison25eb0f32012-08-23 16:48:49 -0700397
398
Ningning Xiabd911bd2016-04-19 14:06:03 -0700399 def GenerateBuildbotLinks(self):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700400 """Generate a link formatted to meet buildbot expectations.
401
Ningning Xiabd911bd2016-04-19 14:06:03 -0700402 If there is a bug associated with this link, report a link to the bug
403 and a link to the job logs;
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700404 otherwise report a link to the job logs.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700405
Ningning Xiabd911bd2016-04-19 14:06:03 -0700406 @return A list of links formatted for the buildbot log annotator.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700407 """
Ningning Xiabd911bd2016-04-19 14:06:03 -0700408 buildbot_links = []
409 bug_info_strings = []
Fang Dengaeab6172014-05-07 17:17:04 -0700410 info_strings = []
Ningning Xiabd911bd2016-04-19 14:06:03 -0700411
Fang Dengaeab6172014-05-07 17:17:04 -0700412 if self.retry_count > 0:
413 info_strings.append('retry_count: %d' % self.retry_count)
Ningning Xiabd911bd2016-04-19 14:06:03 -0700414 bug_info_strings.append('retry_count: %d' % self.retry_count)
Fang Dengaeab6172014-05-07 17:17:04 -0700415
Ningning Xiabd911bd2016-04-19 14:06:03 -0700416 # Add the bug link to buildbot_links
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700417 if self.bug_id:
Ningning Xiabd911bd2016-04-19 14:06:03 -0700418 bug_url = self.get_bug_link(self.bug_id)
beepsad4daf82013-09-26 10:07:33 -0700419 if self.bug_count is None:
Fang Dengaeab6172014-05-07 17:17:04 -0700420 bug_info = 'unknown number of reports'
beepsad4daf82013-09-26 10:07:33 -0700421 elif self.bug_count == 1:
Fang Dengaeab6172014-05-07 17:17:04 -0700422 bug_info = 'new report'
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700423 else:
Fang Dengaeab6172014-05-07 17:17:04 -0700424 bug_info = '%s reports' % self.bug_count
Ningning Xiabd911bd2016-04-19 14:06:03 -0700425 bug_info_strings.append(bug_info)
Fang Deng53c6ff52014-02-24 17:51:24 -0800426
Ningning Xiabd911bd2016-04-19 14:06:03 -0700427 if self.reason:
428 bug_info_strings.append(self.reason.strip())
429
430 bug_anchor_text = self.get_anchor_text(self._BUG_LINK_PREFIX,
431 bug_info_strings)
432
433 buildbot_links.append(GetBuildbotStepLink(bug_anchor_text,
434 bug_url))
435
436 # Add the log link to buildbot_links
Fang Deng53c6ff52014-02-24 17:51:24 -0800437 if self.reason:
Fang Dengaeab6172014-05-07 17:17:04 -0700438 info_strings.append(self.reason.strip())
439
Ningning Xiabd911bd2016-04-19 14:06:03 -0700440 anchor_text = self.get_anchor_text(self._LOG_LINK_PREFIX,
441 info_strings)
442 buildbot_links.append(GetBuildbotStepLink(anchor_text, self.url))
443
444 return buildbot_links
445
446
447 def get_anchor_text(self, prefix, info_strings):
448 """Generate the anchor_text given the prefix and info.
449
450 @param prefix The prefix of the anchor text.
451 @param info_strings The infos presented in the anchor text.
452 @return A anchor_text with the right prefix and info strings.
453 """
Fang Dengaeab6172014-05-07 17:17:04 -0700454 if info_strings:
455 info = ', '.join(info_strings)
Ningning Xiabd911bd2016-04-19 14:06:03 -0700456 anchor_text = '[%(prefix)s]: %(anchor)s: %(info)s' % {
457 'prefix': prefix, 'anchor': self.anchor.strip(),
458 'info': info}
Fang Dengaeab6172014-05-07 17:17:04 -0700459 else:
Ningning Xiabd911bd2016-04-19 14:06:03 -0700460 anchor_text = '[%(prefix)s]: %(anchor)s' % {
461 'prefix': prefix, 'anchor': self.anchor.strip()}
462 return anchor_text
Craig Harrison25eb0f32012-08-23 16:48:49 -0700463
Craig Harrisond8451572012-08-31 10:29:33 -0700464 def GenerateTextLink(self):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700465 """Generate a link to the job's logs, for consumption by a human.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700466
Craig Harrisond8451572012-08-31 10:29:33 -0700467 @return A link formatted for human readability.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700468 """
Fang Deng53c6ff52014-02-24 17:51:24 -0800469 return '%s%s' % (self.anchor, self.url)
Craig Harrison25eb0f32012-08-23 16:48:49 -0700470
471
Simran Basi7203d4e2015-02-03 15:50:18 -0800472 def GenerateWmatrixRetryLink(self):
473 """Generate a link to the wmatrix retry dashboard.
474
475 @return A link formatted for the buildbot log annotator.
476 """
477 if not self.testname:
478 return None
479
Kevin Cheng2bdd3722016-03-24 21:30:52 -0700480 if self.testname in self._SKIP_RETRY_DASHBOARD:
481 return None
482
Aviv Keshetd03c23c2016-05-09 12:06:11 -0700483 return GetBuildbotStepLink('[Flake-Dashboard]: %s' % self.testname,
484 WMATRIX_RETRY_URL % self.testname)
Simran Basi7203d4e2015-02-03 15:50:18 -0800485
486
Chris Masoneb61b4052012-04-30 14:35:28 -0700487class Timings(object):
488 """Timings for important events during a suite.
489
490 All timestamps are datetime.datetime objects.
491
Fang Dengdd20e452014-04-07 15:39:47 -0700492 @var suite_job_id: the afe job id of the suite job for which
493 we are recording the timing for.
494 @var download_start_time: the time the devserver starts staging
495 the build artifacts. Recorded in create_suite_job.
496 @var payload_end_time: the time when the artifacts only necessary to start
497 installsing images onto DUT's are staged.
498 Recorded in create_suite_job.
499 @var artifact_end_time: the remaining artifacts are downloaded after we kick
500 off the reimaging job, at which point we record
501 artifact_end_time. Recorded in dynamic_suite.py.
Chris Masoneb61b4052012-04-30 14:35:28 -0700502 @var suite_start_time: the time the suite started.
Chris Masoneb61b4052012-04-30 14:35:28 -0700503 @var tests_start_time: the time the first test started running.
Fang Dengdd20e452014-04-07 15:39:47 -0700504 @var tests_end_time: the time the last test finished running.
Chris Masoneb61b4052012-04-30 14:35:28 -0700505 """
beeps6f02d192013-03-22 13:15:49 -0700506
Fang Dengdd20e452014-04-07 15:39:47 -0700507 def __init__(self, suite_job_id):
508 self.suite_job_id = suite_job_id
509 # Timings related to staging artifacts on devserver.
510 self.download_start_time = None
511 self.payload_end_time = None
512 self.artifact_end_time = None
beeps6f02d192013-03-22 13:15:49 -0700513
Fang Dengdd20e452014-04-07 15:39:47 -0700514 # The test_start_time, but taken off the view that corresponds to the
515 # suite instead of an individual test.
516 self.suite_start_time = None
beeps6f02d192013-03-22 13:15:49 -0700517
Fang Dengdd20e452014-04-07 15:39:47 -0700518 # Earliest and Latest tests in the set of TestViews passed to us.
519 self.tests_start_time = None
520 self.tests_end_time = None
521
Chris Masoneb61b4052012-04-30 14:35:28 -0700522
Chris Masoned9f13c52012-08-29 10:37:08 -0700523 def RecordTiming(self, view):
524 """Given a test report view, extract and record pertinent time info.
Chris Masoneb61b4052012-04-30 14:35:28 -0700525
526 get_detailed_test_views() returns a list of entries that provide
527 info about the various parts of a suite run. This method can take
528 any one of these entries and look up timestamp info we might want
529 and record it.
530
Chris Masonecfa7efc2012-09-06 16:00:07 -0700531 If timestamps are unavailable, datetime.datetime.min/max will be used.
532
Fang Dengaeab6172014-05-07 17:17:04 -0700533 @param view: A TestView object.
Chris Masoneb61b4052012-04-30 14:35:28 -0700534 """
Chris Masonecfa7efc2012-09-06 16:00:07 -0700535 start_candidate = datetime.min
536 end_candidate = datetime.max
537 if view['test_started_time']:
Dan Shidfea3682014-08-10 23:38:40 -0700538 start_candidate = time_utils.time_string_to_datetime(
539 view['test_started_time'])
Chris Masonecfa7efc2012-09-06 16:00:07 -0700540 if view['test_finished_time']:
Dan Shidfea3682014-08-10 23:38:40 -0700541 end_candidate = time_utils.time_string_to_datetime(
542 view['test_finished_time'])
Chris Masonecfa7efc2012-09-06 16:00:07 -0700543
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800544 if view.get_testname() == TestView.SUITE_JOB:
Chris Masoneb61b4052012-04-30 14:35:28 -0700545 self.suite_start_time = start_candidate
Chris Masoneb61b4052012-04-30 14:35:28 -0700546 else:
547 self._UpdateFirstTestStartTime(start_candidate)
548 self._UpdateLastTestEndTime(end_candidate)
Fang Dengdd20e452014-04-07 15:39:47 -0700549 if view['afe_job_id'] == self.suite_job_id and 'job_keyvals' in view:
Chris Masoned9f13c52012-08-29 10:37:08 -0700550 keyvals = view['job_keyvals']
Dan Shidfea3682014-08-10 23:38:40 -0700551 self.download_start_time = time_utils.time_string_to_datetime(
552 keyvals.get(constants.DOWNLOAD_STARTED_TIME),
553 handle_type_error=True)
beeps6f02d192013-03-22 13:15:49 -0700554
Dan Shidfea3682014-08-10 23:38:40 -0700555 self.payload_end_time = time_utils.time_string_to_datetime(
556 keyvals.get(constants.PAYLOAD_FINISHED_TIME),
557 handle_type_error=True)
beeps6f02d192013-03-22 13:15:49 -0700558
Dan Shidfea3682014-08-10 23:38:40 -0700559 self.artifact_end_time = time_utils.time_string_to_datetime(
560 keyvals.get(constants.ARTIFACT_FINISHED_TIME),
561 handle_type_error=True)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700562
Chris Masoneb61b4052012-04-30 14:35:28 -0700563
564 def _UpdateFirstTestStartTime(self, candidate):
565 """Update self.tests_start_time, iff candidate is an earlier time.
566
567 @param candidate: a datetime.datetime object.
568 """
569 if not self.tests_start_time or candidate < self.tests_start_time:
570 self.tests_start_time = candidate
571
572
573 def _UpdateLastTestEndTime(self, candidate):
574 """Update self.tests_end_time, iff candidate is a later time.
575
576 @param candidate: a datetime.datetime object.
577 """
578 if not self.tests_end_time or candidate > self.tests_end_time:
579 self.tests_end_time = candidate
580
581
582 def __str__(self):
583 return ('\n'
584 'Suite timings:\n'
Chris Masonea8066a92012-05-01 16:52:31 -0700585 'Downloads started at %s\n'
586 'Payload downloads ended at %s\n'
Chris Masoneb61b4052012-04-30 14:35:28 -0700587 'Suite started at %s\n'
Chris Masonea8066a92012-05-01 16:52:31 -0700588 'Artifact downloads ended (at latest) at %s\n'
Chris Masoneb61b4052012-04-30 14:35:28 -0700589 'Testing started at %s\n'
Chris Masonea8066a92012-05-01 16:52:31 -0700590 'Testing ended at %s\n' % (self.download_start_time,
591 self.payload_end_time,
592 self.suite_start_time,
Chris Masonea8066a92012-05-01 16:52:31 -0700593 self.artifact_end_time,
Chris Masoneb61b4052012-04-30 14:35:28 -0700594 self.tests_start_time,
595 self.tests_end_time))
596
597
beeps6f02d192013-03-22 13:15:49 -0700598 def SendResultsToStatsd(self, suite, build, board):
599 """
600 Sends data to statsd.
601
602 1. Makes a data_key of the form: run_suite.$board.$branch.$suite
603 eg: stats/gauges/<hostname>/run_suite/<board>/<branch>/<suite>/
604 2. Computes timings for several start and end event pairs.
Alex Miller9a1987a2013-08-21 15:51:16 -0700605 3. Sends all timing values to statsd.
beeps6f02d192013-03-22 13:15:49 -0700606
607 @param suite: scheduled suite that we want to record the results of.
608 @param build: the build that this suite ran on.
609 eg: 'lumpy-release/R26-3570.0.0'
610 @param board: the board that this suite ran on.
611 """
612 if sys.version_info < (2, 7):
613 logging.error('Sending run_suite perf data to statsd requires'
614 'python 2.7 or greater.')
615 return
616
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700617 # Constructs the key used for logging statsd timing data.
618 data_key = utils.get_data_key('run_suite', suite, build, board)
beeps6f02d192013-03-22 13:15:49 -0700619
620 # Since we don't want to try subtracting corrupted datetime values
Dan Shidfea3682014-08-10 23:38:40 -0700621 # we catch TypeErrors in time_utils.time_string_to_datetime and insert
622 # None instead. This means that even if, say,
623 # keyvals.get(constants.ARTIFACT_FINISHED_TIME) returns a corrupt
624 # value the member artifact_end_time is set to None.
beeps6f02d192013-03-22 13:15:49 -0700625 if self.download_start_time:
626 if self.payload_end_time:
Gabe Black1e1c41b2015-02-04 23:55:15 -0800627 autotest_stats.Timer(data_key).send('payload_download_time',
628 (self.payload_end_time -
629 self.download_start_time).total_seconds())
beeps6f02d192013-03-22 13:15:49 -0700630
631 if self.artifact_end_time:
Gabe Black1e1c41b2015-02-04 23:55:15 -0800632 autotest_stats.Timer(data_key).send('artifact_download_time',
633 (self.artifact_end_time -
634 self.download_start_time).total_seconds())
beeps6f02d192013-03-22 13:15:49 -0700635
636 if self.tests_end_time:
637 if self.suite_start_time:
Gabe Black1e1c41b2015-02-04 23:55:15 -0800638 autotest_stats.Timer(data_key).send('suite_run_time',
639 (self.tests_end_time -
640 self.suite_start_time).total_seconds())
beeps6f02d192013-03-22 13:15:49 -0700641
642 if self.tests_start_time:
Gabe Black1e1c41b2015-02-04 23:55:15 -0800643 autotest_stats.Timer(data_key).send('tests_run_time',
644 (self.tests_end_time -
645 self.tests_start_time).total_seconds())
beeps6f02d192013-03-22 13:15:49 -0700646
beeps6f02d192013-03-22 13:15:49 -0700647
Alex Millerc7a59522013-10-30 15:18:57 -0700648_DEFAULT_AUTOTEST_INSTANCE = CONFIG.get_config_value(
649 'SERVER', 'hostname', type=str)
650
651
652def instance_for_pool(pool_name):
653 """
654 Return the hostname of the server that should be used to service a suite
655 for the specified pool.
656
657 @param pool_name: The pool (without 'pool:' to schedule the suite against.
658 @return: The correct host that should be used to service this suite run.
659 """
660 return CONFIG.get_config_value(
661 'POOL_INSTANCE_SHARDING', pool_name,
662 default=_DEFAULT_AUTOTEST_INSTANCE)
663
664
Fang Dengaeab6172014-05-07 17:17:04 -0700665class TestView(object):
666 """Represents a test view and provides a set of helper functions."""
667
668
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800669 SUITE_JOB = 'Suite job'
Fang Deng95af42f2014-09-12 14:16:11 -0700670 INFRA_TESTS = ['provision']
Fang Dengaeab6172014-05-07 17:17:04 -0700671
672
Simran Basi17ca77c2015-10-14 19:05:00 -0700673 def __init__(self, view, afe_job, suite_name, build, user,
674 solo_test_run=False):
Fang Dengaeab6172014-05-07 17:17:04 -0700675 """Init a TestView object representing a tko test view.
676
677 @param view: A dictionary representing a tko test view.
Fang Dengf8503532014-06-12 18:21:55 -0700678 @param afe_job: An instance of frontend.afe.models.Job
679 representing the job that kicked off the test.
Fang Dengaeab6172014-05-07 17:17:04 -0700680 @param suite_name: The name of the suite
681 that the test belongs to.
682 @param build: The build for which the test is run.
Simran Basi01984f52015-10-12 15:36:45 -0700683 @param user: The user for which the test is run.
Simran Basi17ca77c2015-10-14 19:05:00 -0700684 @param solo_test_run: This is a solo test run not part of a suite.
Fang Dengaeab6172014-05-07 17:17:04 -0700685 """
686 self.view = view
Fang Dengf8503532014-06-12 18:21:55 -0700687 self.afe_job = afe_job
Fang Dengaeab6172014-05-07 17:17:04 -0700688 self.suite_name = suite_name
689 self.build = build
Simran Basi17ca77c2015-10-14 19:05:00 -0700690 self.is_suite_view = afe_job.parent_job is None and not solo_test_run
Fang Dengaeab6172014-05-07 17:17:04 -0700691 # This is the test name that will be shown in the output.
692 self.testname = None
Simran Basi01984f52015-10-12 15:36:45 -0700693 self.user = user
Fang Dengaeab6172014-05-07 17:17:04 -0700694
Fang Dengf8503532014-06-12 18:21:55 -0700695 # The case that a job was aborted before it got a chance to run
696 # usually indicates suite has timed out (unless aborted by user).
697 # In this case, the abort reason will be None.
698 # Update the reason with proper information.
699 if (self.is_relevant_suite_view() and
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800700 not self.get_testname() == self.SUITE_JOB and
Fang Dengf8503532014-06-12 18:21:55 -0700701 self.view['status'] == 'ABORT' and
702 not self.view['reason']):
703 self.view['reason'] = 'Timed out, did not run.'
704
Fang Dengaeab6172014-05-07 17:17:04 -0700705
706 def __getitem__(self, key):
707 """Overload __getitem__ so that we can still use []
708
709 @param key: A key of the tko test view.
710
711 @returns: The value of an attribute in the view.
712
713 """
714 return self.view[key]
715
716
Fang Dengaeab6172014-05-07 17:17:04 -0700717 def __iter__(self):
718 """Overload __iter__ so that it supports 'in' operator."""
719 return iter(self.view)
720
721
722 def get_testname(self):
723 """Get test name that should be shown in the output.
724
725 Formalize the test_name we got from the test view.
726
727 Remove 'build/suite' prefix if any. And append 'experimental' prefix
728 for experimental tests if their names do not start with 'experimental'.
729
730 If one runs a test in control file via the following code,
731 job.runtest('my_Test', tag='tag')
732 for most of the cases, view['test_name'] would look like 'my_Test.tag'.
733 If this is the case, this method will just return the original
734 test name, i.e. 'my_Test.tag'.
735
736 There are four special cases.
737 1) A test view is for the suite job's SERVER_JOB.
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800738 In this case, this method will return 'Suite job'.
Fang Dengaeab6172014-05-07 17:17:04 -0700739
Simran Basi17ca77c2015-10-14 19:05:00 -0700740 2) A test view is of a child job or a solo test run not part of a
741 suite, and for a SERVER_JOB or CLIENT_JOB.
Fang Dengaeab6172014-05-07 17:17:04 -0700742 In this case, we will take the job name, remove the build/suite
743 prefix from the job name, and append the rest to 'SERVER_JOB'
744 or 'CLIENT_JOB' as a prefix. So the names returned by this
745 method will look like:
746 'experimental_Telemetry Smoothness Measurement_SERVER_JOB'
747 'experimental_dummy_Pass_SERVER_JOB'
748 'dummy_Fail_SERVER_JOB'
749
Fang Dengf8503532014-06-12 18:21:55 -0700750 3) A test view is of a suite job and its status is ABORT.
Fang Dengaeab6172014-05-07 17:17:04 -0700751 In this case, the view['test_name'] is the child job's name.
752 If it is an experimental test, 'experimental' will be part
753 of the name. For instance,
754 'lumpy-release/R35-5712.0.0/perf_v2/
755 experimental_Telemetry Smoothness Measurement'
756 'lumpy-release/R35-5712.0.0/dummy/experimental_dummy_Pass'
757 'lumpy-release/R35-5712.0.0/dummy/dummy_Fail'
758 The above names will be converted to the following:
759 'experimental_Telemetry Smoothness Measurement'
760 'experimental_dummy_Pass'
761 'dummy_Fail'
762
Fang Dengf8503532014-06-12 18:21:55 -0700763 4) A test view's status is of a suite job and its status is TEST_NA.
Fang Dengaeab6172014-05-07 17:17:04 -0700764 In this case, the view['test_name'] is the NAME field of the control
765 file. If it is an experimental test, 'experimental' will part of
766 the name. For instance,
767 'experimental_Telemetry Smoothness Measurement'
768 'experimental_dummy_Pass'
769 'dummy_Fail'
770 This method will not modify these names.
771
772 @returns: Test name after normalization.
773
774 """
775 if self.testname is not None:
776 return self.testname
777
778 if (self.is_suite_view and
779 self.view['test_name'].startswith('SERVER_JOB')):
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800780 # Rename suite job's SERVER_JOB to 'Suite job'.
781 self.testname = self.SUITE_JOB
Fang Dengaeab6172014-05-07 17:17:04 -0700782 return self.testname
783
784 if (self.view['test_name'].startswith('SERVER_JOB') or
785 self.view['test_name'].startswith('CLIENT_JOB')):
786 # Append job name as a prefix for SERVER_JOB and CLIENT_JOB
787 testname= '%s_%s' % (self.view['job_name'], self.view['test_name'])
788 else:
789 testname = self.view['test_name']
790 experimental = self.is_experimental()
791 # Remove the build and suite name from testname if any.
792 testname = tools.get_test_name(
793 self.build, self.suite_name, testname)
794 # If an experimental test was aborted, testname
795 # would include the 'experimental' prefix already.
796 prefix = constants.EXPERIMENTAL_PREFIX if (
797 experimental and not
798 testname.startswith(constants.EXPERIMENTAL_PREFIX)) else ''
799 self.testname = prefix + testname
800 return self.testname
801
802
803 def is_relevant_suite_view(self):
804 """Checks whether this is a suite view we should care about.
805
806 @returns: True if it is relevant. False otherwise.
807 """
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800808 return (self.get_testname() == self.SUITE_JOB or
Fang Dengaeab6172014-05-07 17:17:04 -0700809 (self.is_suite_view and
810 not self.view['test_name'].startswith('CLIENT_JOB') and
811 not self.view['subdir']))
812
813
814 def is_test(self):
815 """Return whether the view is for an actual test.
816
817 @returns True if the view is for an actual test.
818 False if the view is for SERVER_JOB or CLIENT_JOB.
819
820 """
821 return not (self.view['test_name'].startswith('SERVER_JOB') or
822 self.view['test_name'].startswith('CLIENT_JOB'))
823
824
825 def is_retry(self):
826 """Check whether the view is for a retry.
827
828 @returns: True, if the view is for a retry; False otherwise.
829
830 """
831 return self.view['job_keyvals'].get('retry_original_job_id') is not None
832
833
834 def is_experimental(self):
835 """Check whether a test view is for an experimental test.
836
837 @returns: True if it is for an experimental test, False otherwise.
838
839 """
840 return (self.view['job_keyvals'].get('experimental') == 'True' or
841 tools.get_test_name(self.build, self.suite_name,
842 self.view['test_name']).startswith('experimental'))
843
844
Fang Dengf8503532014-06-12 18:21:55 -0700845 def hit_timeout(self):
846 """Check whether the corresponding job has hit its own timeout.
Fang Dengaeab6172014-05-07 17:17:04 -0700847
Fang Dengf8503532014-06-12 18:21:55 -0700848 Note this method should not be called for those test views
849 that belongs to a suite job and are determined as irrelevant
850 by is_relevant_suite_view. This is because they are associated
851 to the suite job, whose job start/finished time make no sense
852 to an irrelevant test view.
Fang Dengaeab6172014-05-07 17:17:04 -0700853
Fang Dengf8503532014-06-12 18:21:55 -0700854 @returns: True if the corresponding afe job has hit timeout.
855 False otherwise.
856 """
857 if (self.is_relevant_suite_view() and
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800858 self.get_testname() != self.SUITE_JOB):
859 # Any relevant suite test view except SUITE_JOB
Fang Dengf8503532014-06-12 18:21:55 -0700860 # did not hit its own timeout because it was not ever run.
861 return False
862 start = (datetime.strptime(
Dan Shidfea3682014-08-10 23:38:40 -0700863 self.view['job_started_time'], time_utils.TIME_FMT)
Fang Dengf8503532014-06-12 18:21:55 -0700864 if self.view['job_started_time'] else None)
865 end = (datetime.strptime(
Dan Shidfea3682014-08-10 23:38:40 -0700866 self.view['job_finished_time'], time_utils.TIME_FMT)
Fang Dengf8503532014-06-12 18:21:55 -0700867 if self.view['job_finished_time'] else None)
868 if not start or not end:
869 return False
870 else:
871 return ((end - start).total_seconds()/60.0
872 > self.afe_job.max_runtime_mins)
873
874
875 def is_aborted(self):
876 """Check if the view was aborted.
877
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800878 For suite job and child job test views, we check job keyval
Fang Dengf8503532014-06-12 18:21:55 -0700879 'aborted_by' and test status.
880
881 For relevant suite job test views, we only check test status
882 because the suite job keyval won't make sense to individual
883 test views.
884
885 @returns: True if the test was as aborted, False otherwise.
Fang Dengaeab6172014-05-07 17:17:04 -0700886
887 """
Fang Dengf8503532014-06-12 18:21:55 -0700888
889 if (self.is_relevant_suite_view() and
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800890 self.get_testname() != self.SUITE_JOB):
Fang Dengf8503532014-06-12 18:21:55 -0700891 return self.view['status'] == 'ABORT'
892 else:
893 return (bool(self.view['job_keyvals'].get('aborted_by')) and
894 self.view['status'] in ['ABORT', 'RUNNING'])
Fang Dengaeab6172014-05-07 17:17:04 -0700895
896
897 def is_in_fail_status(self):
Fang Deng95af42f2014-09-12 14:16:11 -0700898 """Check if the given test's status corresponds to a failure.
Fang Dengaeab6172014-05-07 17:17:04 -0700899
900 @returns: True if the test's status is FAIL or ERROR. False otherwise.
901
902 """
903 # All the statuses tests can have when they fail.
904 return self.view['status'] in ['FAIL', 'ERROR', 'ABORT']
905
906
Fang Deng95af42f2014-09-12 14:16:11 -0700907 def is_infra_test(self):
908 """Check whether this is a test that only lab infra is concerned.
909
910 @returns: True if only lab infra is concerned, False otherwise.
911
912 """
913 return self.get_testname() in self.INFRA_TESTS
914
915
Fang Dengaeab6172014-05-07 17:17:04 -0700916 def get_buildbot_link_reason(self):
917 """Generate the buildbot link reason for the test.
918
919 @returns: A string representing the reason.
920
921 """
922 return ('%s: %s' % (self.view['status'], self.view['reason'])
923 if self.view['reason'] else self.view['status'])
924
925
926 def get_job_id_owner_str(self):
927 """Generate the job_id_owner string for a test.
928
929 @returns: A string which looks like 135036-username
930
931 """
Simran Basi01984f52015-10-12 15:36:45 -0700932 return '%s-%s' % (self.view['afe_job_id'], self.user)
Fang Dengaeab6172014-05-07 17:17:04 -0700933
934
935 def get_bug_info(self, suite_job_keyvals):
936 """Get the bug info from suite_job_keyvals.
937
938 If a bug has been filed for the test, its bug info (bug id and counts)
939 will be stored in the suite job's keyvals. This method attempts to
940 retrieve bug info of the test from |suite_job_keyvals|. It will return
941 None if no bug info is found. No need to check bug info if the view is
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800942 SUITE_JOB.
Fang Dengaeab6172014-05-07 17:17:04 -0700943
944 @param suite_job_keyvals: The job keyval dictionary of the suite job.
945 All the bug info about child jobs are stored in
946 suite job's keyvals.
947
948 @returns: None if there is no bug info, or a pair with the
949 id of the bug, and the count of the number of
950 times the bug has been seen.
951
952 """
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800953 if self.get_testname() == self.SUITE_JOB:
Fang Dengaeab6172014-05-07 17:17:04 -0700954 return None
955 if (self.view['test_name'].startswith('SERVER_JOB') or
956 self.view['test_name'].startswith('CLIENT_JOB')):
957 # Append job name as a prefix for SERVER_JOB and CLIENT_JOB
958 testname= '%s_%s' % (self.view['job_name'], self.view['test_name'])
959 else:
960 testname = self.view['test_name']
961
962 return tools.get_test_failure_bug_info(
963 suite_job_keyvals, self.view['afe_job_id'],
964 testname)
965
966
967 def should_display_buildbot_link(self):
968 """Check whether a buildbot link should show for this view.
969
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800970 For suite job view, show buildbot link if it fails.
Fang Dengf8503532014-06-12 18:21:55 -0700971 For normal test view,
972 show buildbot link if it is a retry
973 show buildbot link if it hits its own timeout.
974 show buildbot link if it fails. This doesn't
975 include the case where it was aborted but has
976 not hit its own timeout (most likely it was aborted because
977 suite has timed out).
Fang Dengaeab6172014-05-07 17:17:04 -0700978
979 @returns: True if we should show the buildbot link.
980 False otherwise.
981 """
982 is_bad_status = (self.view['status'] != 'GOOD' and
983 self.view['status'] != 'TEST_NA')
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800984 if self.get_testname() == self.SUITE_JOB:
Fang Dengf8503532014-06-12 18:21:55 -0700985 return is_bad_status
986 else:
987 if self.is_retry():
988 return True
989 if is_bad_status:
990 return not self.is_aborted() or self.hit_timeout()
Fang Dengaeab6172014-05-07 17:17:04 -0700991
992
Shuqian Zhao2fecacd2015-08-05 22:56:30 -0700993 def get_control_file_attributes(self):
994 """Get the attributes from the control file of the test.
995
996 @returns: A list of test attribute or None.
997 """
998 control_file = self.afe_job.control_file
999 attributes = None
1000 if control_file:
1001 cd = control_data.parse_control_string(control_file)
1002 attributes = list(cd.attributes)
1003 return attributes
1004
1005
Fang Dengdd20e452014-04-07 15:39:47 -07001006class ResultCollector(object):
Simran Basi17ca77c2015-10-14 19:05:00 -07001007 """Collect test results of a suite or a single test run.
Fang Dengdd20e452014-04-07 15:39:47 -07001008
1009 Once a suite job has finished, use this class to collect test results.
1010 `run` is the core method that is to be called first. Then the caller
1011 could retrieve information like return code, return message, is_aborted,
1012 and timings by accessing the collector's public attributes. And output
1013 the test results and links by calling the 'output_*' methods.
1014
1015 Here is a overview of what `run` method does.
1016
1017 1) Collect the suite job's results from tko_test_view_2.
1018 For the suite job, we only pull test views without a 'subdir'.
1019 A NULL subdir indicates that the test was _not_ executed. This could be
1020 that no child job was scheduled for this test or the child job got
1021 aborted before starts running.
1022 (Note 'SERVER_JOB'/'CLIENT_JOB' are handled specially)
1023
1024 2) Collect the child jobs' results from tko_test_view_2.
1025 For child jobs, we pull all the test views associated with them.
1026 (Note 'SERVER_JOB'/'CLIENT_JOB' are handled speically)
1027
Fang Dengaeab6172014-05-07 17:17:04 -07001028 3) Generate web and buildbot links.
Fang Dengdd20e452014-04-07 15:39:47 -07001029 4) Compute timings of the suite run.
1030 5) Compute the return code based on test results.
1031
1032 @var _instance_server: The hostname of the server that is used
1033 to service the suite.
1034 @var _afe: The afe rpc client.
1035 @var _tko: The tko rpc client.
1036 @var _build: The build for which the suite is run,
1037 e.g. 'lumpy-release/R35-5712.0.0'
MK Ryu977a9752014-10-21 11:58:09 -07001038 @var _board: The target board for which the suite is run,
1039 e.g., 'lumpy', 'link'.
Fang Dengdd20e452014-04-07 15:39:47 -07001040 @var _suite_name: The suite name, e.g. 'bvt', 'dummy'.
1041 @var _suite_job_id: The job id of the suite for which we are going to
1042 collect results.
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -07001043 @var _original_suite_name: The suite name we record timing would be
1044 different from _suite_name when running
1045 suite_attr_wrapper.
Fang Dengaeab6172014-05-07 17:17:04 -07001046 @var _suite_views: A list of TestView objects, representing relevant
1047 test views of the suite job.
1048 @var _child_views: A list of TestView objects, representing test views
1049 of the child jobs.
1050 @var _test_views: A list of TestView objects, representing all test views
1051 from _suite_views and _child_views.
Fang Dengdd20e452014-04-07 15:39:47 -07001052 @var _web_links: A list of web links pointing to the results of jobs.
1053 @var _buildbot_links: A list of buildbot links for non-passing tests.
Fang Dengaeab6172014-05-07 17:17:04 -07001054 @var _max_testname_width: Max width of all test names.
Simran Basi17ca77c2015-10-14 19:05:00 -07001055 @var _solo_test_run: True if this is a single test run.
Fang Dengdd20e452014-04-07 15:39:47 -07001056 @var return_code: The exit code that should be returned by run_suite.
1057 @var return_message: Any message that should be displayed to explain
1058 the return code.
1059 @var is_aborted: Whether the suite was aborted or not.
1060 True, False or None (aborting status is unknown yet)
1061 @var timings: A Timing object that records the suite's timings.
1062
1063 """
1064
1065
MK Ryu977a9752014-10-21 11:58:09 -07001066 def __init__(self, instance_server, afe, tko, build, board,
Simran Basi01984f52015-10-12 15:36:45 -07001067 suite_name, suite_job_id, original_suite_name=None,
Simran Basi17ca77c2015-10-14 19:05:00 -07001068 user=None, solo_test_run=False):
Fang Dengdd20e452014-04-07 15:39:47 -07001069 self._instance_server = instance_server
1070 self._afe = afe
1071 self._tko = tko
1072 self._build = build
MK Ryu977a9752014-10-21 11:58:09 -07001073 self._board = board
Fang Dengdd20e452014-04-07 15:39:47 -07001074 self._suite_name = suite_name
1075 self._suite_job_id = suite_job_id
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -07001076 self._original_suite_name = original_suite_name or suite_name
Fang Deng0454e632014-04-07 15:39:47 -07001077 self._suite_views = []
1078 self._child_views = []
Fang Dengdd20e452014-04-07 15:39:47 -07001079 self._test_views = []
Fang Dengaeab6172014-05-07 17:17:04 -07001080 self._retry_counts = {}
Fang Dengdd20e452014-04-07 15:39:47 -07001081 self._web_links = []
1082 self._buildbot_links = []
Fang Deng0454e632014-04-07 15:39:47 -07001083 self._max_testname_width = 0
MK Ryu977a9752014-10-21 11:58:09 -07001084 self._num_child_jobs = 0
Fang Dengdd20e452014-04-07 15:39:47 -07001085 self.return_code = None
Fang Deng0454e632014-04-07 15:39:47 -07001086 self.return_message = ''
Fang Dengdd20e452014-04-07 15:39:47 -07001087 self.is_aborted = None
1088 self.timings = None
Simran Basi01984f52015-10-12 15:36:45 -07001089 self._user = user or getpass.getuser()
Simran Basi17ca77c2015-10-14 19:05:00 -07001090 self._solo_test_run = solo_test_run
Fang Dengdd20e452014-04-07 15:39:47 -07001091
1092
Fang Dengdd20e452014-04-07 15:39:47 -07001093 def _fetch_relevant_test_views_of_suite(self):
1094 """Fetch relevant test views of the suite job.
1095
1096 For the suite job, there will be a test view for SERVER_JOB, and views
1097 for results of its child jobs. For example, assume we've ceated
1098 a suite job (afe_job_id: 40) that runs dummy_Pass, dummy_Fail,
1099 dummy_Pass.bluetooth. Assume dummy_Pass was aborted before running while
1100 dummy_Path.bluetooth got TEST_NA as no duts have bluetooth.
1101 So the suite job's test views would look like
1102 _____________________________________________________________________
1103 test_idx| job_idx|test_name |subdir |afe_job_id|status
1104 10 | 1000 |SERVER_JOB |---- |40 |GOOD
1105 11 | 1000 |dummy_Pass |NULL |40 |ABORT
1106 12 | 1000 |dummy_Fail.Fail |41-onwer/...|40 |FAIL
1107 13 | 1000 |dummy_Fail.Error |42-owner/...|40 |ERROR
1108 14 | 1000 |dummy_Pass.bluetooth|NULL |40 |TEST_NA
1109
1110 For a suite job, we only care about
1111 a) The test view for the suite job's SERVER_JOB
1112 b) The test views for real tests without a subdir. A NULL subdir
1113 indicates that a test didn't get executed.
1114 So, for the above example, we only keep test views whose test_idxs
1115 are 10, 11, 14.
1116
Fang Dengaeab6172014-05-07 17:17:04 -07001117 @returns: A list of TestView objects, representing relevant
1118 test views of the suite job.
Fang Dengdd20e452014-04-07 15:39:47 -07001119
1120 """
Fang Dengf8503532014-06-12 18:21:55 -07001121 suite_job = self._afe.get_jobs(id=self._suite_job_id)[0]
Fang Deng0454e632014-04-07 15:39:47 -07001122 views = self._tko.run(call='get_detailed_test_views',
1123 afe_job_id=self._suite_job_id)
Fang Dengdd20e452014-04-07 15:39:47 -07001124 relevant_views = []
1125 for v in views:
Simran Basi17ca77c2015-10-14 19:05:00 -07001126 v = TestView(v, suite_job, self._suite_name, self._build, self._user,
1127 solo_test_run=self._solo_test_run)
Fang Dengaeab6172014-05-07 17:17:04 -07001128 if v.is_relevant_suite_view():
Fang Dengdd20e452014-04-07 15:39:47 -07001129 relevant_views.append(v)
Fang Dengdd20e452014-04-07 15:39:47 -07001130 return relevant_views
1131
1132
Fang Dengaeab6172014-05-07 17:17:04 -07001133 def _compute_retry_count(self, view):
1134 """Return how many times the test has been retried.
1135
1136 @param view: A TestView instance.
1137 @returns: An int value indicating the retry count.
1138
1139 """
1140 old_job = view['job_keyvals'].get('retry_original_job_id')
1141 count = 0
1142 while old_job:
1143 count += 1
1144 views = self._tko.run(
1145 call='get_detailed_test_views', afe_job_id=old_job)
1146 old_job = (views[0]['job_keyvals'].get('retry_original_job_id')
1147 if views else None)
1148 return count
1149
1150
Simran Basi17ca77c2015-10-14 19:05:00 -07001151 def _fetch_test_views_of_child_jobs(self, jobs=None):
Fang Dengdd20e452014-04-07 15:39:47 -07001152 """Fetch test views of child jobs.
1153
Fang Dengaeab6172014-05-07 17:17:04 -07001154 @returns: A tuple (child_views, retry_counts)
1155 child_views is list of TestView objects, representing
1156 all valid views. retry_counts is a dictionary that maps
1157 test_idx to retry counts. It only stores retry
1158 counts that are greater than 0.
Fang Deng0454e632014-04-07 15:39:47 -07001159
Fang Dengdd20e452014-04-07 15:39:47 -07001160 """
Fang Dengdd20e452014-04-07 15:39:47 -07001161 child_views = []
Fang Dengaeab6172014-05-07 17:17:04 -07001162 retry_counts = {}
Simran Basi17ca77c2015-10-14 19:05:00 -07001163 child_jobs = jobs or self._afe.get_jobs(parent_job_id=self._suite_job_id)
MK Ryu977a9752014-10-21 11:58:09 -07001164 if child_jobs:
1165 self._num_child_jobs = len(child_jobs)
Fang Dengf8503532014-06-12 18:21:55 -07001166 for job in child_jobs:
Simran Basi01984f52015-10-12 15:36:45 -07001167 views = [TestView(v, job, self._suite_name, self._build, self._user)
Fang Dengaeab6172014-05-07 17:17:04 -07001168 for v in self._tko.run(
Fang Dengf8503532014-06-12 18:21:55 -07001169 call='get_detailed_test_views', afe_job_id=job.id,
Fang Dengaeab6172014-05-07 17:17:04 -07001170 invalid=0)]
Fang Dengdd20e452014-04-07 15:39:47 -07001171 contains_test_failure = any(
Fang Dengaeab6172014-05-07 17:17:04 -07001172 v.is_test() and v['status'] != 'GOOD' for v in views)
Fang Dengdd20e452014-04-07 15:39:47 -07001173 for v in views:
Fang Dengaeab6172014-05-07 17:17:04 -07001174 if (v.is_test() or
1175 v['status'] != 'GOOD' and not contains_test_failure):
1176 # For normal test view, just keep it.
1177 # For SERVER_JOB or CLIENT_JOB, only keep it
1178 # if it fails and no other test failure.
Fang Dengdd20e452014-04-07 15:39:47 -07001179 child_views.append(v)
Fang Dengaeab6172014-05-07 17:17:04 -07001180 retry_count = self._compute_retry_count(v)
1181 if retry_count > 0:
1182 retry_counts[v['test_idx']] = retry_count
1183 return child_views, retry_counts
Fang Dengdd20e452014-04-07 15:39:47 -07001184
1185
1186 def _generate_web_and_buildbot_links(self):
1187 """Generate web links and buildbot links."""
1188 # TODO(fdeng): If a job was aborted before it reaches Running
1189 # state, we read the test view from the suite job
1190 # and thus this method generates a link pointing to the
1191 # suite job's page for the aborted job. Need a fix.
1192 self._web_links = []
1193 self._buildbot_links = []
1194 # Bug info are stored in the suite job's keyvals.
Simran Basi17ca77c2015-10-14 19:05:00 -07001195 if self._solo_test_run:
1196 suite_job_keyvals = {}
1197 else:
1198 suite_job_keyvals = self._suite_views[0]['job_keyvals']
Fang Dengdd20e452014-04-07 15:39:47 -07001199 for v in self._test_views:
Fang Dengaeab6172014-05-07 17:17:04 -07001200 retry_count = self._retry_counts.get(v['test_idx'], 0)
1201 bug_info = v.get_bug_info(suite_job_keyvals)
1202 job_id_owner = v.get_job_id_owner_str()
Fang Dengdd20e452014-04-07 15:39:47 -07001203 link = LogLink(
Fang Dengaeab6172014-05-07 17:17:04 -07001204 anchor=v.get_testname().ljust(
Fang Dengdd20e452014-04-07 15:39:47 -07001205 self._max_testname_width),
1206 server=self._instance_server,
1207 job_string=job_id_owner,
Simran Basi7203d4e2015-02-03 15:50:18 -08001208 bug_info=bug_info, retry_count=retry_count,
1209 testname=v.get_testname())
Fang Dengdd20e452014-04-07 15:39:47 -07001210 self._web_links.append(link)
1211
Fang Dengaeab6172014-05-07 17:17:04 -07001212 if v.should_display_buildbot_link():
1213 link.reason = v.get_buildbot_link_reason()
Fang Dengdd20e452014-04-07 15:39:47 -07001214 self._buildbot_links.append(link)
1215
1216
1217 def _record_timings(self):
1218 """Record suite timings."""
1219 self.timings = Timings(self._suite_job_id)
1220 for v in self._test_views:
1221 self.timings.RecordTiming(v)
1222
1223
Fang Dengaeab6172014-05-07 17:17:04 -07001224 def _get_return_msg(self, code, tests_passed_after_retry):
1225 """Return the proper message for a given return code.
1226
1227 @param code: An enum value of RETURN_CODES
1228 @param test_passed_after_retry: True/False, indicating
1229 whether there are test(s) that have passed after retry.
1230
1231 @returns: A string, representing the message.
1232
1233 """
1234 if code == RETURN_CODES.INFRA_FAILURE:
Fang Deng95af42f2014-09-12 14:16:11 -07001235 return 'Suite job failed or provisioning failed.'
Fang Dengaeab6172014-05-07 17:17:04 -07001236 elif code == RETURN_CODES.SUITE_TIMEOUT:
1237 return ('Some test(s) was aborted before running,'
1238 ' suite must have timed out.')
1239 elif code == RETURN_CODES.WARNING:
1240 if tests_passed_after_retry:
1241 return 'Some test(s) passed after retry.'
1242 else:
1243 return 'Some test(s) raised a warning.'
1244 elif code == RETURN_CODES.ERROR:
1245 return 'Some test(s) failed.'
1246 else:
1247 return ''
1248
1249
Fang Dengdd20e452014-04-07 15:39:47 -07001250 def _compute_return_code(self):
1251 """Compute the exit code based on test results."""
1252 code = RETURN_CODES.OK
Fang Dengaeab6172014-05-07 17:17:04 -07001253 tests_passed_after_retry = False
1254
Fang Dengdd20e452014-04-07 15:39:47 -07001255 for v in self._test_views:
Fang Dengf8503532014-06-12 18:21:55 -07001256 # The order of checking each case is important.
Fang Dengaeab6172014-05-07 17:17:04 -07001257 if v.is_experimental():
Fang Deng5a43be62014-05-07 17:17:04 -07001258 continue
Shuqian Zhaoc085abb2016-02-24 11:27:26 -08001259 if v.get_testname() == TestView.SUITE_JOB:
Fang Dengf8503532014-06-12 18:21:55 -07001260 if v.is_aborted() and v.hit_timeout():
1261 current_code = RETURN_CODES.SUITE_TIMEOUT
1262 elif v.is_in_fail_status():
1263 current_code = RETURN_CODES.INFRA_FAILURE
1264 elif v['status'] == 'WARN':
1265 current_code = RETURN_CODES.WARNING
1266 else:
1267 current_code = RETURN_CODES.OK
Fang Deng5a43be62014-05-07 17:17:04 -07001268 else:
Fang Dengf8503532014-06-12 18:21:55 -07001269 if v.is_aborted() and v.is_relevant_suite_view():
1270 # The test was aborted before started
1271 # This gurantees that the suite has timed out.
1272 current_code = RETURN_CODES.SUITE_TIMEOUT
1273 elif v.is_aborted() and not v.hit_timeout():
1274 # The test was aborted, but
1275 # not due to a timeout. This is most likely
1276 # because the suite has timed out, but may
1277 # also because it was aborted by the user.
1278 # Since suite timing out is determined by checking
Shuqian Zhaoc085abb2016-02-24 11:27:26 -08001279 # the suite job view, we simply ignore this view here.
Fang Dengf8503532014-06-12 18:21:55 -07001280 current_code = RETURN_CODES.OK
1281 elif v.is_in_fail_status():
1282 # The test job failed.
Fang Deng95af42f2014-09-12 14:16:11 -07001283 if v.is_infra_test():
1284 current_code = RETURN_CODES.INFRA_FAILURE
1285 else:
1286 current_code = RETURN_CODES.ERROR
Fang Dengf8503532014-06-12 18:21:55 -07001287 elif v['status'] == 'WARN':
1288 # The test/suite job raised a wanrning.
1289 current_code = RETURN_CODES.WARNING
1290 elif v.is_retry():
1291 # The test is a passing retry.
1292 current_code = RETURN_CODES.WARNING
1293 tests_passed_after_retry = True
1294 else:
1295 current_code = RETURN_CODES.OK
1296 code = get_worse_code(code, current_code)
1297
Fang Dengdd20e452014-04-07 15:39:47 -07001298 self.return_code = code
Fang Dengaeab6172014-05-07 17:17:04 -07001299 self.return_message = self._get_return_msg(
1300 code, tests_passed_after_retry)
Fang Dengdd20e452014-04-07 15:39:47 -07001301
1302
1303 def output_results(self):
1304 """Output test results, timings and web links."""
1305 # Output test results
1306 for v in self._test_views:
Fang Dengaeab6172014-05-07 17:17:04 -07001307 display_name = v.get_testname().ljust(self._max_testname_width)
Fang Dengdd20e452014-04-07 15:39:47 -07001308 logging.info('%s%s', display_name,
1309 get_pretty_status(v['status']))
1310 if v['status'] != 'GOOD':
Fang Dengaeab6172014-05-07 17:17:04 -07001311 logging.info('%s %s: %s', display_name, v['status'],
Fang Dengdd20e452014-04-07 15:39:47 -07001312 v['reason'])
Fang Dengaeab6172014-05-07 17:17:04 -07001313 if v.is_retry():
1314 retry_count = self._retry_counts.get(v['test_idx'], 0)
1315 logging.info('%s retry_count: %s',
1316 display_name, retry_count)
Fang Dengdd20e452014-04-07 15:39:47 -07001317 # Output suite timings
1318 logging.info(self.timings)
1319 # Output links to test logs
1320 logging.info('\nLinks to test logs:')
1321 for link in self._web_links:
1322 logging.info(link.GenerateTextLink())
Fang Deng5a43be62014-05-07 17:17:04 -07001323 logging.info('\n')
Fang Dengdd20e452014-04-07 15:39:47 -07001324
1325
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001326 def get_results_dict(self):
1327 """Write test results, timings and web links into a dict.
1328
1329 @returns: A dict of results in the format like:
1330 {
1331 'tests': {
1332 'test_1': {'status': 'PASSED', 'attributes': [1,2], ...}
1333 'test_2': {'status': 'FAILED', 'attributes': [1],...}
1334 }
1335 'suite_timings': {
1336 'download_start': '1998-07-17 00:00:00',
1337 'payload_download_end': '1998-07-17 00:00:05',
1338 ...
1339 }
1340 }
1341 """
1342 output_dict = {}
1343 tests_dict = output_dict.setdefault('tests', {})
1344 for v in self._test_views:
1345 test_name = v.get_testname()
1346 test_info = tests_dict.setdefault(test_name, {})
1347 test_info.update({
1348 'status': v['status'],
1349 'attributes': v.get_control_file_attributes() or list(),
1350 'reason': v['reason'],
1351 'retry_count': self._retry_counts.get(v['test_idx'], 0),
1352 })
1353
1354 # Write the links to test logs into the |tests_dict| of |output_dict|.
1355 # For test whose status is not 'GOOD', the link is also buildbot_link.
1356 for link in self._web_links:
1357 test_name = link.anchor.strip()
1358 test_info = tests_dict.get(test_name)
1359 if test_info:
1360 test_info['link_to_logs'] = link.url
1361 # Write the wmatrix link into the dict.
1362 if link in self._buildbot_links and link.testname:
1363 test_info['wmatrix_link'] = WMATRIX_RETRY_URL % link.testname
1364 # Write the bug url into the dict.
1365 if link.bug_id:
1366 test_info['bug_url'] = link.get_bug_link(link.bug_id)
1367
1368 # Write the suite timings into |output_dict|
1369 time_dict = output_dict.setdefault('suite_timings', {})
1370 time_dict.update({
1371 'download_start' : str(self.timings.download_start_time),
1372 'payload_download_end' : str(self.timings.payload_end_time),
1373 'suite_start' : str(self.timings.suite_start_time),
1374 'artifact_download_end' : str(self.timings.artifact_end_time),
1375 'tests_start' : str(self.timings.tests_start_time),
1376 'tests_end' : str(self.timings.tests_end_time),
1377 })
1378
1379 output_dict['suite_job_id'] = self._suite_job_id
1380
1381 return output_dict
1382
1383
Fang Dengdd20e452014-04-07 15:39:47 -07001384 def output_buildbot_links(self):
1385 """Output buildbot links."""
1386 for link in self._buildbot_links:
Ningning Xiabd911bd2016-04-19 14:06:03 -07001387 for generate_link in link.GenerateBuildbotLinks():
1388 logging.info(generate_link)
Simran Basi7203d4e2015-02-03 15:50:18 -08001389 wmatrix_link = link.GenerateWmatrixRetryLink()
1390 if wmatrix_link:
1391 logging.info(wmatrix_link)
Fang Dengdd20e452014-04-07 15:39:47 -07001392
1393
1394 def run(self):
1395 """Collect test results.
1396
1397 This method goes through the following steps:
1398 Fetch relevent test views of the suite job.
1399 Fetch test views of child jobs
1400 Check whether the suite was aborted.
Fang Dengaeab6172014-05-07 17:17:04 -07001401 Generate links.
Fang Dengdd20e452014-04-07 15:39:47 -07001402 Calculate suite timings.
1403 Compute return code based on the test result.
1404
1405 """
Simran Basi17ca77c2015-10-14 19:05:00 -07001406 if self._solo_test_run:
1407 self._test_views, self.retry_count = (
1408 self._fetch_test_views_of_child_jobs(
1409 jobs=self._afe.get_jobs(id=self._suite_job_id)))
1410 else:
1411 self._suite_views = self._fetch_relevant_test_views_of_suite()
1412 self._child_views, self._retry_counts = (
1413 self._fetch_test_views_of_child_jobs())
1414 self._test_views = self._suite_views + self._child_views
Fang Dengdd20e452014-04-07 15:39:47 -07001415 # For hostless job in Starting status, there is no test view associated.
1416 # This can happen when a suite job in Starting status is aborted. When
1417 # the scheduler hits some limit, e.g., max_hostless_jobs_per_drone,
1418 # max_jobs_started_per_cycle, a suite job can stays in Starting status.
1419 if not self._test_views:
Fang Deng5a43be62014-05-07 17:17:04 -07001420 self.return_code = RETURN_CODES.INFRA_FAILURE
Fang Dengdd20e452014-04-07 15:39:47 -07001421 self.return_message = 'No test view was found.'
1422 return
1423 self.is_aborted = any([view['job_keyvals'].get('aborted_by')
1424 for view in self._suite_views])
Fang Dengaeab6172014-05-07 17:17:04 -07001425 self._max_testname_width = max(
1426 [len(v.get_testname()) for v in self._test_views]) + 3
Fang Dengdd20e452014-04-07 15:39:47 -07001427 self._generate_web_and_buildbot_links()
1428 self._record_timings()
1429 self._compute_return_code()
1430
1431
MK Ryu977a9752014-10-21 11:58:09 -07001432 def gather_timing_stats(self):
1433 """Collect timing related statistics."""
1434 # Send timings to statsd.
1435 self.timings.SendResultsToStatsd(
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -07001436 self._original_suite_name, self._build, self._board)
MK Ryu977a9752014-10-21 11:58:09 -07001437
1438 # Record suite runtime in metadata db.
Prathmesh Prabhua3713a02015-03-11 13:50:55 -07001439 # Some failure modes can leave times unassigned, report sentinel value
1440 # in that case.
1441 runtime_in_secs = -1
1442 if (self.timings.tests_end_time is not None and
1443 self.timings.suite_start_time is not None):
Dan Shi0723bf52015-06-24 10:52:38 -07001444 runtime_in_secs = (self.timings.tests_end_time -
1445 self.timings.suite_start_time).total_seconds()
Prathmesh Prabhua3713a02015-03-11 13:50:55 -07001446
MK Ryu977a9752014-10-21 11:58:09 -07001447 job_overhead.record_suite_runtime(self._suite_job_id, self._suite_name,
1448 self._board, self._build, self._num_child_jobs, runtime_in_secs)
1449
1450
Prashanth B6285f6a2014-05-08 18:01:27 -07001451@retry.retry(error.StageControlFileFailure, timeout_min=10)
1452def create_suite(afe, options):
1453 """Create a suite with retries.
1454
1455 @param afe: The afe object to insert the new suite job into.
1456 @param options: The options to use in creating the suite.
1457
1458 @return: The afe_job_id of the new suite job.
1459 """
Dan Shi36cfd832014-10-10 13:38:51 -07001460 builds = {}
1461 if options.build:
Simran Basi5ace6f22016-01-06 17:30:44 -08001462 if re.match(ANDROID_BUILD_REGEX, options.build):
1463 builds[provision.ANDROID_BUILD_VERSION_PREFIX] = options.build
1464 else:
1465 builds[provision.CROS_VERSION_PREFIX] = options.build
Dan Shi0723bf52015-06-24 10:52:38 -07001466 if options.firmware_rw_build:
1467 builds[provision.FW_RW_VERSION_PREFIX] = options.firmware_rw_build
Dan Shi36cfd832014-10-10 13:38:51 -07001468 if options.firmware_ro_build:
1469 builds[provision.FW_RO_VERSION_PREFIX] = options.firmware_ro_build
Prashanth B6285f6a2014-05-08 18:01:27 -07001470 wait = options.no_wait == 'False'
1471 file_bugs = options.file_bugs == 'True'
1472 retry = options.retry == 'True'
Simran Basi1e10e922015-04-16 15:09:56 -07001473 offload_failures_only = options.offload_failures_only == 'True'
Prashanth B6285f6a2014-05-08 18:01:27 -07001474 try:
1475 priority = int(options.priority)
1476 except ValueError:
1477 try:
1478 priority = priorities.Priority.get_value(options.priority)
1479 except AttributeError:
1480 print 'Unknown priority level %s. Try one of %s.' % (
1481 options.priority, ', '.join(priorities.Priority.names))
1482 raise
1483 logging.info('%s Submitted create_suite_job rpc',
1484 diagnosis_utils.JobTimer.format_time(datetime.now()))
Dan Shi059261a2016-02-22 12:06:37 -08001485 # Adjust timeout based on the delay_minutes setting.
1486 timeout_mins = options.timeout_mins + options.delay_minutes
1487 max_runtime_mins = options.max_runtime_mins + options.delay_minutes
Prashanth B6285f6a2014-05-08 18:01:27 -07001488 return afe.run('create_suite_job', name=options.name,
1489 board=options.board, build=options.build,
Dan Shi36cfd832014-10-10 13:38:51 -07001490 builds=builds, test_source_build=options.test_source_build,
Prashanth B6285f6a2014-05-08 18:01:27 -07001491 check_hosts=wait, pool=options.pool,
1492 num=options.num,
1493 file_bugs=file_bugs, priority=priority,
1494 suite_args=options.suite_args,
1495 wait_for_results=wait,
Dan Shi059261a2016-02-22 12:06:37 -08001496 timeout_mins=timeout_mins, max_runtime_mins=max_runtime_mins,
Fang Deng443f1952015-01-02 14:51:49 -08001497 job_retry=retry, max_retries=options.max_retries,
Simran Basi1e10e922015-04-16 15:09:56 -07001498 suite_min_duts=options.suite_min_duts,
Simran Basi5ace6f22016-01-06 17:30:44 -08001499 offload_failures_only=offload_failures_only,
Dan Shi059261a2016-02-22 12:06:37 -08001500 run_prod_code=options.run_prod_code,
1501 delay_minutes=options.delay_minutes)
Prashanth B6285f6a2014-05-08 18:01:27 -07001502
1503
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001504def main_without_exception_handling(options):
Aviv Keshet1480c4a2013-03-21 16:38:31 -07001505 """
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001506 run_suite script without exception handling.
Shuqian Zhaod2351072015-08-06 01:48:23 +00001507
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001508 @param options: The parsed options.
1509
1510 @returns: A tuple contains the return_code of run_suite and the dictionary
1511 of the output.
1512
1513 """
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -07001514 # If indicate to use the new style suite control file, convert the args
1515 if options.use_suite_attr:
1516 options = change_options_for_suite_attr(options)
1517
Chris Masone3a850642012-07-11 11:11:18 -07001518 log_name = 'run_suite-default.log'
Fang Deng6865aab2015-02-20 14:49:47 -08001519 if options.build:
Chris Masone3a850642012-07-11 11:11:18 -07001520 # convert build name from containing / to containing only _
1521 log_name = 'run_suite-%s.log' % options.build.replace('/', '_')
1522 log_dir = os.path.join(common.autotest_dir, 'logs')
1523 if os.path.exists(log_dir):
1524 log_name = os.path.join(log_dir, log_name)
Alex Miller88762a82013-09-04 15:41:28 -07001525
MK Ryu83184352014-12-10 14:59:40 -08001526 utils.setup_logging(logfile=log_name)
Alex Miller88762a82013-09-04 15:41:28 -07001527
Fang Deng6197da32014-09-25 10:18:48 -07001528 if not options.bypass_labstatus:
1529 utils.check_lab_status(options.build)
Prashanth Balasubramanian673016d2014-11-04 10:40:48 -08001530 instance_server = (options.web if options.web else
1531 instance_for_pool(options.pool))
Alex Millerc7a59522013-10-30 15:18:57 -07001532 afe = frontend_wrappers.RetryingAFE(server=instance_server,
Simran Basi25effe32013-11-26 13:02:11 -08001533 timeout_min=options.afe_timeout_mins,
Chris Masone8ac66712012-02-15 14:21:02 -08001534 delay_sec=options.delay_sec)
Alex Millerc7a59522013-10-30 15:18:57 -07001535 logging.info('Autotest instance: %s', instance_server)
Chris Masone359c0fd2012-03-13 15:18:59 -07001536
Dan Shi20952c12014-05-14 17:07:38 -07001537 rpc_helper = diagnosis_utils.RPCHelper(afe)
Fang Deng6865aab2015-02-20 14:49:47 -08001538 is_real_time = True
Chris Masone986459e2012-04-11 11:36:48 -07001539 if options.mock_job_id:
1540 job_id = int(options.mock_job_id)
Fang Deng6865aab2015-02-20 14:49:47 -08001541 existing_job = afe.get_jobs(id=job_id, finished=True)
1542 if existing_job:
1543 is_real_time = False
1544 else:
1545 existing_job = afe.get_jobs(id=job_id)
1546 if existing_job:
1547 job_created_on = time_utils.date_string_to_epoch_time(
1548 existing_job[0].created_on)
1549 else:
1550 raise utils.TestLabException('Failed to retrieve job: %d' % job_id)
Chris Masone986459e2012-04-11 11:36:48 -07001551 else:
Fang Deng5a43be62014-05-07 17:17:04 -07001552 try:
Fang Deng6865aab2015-02-20 14:49:47 -08001553 rpc_helper.check_dut_availability(options.board, options.pool,
Ningning Xiaf2c206c2016-04-13 14:15:51 -07001554 options.minimum_duts,
1555 options.skip_duts_check)
Prashanth B6285f6a2014-05-08 18:01:27 -07001556 job_id = create_suite(afe, options)
Fang Deng6865aab2015-02-20 14:49:47 -08001557 job_created_on = time.time()
1558 except diagnosis_utils.NotEnoughDutsError:
1559 logging.info(GetBuildbotStepLink(
1560 'Pool Health Bug', LogLink.get_bug_link(rpc_helper.bug)))
1561 raise
Fang Deng5a43be62014-05-07 17:17:04 -07001562 except (error.CrosDynamicSuiteException,
1563 error.RPCException, proxy.JSONRPCException) as e:
1564 logging.warning('Error Message: %s', e)
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001565 return (RETURN_CODES.INFRA_FAILURE, {'return_message': e})
Prashanth B6285f6a2014-05-08 18:01:27 -07001566 except AttributeError:
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001567 return (RETURN_CODES.INVALID_OPTIONS, {})
Fang Deng5a43be62014-05-07 17:17:04 -07001568
Prashanth B923ca262014-03-14 12:36:29 -07001569 job_timer = diagnosis_utils.JobTimer(
Fang Deng6865aab2015-02-20 14:49:47 -08001570 job_created_on, float(options.timeout_mins))
Aviv Keshet9afee5e2014-10-09 16:33:09 -07001571 job_url = reporting_utils.link_job(job_id,
1572 instance_server=instance_server)
Prashanth B923ca262014-03-14 12:36:29 -07001573 logging.info('%s Created suite job: %s',
1574 job_timer.format_time(job_timer.job_created_time),
Aviv Keshet9afee5e2014-10-09 16:33:09 -07001575 job_url)
Aviv Keshetdb321de2015-04-10 19:09:58 -07001576 # TODO(akeshet): Move this link-printing to chromite.
Aviv Keshet20bae472016-03-15 12:28:18 -07001577 logging.info(GetBuildbotStepLink('Link to suite', job_url))
Aviv Keshetdb321de2015-04-10 19:09:58 -07001578
1579 if options.create_and_return:
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001580 msg = '--create_and_return was specified, terminating now.'
1581 logging.info(msg)
1582 return (RETURN_CODES.OK, {'return_message':msg})
Aviv Keshetdb321de2015-04-10 19:09:58 -07001583
Alex Millerc7a59522013-10-30 15:18:57 -07001584 TKO = frontend_wrappers.RetryingTKO(server=instance_server,
Simran Basi25effe32013-11-26 13:02:11 -08001585 timeout_min=options.afe_timeout_mins,
Chris Masone8ac66712012-02-15 14:21:02 -08001586 delay_sec=options.delay_sec)
Aviv Keshet1480c4a2013-03-21 16:38:31 -07001587 code = RETURN_CODES.OK
Prashanth B6285f6a2014-05-08 18:01:27 -07001588 wait = options.no_wait == 'False'
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001589 output_dict = {}
J. Richard Barnette712eb402013-08-13 18:03:00 -07001590 if wait:
1591 while not afe.get_jobs(id=job_id, finished=True):
Prashanth B923ca262014-03-14 12:36:29 -07001592 # Note that this call logs output, preventing buildbot's
1593 # 9000 second silent timeout from kicking in. Let there be no
1594 # doubt, this is a hack. The timeout is from upstream buildbot and
1595 # this is the easiest work around.
1596 if job_timer.first_past_halftime():
MK Ryu4790eec2014-07-31 11:39:02 -07001597 rpc_helper.diagnose_job(job_id, instance_server)
Prashanth Ba7be2072014-07-15 15:03:21 -07001598 if job_timer.debug_output_timer.poll():
1599 logging.info('The suite job has another %s till timeout.',
Prashanth B923ca262014-03-14 12:36:29 -07001600 job_timer.timeout_hours - job_timer.elapsed_time())
Alex Miller764227d2013-11-15 10:28:56 -08001601 time.sleep(10)
Fang Dengf8503532014-06-12 18:21:55 -07001602 # For most cases, ResultCollector should be able to determine whether
1603 # a suite has timed out by checking information in the test view.
1604 # However, occationally tko parser may fail on parsing the
1605 # job_finished time from the job's keyval file. So we add another
1606 # layer of timeout check in run_suite. We do the check right after
1607 # the suite finishes to make it as accurate as possible.
1608 # There is a minor race condition here where we might have aborted
1609 # for some reason other than a timeout, and the job_timer thinks
1610 # it's a timeout because of the jitter in waiting for results.
1611 # The consequence would be that run_suite exits with code
1612 # SUITE_TIMEOUT while it should have returned INFRA_FAILURE
1613 # instead, which should happen very rarely.
1614 # Note the timeout will have no sense when using -m option.
1615 is_suite_timeout = job_timer.is_suite_timeout()
J. Richard Barnette712eb402013-08-13 18:03:00 -07001616
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -07001617 # Extract the original suite name to record timing.
1618 original_suite_name = get_original_suite_name(options.name,
1619 options.suite_args)
Fang Dengdd20e452014-04-07 15:39:47 -07001620 # Start collecting test results.
1621 collector = ResultCollector(instance_server=instance_server,
1622 afe=afe, tko=TKO, build=options.build,
MK Ryu977a9752014-10-21 11:58:09 -07001623 board=options.board,
Fang Dengdd20e452014-04-07 15:39:47 -07001624 suite_name=options.name,
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -07001625 suite_job_id=job_id,
1626 original_suite_name=original_suite_name)
Fang Dengdd20e452014-04-07 15:39:47 -07001627 collector.run()
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001628 # Dump test outputs into json.
1629 output_dict = collector.get_results_dict()
1630 output_dict['autotest_instance'] = instance_server
1631 if not options.json_dump:
1632 collector.output_results()
Fang Dengdd20e452014-04-07 15:39:47 -07001633 code = collector.return_code
Fang Deng5a43be62014-05-07 17:17:04 -07001634 return_message = collector.return_message
Fang Deng6865aab2015-02-20 14:49:47 -08001635 if is_real_time:
MK Ryu977a9752014-10-21 11:58:09 -07001636 # Do not record stats if the suite was aborted (either by a user
1637 # or through the golo rpc).
Fang Deng5a43be62014-05-07 17:17:04 -07001638 # Also do not record stats if is_aborted is None, indicating
1639 # aborting status is unknown yet.
1640 if collector.is_aborted == False:
MK Ryu977a9752014-10-21 11:58:09 -07001641 collector.gather_timing_stats()
Fang Deng6865aab2015-02-20 14:49:47 -08001642
Fang Deng5a43be62014-05-07 17:17:04 -07001643 if collector.is_aborted == True and is_suite_timeout:
1644 # There are two possible cases when a suite times out.
1645 # 1. the suite job was aborted due to timing out
1646 # 2. the suite job succeeded, but some child jobs
1647 # were already aborted before the suite job exited.
1648 # The case 2 was handled by ResultCollector,
1649 # here we handle case 1.
1650 old_code = code
Fang Dengaeab6172014-05-07 17:17:04 -07001651 code = get_worse_code(
1652 code, RETURN_CODES.SUITE_TIMEOUT)
Fang Deng5a43be62014-05-07 17:17:04 -07001653 if old_code != code:
Fang Dengaeab6172014-05-07 17:17:04 -07001654 return_message = 'Suite job timed out.'
Fang Deng5a43be62014-05-07 17:17:04 -07001655 logging.info('Upgrade return code from %s to %s '
1656 'because suite job has timed out.',
1657 RETURN_CODES.get_string(old_code),
1658 RETURN_CODES.get_string(code))
Fang Deng5a43be62014-05-07 17:17:04 -07001659 if is_suite_timeout:
1660 logging.info('\nAttempting to diagnose pool: %s', options.pool)
Fang Deng5a43be62014-05-07 17:17:04 -07001661 try:
1662 # Add some jitter to make up for any latency in
1663 # aborting the suite or checking for results.
1664 cutoff = (job_timer.timeout_hours +
1665 datetime_base.timedelta(hours=0.3))
1666 rpc_helper.diagnose_pool(
1667 options.board, options.pool, cutoff)
1668 except proxy.JSONRPCException as e:
1669 logging.warning('Unable to diagnose suite abort.')
1670
1671 # And output return message.
Fang Deng5a43be62014-05-07 17:17:04 -07001672 if return_message:
1673 logging.info('Reason: %s', return_message)
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001674 output_dict['return_message'] = return_message
Prashanth B923ca262014-03-14 12:36:29 -07001675
Fang Dengdd20e452014-04-07 15:39:47 -07001676 logging.info('\nOutput below this line is for buildbot consumption:')
1677 collector.output_buildbot_links()
Chris Masoned5939fe2012-03-13 10:11:06 -07001678 else:
Scott Zawalski94457b72012-07-02 18:45:07 -04001679 logging.info('Created suite job: %r', job_id)
Alex Millera05498f2013-11-01 16:16:21 -07001680 link = LogLink(options.name, instance_server,
1681 '%s-%s' % (job_id, getpass.getuser()))
Ningning Xiabd911bd2016-04-19 14:06:03 -07001682 for generate_link in link.GenerateBuildbotLinks():
1683 logging.info(generate_link)
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001684 output_dict['return_message'] = '--no_wait specified; Exiting.'
Scott Zawalski94457b72012-07-02 18:45:07 -04001685 logging.info('--no_wait specified; Exiting.')
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001686 return (code, output_dict)
Chris Masone24b80f12012-02-14 14:18:01 -08001687
Fang Dengdd20e452014-04-07 15:39:47 -07001688
Fang Dengfb4a9492014-09-18 17:52:06 -07001689def main():
1690 """Entry point."""
Simran Basi9f364a62015-12-07 14:15:19 -08001691 utils.verify_not_root_user()
Fang Deng6197da32014-09-25 10:18:48 -07001692 code = RETURN_CODES.OK
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001693 output_dict = {}
1694
Fang Dengfb4a9492014-09-18 17:52:06 -07001695 try:
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001696 parser, options, args = parse_options()
1697 # Silence the log when dumping outputs into json
1698 if options.json_dump:
1699 logging.disable(logging.CRITICAL)
1700
1701 if not verify_options_and_args(options, args):
1702 parser.print_help()
1703 code = RETURN_CODES.INVALID_OPTIONS
1704 else:
1705 (code, output_dict) = main_without_exception_handling(options)
Shuqian Zhaoade6e7d2015-12-07 18:01:11 -08001706 except diagnosis_utils.BoardNotAvailableError as e:
1707 output_dict['return_message'] = 'Skipping testing: %s' % e.message
1708 code = RETURN_CODES.BOARD_NOT_AVAILABLE
1709 logging.info(output_dict['return_message'])
1710 except utils.TestLabException as e:
1711 output_dict['return_message'] = 'TestLabException: %s' % e
1712 code = RETURN_CODES.INFRA_FAILURE
1713 logging.exception(output_dict['return_message'])
Fang Dengfb4a9492014-09-18 17:52:06 -07001714 except Exception as e:
Shuqian Zhaoade6e7d2015-12-07 18:01:11 -08001715 output_dict['return_message'] = 'Unhandled run_suite exception: %s' % e
1716 code = RETURN_CODES.INFRA_FAILURE
1717 logging.exception(output_dict['return_message'])
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001718
1719 # Dump test outputs into json.
1720 output_dict['return_code'] = code
1721 output_json = json.dumps(output_dict, sort_keys=True)
1722 if options.json_dump:
Shuqian Zhao74ca35d2015-11-25 14:33:50 -08001723 output_json_marked = '#JSON_START#%s#JSON_END#' % output_json.strip()
1724 sys.stdout.write(output_json_marked)
Fang Deng6197da32014-09-25 10:18:48 -07001725
1726 logging.info('Will return from run_suite with status: %s',
1727 RETURN_CODES.get_string(code))
Gabe Black1e1c41b2015-02-04 23:55:15 -08001728 autotest_stats.Counter('run_suite.%s' %
1729 RETURN_CODES.get_string(code)).increment()
Fang Deng6197da32014-09-25 10:18:48 -07001730 return code
Fang Dengfb4a9492014-09-18 17:52:06 -07001731
1732
Chris Masone24b80f12012-02-14 14:18:01 -08001733if __name__ == "__main__":
1734 sys.exit(main())