blob: b7cc906badbb84db8d659a6c2a1d474bf801518b [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
Allen Li93f4db52016-09-14 14:44:59 -070041import argparse
42import ast
Allen Li340414e2016-08-16 14:19:08 -070043from collections import namedtuple
Chris Masonecfa7efc2012-09-06 16:00:07 -070044from datetime import datetime
Allen Li93f4db52016-09-14 14:44:59 -070045from datetime import timedelta
46import getpass
47import json
48import logging
49import os
50import re
51import sys
52import time
Chris Masonecfa7efc2012-09-06 16:00:07 -070053
Chris Masone24b80f12012-02-14 14:18:01 -080054import common
Allen Lie082ced2016-09-14 15:19:20 -070055from chromite.lib import buildbot_annotations as annotations
56
Shuqian Zhao2fecacd2015-08-05 22:56:30 -070057from autotest_lib.client.common_lib import control_data
Fang Deng5a43be62014-05-07 17:17:04 -070058from autotest_lib.client.common_lib import error
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080059from autotest_lib.client.common_lib import global_config, enum
60from autotest_lib.client.common_lib import priorities
Dan Shidfea3682014-08-10 23:38:40 -070061from autotest_lib.client.common_lib import time_utils
Prashanth B6285f6a2014-05-08 18:01:27 -070062from autotest_lib.client.common_lib.cros import retry
Prashanth B923ca262014-03-14 12:36:29 -070063from autotest_lib.frontend.afe.json_rpc import proxy
xixuanae791b12017-06-29 15:40:19 -070064from autotest_lib.server import site_utils
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080065from autotest_lib.server import utils
Dan Shi36cfd832014-10-10 13:38:51 -070066from autotest_lib.server.cros import provision
Chris Masone44e4d6c2012-08-15 14:25:53 -070067from autotest_lib.server.cros.dynamic_suite import constants
Chris Masoneb4935552012-08-14 12:05:54 -070068from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Allen Li6a612392016-08-18 12:09:32 -070069from autotest_lib.server.cros.dynamic_suite import reporting
Prashanth B923ca262014-03-14 12:36:29 -070070from autotest_lib.server.cros.dynamic_suite import reporting_utils
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -070071from autotest_lib.server.cros.dynamic_suite import tools
Prashanth B923ca262014-03-14 12:36:29 -070072from autotest_lib.site_utils import diagnosis_utils
MK Ryu977a9752014-10-21 11:58:09 -070073from autotest_lib.site_utils import job_overhead
74
Chris Masone1120cdf2012-02-27 17:35:07 -080075CONFIG = global_config.global_config
76
Allen Lidc2c69a2016-09-14 19:05:47 -070077_DEFAULT_AUTOTEST_INSTANCE = CONFIG.get_config_value(
78 'SERVER', 'hostname', type=str)
79_URL_PATTERN = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str)
Simran Basi7203d4e2015-02-03 15:50:18 -080080
Simran Basi22aa9fe2012-12-07 16:37:09 -080081# Return code that will be sent back to autotest_rpc_server.py
Fang Deng5a43be62014-05-07 17:17:04 -070082RETURN_CODES = enum.Enum(
Fang Dengfb4a9492014-09-18 17:52:06 -070083 'OK', 'ERROR', 'WARNING', 'INFRA_FAILURE', 'SUITE_TIMEOUT',
Simran Basiba90ec82017-02-17 02:02:50 +000084 'BOARD_NOT_AVAILABLE', 'INVALID_OPTIONS')
Fang Deng5a43be62014-05-07 17:17:04 -070085# The severity of return code. If multiple codes
86# apply, the script should always return the severest one.
87# E.g. if we have a test failure and the suite also timed out,
88# we should return 'ERROR'.
89SEVERITY = {RETURN_CODES.OK: 0,
90 RETURN_CODES.WARNING: 1,
Fang Deng95af42f2014-09-12 14:16:11 -070091 RETURN_CODES.SUITE_TIMEOUT: 2,
92 RETURN_CODES.INFRA_FAILURE: 3,
Fang Deng6197da32014-09-25 10:18:48 -070093 RETURN_CODES.ERROR: 4}
Fang Deng5a43be62014-05-07 17:17:04 -070094
xixuanae791b12017-06-29 15:40:19 -070095# Minimum RPC timeout setting for calls expected to take long time, e.g.,
96# create_suite_job. If default socket time (socket.getdefaulttimeout()) is
97# None or greater than this value, the default will be used.
98# The value here is set to be the same as the timeout for the RetryingAFE object
99# so long running RPCs can wait long enough before being aborted.
100_MIN_RPC_TIMEOUT = 600
101
102# Number of days back to search for existing job.
103_SEARCH_JOB_MAX_DAYS = 14
104
Fang Deng5a43be62014-05-07 17:17:04 -0700105
106def get_worse_code(code1, code2):
Fang Dengaeab6172014-05-07 17:17:04 -0700107 """Compare the severity of two codes and return the worse code.
Fang Deng5a43be62014-05-07 17:17:04 -0700108
109 @param code1: An enum value of RETURN_CODES
110 @param code2: An enum value of RETURN_CODES
111
Fang Dengaeab6172014-05-07 17:17:04 -0700112 @returns: the more severe one between code1 and code2.
Fang Deng5a43be62014-05-07 17:17:04 -0700113
114 """
Fang Dengaeab6172014-05-07 17:17:04 -0700115 return code1 if SEVERITY[code1] >= SEVERITY[code2] else code2
Simran Basi22aa9fe2012-12-07 16:37:09 -0800116
Chris Masonedfa0beba2012-03-19 11:41:47 -0700117
Allen Li93f4db52016-09-14 14:44:59 -0700118def bool_str(x):
119 """Boolean string type for option arguments.
120
121 @param x: string representation of boolean value.
122
123 """
124 if x == 'True':
125 return True
126 elif x == 'False':
127 return False
128 else:
129 raise argparse.ArgumentTypeError(
130 '%s is not one of True or False' % (x,))
131
132
Allen Li603728a2016-12-08 13:58:11 -0800133def _get_priority_value(x):
134 """Convert a priority representation to its int value.
135
136 Priorities can be described either by an int value (possibly as a string)
137 or a name string. This function coerces both forms to an int value.
138
139 This function is intended for casting command line arguments during
140 parsing.
141
142 @param x: priority value as an int, int string, or name string
143
144 @returns: int value of priority
145 """
146 try:
147 return int(x)
148 except ValueError:
149 try:
150 return priorities.Priority.get_value(x)
151 except AttributeError:
152 raise argparse.ArgumentTypeError(
153 'Unknown priority level %s. Try one of %s.'
154 % (x, ', '.join(priorities.Priority.names)))
155
156
Allen Li93f4db52016-09-14 14:44:59 -0700157def make_parser():
158 """Make ArgumentParser instance for run_suite.py."""
159 parser = argparse.ArgumentParser(
160 usage="%(prog)s [options]")
161 parser.add_argument("-b", "--board", dest="board")
162 parser.add_argument("-i", "--build", dest="build")
163 parser.add_argument(
164 "-w", "--web", dest="web", default=None,
165 help="Address of a webserver to receive suite requests.")
166 parser.add_argument(
167 '--firmware_rw_build', dest='firmware_rw_build', default=None,
168 help='Firmware build to be installed in dut RW firmware.')
169 parser.add_argument(
170 '--firmware_ro_build', dest='firmware_ro_build', default=None,
171 help='Firmware build to be installed in dut RO firmware.')
172 parser.add_argument(
173 '--test_source_build', dest='test_source_build', default=None,
174 help=('Build that contains the test code, '
175 'e.g., it can be the value of `--build`, '
176 '`--firmware_rw_build` or `--firmware_ro_build` '
177 'arguments. Default is None, that is, use the test '
178 'code from `--build` (CrOS image)'))
Chris Masone359c0fd2012-03-13 15:18:59 -0700179 # This should just be a boolean flag, but the autotest "proxy" code
180 # can't handle flags that don't take arguments.
Allen Li93f4db52016-09-14 14:44:59 -0700181 parser.add_argument(
182 "-n", "--no_wait", dest="no_wait", default=False, type=bool_str,
183 help='Must pass "True" or "False" if used.')
Alex Miller0032e932013-10-23 12:52:58 -0700184 # If you really want no pool, --pool="" will do it. USE WITH CARE.
Allen Li93f4db52016-09-14 14:44:59 -0700185 parser.add_argument("-p", "--pool", dest="pool", default="suites")
186 parser.add_argument("-s", "--suite_name", dest="name")
187 parser.add_argument("-a", "--afe_timeout_mins", type=int,
188 dest="afe_timeout_mins", default=30)
189 parser.add_argument("-t", "--timeout_mins", type=int,
190 dest="timeout_mins", default=1440)
191 parser.add_argument("-x", "--max_runtime_mins", type=int,
192 dest="max_runtime_mins", default=1440)
193 parser.add_argument("-d", "--delay_sec", type=int,
194 dest="delay_sec", default=10)
195 parser.add_argument("-m", "--mock_job_id", dest="mock_job_id",
196 help="Attach to existing job id for already running "
197 "suite, and creates report.")
Aviv Keshetdb321de2015-04-10 19:09:58 -0700198 # NOTE(akeshet): This looks similar to --no_wait, but behaves differently.
199 # --no_wait is passed in to the suite rpc itself and affects the suite,
200 # while this does not.
Allen Li93f4db52016-09-14 14:44:59 -0700201 parser.add_argument("-c", "--create_and_return", dest="create_and_return",
202 action="store_true",
203 help="Create the suite and print the job id, then "
204 "finish immediately.")
205 parser.add_argument("-u", "--num", dest="num", type=int, default=None,
206 help="Run on at most NUM machines.")
Alex Millerf43d0eb2012-10-01 13:43:13 -0700207 # Same boolean flag issue applies here.
Allen Li93f4db52016-09-14 14:44:59 -0700208 parser.add_argument(
209 "-f", "--file_bugs", dest="file_bugs", default=False, type=bool_str,
210 help=('File bugs on test failures. Must pass "True" or '
211 '"False" if used.'))
212 parser.add_argument("-l", "--bypass_labstatus", dest="bypass_labstatus",
213 action="store_true", help='Bypass lab status check.')
Alex Miller88762a82013-09-04 15:41:28 -0700214 # We allow either a number or a string for the priority. This way, if you
215 # know what you're doing, one can specify a custom priority level between
216 # other levels.
Allen Li93f4db52016-09-14 14:44:59 -0700217 parser.add_argument("-r", "--priority", dest="priority",
Allen Li603728a2016-12-08 13:58:11 -0800218 type=_get_priority_value,
Allen Li93f4db52016-09-14 14:44:59 -0700219 default=priorities.Priority.DEFAULT,
220 action="store",
221 help="Priority of suite. Either numerical value, or "
222 "one of (" + ", ".join(priorities.Priority.names)
223 + ").")
224 parser.add_argument(
225 '--retry', dest='retry', default=False, type=bool_str, action='store',
226 help='Enable test retry. Must pass "True" or "False" if used.')
227 parser.add_argument('--max_retries', dest='max_retries', default=None,
228 type=int, action='store', help='Maximum retries'
229 'allowed at suite level. No limit if not specified.')
230 parser.add_argument('--minimum_duts', dest='minimum_duts', type=int,
231 default=0, action='store',
232 help='Check that the pool has at least such many '
233 'healthy machines, otherwise suite will not run. '
234 'Default to 0.')
235 parser.add_argument('--suite_min_duts', dest='suite_min_duts', type=int,
236 default=0, action='store',
237 help='Preferred minimum number of machines. Scheduler '
238 'will prioritize on getting such many machines for '
239 'the suite when it is competing with another suite '
240 'that has a higher priority but already got minimum '
241 'machines it needs. Default to 0.')
242 parser.add_argument("--suite_args", dest="suite_args",
243 default=None, action="store",
244 help="Argument string for suite control file.")
245 parser.add_argument('--offload_failures_only',
Allen Li40599a32016-12-08 13:23:35 -0800246 dest='offload_failures_only', type=bool_str,
247 action='store', default=False,
Allen Li93f4db52016-09-14 14:44:59 -0700248 help='Only enable gs_offloading for failed tests. '
249 'Successful tests will be deleted. Must pass "True"'
250 ' or "False" if used.')
251 parser.add_argument('--use_suite_attr', dest='use_suite_attr',
252 action='store_true', default=False,
253 help='Advanced. Run the suite based on ATTRIBUTES of '
254 'control files, rather than SUITE.')
255 parser.add_argument('--json_dump', dest='json_dump', action='store_true',
256 default=False,
257 help='Dump the output of run_suite to stdout.')
258 parser.add_argument(
259 '--run_prod_code', dest='run_prod_code',
260 action='store_true', default=False,
261 help='Run the test code that lives in prod aka the test '
262 'code currently on the lab servers.')
263 parser.add_argument(
264 '--delay_minutes', type=int, default=0,
265 help=('Delay the creation of test jobs for a given '
266 'number of minutes. This argument can be used to '
267 'force provision jobs being delayed, which helps '
268 'to distribute loads across devservers.'))
269 parser.add_argument(
270 '--skip_duts_check', dest='skip_duts_check', action='store_true',
271 default=False, help='If True, skip minimum available DUTs check')
Shuqian Zhao843ae5c72017-02-22 11:25:01 -0800272 parser.add_argument(
Shuqian Zhao637d22c2017-03-06 15:52:32 -0800273 '--job_keyvals', dest='job_keyvals', type=ast.literal_eval,
Shuqian Zhao843ae5c72017-02-22 11:25:01 -0800274 action='store', default=None,
275 help='A dict of job keyvals to be inject to suite control file')
Shuqian Zhaoed0da862017-03-06 14:47:13 -0800276 parser.add_argument(
277 '--test_args', dest='test_args', type=ast.literal_eval,
278 action='store', default=None,
279 help=('A dict of args passed all the way to each individual test that '
280 'will be actually ran.'))
xixuand3cb33d2017-07-07 14:47:53 -0700281 parser.add_argument(
xixuan99eba0b2017-07-12 15:10:01 -0700282 '--require_logfile', action='store_true',
xixuand3cb33d2017-07-07 14:47:53 -0700283 help=('Stream logs of run_suite.py to a local file named '
284 'run_suite-<build name>.log.'))
Aviv Keshet97bebd42017-05-24 21:02:32 -0700285
286 # Used for monitoring purposes, to measure no-op swarming proxy latency.
287 parser.add_argument('--do_nothing', action='store_true',
288 help=argparse.SUPPRESS)
289
xixuanae791b12017-06-29 15:40:19 -0700290 # Used when lab/job status checking is needed. Currently its only user is
291 # suite scheduler v2.
292 parser.add_argument(
293 '--pre_check', action='store_true',
294 help=('Check lab and job status before kicking off a suite. Used by '
295 'suite scheduler v2.'))
296
Allen Li93f4db52016-09-14 14:44:59 -0700297 return parser
Chris Masone24b80f12012-02-14 14:18:01 -0800298
299
Allen Li93f4db52016-09-14 14:44:59 -0700300def verify_options(options):
301 """Verify the validity of options.
Fang Dengdd20e452014-04-07 15:39:47 -0700302
Fang Dengdd20e452014-04-07 15:39:47 -0700303 @param options: The parsed options to verify.
Fang Dengdd20e452014-04-07 15:39:47 -0700304
305 @returns: True if verification passes, False otherwise.
306
307 """
Fang Deng6865aab2015-02-20 14:49:47 -0800308 if options.mock_job_id and (
309 not options.build or not options.name or not options.board):
310 print ('When using -m, need to specify build, board and suite '
311 'name which you have used for creating the original job')
312 return False
313 else:
Fang Dengdd20e452014-04-07 15:39:47 -0700314 if not options.build:
315 print 'Need to specify which build to use'
316 return False
317 if not options.board:
318 print 'Need to specify board'
319 return False
320 if not options.name:
321 print 'Need to specify suite name'
322 return False
323 if options.num is not None and options.num < 1:
324 print 'Number of machines must be more than 0, if specified.'
325 return False
Allen Li93f4db52016-09-14 14:44:59 -0700326 if not options.retry and options.max_retries is not None:
Fang Deng443f1952015-01-02 14:51:49 -0800327 print 'max_retries can only be used with --retry=True'
328 return False
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -0700329 if options.use_suite_attr and options.suite_args is not None:
330 print ('The new suite control file cannot parse the suite_args: %s.'
331 'Please not specify any suite_args here.' % options.suite_args)
332 return False
Allen Li93f4db52016-09-14 14:44:59 -0700333 if options.no_wait and options.retry:
Fang Deng058860c2014-05-15 15:41:50 -0700334 print 'Test retry is not available when using --no_wait=True'
Dan Shi36cfd832014-10-10 13:38:51 -0700335 # Default to use the test code in CrOS build.
336 if not options.test_source_build and options.build:
337 options.test_source_build = options.build
Fang Dengdd20e452014-04-07 15:39:47 -0700338 return True
339
340
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -0700341def change_options_for_suite_attr(options):
342 """Change options to be prepared to run the suite_attr_wrapper.
343
344 If specify 'use_suite_attr' from the cmd line, it indicates to run the
345 new style suite control file, suite_attr_wrapper. Then, change the
Allen Li6a612392016-08-18 12:09:32 -0700346 options.name to 'suite_attr_wrapper', change the options.suite_args to
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -0700347 include the arguments needed by suite_attr_wrapper.
348
349 @param options: The verified options.
350
351 @returns: The changed options.
352
353 """
354 # Convert the suite_name to attribute boolean expression.
355 if type(options.name) is str:
356 attr_filter_val = 'suite:%s' % options.name
357 else:
358 attr_filter_val = ' or '.join(['suite:%s' % x for x in options.name])
359
360 # change the suite_args to be a dict of arguments for suite_attr_wrapper
361 # if suite_args is not None, store the values in 'other_args' of the dict
362 args_dict = {}
363 args_dict['attr_filter'] = attr_filter_val
364 options.suite_args = str(args_dict)
365 options.name = 'suite_attr_wrapper'
366
367 return options
368
369
Allen Li34613242016-09-02 11:52:34 -0700370class TestResult(object):
Aviv Keshet1480c4a2013-03-21 16:38:31 -0700371
Allen Li34613242016-09-02 11:52:34 -0700372 """Represents the result of a TestView."""
Aviv Keshet1480c4a2013-03-21 16:38:31 -0700373
Allen Li34613242016-09-02 11:52:34 -0700374 def __init__(self, test_view, retry_count=0):
375 """Initialize instance.
376
377 @param test_view: TestView instance.
378 @param retry_count: Retry count for test. Optional.
379 """
380 self.name = test_view.get_testname()
381 self.status = test_view['status']
382 self.reason = test_view['reason']
383 self.retry_count = retry_count
384
385 _PRETTY_STATUS_MAP = {
386 'GOOD': '[ PASSED ]',
387 'TEST_NA': '[ INFO ]',
388 }
389
390 @property
391 def _pretty_status(self):
392 """Pretty status string."""
393 return self._PRETTY_STATUS_MAP.get(self.status, '[ FAILED ]')
394
395 def log_using(self, log_function, name_column_width):
396 """Log the test result using the given log function.
397
398 @param log_function: Log function to use. Example: logging.info
399 @param name_column_width: Width of name column for formatting.
400 """
401 padded_name = self.name.ljust(name_column_width)
402 log_function('%s%s', padded_name, self._pretty_status)
403 if self.status != 'GOOD':
404 log_function('%s %s: %s', padded_name, self.status, self.reason)
405 if self.retry_count > 0:
406 log_function('%s retry_count: %s', padded_name, self.retry_count)
Chris Masone24b80f12012-02-14 14:18:01 -0800407
Fang Dengdd20e452014-04-07 15:39:47 -0700408
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -0700409def get_original_suite_name(suite_name, suite_args):
410 """Get the original suite name when running suite_attr_wrapper.
411
412 @param suite_name: the name of the suite launched in afe. When it is
413 suite_attr_wrapper, the suite that actually running is
414 specified in the suite_args.
415 @param suite_args: the parsed option which contains the original suite name.
416
417 @returns: the original suite name.
418
419 """
420 if suite_name == 'suite_attr_wrapper':
421 attrs = ast.literal_eval(suite_args).get('attr_filter', '')
422 suite_list = ([x[6:] for x in re.split('[() ]', attrs)
423 if x and x.startswith('suite:')])
424 return suite_list[0] if suite_list else suite_name
425 return suite_name
426
427
Craig Harrison25eb0f32012-08-23 16:48:49 -0700428class LogLink(object):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700429 """Information needed to record a link in the logs.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700430
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700431 Depending on context and the information provided at
432 construction time, the link may point to either to log files for
433 a job, or to a bug filed for a failure in the job.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700434
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700435 @var anchor The link text.
436 @var url The link url.
437 @var bug_id Id of a bug to link to, or None.
438 """
439
Kevin Cheng2bdd3722016-03-24 21:30:52 -0700440 # A list of tests that don't get retried so skip the dashboard.
441 _SKIP_RETRY_DASHBOARD = ['provision']
442
Ningning Xiabd911bd2016-04-19 14:06:03 -0700443 _BUG_LINK_PREFIX = 'Auto-Bug'
444 _LOG_LINK_PREFIX = 'Test-Logs'
445
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700446
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700447 @classmethod
448 def get_bug_link(cls, bug_id):
449 """Generate a bug link for the given bug_id.
450
451 @param bug_id: The id of the bug.
452 @return: A link, eg: https://crbug.com/<bug_id>.
453 """
Allen Lidc2c69a2016-09-14 19:05:47 -0700454 return reporting_utils.link_crbug(bug_id)
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700455
456
Fang Dengaeab6172014-05-07 17:17:04 -0700457 def __init__(self, anchor, server, job_string, bug_info=None, reason=None,
Simran Basi7203d4e2015-02-03 15:50:18 -0800458 retry_count=0, testname=None):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700459 """Initialize the LogLink by generating the log URL.
460
461 @param anchor The link text.
Alex Millerc7a59522013-10-30 15:18:57 -0700462 @param server The hostname of the server this suite ran on.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700463 @param job_string The job whose logs we'd like to link to.
464 @param bug_info Info about the bug, if one was filed.
Fang Deng53c6ff52014-02-24 17:51:24 -0800465 @param reason A string representing the reason of failure if any.
Fang Dengaeab6172014-05-07 17:17:04 -0700466 @param retry_count How many times the test has been retried.
Simran Basi7203d4e2015-02-03 15:50:18 -0800467 @param testname Optional Arg that supplies the testname.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700468 """
469 self.anchor = anchor
Allen Lidc2c69a2016-09-14 19:05:47 -0700470 self.url = _URL_PATTERN % (server, job_string)
Fang Deng53c6ff52014-02-24 17:51:24 -0800471 self.reason = reason
Fang Dengaeab6172014-05-07 17:17:04 -0700472 self.retry_count = retry_count
Simran Basi7203d4e2015-02-03 15:50:18 -0800473 self.testname = testname
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700474 if bug_info:
475 self.bug_id, self.bug_count = bug_info
476 else:
477 self.bug_id = None
478 self.bug_count = None
Craig Harrison25eb0f32012-08-23 16:48:49 -0700479
480
Allen Lie082ced2016-09-14 15:19:20 -0700481 @property
482 def bug_url(self):
483 """URL of associated bug."""
484 if self.bug_id:
485 return reporting_utils.link_crbug(self.bug_id)
486 else:
487 return None
488
489
490 @property
491 def _bug_count_text(self):
492 """Return bug count as human friendly text."""
493 if self.bug_count is None:
494 bug_info = 'unknown number of reports'
495 elif self.bug_count == 1:
496 bug_info = 'new report'
497 else:
498 bug_info = '%s reports' % self.bug_count
499 return bug_info
500
501
Ningning Xiabd911bd2016-04-19 14:06:03 -0700502 def GenerateBuildbotLinks(self):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700503 """Generate a link formatted to meet buildbot expectations.
504
Ningning Xiabd911bd2016-04-19 14:06:03 -0700505 If there is a bug associated with this link, report a link to the bug
506 and a link to the job logs;
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700507 otherwise report a link to the job logs.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700508
Ningning Xiabd911bd2016-04-19 14:06:03 -0700509 @return A list of links formatted for the buildbot log annotator.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700510 """
Ningning Xiabd911bd2016-04-19 14:06:03 -0700511 bug_info_strings = []
Fang Dengaeab6172014-05-07 17:17:04 -0700512 info_strings = []
Ningning Xiabd911bd2016-04-19 14:06:03 -0700513
Fang Dengaeab6172014-05-07 17:17:04 -0700514 if self.retry_count > 0:
515 info_strings.append('retry_count: %d' % self.retry_count)
Ningning Xiabd911bd2016-04-19 14:06:03 -0700516 bug_info_strings.append('retry_count: %d' % self.retry_count)
Fang Dengaeab6172014-05-07 17:17:04 -0700517
Fang Deng53c6ff52014-02-24 17:51:24 -0800518 if self.reason:
Allen Lie082ced2016-09-14 15:19:20 -0700519 bug_info_strings.append(self.reason)
520 info_strings.append(self.reason)
Fang Dengaeab6172014-05-07 17:17:04 -0700521
Allen Lie082ced2016-09-14 15:19:20 -0700522 # Add the bug link to buildbot_links
523 if self.bug_url:
524 bug_info_strings.append(self._bug_count_text)
Ningning Xiabd911bd2016-04-19 14:06:03 -0700525
Allen Lie082ced2016-09-14 15:19:20 -0700526 bug_anchor_text = self._format_anchor_text(self._BUG_LINK_PREFIX,
527 bug_info_strings)
528
529 yield annotations.StepLink(bug_anchor_text, self.bug_url)
530
531 anchor_text = self._format_anchor_text(self._LOG_LINK_PREFIX,
532 info_strings)
533 yield annotations.StepLink(anchor_text, self.url)
Ningning Xiabd911bd2016-04-19 14:06:03 -0700534
535
Allen Lie082ced2016-09-14 15:19:20 -0700536 def _format_anchor_text(self, prefix, info_strings):
537 """Format anchor text given a prefix and info strings.
Ningning Xiabd911bd2016-04-19 14:06:03 -0700538
539 @param prefix The prefix of the anchor text.
540 @param info_strings The infos presented in the anchor text.
541 @return A anchor_text with the right prefix and info strings.
542 """
Allen Lie082ced2016-09-14 15:19:20 -0700543 anchor_text = '[{prefix}]: {anchor}'.format(
544 prefix=prefix,
545 anchor=self.anchor.strip())
Fang Dengaeab6172014-05-07 17:17:04 -0700546 if info_strings:
Allen Lie082ced2016-09-14 15:19:20 -0700547 info_text = ', '.join(info_strings)
548 anchor_text += ': ' + info_text
Ningning Xiabd911bd2016-04-19 14:06:03 -0700549 return anchor_text
Craig Harrison25eb0f32012-08-23 16:48:49 -0700550
Allen Lie082ced2016-09-14 15:19:20 -0700551 @property
552 def text_link(self):
553 """Link to the job's logs, for consumption by a human.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700554
Craig Harrisond8451572012-08-31 10:29:33 -0700555 @return A link formatted for human readability.
Craig Harrison25eb0f32012-08-23 16:48:49 -0700556 """
Aviv Keshet269848b2016-10-03 00:13:19 -0700557 return '%s %s' % (self.anchor, self.url)
Craig Harrison25eb0f32012-08-23 16:48:49 -0700558
559
Simran Basi7203d4e2015-02-03 15:50:18 -0800560 def GenerateWmatrixRetryLink(self):
561 """Generate a link to the wmatrix retry dashboard.
562
563 @return A link formatted for the buildbot log annotator.
564 """
Allen Lie082ced2016-09-14 15:19:20 -0700565 if not self.testname or self.testname in self._SKIP_RETRY_DASHBOARD:
Simran Basi7203d4e2015-02-03 15:50:18 -0800566 return None
Allen Lie082ced2016-09-14 15:19:20 -0700567 return annotations.StepLink(
568 text='[Flake-Dashboard]: %s' % self.testname,
569 url=reporting_utils.link_retry_url(self.testname))
Simran Basi7203d4e2015-02-03 15:50:18 -0800570
571
Chris Masoneb61b4052012-04-30 14:35:28 -0700572class Timings(object):
573 """Timings for important events during a suite.
574
575 All timestamps are datetime.datetime objects.
576
Fang Dengdd20e452014-04-07 15:39:47 -0700577 @var suite_job_id: the afe job id of the suite job for which
578 we are recording the timing for.
579 @var download_start_time: the time the devserver starts staging
580 the build artifacts. Recorded in create_suite_job.
581 @var payload_end_time: the time when the artifacts only necessary to start
582 installsing images onto DUT's are staged.
583 Recorded in create_suite_job.
584 @var artifact_end_time: the remaining artifacts are downloaded after we kick
585 off the reimaging job, at which point we record
586 artifact_end_time. Recorded in dynamic_suite.py.
Chris Masoneb61b4052012-04-30 14:35:28 -0700587 @var suite_start_time: the time the suite started.
Chris Masoneb61b4052012-04-30 14:35:28 -0700588 @var tests_start_time: the time the first test started running.
Fang Dengdd20e452014-04-07 15:39:47 -0700589 @var tests_end_time: the time the last test finished running.
Chris Masoneb61b4052012-04-30 14:35:28 -0700590 """
beeps6f02d192013-03-22 13:15:49 -0700591
Fang Dengdd20e452014-04-07 15:39:47 -0700592 def __init__(self, suite_job_id):
593 self.suite_job_id = suite_job_id
594 # Timings related to staging artifacts on devserver.
595 self.download_start_time = None
596 self.payload_end_time = None
597 self.artifact_end_time = None
beeps6f02d192013-03-22 13:15:49 -0700598
Fang Dengdd20e452014-04-07 15:39:47 -0700599 # The test_start_time, but taken off the view that corresponds to the
600 # suite instead of an individual test.
601 self.suite_start_time = None
beeps6f02d192013-03-22 13:15:49 -0700602
Fang Dengdd20e452014-04-07 15:39:47 -0700603 # Earliest and Latest tests in the set of TestViews passed to us.
604 self.tests_start_time = None
605 self.tests_end_time = None
606
Chris Masoneb61b4052012-04-30 14:35:28 -0700607
Chris Masoned9f13c52012-08-29 10:37:08 -0700608 def RecordTiming(self, view):
609 """Given a test report view, extract and record pertinent time info.
Chris Masoneb61b4052012-04-30 14:35:28 -0700610
611 get_detailed_test_views() returns a list of entries that provide
612 info about the various parts of a suite run. This method can take
613 any one of these entries and look up timestamp info we might want
614 and record it.
615
Chris Masonecfa7efc2012-09-06 16:00:07 -0700616 If timestamps are unavailable, datetime.datetime.min/max will be used.
617
Fang Dengaeab6172014-05-07 17:17:04 -0700618 @param view: A TestView object.
Chris Masoneb61b4052012-04-30 14:35:28 -0700619 """
Chris Masonecfa7efc2012-09-06 16:00:07 -0700620 start_candidate = datetime.min
621 end_candidate = datetime.max
622 if view['test_started_time']:
Dan Shidfea3682014-08-10 23:38:40 -0700623 start_candidate = time_utils.time_string_to_datetime(
624 view['test_started_time'])
Chris Masonecfa7efc2012-09-06 16:00:07 -0700625 if view['test_finished_time']:
Dan Shidfea3682014-08-10 23:38:40 -0700626 end_candidate = time_utils.time_string_to_datetime(
627 view['test_finished_time'])
Chris Masonecfa7efc2012-09-06 16:00:07 -0700628
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800629 if view.get_testname() == TestView.SUITE_JOB:
Chris Masoneb61b4052012-04-30 14:35:28 -0700630 self.suite_start_time = start_candidate
Chris Masoneb61b4052012-04-30 14:35:28 -0700631 else:
632 self._UpdateFirstTestStartTime(start_candidate)
633 self._UpdateLastTestEndTime(end_candidate)
Fang Dengdd20e452014-04-07 15:39:47 -0700634 if view['afe_job_id'] == self.suite_job_id and 'job_keyvals' in view:
Chris Masoned9f13c52012-08-29 10:37:08 -0700635 keyvals = view['job_keyvals']
Dan Shidfea3682014-08-10 23:38:40 -0700636 self.download_start_time = time_utils.time_string_to_datetime(
637 keyvals.get(constants.DOWNLOAD_STARTED_TIME),
638 handle_type_error=True)
beeps6f02d192013-03-22 13:15:49 -0700639
Dan Shidfea3682014-08-10 23:38:40 -0700640 self.payload_end_time = time_utils.time_string_to_datetime(
641 keyvals.get(constants.PAYLOAD_FINISHED_TIME),
642 handle_type_error=True)
beeps6f02d192013-03-22 13:15:49 -0700643
Dan Shidfea3682014-08-10 23:38:40 -0700644 self.artifact_end_time = time_utils.time_string_to_datetime(
645 keyvals.get(constants.ARTIFACT_FINISHED_TIME),
646 handle_type_error=True)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700647
Chris Masoneb61b4052012-04-30 14:35:28 -0700648
649 def _UpdateFirstTestStartTime(self, candidate):
650 """Update self.tests_start_time, iff candidate is an earlier time.
651
652 @param candidate: a datetime.datetime object.
653 """
654 if not self.tests_start_time or candidate < self.tests_start_time:
655 self.tests_start_time = candidate
656
657
658 def _UpdateLastTestEndTime(self, candidate):
659 """Update self.tests_end_time, iff candidate is a later time.
660
661 @param candidate: a datetime.datetime object.
662 """
663 if not self.tests_end_time or candidate > self.tests_end_time:
664 self.tests_end_time = candidate
665
666
667 def __str__(self):
668 return ('\n'
669 'Suite timings:\n'
Chris Masonea8066a92012-05-01 16:52:31 -0700670 'Downloads started at %s\n'
671 'Payload downloads ended at %s\n'
Chris Masoneb61b4052012-04-30 14:35:28 -0700672 'Suite started at %s\n'
Chris Masonea8066a92012-05-01 16:52:31 -0700673 'Artifact downloads ended (at latest) at %s\n'
Chris Masoneb61b4052012-04-30 14:35:28 -0700674 'Testing started at %s\n'
Chris Masonea8066a92012-05-01 16:52:31 -0700675 'Testing ended at %s\n' % (self.download_start_time,
676 self.payload_end_time,
677 self.suite_start_time,
Chris Masonea8066a92012-05-01 16:52:31 -0700678 self.artifact_end_time,
Chris Masoneb61b4052012-04-30 14:35:28 -0700679 self.tests_start_time,
680 self.tests_end_time))
681
682
Alex Millerc7a59522013-10-30 15:18:57 -0700683def instance_for_pool(pool_name):
684 """
685 Return the hostname of the server that should be used to service a suite
686 for the specified pool.
687
688 @param pool_name: The pool (without 'pool:' to schedule the suite against.
689 @return: The correct host that should be used to service this suite run.
690 """
691 return CONFIG.get_config_value(
692 'POOL_INSTANCE_SHARDING', pool_name,
693 default=_DEFAULT_AUTOTEST_INSTANCE)
694
695
Fang Dengaeab6172014-05-07 17:17:04 -0700696class TestView(object):
697 """Represents a test view and provides a set of helper functions."""
698
699
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800700 SUITE_JOB = 'Suite job'
Fang Deng95af42f2014-09-12 14:16:11 -0700701 INFRA_TESTS = ['provision']
Fang Dengaeab6172014-05-07 17:17:04 -0700702
703
Simran Basi17ca77c2015-10-14 19:05:00 -0700704 def __init__(self, view, afe_job, suite_name, build, user,
705 solo_test_run=False):
Fang Dengaeab6172014-05-07 17:17:04 -0700706 """Init a TestView object representing a tko test view.
707
708 @param view: A dictionary representing a tko test view.
Fang Dengf8503532014-06-12 18:21:55 -0700709 @param afe_job: An instance of frontend.afe.models.Job
710 representing the job that kicked off the test.
Fang Dengaeab6172014-05-07 17:17:04 -0700711 @param suite_name: The name of the suite
712 that the test belongs to.
713 @param build: The build for which the test is run.
Simran Basi01984f52015-10-12 15:36:45 -0700714 @param user: The user for which the test is run.
Simran Basi17ca77c2015-10-14 19:05:00 -0700715 @param solo_test_run: This is a solo test run not part of a suite.
Fang Dengaeab6172014-05-07 17:17:04 -0700716 """
717 self.view = view
Fang Dengf8503532014-06-12 18:21:55 -0700718 self.afe_job = afe_job
Fang Dengaeab6172014-05-07 17:17:04 -0700719 self.suite_name = suite_name
720 self.build = build
Simran Basi17ca77c2015-10-14 19:05:00 -0700721 self.is_suite_view = afe_job.parent_job is None and not solo_test_run
Fang Dengaeab6172014-05-07 17:17:04 -0700722 # This is the test name that will be shown in the output.
723 self.testname = None
Simran Basi01984f52015-10-12 15:36:45 -0700724 self.user = user
Fang Dengaeab6172014-05-07 17:17:04 -0700725
Fang Dengf8503532014-06-12 18:21:55 -0700726 # The case that a job was aborted before it got a chance to run
727 # usually indicates suite has timed out (unless aborted by user).
728 # In this case, the abort reason will be None.
729 # Update the reason with proper information.
730 if (self.is_relevant_suite_view() and
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800731 not self.get_testname() == self.SUITE_JOB and
Fang Dengf8503532014-06-12 18:21:55 -0700732 self.view['status'] == 'ABORT' and
733 not self.view['reason']):
734 self.view['reason'] = 'Timed out, did not run.'
735
Fang Dengaeab6172014-05-07 17:17:04 -0700736
737 def __getitem__(self, key):
738 """Overload __getitem__ so that we can still use []
739
740 @param key: A key of the tko test view.
741
742 @returns: The value of an attribute in the view.
743
744 """
745 return self.view[key]
746
747
Fang Dengaeab6172014-05-07 17:17:04 -0700748 def __iter__(self):
749 """Overload __iter__ so that it supports 'in' operator."""
750 return iter(self.view)
751
752
753 def get_testname(self):
754 """Get test name that should be shown in the output.
755
756 Formalize the test_name we got from the test view.
757
Allen Lie6236ec2017-07-05 12:52:36 -0700758 Remove 'build/suite' prefix if any.
Fang Dengaeab6172014-05-07 17:17:04 -0700759
760 If one runs a test in control file via the following code,
761 job.runtest('my_Test', tag='tag')
762 for most of the cases, view['test_name'] would look like 'my_Test.tag'.
763 If this is the case, this method will just return the original
764 test name, i.e. 'my_Test.tag'.
765
766 There are four special cases.
767 1) A test view is for the suite job's SERVER_JOB.
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800768 In this case, this method will return 'Suite job'.
Fang Dengaeab6172014-05-07 17:17:04 -0700769
Simran Basi17ca77c2015-10-14 19:05:00 -0700770 2) A test view is of a child job or a solo test run not part of a
771 suite, and for a SERVER_JOB or CLIENT_JOB.
Fang Dengaeab6172014-05-07 17:17:04 -0700772 In this case, we will take the job name, remove the build/suite
773 prefix from the job name, and append the rest to 'SERVER_JOB'
774 or 'CLIENT_JOB' as a prefix. So the names returned by this
775 method will look like:
Allen Lie6236ec2017-07-05 12:52:36 -0700776 'Telemetry Smoothness Measurement_SERVER_JOB'
777 'dummy_Pass_SERVER_JOB'
Fang Dengaeab6172014-05-07 17:17:04 -0700778 'dummy_Fail_SERVER_JOB'
779
Fang Dengf8503532014-06-12 18:21:55 -0700780 3) A test view is of a suite job and its status is ABORT.
Fang Dengaeab6172014-05-07 17:17:04 -0700781 In this case, the view['test_name'] is the child job's name.
Allen Lie6236ec2017-07-05 12:52:36 -0700782 For instance,
Fang Dengaeab6172014-05-07 17:17:04 -0700783 'lumpy-release/R35-5712.0.0/perf_v2/
Allen Lie6236ec2017-07-05 12:52:36 -0700784 Telemetry Smoothness Measurement'
785 'lumpy-release/R35-5712.0.0/dummy/dummy_Pass'
Fang Dengaeab6172014-05-07 17:17:04 -0700786 'lumpy-release/R35-5712.0.0/dummy/dummy_Fail'
787 The above names will be converted to the following:
Allen Lie6236ec2017-07-05 12:52:36 -0700788 'Telemetry Smoothness Measurement'
789 'dummy_Pass'
Fang Dengaeab6172014-05-07 17:17:04 -0700790 'dummy_Fail'
791
Fang Dengf8503532014-06-12 18:21:55 -0700792 4) A test view's status is of a suite job and its status is TEST_NA.
Fang Dengaeab6172014-05-07 17:17:04 -0700793 In this case, the view['test_name'] is the NAME field of the control
Allen Lie6236ec2017-07-05 12:52:36 -0700794 file. For instance,
795 'Telemetry Smoothness Measurement'
796 'dummy_Pass'
Fang Dengaeab6172014-05-07 17:17:04 -0700797 'dummy_Fail'
798 This method will not modify these names.
799
800 @returns: Test name after normalization.
801
802 """
803 if self.testname is not None:
804 return self.testname
805
806 if (self.is_suite_view and
807 self.view['test_name'].startswith('SERVER_JOB')):
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800808 # Rename suite job's SERVER_JOB to 'Suite job'.
809 self.testname = self.SUITE_JOB
Fang Dengaeab6172014-05-07 17:17:04 -0700810 return self.testname
811
812 if (self.view['test_name'].startswith('SERVER_JOB') or
813 self.view['test_name'].startswith('CLIENT_JOB')):
814 # Append job name as a prefix for SERVER_JOB and CLIENT_JOB
815 testname= '%s_%s' % (self.view['job_name'], self.view['test_name'])
816 else:
817 testname = self.view['test_name']
Fang Dengaeab6172014-05-07 17:17:04 -0700818 # Remove the build and suite name from testname if any.
Allen Lie6236ec2017-07-05 12:52:36 -0700819 self.testname = tools.get_test_name(
Fang Dengaeab6172014-05-07 17:17:04 -0700820 self.build, self.suite_name, testname)
Fang Dengaeab6172014-05-07 17:17:04 -0700821 return self.testname
822
823
824 def is_relevant_suite_view(self):
825 """Checks whether this is a suite view we should care about.
826
827 @returns: True if it is relevant. False otherwise.
828 """
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800829 return (self.get_testname() == self.SUITE_JOB or
Fang Dengaeab6172014-05-07 17:17:04 -0700830 (self.is_suite_view and
831 not self.view['test_name'].startswith('CLIENT_JOB') and
832 not self.view['subdir']))
833
834
835 def is_test(self):
836 """Return whether the view is for an actual test.
837
838 @returns True if the view is for an actual test.
839 False if the view is for SERVER_JOB or CLIENT_JOB.
840
841 """
842 return not (self.view['test_name'].startswith('SERVER_JOB') or
843 self.view['test_name'].startswith('CLIENT_JOB'))
844
845
846 def is_retry(self):
847 """Check whether the view is for a retry.
848
849 @returns: True, if the view is for a retry; False otherwise.
850
851 """
852 return self.view['job_keyvals'].get('retry_original_job_id') is not None
853
854
Fang Dengf8503532014-06-12 18:21:55 -0700855 def hit_timeout(self):
856 """Check whether the corresponding job has hit its own timeout.
Fang Dengaeab6172014-05-07 17:17:04 -0700857
Fang Dengf8503532014-06-12 18:21:55 -0700858 Note this method should not be called for those test views
859 that belongs to a suite job and are determined as irrelevant
860 by is_relevant_suite_view. This is because they are associated
861 to the suite job, whose job start/finished time make no sense
862 to an irrelevant test view.
Fang Dengaeab6172014-05-07 17:17:04 -0700863
Fang Dengf8503532014-06-12 18:21:55 -0700864 @returns: True if the corresponding afe job has hit timeout.
865 False otherwise.
866 """
867 if (self.is_relevant_suite_view() and
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800868 self.get_testname() != self.SUITE_JOB):
869 # Any relevant suite test view except SUITE_JOB
Fang Dengf8503532014-06-12 18:21:55 -0700870 # did not hit its own timeout because it was not ever run.
871 return False
872 start = (datetime.strptime(
Dan Shidfea3682014-08-10 23:38:40 -0700873 self.view['job_started_time'], time_utils.TIME_FMT)
Fang Dengf8503532014-06-12 18:21:55 -0700874 if self.view['job_started_time'] else None)
875 end = (datetime.strptime(
Dan Shidfea3682014-08-10 23:38:40 -0700876 self.view['job_finished_time'], time_utils.TIME_FMT)
Fang Dengf8503532014-06-12 18:21:55 -0700877 if self.view['job_finished_time'] else None)
878 if not start or not end:
879 return False
880 else:
881 return ((end - start).total_seconds()/60.0
882 > self.afe_job.max_runtime_mins)
883
884
885 def is_aborted(self):
886 """Check if the view was aborted.
887
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800888 For suite job and child job test views, we check job keyval
Fang Dengf8503532014-06-12 18:21:55 -0700889 'aborted_by' and test status.
890
891 For relevant suite job test views, we only check test status
892 because the suite job keyval won't make sense to individual
893 test views.
894
895 @returns: True if the test was as aborted, False otherwise.
Fang Dengaeab6172014-05-07 17:17:04 -0700896
897 """
Fang Dengf8503532014-06-12 18:21:55 -0700898
899 if (self.is_relevant_suite_view() and
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800900 self.get_testname() != self.SUITE_JOB):
Fang Dengf8503532014-06-12 18:21:55 -0700901 return self.view['status'] == 'ABORT'
902 else:
903 return (bool(self.view['job_keyvals'].get('aborted_by')) and
904 self.view['status'] in ['ABORT', 'RUNNING'])
Fang Dengaeab6172014-05-07 17:17:04 -0700905
906
907 def is_in_fail_status(self):
Fang Deng95af42f2014-09-12 14:16:11 -0700908 """Check if the given test's status corresponds to a failure.
Fang Dengaeab6172014-05-07 17:17:04 -0700909
910 @returns: True if the test's status is FAIL or ERROR. False otherwise.
911
912 """
913 # All the statuses tests can have when they fail.
914 return self.view['status'] in ['FAIL', 'ERROR', 'ABORT']
915
916
Fang Deng95af42f2014-09-12 14:16:11 -0700917 def is_infra_test(self):
918 """Check whether this is a test that only lab infra is concerned.
919
920 @returns: True if only lab infra is concerned, False otherwise.
921
922 """
923 return self.get_testname() in self.INFRA_TESTS
924
925
Fang Dengaeab6172014-05-07 17:17:04 -0700926 def get_buildbot_link_reason(self):
927 """Generate the buildbot link reason for the test.
928
929 @returns: A string representing the reason.
930
931 """
932 return ('%s: %s' % (self.view['status'], self.view['reason'])
933 if self.view['reason'] else self.view['status'])
934
935
936 def get_job_id_owner_str(self):
937 """Generate the job_id_owner string for a test.
938
939 @returns: A string which looks like 135036-username
940
941 """
Simran Basi01984f52015-10-12 15:36:45 -0700942 return '%s-%s' % (self.view['afe_job_id'], self.user)
Fang Dengaeab6172014-05-07 17:17:04 -0700943
944
945 def get_bug_info(self, suite_job_keyvals):
946 """Get the bug info from suite_job_keyvals.
947
948 If a bug has been filed for the test, its bug info (bug id and counts)
949 will be stored in the suite job's keyvals. This method attempts to
950 retrieve bug info of the test from |suite_job_keyvals|. It will return
951 None if no bug info is found. No need to check bug info if the view is
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800952 SUITE_JOB.
Fang Dengaeab6172014-05-07 17:17:04 -0700953
954 @param suite_job_keyvals: The job keyval dictionary of the suite job.
955 All the bug info about child jobs are stored in
956 suite job's keyvals.
957
958 @returns: None if there is no bug info, or a pair with the
959 id of the bug, and the count of the number of
960 times the bug has been seen.
961
962 """
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800963 if self.get_testname() == self.SUITE_JOB:
Fang Dengaeab6172014-05-07 17:17:04 -0700964 return None
965 if (self.view['test_name'].startswith('SERVER_JOB') or
966 self.view['test_name'].startswith('CLIENT_JOB')):
967 # Append job name as a prefix for SERVER_JOB and CLIENT_JOB
968 testname= '%s_%s' % (self.view['job_name'], self.view['test_name'])
969 else:
970 testname = self.view['test_name']
971
972 return tools.get_test_failure_bug_info(
973 suite_job_keyvals, self.view['afe_job_id'],
974 testname)
975
976
977 def should_display_buildbot_link(self):
978 """Check whether a buildbot link should show for this view.
979
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800980 For suite job view, show buildbot link if it fails.
Fang Dengf8503532014-06-12 18:21:55 -0700981 For normal test view,
982 show buildbot link if it is a retry
983 show buildbot link if it hits its own timeout.
984 show buildbot link if it fails. This doesn't
985 include the case where it was aborted but has
986 not hit its own timeout (most likely it was aborted because
987 suite has timed out).
Fang Dengaeab6172014-05-07 17:17:04 -0700988
989 @returns: True if we should show the buildbot link.
990 False otherwise.
991 """
992 is_bad_status = (self.view['status'] != 'GOOD' and
993 self.view['status'] != 'TEST_NA')
Shuqian Zhaoc085abb2016-02-24 11:27:26 -0800994 if self.get_testname() == self.SUITE_JOB:
Fang Dengf8503532014-06-12 18:21:55 -0700995 return is_bad_status
996 else:
997 if self.is_retry():
998 return True
999 if is_bad_status:
1000 return not self.is_aborted() or self.hit_timeout()
Fang Dengaeab6172014-05-07 17:17:04 -07001001
1002
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001003 def get_control_file_attributes(self):
1004 """Get the attributes from the control file of the test.
1005
1006 @returns: A list of test attribute or None.
1007 """
1008 control_file = self.afe_job.control_file
1009 attributes = None
1010 if control_file:
1011 cd = control_data.parse_control_string(control_file)
1012 attributes = list(cd.attributes)
1013 return attributes
1014
1015
David Rileydcd1a642017-03-01 23:15:08 -08001016 def override_afe_job_id(self, afe_job_id):
1017 """Overrides the AFE job id for the test.
1018
1019 @param afe_job_id: The new AFE job id to use.
1020 """
1021 self.view['afe_job_id'] = afe_job_id
1022
1023
Allen Lidc2c69a2016-09-14 19:05:47 -07001024def log_buildbot_links(log_func, links):
1025 """Output buildbot links to log.
1026
1027 @param log_func: Logging function to use.
1028 @param links: Iterable of LogLink instances.
1029 """
1030 for link in links:
1031 for generated_link in link.GenerateBuildbotLinks():
1032 log_func(generated_link)
1033 wmatrix_link = link.GenerateWmatrixRetryLink()
1034 if wmatrix_link:
1035 log_func(wmatrix_link)
1036
1037
Fang Dengdd20e452014-04-07 15:39:47 -07001038class ResultCollector(object):
Simran Basi17ca77c2015-10-14 19:05:00 -07001039 """Collect test results of a suite or a single test run.
Fang Dengdd20e452014-04-07 15:39:47 -07001040
1041 Once a suite job has finished, use this class to collect test results.
1042 `run` is the core method that is to be called first. Then the caller
1043 could retrieve information like return code, return message, is_aborted,
1044 and timings by accessing the collector's public attributes. And output
1045 the test results and links by calling the 'output_*' methods.
1046
1047 Here is a overview of what `run` method does.
1048
1049 1) Collect the suite job's results from tko_test_view_2.
1050 For the suite job, we only pull test views without a 'subdir'.
1051 A NULL subdir indicates that the test was _not_ executed. This could be
1052 that no child job was scheduled for this test or the child job got
1053 aborted before starts running.
1054 (Note 'SERVER_JOB'/'CLIENT_JOB' are handled specially)
1055
1056 2) Collect the child jobs' results from tko_test_view_2.
1057 For child jobs, we pull all the test views associated with them.
Allen Lidc2c69a2016-09-14 19:05:47 -07001058 (Note 'SERVER_JOB'/'CLIENT_JOB' are handled specially)
Fang Dengdd20e452014-04-07 15:39:47 -07001059
Fang Dengaeab6172014-05-07 17:17:04 -07001060 3) Generate web and buildbot links.
Fang Dengdd20e452014-04-07 15:39:47 -07001061 4) Compute timings of the suite run.
1062 5) Compute the return code based on test results.
1063
1064 @var _instance_server: The hostname of the server that is used
1065 to service the suite.
1066 @var _afe: The afe rpc client.
1067 @var _tko: The tko rpc client.
1068 @var _build: The build for which the suite is run,
1069 e.g. 'lumpy-release/R35-5712.0.0'
MK Ryu977a9752014-10-21 11:58:09 -07001070 @var _board: The target board for which the suite is run,
1071 e.g., 'lumpy', 'link'.
Fang Dengdd20e452014-04-07 15:39:47 -07001072 @var _suite_name: The suite name, e.g. 'bvt', 'dummy'.
1073 @var _suite_job_id: The job id of the suite for which we are going to
1074 collect results.
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -07001075 @var _original_suite_name: The suite name we record timing would be
1076 different from _suite_name when running
1077 suite_attr_wrapper.
Fang Dengaeab6172014-05-07 17:17:04 -07001078 @var _suite_views: A list of TestView objects, representing relevant
1079 test views of the suite job.
1080 @var _child_views: A list of TestView objects, representing test views
1081 of the child jobs.
1082 @var _test_views: A list of TestView objects, representing all test views
1083 from _suite_views and _child_views.
Fang Dengdd20e452014-04-07 15:39:47 -07001084 @var _web_links: A list of web links pointing to the results of jobs.
1085 @var _buildbot_links: A list of buildbot links for non-passing tests.
Simran Basi17ca77c2015-10-14 19:05:00 -07001086 @var _solo_test_run: True if this is a single test run.
Fang Dengdd20e452014-04-07 15:39:47 -07001087 @var return_code: The exit code that should be returned by run_suite.
1088 @var return_message: Any message that should be displayed to explain
1089 the return code.
1090 @var is_aborted: Whether the suite was aborted or not.
1091 True, False or None (aborting status is unknown yet)
1092 @var timings: A Timing object that records the suite's timings.
1093
1094 """
1095
1096
MK Ryu977a9752014-10-21 11:58:09 -07001097 def __init__(self, instance_server, afe, tko, build, board,
Simran Basi01984f52015-10-12 15:36:45 -07001098 suite_name, suite_job_id, original_suite_name=None,
Simran Basi17ca77c2015-10-14 19:05:00 -07001099 user=None, solo_test_run=False):
Fang Dengdd20e452014-04-07 15:39:47 -07001100 self._instance_server = instance_server
1101 self._afe = afe
1102 self._tko = tko
1103 self._build = build
MK Ryu977a9752014-10-21 11:58:09 -07001104 self._board = board
Fang Dengdd20e452014-04-07 15:39:47 -07001105 self._suite_name = suite_name
1106 self._suite_job_id = suite_job_id
Shuqian Zhaof39bf2a2015-09-29 14:19:28 -07001107 self._original_suite_name = original_suite_name or suite_name
Fang Deng0454e632014-04-07 15:39:47 -07001108 self._suite_views = []
1109 self._child_views = []
Fang Dengdd20e452014-04-07 15:39:47 -07001110 self._test_views = []
Fang Dengaeab6172014-05-07 17:17:04 -07001111 self._retry_counts = {}
David Rileydcd1a642017-03-01 23:15:08 -08001112 self._missing_results = {}
Fang Dengdd20e452014-04-07 15:39:47 -07001113 self._web_links = []
1114 self._buildbot_links = []
MK Ryu977a9752014-10-21 11:58:09 -07001115 self._num_child_jobs = 0
Fang Dengdd20e452014-04-07 15:39:47 -07001116 self.return_code = None
Fang Deng0454e632014-04-07 15:39:47 -07001117 self.return_message = ''
Fang Dengdd20e452014-04-07 15:39:47 -07001118 self.is_aborted = None
1119 self.timings = None
Simran Basi01984f52015-10-12 15:36:45 -07001120 self._user = user or getpass.getuser()
Simran Basi17ca77c2015-10-14 19:05:00 -07001121 self._solo_test_run = solo_test_run
Fang Dengdd20e452014-04-07 15:39:47 -07001122
1123
Allen Lidc2c69a2016-09-14 19:05:47 -07001124 @property
1125 def buildbot_links(self):
1126 """Provide public access to buildbot links."""
1127 return self._buildbot_links
1128
1129
Fang Dengdd20e452014-04-07 15:39:47 -07001130 def _fetch_relevant_test_views_of_suite(self):
1131 """Fetch relevant test views of the suite job.
1132
1133 For the suite job, there will be a test view for SERVER_JOB, and views
Allen Lidc2c69a2016-09-14 19:05:47 -07001134 for results of its child jobs. For example, assume we've created
Fang Dengdd20e452014-04-07 15:39:47 -07001135 a suite job (afe_job_id: 40) that runs dummy_Pass, dummy_Fail,
1136 dummy_Pass.bluetooth. Assume dummy_Pass was aborted before running while
1137 dummy_Path.bluetooth got TEST_NA as no duts have bluetooth.
1138 So the suite job's test views would look like
1139 _____________________________________________________________________
1140 test_idx| job_idx|test_name |subdir |afe_job_id|status
1141 10 | 1000 |SERVER_JOB |---- |40 |GOOD
1142 11 | 1000 |dummy_Pass |NULL |40 |ABORT
1143 12 | 1000 |dummy_Fail.Fail |41-onwer/...|40 |FAIL
1144 13 | 1000 |dummy_Fail.Error |42-owner/...|40 |ERROR
1145 14 | 1000 |dummy_Pass.bluetooth|NULL |40 |TEST_NA
1146
1147 For a suite job, we only care about
1148 a) The test view for the suite job's SERVER_JOB
1149 b) The test views for real tests without a subdir. A NULL subdir
1150 indicates that a test didn't get executed.
1151 So, for the above example, we only keep test views whose test_idxs
1152 are 10, 11, 14.
1153
Fang Dengaeab6172014-05-07 17:17:04 -07001154 @returns: A list of TestView objects, representing relevant
1155 test views of the suite job.
Fang Dengdd20e452014-04-07 15:39:47 -07001156
1157 """
Fang Dengf8503532014-06-12 18:21:55 -07001158 suite_job = self._afe.get_jobs(id=self._suite_job_id)[0]
Fang Deng0454e632014-04-07 15:39:47 -07001159 views = self._tko.run(call='get_detailed_test_views',
1160 afe_job_id=self._suite_job_id)
Fang Dengdd20e452014-04-07 15:39:47 -07001161 relevant_views = []
1162 for v in views:
Simran Basi17ca77c2015-10-14 19:05:00 -07001163 v = TestView(v, suite_job, self._suite_name, self._build, self._user,
1164 solo_test_run=self._solo_test_run)
Fang Dengaeab6172014-05-07 17:17:04 -07001165 if v.is_relevant_suite_view():
David Rileydcd1a642017-03-01 23:15:08 -08001166 # If the test doesn't have results in TKO and is being
1167 # displayed in the suite view instead of the child view,
1168 # then afe_job_id is incorrect and from the suite.
1169 # Override it based on the AFE job id which was missing
1170 # results.
1171 # TODO: This is likely inaccurate if a test has multiple
1172 # tries which all fail TKO parse stage.
1173 if v['test_name'] in self._missing_results:
1174 v.override_afe_job_id(
1175 self._missing_results[v['test_name']][0])
Fang Dengdd20e452014-04-07 15:39:47 -07001176 relevant_views.append(v)
Fang Dengdd20e452014-04-07 15:39:47 -07001177 return relevant_views
1178
1179
Fang Dengaeab6172014-05-07 17:17:04 -07001180 def _compute_retry_count(self, view):
1181 """Return how many times the test has been retried.
1182
1183 @param view: A TestView instance.
1184 @returns: An int value indicating the retry count.
1185
1186 """
1187 old_job = view['job_keyvals'].get('retry_original_job_id')
1188 count = 0
1189 while old_job:
1190 count += 1
1191 views = self._tko.run(
1192 call='get_detailed_test_views', afe_job_id=old_job)
1193 old_job = (views[0]['job_keyvals'].get('retry_original_job_id')
1194 if views else None)
1195 return count
1196
1197
Simran Basi17ca77c2015-10-14 19:05:00 -07001198 def _fetch_test_views_of_child_jobs(self, jobs=None):
Fang Dengdd20e452014-04-07 15:39:47 -07001199 """Fetch test views of child jobs.
1200
David Rileydcd1a642017-03-01 23:15:08 -08001201 @returns: A tuple (child_views, retry_counts, missing_results)
Fang Dengaeab6172014-05-07 17:17:04 -07001202 child_views is list of TestView objects, representing
David Rileydcd1a642017-03-01 23:15:08 -08001203 all valid views.
1204 retry_counts is a dictionary that maps test_idx to retry
1205 counts. It only stores retry counts that are greater than 0.
1206 missing_results is a dictionary that maps test names to
1207 lists of job ids.
Fang Deng0454e632014-04-07 15:39:47 -07001208
Fang Dengdd20e452014-04-07 15:39:47 -07001209 """
Fang Dengdd20e452014-04-07 15:39:47 -07001210 child_views = []
Fang Dengaeab6172014-05-07 17:17:04 -07001211 retry_counts = {}
David Rileydcd1a642017-03-01 23:15:08 -08001212 missing_results = {}
Simran Basi17ca77c2015-10-14 19:05:00 -07001213 child_jobs = jobs or self._afe.get_jobs(parent_job_id=self._suite_job_id)
MK Ryu977a9752014-10-21 11:58:09 -07001214 if child_jobs:
1215 self._num_child_jobs = len(child_jobs)
Fang Dengf8503532014-06-12 18:21:55 -07001216 for job in child_jobs:
Simran Basi01984f52015-10-12 15:36:45 -07001217 views = [TestView(v, job, self._suite_name, self._build, self._user)
Fang Dengaeab6172014-05-07 17:17:04 -07001218 for v in self._tko.run(
Fang Dengf8503532014-06-12 18:21:55 -07001219 call='get_detailed_test_views', afe_job_id=job.id,
Fang Dengaeab6172014-05-07 17:17:04 -07001220 invalid=0)]
David Rileydcd1a642017-03-01 23:15:08 -08001221 if len(views) == 0:
1222 missing_results.setdefault(job.name, []).append(job.id)
Fang Dengdd20e452014-04-07 15:39:47 -07001223 contains_test_failure = any(
Fang Dengaeab6172014-05-07 17:17:04 -07001224 v.is_test() and v['status'] != 'GOOD' for v in views)
Fang Dengdd20e452014-04-07 15:39:47 -07001225 for v in views:
Fang Dengaeab6172014-05-07 17:17:04 -07001226 if (v.is_test() or
1227 v['status'] != 'GOOD' and not contains_test_failure):
1228 # For normal test view, just keep it.
1229 # For SERVER_JOB or CLIENT_JOB, only keep it
1230 # if it fails and no other test failure.
Fang Dengdd20e452014-04-07 15:39:47 -07001231 child_views.append(v)
Fang Dengaeab6172014-05-07 17:17:04 -07001232 retry_count = self._compute_retry_count(v)
1233 if retry_count > 0:
1234 retry_counts[v['test_idx']] = retry_count
David Rileydcd1a642017-03-01 23:15:08 -08001235 return child_views, retry_counts, missing_results
Fang Dengdd20e452014-04-07 15:39:47 -07001236
1237
1238 def _generate_web_and_buildbot_links(self):
1239 """Generate web links and buildbot links."""
1240 # TODO(fdeng): If a job was aborted before it reaches Running
1241 # state, we read the test view from the suite job
1242 # and thus this method generates a link pointing to the
1243 # suite job's page for the aborted job. Need a fix.
1244 self._web_links = []
1245 self._buildbot_links = []
1246 # Bug info are stored in the suite job's keyvals.
Simran Basi17ca77c2015-10-14 19:05:00 -07001247 if self._solo_test_run:
1248 suite_job_keyvals = {}
1249 else:
1250 suite_job_keyvals = self._suite_views[0]['job_keyvals']
Fang Dengdd20e452014-04-07 15:39:47 -07001251 for v in self._test_views:
Fang Dengaeab6172014-05-07 17:17:04 -07001252 retry_count = self._retry_counts.get(v['test_idx'], 0)
1253 bug_info = v.get_bug_info(suite_job_keyvals)
1254 job_id_owner = v.get_job_id_owner_str()
Fang Dengdd20e452014-04-07 15:39:47 -07001255 link = LogLink(
Allen Li34613242016-09-02 11:52:34 -07001256 anchor=v.get_testname(),
Fang Dengdd20e452014-04-07 15:39:47 -07001257 server=self._instance_server,
1258 job_string=job_id_owner,
Simran Basi7203d4e2015-02-03 15:50:18 -08001259 bug_info=bug_info, retry_count=retry_count,
1260 testname=v.get_testname())
Fang Dengdd20e452014-04-07 15:39:47 -07001261 self._web_links.append(link)
1262
Fang Dengaeab6172014-05-07 17:17:04 -07001263 if v.should_display_buildbot_link():
1264 link.reason = v.get_buildbot_link_reason()
Fang Dengdd20e452014-04-07 15:39:47 -07001265 self._buildbot_links.append(link)
1266
1267
1268 def _record_timings(self):
1269 """Record suite timings."""
1270 self.timings = Timings(self._suite_job_id)
1271 for v in self._test_views:
1272 self.timings.RecordTiming(v)
1273
1274
Fang Dengaeab6172014-05-07 17:17:04 -07001275 def _get_return_msg(self, code, tests_passed_after_retry):
1276 """Return the proper message for a given return code.
1277
1278 @param code: An enum value of RETURN_CODES
1279 @param test_passed_after_retry: True/False, indicating
1280 whether there are test(s) that have passed after retry.
1281
1282 @returns: A string, representing the message.
1283
1284 """
1285 if code == RETURN_CODES.INFRA_FAILURE:
Fang Deng95af42f2014-09-12 14:16:11 -07001286 return 'Suite job failed or provisioning failed.'
Fang Dengaeab6172014-05-07 17:17:04 -07001287 elif code == RETURN_CODES.SUITE_TIMEOUT:
1288 return ('Some test(s) was aborted before running,'
1289 ' suite must have timed out.')
1290 elif code == RETURN_CODES.WARNING:
1291 if tests_passed_after_retry:
1292 return 'Some test(s) passed after retry.'
1293 else:
1294 return 'Some test(s) raised a warning.'
1295 elif code == RETURN_CODES.ERROR:
1296 return 'Some test(s) failed.'
1297 else:
1298 return ''
1299
1300
Fang Dengdd20e452014-04-07 15:39:47 -07001301 def _compute_return_code(self):
1302 """Compute the exit code based on test results."""
1303 code = RETURN_CODES.OK
Fang Dengaeab6172014-05-07 17:17:04 -07001304 tests_passed_after_retry = False
1305
Fang Dengdd20e452014-04-07 15:39:47 -07001306 for v in self._test_views:
Fang Dengf8503532014-06-12 18:21:55 -07001307 # The order of checking each case is important.
Shuqian Zhaoc085abb2016-02-24 11:27:26 -08001308 if v.get_testname() == TestView.SUITE_JOB:
Fang Dengf8503532014-06-12 18:21:55 -07001309 if v.is_aborted() and v.hit_timeout():
1310 current_code = RETURN_CODES.SUITE_TIMEOUT
1311 elif v.is_in_fail_status():
1312 current_code = RETURN_CODES.INFRA_FAILURE
1313 elif v['status'] == 'WARN':
1314 current_code = RETURN_CODES.WARNING
1315 else:
1316 current_code = RETURN_CODES.OK
Fang Deng5a43be62014-05-07 17:17:04 -07001317 else:
Fang Dengf8503532014-06-12 18:21:55 -07001318 if v.is_aborted() and v.is_relevant_suite_view():
1319 # The test was aborted before started
1320 # This gurantees that the suite has timed out.
1321 current_code = RETURN_CODES.SUITE_TIMEOUT
1322 elif v.is_aborted() and not v.hit_timeout():
1323 # The test was aborted, but
1324 # not due to a timeout. This is most likely
1325 # because the suite has timed out, but may
1326 # also because it was aborted by the user.
1327 # Since suite timing out is determined by checking
Shuqian Zhaoc085abb2016-02-24 11:27:26 -08001328 # the suite job view, we simply ignore this view here.
Fang Dengf8503532014-06-12 18:21:55 -07001329 current_code = RETURN_CODES.OK
1330 elif v.is_in_fail_status():
1331 # The test job failed.
Fang Deng95af42f2014-09-12 14:16:11 -07001332 if v.is_infra_test():
1333 current_code = RETURN_CODES.INFRA_FAILURE
1334 else:
1335 current_code = RETURN_CODES.ERROR
Fang Dengf8503532014-06-12 18:21:55 -07001336 elif v['status'] == 'WARN':
1337 # The test/suite job raised a wanrning.
1338 current_code = RETURN_CODES.WARNING
1339 elif v.is_retry():
1340 # The test is a passing retry.
1341 current_code = RETURN_CODES.WARNING
1342 tests_passed_after_retry = True
1343 else:
1344 current_code = RETURN_CODES.OK
1345 code = get_worse_code(code, current_code)
1346
Fang Dengdd20e452014-04-07 15:39:47 -07001347 self.return_code = code
Fang Dengaeab6172014-05-07 17:17:04 -07001348 self.return_message = self._get_return_msg(
1349 code, tests_passed_after_retry)
Fang Dengdd20e452014-04-07 15:39:47 -07001350
1351
Allen Li34613242016-09-02 11:52:34 -07001352 def _make_test_results(self):
1353 """Make TestResults for collected tests.
1354
1355 @returns: List of TestResult instances.
1356 """
1357 test_results = []
1358 for test_view in self._test_views:
1359 test_result = TestResult(
1360 test_view=test_view,
1361 retry_count=self._retry_counts.get(test_view['test_idx'], 0))
1362 test_results.append(test_result)
1363 return test_results
1364
1365
Fang Dengdd20e452014-04-07 15:39:47 -07001366 def output_results(self):
1367 """Output test results, timings and web links."""
1368 # Output test results
Allen Li34613242016-09-02 11:52:34 -07001369 test_results = self._make_test_results()
1370 max_name_length = max(len(test_result.name)
1371 for test_result in test_results)
1372 for test_result in test_results:
1373 test_result.log_using(logging.info, max_name_length + 3)
Fang Dengdd20e452014-04-07 15:39:47 -07001374 # Output suite timings
1375 logging.info(self.timings)
1376 # Output links to test logs
1377 logging.info('\nLinks to test logs:')
1378 for link in self._web_links:
Allen Lie082ced2016-09-14 15:19:20 -07001379 logging.info(link.text_link)
Fang Deng5a43be62014-05-07 17:17:04 -07001380 logging.info('\n')
Fang Dengdd20e452014-04-07 15:39:47 -07001381
1382
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001383 def get_results_dict(self):
1384 """Write test results, timings and web links into a dict.
1385
1386 @returns: A dict of results in the format like:
1387 {
1388 'tests': {
1389 'test_1': {'status': 'PASSED', 'attributes': [1,2], ...}
1390 'test_2': {'status': 'FAILED', 'attributes': [1],...}
1391 }
1392 'suite_timings': {
1393 'download_start': '1998-07-17 00:00:00',
1394 'payload_download_end': '1998-07-17 00:00:05',
1395 ...
1396 }
1397 }
1398 """
1399 output_dict = {}
1400 tests_dict = output_dict.setdefault('tests', {})
1401 for v in self._test_views:
Shuqian Zhaofae149c2017-01-30 16:46:53 -08001402 test_name = v.get_testname()
1403 test_info = tests_dict.setdefault(test_name, {})
1404 test_info.update({
1405 'status': v['status'],
1406 'attributes': v.get_control_file_attributes() or list(),
1407 'reason': v['reason'],
1408 'retry_count': self._retry_counts.get(v['test_idx'], 0),
1409 })
1410 # For aborted test, the control file will not be parsed and thus
1411 # fail to get the attributes info. Therefore, the subsystems the
1412 # abort test testing will be missing. For this case, we will assume
1413 # the aborted test will test all subsystems, set subsystem:default.
1414 if (test_info['status'] == 'ABORT' and
1415 not any('subsystem:' in a for a in test_info['attributes'])):
1416 test_info['attributes'].append('subsystem:default')
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001417
1418 # Write the links to test logs into the |tests_dict| of |output_dict|.
1419 # For test whose status is not 'GOOD', the link is also buildbot_link.
1420 for link in self._web_links:
Shuqian Zhaofae149c2017-01-30 16:46:53 -08001421 test_name = link.anchor.strip()
1422 test_info = tests_dict.get(test_name)
1423 if test_info:
1424 test_info['link_to_logs'] = link.url
1425 # Write the wmatrix link into the dict.
1426 if link in self._buildbot_links and link.testname:
1427 test_info['wmatrix_link'] \
1428 = reporting_utils.link_retry_url(link.testname)
1429 # Write the bug url into the dict.
1430 if link.bug_id:
1431 test_info['bug_url'] = link.bug_url
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001432
1433 # Write the suite timings into |output_dict|
Allen Li2c5d44b2016-08-15 17:58:58 -07001434 timings = self.timings
1435 if timings is not None:
1436 time_dict = output_dict.setdefault('suite_timings', {})
1437 time_dict.update({
1438 'download_start' : str(timings.download_start_time),
1439 'payload_download_end' : str(timings.payload_end_time),
1440 'suite_start' : str(timings.suite_start_time),
1441 'artifact_download_end' : str(timings.artifact_end_time),
1442 'tests_start' : str(timings.tests_start_time),
1443 'tests_end' : str(timings.tests_end_time),
1444 })
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001445
1446 output_dict['suite_job_id'] = self._suite_job_id
1447
1448 return output_dict
1449
1450
Fang Dengdd20e452014-04-07 15:39:47 -07001451 def run(self):
1452 """Collect test results.
1453
1454 This method goes through the following steps:
1455 Fetch relevent test views of the suite job.
1456 Fetch test views of child jobs
1457 Check whether the suite was aborted.
Fang Dengaeab6172014-05-07 17:17:04 -07001458 Generate links.
Fang Dengdd20e452014-04-07 15:39:47 -07001459 Calculate suite timings.
1460 Compute return code based on the test result.
1461
1462 """
Simran Basi17ca77c2015-10-14 19:05:00 -07001463 if self._solo_test_run:
David Rileydcd1a642017-03-01 23:15:08 -08001464 self._test_views, self.retry_count, self._missing_results = (
Simran Basi17ca77c2015-10-14 19:05:00 -07001465 self._fetch_test_views_of_child_jobs(
1466 jobs=self._afe.get_jobs(id=self._suite_job_id)))
1467 else:
David Rileydcd1a642017-03-01 23:15:08 -08001468 self._child_views, self._retry_counts, self._missing_results = (
Simran Basi17ca77c2015-10-14 19:05:00 -07001469 self._fetch_test_views_of_child_jobs())
David Rileydcd1a642017-03-01 23:15:08 -08001470 self._suite_views = self._fetch_relevant_test_views_of_suite()
Simran Basi17ca77c2015-10-14 19:05:00 -07001471 self._test_views = self._suite_views + self._child_views
Fang Dengdd20e452014-04-07 15:39:47 -07001472 # For hostless job in Starting status, there is no test view associated.
1473 # This can happen when a suite job in Starting status is aborted. When
1474 # the scheduler hits some limit, e.g., max_hostless_jobs_per_drone,
1475 # max_jobs_started_per_cycle, a suite job can stays in Starting status.
1476 if not self._test_views:
Fang Deng5a43be62014-05-07 17:17:04 -07001477 self.return_code = RETURN_CODES.INFRA_FAILURE
Fang Dengdd20e452014-04-07 15:39:47 -07001478 self.return_message = 'No test view was found.'
1479 return
1480 self.is_aborted = any([view['job_keyvals'].get('aborted_by')
1481 for view in self._suite_views])
Fang Dengdd20e452014-04-07 15:39:47 -07001482 self._generate_web_and_buildbot_links()
1483 self._record_timings()
1484 self._compute_return_code()
1485
1486
MK Ryu977a9752014-10-21 11:58:09 -07001487 def gather_timing_stats(self):
1488 """Collect timing related statistics."""
MK Ryu977a9752014-10-21 11:58:09 -07001489 # Record suite runtime in metadata db.
Prathmesh Prabhua3713a02015-03-11 13:50:55 -07001490 # Some failure modes can leave times unassigned, report sentinel value
1491 # in that case.
1492 runtime_in_secs = -1
1493 if (self.timings.tests_end_time is not None and
1494 self.timings.suite_start_time is not None):
Dan Shi0723bf52015-06-24 10:52:38 -07001495 runtime_in_secs = (self.timings.tests_end_time -
1496 self.timings.suite_start_time).total_seconds()
Prathmesh Prabhua3713a02015-03-11 13:50:55 -07001497
MK Ryu977a9752014-10-21 11:58:09 -07001498 job_overhead.record_suite_runtime(self._suite_job_id, self._suite_name,
1499 self._board, self._build, self._num_child_jobs, runtime_in_secs)
1500
1501
Allen Li0716efa2016-12-08 13:51:31 -08001502def _make_builds_from_options(options):
1503 """Create a dict of builds for creating a suite job.
Prashanth B6285f6a2014-05-08 18:01:27 -07001504
Allen Li0716efa2016-12-08 13:51:31 -08001505 The returned dict maps version label prefixes to build names. Together,
1506 each key-value pair describes a complete label.
Prashanth B6285f6a2014-05-08 18:01:27 -07001507
Allen Li0716efa2016-12-08 13:51:31 -08001508 @param options: SimpleNamespace from argument parsing.
1509
1510 @return: dict mapping version label prefixes to build names
Prashanth B6285f6a2014-05-08 18:01:27 -07001511 """
Dan Shi36cfd832014-10-10 13:38:51 -07001512 builds = {}
1513 if options.build:
Richard Barnette6c2b70a2017-01-26 13:40:51 -08001514 prefix = provision.get_version_label_prefix(options.build)
1515 builds[prefix] = options.build
Dan Shi0723bf52015-06-24 10:52:38 -07001516 if options.firmware_rw_build:
1517 builds[provision.FW_RW_VERSION_PREFIX] = options.firmware_rw_build
Dan Shi36cfd832014-10-10 13:38:51 -07001518 if options.firmware_ro_build:
1519 builds[provision.FW_RO_VERSION_PREFIX] = options.firmware_ro_build
Allen Li0716efa2016-12-08 13:51:31 -08001520 return builds
1521
1522
1523@retry.retry(error.StageControlFileFailure, timeout_min=10)
1524def create_suite(afe, options):
1525 """Create a suite with retries.
1526
1527 @param afe: The afe object to insert the new suite job into.
1528 @param options: The options to use in creating the suite.
1529
1530 @return: The afe_job_id of the new suite job.
1531 """
Prashanth B6285f6a2014-05-08 18:01:27 -07001532 logging.info('%s Submitted create_suite_job rpc',
1533 diagnosis_utils.JobTimer.format_time(datetime.now()))
Allen Li53121702016-12-08 12:50:22 -08001534 return afe.run(
1535 'create_suite_job',
1536 name=options.name,
1537 board=options.board,
Allen Li0716efa2016-12-08 13:51:31 -08001538 builds=_make_builds_from_options(options),
Allen Li53121702016-12-08 12:50:22 -08001539 test_source_build=options.test_source_build,
Allen Li0fd08892016-12-08 13:47:38 -08001540 check_hosts=not options.no_wait,
Allen Li53121702016-12-08 12:50:22 -08001541 pool=options.pool,
1542 num=options.num,
Allen Lid3758d42016-12-08 13:46:17 -08001543 file_bugs=options.file_bugs,
Allen Li603728a2016-12-08 13:58:11 -08001544 priority=options.priority,
Allen Li53121702016-12-08 12:50:22 -08001545 suite_args=options.suite_args,
Allen Li0fd08892016-12-08 13:47:38 -08001546 wait_for_results=not options.no_wait,
Allen Li53121702016-12-08 12:50:22 -08001547 timeout_mins=options.timeout_mins + options.delay_minutes,
1548 max_runtime_mins=options.max_runtime_mins + options.delay_minutes,
1549 job_retry=options.retry,
1550 max_retries=options.max_retries,
1551 suite_min_duts=options.suite_min_duts,
Allen Li40599a32016-12-08 13:23:35 -08001552 offload_failures_only=options.offload_failures_only,
Allen Li53121702016-12-08 12:50:22 -08001553 run_prod_code=options.run_prod_code,
1554 delay_minutes=options.delay_minutes,
Shuqian Zhao843ae5c72017-02-22 11:25:01 -08001555 job_keyvals=options.job_keyvals,
Shuqian Zhaoed0da862017-03-06 14:47:13 -08001556 test_args=options.test_args,
Allen Li53121702016-12-08 12:50:22 -08001557 )
Prashanth B6285f6a2014-05-08 18:01:27 -07001558
1559
Allen Li340414e2016-08-16 14:19:08 -07001560SuiteResult = namedtuple('SuiteResult', ['return_code', 'output_dict'])
1561
1562
Allen Li5e9c35f2017-07-05 14:24:18 -07001563def _run_suite(options):
Aviv Keshet1480c4a2013-03-21 16:38:31 -07001564 """
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001565 run_suite script without exception handling.
Shuqian Zhaod2351072015-08-06 01:48:23 +00001566
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001567 @param options: The parsed options.
1568
1569 @returns: A tuple contains the return_code of run_suite and the dictionary
1570 of the output.
1571
1572 """
Shuqian Zhaoab1bedc2015-06-02 11:12:28 -07001573 # If indicate to use the new style suite control file, convert the args
1574 if options.use_suite_attr:
1575 options = change_options_for_suite_attr(options)
1576
xixuan99eba0b2017-07-12 15:10:01 -07001577 log_name = _get_log_name(options)
1578 utils.setup_logging(logfile=log_name)
Alex Miller88762a82013-09-04 15:41:28 -07001579
John Carey1425d292016-09-30 15:25:09 -07001580 if not options.bypass_labstatus and not options.web:
Fang Deng6197da32014-09-25 10:18:48 -07001581 utils.check_lab_status(options.build)
xixuanae791b12017-06-29 15:40:19 -07001582
1583 afe = _create_afe(options)
1584 instance_server = afe.server
Chris Masone359c0fd2012-03-13 15:18:59 -07001585
Dan Shi20952c12014-05-14 17:07:38 -07001586 rpc_helper = diagnosis_utils.RPCHelper(afe)
Fang Deng6865aab2015-02-20 14:49:47 -08001587 is_real_time = True
Chris Masone986459e2012-04-11 11:36:48 -07001588 if options.mock_job_id:
1589 job_id = int(options.mock_job_id)
Fang Deng6865aab2015-02-20 14:49:47 -08001590 existing_job = afe.get_jobs(id=job_id, finished=True)
1591 if existing_job:
1592 is_real_time = False
1593 else:
1594 existing_job = afe.get_jobs(id=job_id)
1595 if existing_job:
1596 job_created_on = time_utils.date_string_to_epoch_time(
1597 existing_job[0].created_on)
1598 else:
1599 raise utils.TestLabException('Failed to retrieve job: %d' % job_id)
Chris Masone986459e2012-04-11 11:36:48 -07001600 else:
Fang Deng5a43be62014-05-07 17:17:04 -07001601 try:
Fang Deng6865aab2015-02-20 14:49:47 -08001602 rpc_helper.check_dut_availability(options.board, options.pool,
Ningning Xiaf2c206c2016-04-13 14:15:51 -07001603 options.minimum_duts,
1604 options.skip_duts_check)
Prashanth B6285f6a2014-05-08 18:01:27 -07001605 job_id = create_suite(afe, options)
Fang Deng6865aab2015-02-20 14:49:47 -08001606 job_created_on = time.time()
Allen Li6a612392016-08-18 12:09:32 -07001607 except diagnosis_utils.NotEnoughDutsError as e:
1608 e.add_suite_name(options.name)
1609 e.add_build(options.test_source_build)
1610 pool_health_bug = reporting.PoolHealthBug(e)
1611 bug_id = reporting.Reporter().report(pool_health_bug).bug_id
1612 if bug_id is not None:
Allen Lie082ced2016-09-14 15:19:20 -07001613 logging.info(annotations.StepLink(
1614 text='Pool Health Bug',
1615 url=reporting_utils.link_crbug(bug_id)))
Allen Li6a612392016-08-18 12:09:32 -07001616 e.add_bug_id(bug_id)
1617 raise e
Fang Deng5a43be62014-05-07 17:17:04 -07001618 except (error.CrosDynamicSuiteException,
1619 error.RPCException, proxy.JSONRPCException) as e:
Allen Lic3aa7692016-08-08 11:45:00 -07001620 logging.exception('Error Message: %s', e)
1621 return (RETURN_CODES.INFRA_FAILURE, {'return_message': str(e)})
Prashanth B6285f6a2014-05-08 18:01:27 -07001622 except AttributeError:
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001623 return (RETURN_CODES.INVALID_OPTIONS, {})
Fang Deng5a43be62014-05-07 17:17:04 -07001624
Prashanth B923ca262014-03-14 12:36:29 -07001625 job_timer = diagnosis_utils.JobTimer(
Fang Deng6865aab2015-02-20 14:49:47 -08001626 job_created_on, float(options.timeout_mins))
Aviv Keshet9afee5e2014-10-09 16:33:09 -07001627 job_url = reporting_utils.link_job(job_id,
1628 instance_server=instance_server)
Prashanth B923ca262014-03-14 12:36:29 -07001629 logging.info('%s Created suite job: %s',
1630 job_timer.format_time(job_timer.job_created_time),
Aviv Keshet9afee5e2014-10-09 16:33:09 -07001631 job_url)
Allen Lie082ced2016-09-14 15:19:20 -07001632 logging.info(annotations.StepLink(
1633 text='Link to suite',
1634 url=job_url))
Aviv Keshetdb321de2015-04-10 19:09:58 -07001635
1636 if options.create_and_return:
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001637 msg = '--create_and_return was specified, terminating now.'
1638 logging.info(msg)
1639 return (RETURN_CODES.OK, {'return_message':msg})
Aviv Keshetdb321de2015-04-10 19:09:58 -07001640
Allen Li93f4db52016-09-14 14:44:59 -07001641 if options.no_wait:
Allen Li340414e2016-08-16 14:19:08 -07001642 return _handle_job_nowait(job_id, options, instance_server)
Allen Li93f4db52016-09-14 14:44:59 -07001643 else:
1644 return _handle_job_wait(afe, job_id, options, job_timer, is_real_time)
Allen Li340414e2016-08-16 14:19:08 -07001645
1646
xixuan99eba0b2017-07-12 15:10:01 -07001647def _get_log_name(options):
1648 """Return local log file's name.
1649
1650 @param options: Parsed options.
1651
1652 @return log_name, a string file name.
1653 """
1654 if options.require_logfile:
1655 # options.build is verified to exist in verify_options.
1656 # convert build name from containing / to containing only _.
1657 log_name = 'run_suite-%s.log' % options.build.replace('/', '_')
1658 log_dir = os.path.join(common.autotest_dir, 'logs')
1659 if os.path.exists(log_dir):
1660 log_name = os.path.join(log_dir, log_name)
1661
1662 return log_name
1663 else:
1664 return None
1665
1666
xixuanae791b12017-06-29 15:40:19 -07001667def _create_afe(options):
1668 """Return an afe instance based on options.
1669
1670 @param options Parsed options.
1671
1672 @return afe, an AFE instance.
1673 """
1674 instance_server = (options.web if options.web else
1675 instance_for_pool(options.pool))
1676 afe = frontend_wrappers.RetryingAFE(server=instance_server,
1677 timeout_min=options.afe_timeout_mins,
1678 delay_sec=options.delay_sec)
1679 logging.info('Autotest instance created: %s', instance_server)
1680 return afe
1681
1682
Allen Li340414e2016-08-16 14:19:08 -07001683def _handle_job_wait(afe, job_id, options, job_timer, is_real_time):
1684 """Handle suite job synchronously.
1685
1686 @param afe AFE instance.
1687 @param job_id Suite job id.
1688 @param options Parsed options.
1689 @param job_timer JobTimer for suite job.
1690 @param is_real_time Whether or not to handle job timeout.
1691
1692 @return SuiteResult of suite job.
1693 """
Allen Li340414e2016-08-16 14:19:08 -07001694 output_dict = {}
1695 rpc_helper = diagnosis_utils.RPCHelper(afe)
1696 instance_server = afe.server
1697 while not afe.get_jobs(id=job_id, finished=True):
1698 # Note that this call logs output, preventing buildbot's
1699 # 9000 second silent timeout from kicking in. Let there be no
1700 # doubt, this is a hack. The timeout is from upstream buildbot and
1701 # this is the easiest work around.
1702 if job_timer.first_past_halftime():
1703 rpc_helper.diagnose_job(job_id, instance_server)
1704 if job_timer.debug_output_timer.poll():
1705 logging.info('The suite job has another %s till timeout.',
1706 job_timer.timeout_hours - job_timer.elapsed_time())
1707 time.sleep(10)
xixuana96bd212017-01-13 12:51:22 +08001708 logging.info('%s Suite job is finished.',
1709 diagnosis_utils.JobTimer.format_time(datetime.now()))
Allen Li340414e2016-08-16 14:19:08 -07001710 # For most cases, ResultCollector should be able to determine whether
1711 # a suite has timed out by checking information in the test view.
1712 # However, occationally tko parser may fail on parsing the
1713 # job_finished time from the job's keyval file. So we add another
1714 # layer of timeout check in run_suite. We do the check right after
1715 # the suite finishes to make it as accurate as possible.
1716 # There is a minor race condition here where we might have aborted
1717 # for some reason other than a timeout, and the job_timer thinks
1718 # it's a timeout because of the jitter in waiting for results.
1719 # The consequence would be that run_suite exits with code
1720 # SUITE_TIMEOUT while it should have returned INFRA_FAILURE
1721 # instead, which should happen very rarely.
1722 # Note the timeout will have no sense when using -m option.
1723 is_suite_timeout = job_timer.is_suite_timeout()
1724
1725 # Extract the original suite name to record timing.
1726 original_suite_name = get_original_suite_name(options.name,
1727 options.suite_args)
1728 # Start collecting test results.
Aseda Aboagyed72df752017-05-22 14:30:11 -07001729 logging.info('%s Start collecting test results and dump them to json.',
xixuana96bd212017-01-13 12:51:22 +08001730 diagnosis_utils.JobTimer.format_time(datetime.now()))
Alex Millerc7a59522013-10-30 15:18:57 -07001731 TKO = frontend_wrappers.RetryingTKO(server=instance_server,
Simran Basi25effe32013-11-26 13:02:11 -08001732 timeout_min=options.afe_timeout_mins,
Chris Masone8ac66712012-02-15 14:21:02 -08001733 delay_sec=options.delay_sec)
Allen Li340414e2016-08-16 14:19:08 -07001734 collector = ResultCollector(instance_server=instance_server,
1735 afe=afe, tko=TKO, build=options.build,
1736 board=options.board,
1737 suite_name=options.name,
1738 suite_job_id=job_id,
1739 original_suite_name=original_suite_name)
1740 collector.run()
1741 # Dump test outputs into json.
1742 output_dict = collector.get_results_dict()
1743 output_dict['autotest_instance'] = instance_server
1744 if not options.json_dump:
1745 collector.output_results()
1746 code = collector.return_code
1747 return_message = collector.return_message
1748 if is_real_time:
1749 # Do not record stats if the suite was aborted (either by a user
1750 # or through the golo rpc).
1751 # Also do not record stats if is_aborted is None, indicating
1752 # aborting status is unknown yet.
1753 if collector.is_aborted == False:
xixuana96bd212017-01-13 12:51:22 +08001754 logging.info('%s Gathering timing stats for the suite job.',
1755 diagnosis_utils.JobTimer.format_time(datetime.now()))
Allen Li340414e2016-08-16 14:19:08 -07001756 collector.gather_timing_stats()
J. Richard Barnette712eb402013-08-13 18:03:00 -07001757
Allen Li340414e2016-08-16 14:19:08 -07001758 if collector.is_aborted == True and is_suite_timeout:
1759 # There are two possible cases when a suite times out.
1760 # 1. the suite job was aborted due to timing out
1761 # 2. the suite job succeeded, but some child jobs
1762 # were already aborted before the suite job exited.
1763 # The case 2 was handled by ResultCollector,
1764 # here we handle case 1.
1765 old_code = code
1766 code = get_worse_code(
1767 code, RETURN_CODES.SUITE_TIMEOUT)
1768 if old_code != code:
1769 return_message = 'Suite job timed out.'
1770 logging.info('Upgrade return code from %s to %s '
1771 'because suite job has timed out.',
1772 RETURN_CODES.get_string(old_code),
1773 RETURN_CODES.get_string(code))
Fang Deng6865aab2015-02-20 14:49:47 -08001774
xixuana96bd212017-01-13 12:51:22 +08001775 logging.info('\n %s Attempting to display pool info: %s',
1776 diagnosis_utils.JobTimer.format_time(datetime.now()),
1777 options.pool)
Allen Li340414e2016-08-16 14:19:08 -07001778 try:
1779 # Add some jitter to make up for any latency in
1780 # aborting the suite or checking for results.
1781 cutoff = (job_timer.timeout_hours +
Allen Li93f4db52016-09-14 14:44:59 -07001782 timedelta(hours=0.3))
Allen Li340414e2016-08-16 14:19:08 -07001783 rpc_helper.diagnose_pool(
1784 options.board, options.pool, cutoff)
Allen Lid4aa2fb2016-12-08 14:03:54 -08001785 except proxy.JSONRPCException:
Allen Li340414e2016-08-16 14:19:08 -07001786 logging.warning('Unable to display pool info.')
Aviv Keshet6b1122d2016-06-20 13:29:52 -07001787
Allen Li340414e2016-08-16 14:19:08 -07001788 # And output return message.
1789 if return_message:
1790 logging.info('Reason: %s', return_message)
1791 output_dict['return_message'] = return_message
Fang Deng5a43be62014-05-07 17:17:04 -07001792
xixuana96bd212017-01-13 12:51:22 +08001793 logging.info('\n %s Output below this line is for buildbot consumption:',
1794 diagnosis_utils.JobTimer.format_time(datetime.now()))
Allen Lidc2c69a2016-09-14 19:05:47 -07001795 log_buildbot_links(logging.info, collector._buildbot_links)
Allen Li340414e2016-08-16 14:19:08 -07001796 return SuiteResult(code, output_dict)
Prashanth B923ca262014-03-14 12:36:29 -07001797
Allen Li340414e2016-08-16 14:19:08 -07001798
1799def _handle_job_nowait(job_id, options, instance_server):
1800 """Handle suite job asynchronously.
1801
1802 @param job_id Suite job id.
1803 @param options Parsed options.
1804 @param instance_server Autotest instance hostname.
1805
1806 @return SuiteResult of suite job.
1807 """
1808 logging.info('Created suite job: %r', job_id)
1809 link = LogLink(options.name, instance_server,
1810 '%s-%s' % (job_id, getpass.getuser()))
1811 for generate_link in link.GenerateBuildbotLinks():
1812 logging.info(generate_link)
1813 logging.info('--no_wait specified; Exiting.')
1814 return SuiteResult(RETURN_CODES.OK,
1815 {'return_message': '--no_wait specified; Exiting.'})
Chris Masone24b80f12012-02-14 14:18:01 -08001816
Fang Dengdd20e452014-04-07 15:39:47 -07001817
xixuanae791b12017-06-29 15:40:19 -07001818def _should_run(options):
1819 """Check whether the suite should be run based on lab/job status checking.
1820
1821 @param options Parsed options.
1822 """
1823 try:
1824 site_utils.check_lab_status(options.test_source_build)
1825 except site_utils.TestLabException as ex:
1826 logging.exception('Lab is closed or build is blocked. Skipping '
1827 'suite %s, board %s, build %s: %s',
1828 options.name, options.board,
1829 options.test_source_build, str(ex))
1830 return False
1831
1832 start_time = str(datetime.now() -
1833 timedelta(days=_SEARCH_JOB_MAX_DAYS))
1834 afe = _create_afe(options)
1835 return not afe.get_jobs(
1836 name__istartswith=options.test_source_build,
1837 name__iendswith='control.'+options.name,
1838 created_on__gte=start_time,
1839 min_rpc_timeout=_MIN_RPC_TIMEOUT)
1840
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001841
Allen Li5e9c35f2017-07-05 14:24:18 -07001842def _run_task(parser, options):
1843 """Perform this script's function minus setup.
Aviv Keshet97bebd42017-05-24 21:02:32 -07001844
Allen Li5e9c35f2017-07-05 14:24:18 -07001845 Boilerplate like argument parsing, logging, output formatting happen
1846 elsewhere.
1847 """
Fang Dengfb4a9492014-09-18 17:52:06 -07001848 try:
Allen Li93f4db52016-09-14 14:44:59 -07001849 if not verify_options(options):
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001850 parser.print_help()
1851 code = RETURN_CODES.INVALID_OPTIONS
Allen Lidc2c69a2016-09-14 19:05:47 -07001852 output_dict = {'return_code': RETURN_CODES.INVALID_OPTIONS}
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001853 else:
xixuanae791b12017-06-29 15:40:19 -07001854 if options.pre_check and not _should_run(options):
1855 logging.info('Lab is closed, OR build %s is blocked, OR suite '
1856 '%s for this build has already been kicked off '
1857 'once in past %d days.',
1858 options.test_source_build, options.name,
1859 _SEARCH_JOB_MAX_DAYS)
1860 return
1861
Allen Li5e9c35f2017-07-05 14:24:18 -07001862 code, output_dict = _run_suite(options)
Shuqian Zhaoade6e7d2015-12-07 18:01:11 -08001863 except diagnosis_utils.BoardNotAvailableError as e:
Allen Lidc2c69a2016-09-14 19:05:47 -07001864 output_dict = {'return_message': 'Skipping testing: %s' % e.message}
Shuqian Zhaoade6e7d2015-12-07 18:01:11 -08001865 code = RETURN_CODES.BOARD_NOT_AVAILABLE
1866 logging.info(output_dict['return_message'])
1867 except utils.TestLabException as e:
Allen Lidc2c69a2016-09-14 19:05:47 -07001868 output_dict = {'return_message': 'TestLabException: %s' % e}
Shuqian Zhaoade6e7d2015-12-07 18:01:11 -08001869 code = RETURN_CODES.INFRA_FAILURE
1870 logging.exception(output_dict['return_message'])
Fang Dengfb4a9492014-09-18 17:52:06 -07001871 except Exception as e:
Allen Lidc2c69a2016-09-14 19:05:47 -07001872 output_dict = {
1873 'return_message': 'Unhandled run_suite exception: %s' % e
1874 }
Shuqian Zhaoade6e7d2015-12-07 18:01:11 -08001875 code = RETURN_CODES.INFRA_FAILURE
1876 logging.exception(output_dict['return_message'])
Allen Li5e9c35f2017-07-05 14:24:18 -07001877 return code, output_dict
1878
1879
1880def main():
1881 """Entry point."""
1882 utils.verify_not_root_user()
1883
1884 parser = make_parser()
1885 options = parser.parse_args()
1886 if options.do_nothing:
1887 return
1888 # Silence the log when dumping outputs into json
1889 if options.json_dump:
1890 logging.disable(logging.CRITICAL)
1891
1892 code, output_dict = _run_task(parser, options)
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001893
1894 # Dump test outputs into json.
1895 output_dict['return_code'] = code
Shuqian Zhao2fecacd2015-08-05 22:56:30 -07001896 if options.json_dump:
Allen Lidc2c69a2016-09-14 19:05:47 -07001897 output_json = json.dumps(output_dict, sort_keys=True)
Shuqian Zhao74ca35d2015-11-25 14:33:50 -08001898 output_json_marked = '#JSON_START#%s#JSON_END#' % output_json.strip()
1899 sys.stdout.write(output_json_marked)
Fang Deng6197da32014-09-25 10:18:48 -07001900
1901 logging.info('Will return from run_suite with status: %s',
1902 RETURN_CODES.get_string(code))
1903 return code
Fang Dengfb4a9492014-09-18 17:52:06 -07001904
1905
Chris Masone24b80f12012-02-14 14:18:01 -08001906if __name__ == "__main__":
1907 sys.exit(main())