blob: c8a6a9fbf035f0b30e39d495d414ced117f9596f [file] [log] [blame]
Dan Shi7e04fa82013-07-25 15:08:48 -07001#!/usr/bin/python
2#
3# Copyright (c) 2013 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
7"""Tool to validate code in prod branch before pushing to lab.
8
9The script runs push_to_prod suite to verify code in prod branch is ready to be
10pushed. Link to design document:
11https://docs.google.com/a/google.com/document/d/1JMz0xS3fZRSHMpFkkKAL_rxsdbNZomhHbC3B8L71uuI/edit
12
13To verify if prod branch can be pushed to lab, run following command in
14chromeos-autotest.cbf server:
Michael Liang52d9f1f2014-06-17 15:01:24 -070015/usr/local/autotest/site_utils/test_push.py -e someone@company.com
Dan Shi7e04fa82013-07-25 15:08:48 -070016
Shuqian Zhaof3a114c2016-09-21 11:02:15 -070017The script uses latest gandof stable build as test build by default.
Dan Shi7e04fa82013-07-25 15:08:48 -070018
19"""
20
21import argparse
Shuqian Zhao1f311c02016-09-01 19:30:54 -070022import ast
Dan Shi7e04fa82013-07-25 15:08:48 -070023import getpass
Dan Shief1a5c02015-04-07 17:37:09 -070024import multiprocessing
Dan Shi7e04fa82013-07-25 15:08:48 -070025import os
26import re
27import subprocess
28import sys
Dan Shief1a5c02015-04-07 17:37:09 -070029import time
30import traceback
Dan Shi7e04fa82013-07-25 15:08:48 -070031import urllib2
32
33import common
Dan Shia8da7602014-05-09 15:18:15 -070034try:
35 from autotest_lib.frontend import setup_django_environment
36 from autotest_lib.frontend.afe import models
Shuqian Zhao327b6952016-09-12 10:42:03 -070037 from autotest_lib.frontend.afe import rpc_utils
Dan Shia8da7602014-05-09 15:18:15 -070038except ImportError:
39 # Unittest may not have Django database configured and will fail to import.
40 pass
Dan Shi5fa602c2015-03-26 17:54:13 -070041from autotest_lib.client.common_lib import global_config
Shuqian Zhao327b6952016-09-12 10:42:03 -070042from autotest_lib.client.common_lib import priorities
Dan Shi7e04fa82013-07-25 15:08:48 -070043from autotest_lib.server import site_utils
Shuqian Zhao327b6952016-09-12 10:42:03 -070044from autotest_lib.server import utils
Dan Shi47d32882014-12-22 16:25:05 -080045from autotest_lib.server.cros import provision
Dan Shi7e04fa82013-07-25 15:08:48 -070046from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Dan Shi5fa602c2015-03-26 17:54:13 -070047from autotest_lib.site_utils import gmail_lib
Dan Shi47d32882014-12-22 16:25:05 -080048from autotest_lib.site_utils.suite_scheduler import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070049
Shuqian Zhao12861662016-08-31 19:23:17 -070050AUTOTEST_DIR='/usr/local/autotest'
Dan Shi7e04fa82013-07-25 15:08:48 -070051CONFIG = global_config.global_config
52
Dan Shiefd403e2016-02-03 11:37:02 -080053AFE = frontend_wrappers.RetryingAFE(timeout_min=0.5, delay_sec=2)
Shuqian Zhao327b6952016-09-12 10:42:03 -070054TKO = frontend_wrappers.RetryingTKO(timeout_min=0.1, delay_sec=10)
Dan Shiefd403e2016-02-03 11:37:02 -080055
Dan Shi7e04fa82013-07-25 15:08:48 -070056MAIL_FROM = 'chromeos-test@google.com'
Shuqian Zhao12861662016-08-31 19:23:17 -070057BUILD_REGEX = 'R[\d]+-[\d]+\.[\d]+\.[\d]+'
Dan Shi7e04fa82013-07-25 15:08:48 -070058RUN_SUITE_COMMAND = 'run_suite.py'
59PUSH_TO_PROD_SUITE = 'push_to_prod'
Jakob Juelich8f143912014-10-10 14:08:05 -070060DUMMY_SUITE = 'dummy'
Shuqian Zhaof3a114c2016-09-21 11:02:15 -070061AU_SUITE = 'paygen_au_beta'
Dan Shi81ddc422016-09-09 13:58:31 -070062TESTBED_SUITE = 'testbed_push'
Shuqian Zhao8ac22e82016-09-22 14:26:18 -070063# TODO(shuqianz): Dynamically get android build after crbug.com/646068 fixed
xixuan2d668582016-06-10 14:02:32 -070064DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB = 30
Shuqian Zhao12861662016-08-31 19:23:17 -070065IMAGE_BUCKET = CONFIG.get_config_value('CROS', 'image_storage_server')
Shuqian Zhao8ac22e82016-09-22 14:26:18 -070066DEFAULT_EMAIL = CONFIG.get_config_value(
67 'SCHEDULER', 'notify_email', type=str, default='')
Shuqian Zhao1f311c02016-09-01 19:30:54 -070068DEFAULT_NUM_DUTS = "{'board:gandof': 4, 'board:quawks': 2}"
Dan Shi7e04fa82013-07-25 15:08:48 -070069
Fang Deng6dddf602014-04-17 17:01:47 -070070SUITE_JOB_START_INFO_REGEX = ('^.*Created suite job:.*'
71 'tab_id=view_job&object_id=(\d+)$')
Dan Shi7e04fa82013-07-25 15:08:48 -070072
73# Dictionary of test results keyed by test name regular expression.
74EXPECTED_TEST_RESULTS = {'^SERVER_JOB$': 'GOOD',
75 # This is related to dummy_Fail/control.dependency.
76 'dummy_Fail.dependency$': 'TEST_NA',
Dan Shidc9eb172014-12-09 16:05:02 -080077 'login_LoginSuccess.*': 'GOOD',
Dan Shi47d32882014-12-22 16:25:05 -080078 'provision_AutoUpdate.double': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -070079 'dummy_Pass.*': 'GOOD',
80 'dummy_Fail.Fail$': 'FAIL',
81 'dummy_Fail.RetryFail$': 'FAIL',
82 'dummy_Fail.RetrySuccess': 'GOOD',
83 'dummy_Fail.Error$': 'ERROR',
84 'dummy_Fail.Warn$': 'WARN',
85 'dummy_Fail.NAError$': 'TEST_NA',
86 'dummy_Fail.Crash$': 'GOOD',
87 }
88
Jakob Juelich8f143912014-10-10 14:08:05 -070089EXPECTED_TEST_RESULTS_DUMMY = {'^SERVER_JOB$': 'GOOD',
90 'dummy_Pass.*': 'GOOD',
91 'dummy_Fail.Fail': 'FAIL',
92 'dummy_Fail.Warn': 'WARN',
93 'dummy_Fail.Crash': 'GOOD',
94 'dummy_Fail.Error': 'ERROR',
95 'dummy_Fail.NAError': 'TEST_NA',}
96
Dan Shi7e04fa82013-07-25 15:08:48 -070097EXPECTED_TEST_RESULTS_AU = {'SERVER_JOB$': 'GOOD',
Shuqian Zhaof3a114c2016-09-21 11:02:15 -070098 'autoupdate_EndToEndTest.paygen_au_beta_delta.*': 'GOOD',
99 'autoupdate_EndToEndTest.paygen_au_beta_full.*': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -0700100 }
101
Dan Shi81ddc422016-09-09 13:58:31 -0700102EXPECTED_TEST_RESULTS_TESTBED = {'^SERVER_JOB$': 'GOOD',
103 'testbed_DummyTest': 'GOOD',}
104
Shuqian Zhao327b6952016-09-12 10:42:03 -0700105EXPECTED_TEST_RESULTS_POWERWASH = {'platform_Powerwash': 'GOOD',
106 'SERVER_JOB': 'GOOD'}
107
Dan Shi7e04fa82013-07-25 15:08:48 -0700108URL_HOST = CONFIG.get_config_value('SERVER', 'hostname', type=str)
109URL_PATTERN = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str)
110
Dan Shidc9eb172014-12-09 16:05:02 -0800111# Some test could be missing from the test results for various reasons. Add
112# such test in this list and explain the reason.
113IGNORE_MISSING_TESTS = [
114 # For latest build, npo_test_delta does not exist.
115 'autoupdate_EndToEndTest.npo_test_delta.*',
116 # For trybot build, nmo_test_delta does not exist.
117 'autoupdate_EndToEndTest.nmo_test_delta.*',
118 # Older build does not have login_LoginSuccess test in push_to_prod suite.
119 # TODO(dshi): Remove following lines after R41 is stable.
120 'login_LoginSuccess']
121
Dan Shi7e04fa82013-07-25 15:08:48 -0700122# Save all run_suite command output.
Shuqian Zhao7b682192016-09-16 14:38:41 -0700123manager = multiprocessing.Manager()
124run_suite_output = manager.list()
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700125all_suite_ids = manager.list()
Dan Shi7e04fa82013-07-25 15:08:48 -0700126
127class TestPushException(Exception):
128 """Exception to be raised when the test to push to prod failed."""
129 pass
130
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700131
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700132def check_dut_inventory(required_num_duts):
133 """Check DUT inventory for each board.
134
135 @param required_num_duts: a dict specified the number of DUT each board
136 requires in order to finish push tests.
137 @raise TestPushException: if number of DUTs are less than the requirement.
138 """
139 hosts = AFE.run('get_hosts', status='Ready', locked=False)
140 boards = [[l for l in host['labels'] if l.startswith('board:')][0]
141 for host in hosts]
142 current_inventory = {b:boards.count(b) for b in boards}
143 error_msg = ''
144 for board, req_num in required_num_duts.items():
145 curr_num = current_inventory.get(board, 0)
146 if curr_num < req_num:
147 error_msg += ('\nRequire %d %s DUTs, only %d are Ready now' %
148 (req_num, board, curr_num))
149 if error_msg:
150 raise TestPushException('Not enough DUTs to run push tests. %s' %
151 error_msg)
152
153
Shuqian Zhao327b6952016-09-12 10:42:03 -0700154def powerwash_dut_to_test_repair(hostname, timeout):
155 """Powerwash dut to test repair workflow.
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800156
157 @param hostname: hostname of the dut.
Shuqian Zhao327b6952016-09-12 10:42:03 -0700158 @param timeout: seconds of the powerwash test to hit timeout.
159 @raise TestPushException: if DUT fail to run the test.
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800160 """
Shuqian Zhao327b6952016-09-12 10:42:03 -0700161 t = models.Test.objects.get(name='platform_Powerwash')
162 c = utils.read_file(os.path.join(common.autotest_dir, t.path))
163 job_id = rpc_utils.create_job_common(
164 'powerwash', priority=priorities.Priority.SUPER,
165 control_type='Server', control_file=c, hosts=[hostname])
166
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700167 end = time.time() + timeout
Shuqian Zhao327b6952016-09-12 10:42:03 -0700168 while not TKO.get_job_test_statuses_from_db(job_id):
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700169 if time.time() >= end:
170 AFE.run('abort_host_queue_entries', job=job_id)
Shuqian Zhao327b6952016-09-12 10:42:03 -0700171 raise TestPushException(
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700172 'Powerwash test on %s timeout after %ds, abort it.' %
173 (hostname, timeout))
Shuqian Zhao327b6952016-09-12 10:42:03 -0700174 time.sleep(10)
175 verify_test_results(job_id, EXPECTED_TEST_RESULTS_POWERWASH)
176 # Kick off verify, verify will fail and a repair should be triggered.
177 AFE.reverify_hosts(hostnames=[hostname])
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800178
179
Shuqian Zhaod2a99f02016-09-22 13:31:30 -0700180def reverify_all_push_duts(pool):
181 """Reverify all the push DUTs.
182
183 @param pool: Name of the pool used by test_push.
184 """
185 pool_label = constants.Labels.POOL_PREFIX + pool
186 hosts = [h.hostname for h in AFE.get_hosts(label=pool_label)]
187 AFE.reverify_hosts(hostnames=hosts)
188
189
Shuqian Zhao12861662016-08-31 19:23:17 -0700190def get_default_build(board='gandof'):
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700191 """Get the default build to be used for test.
192
Dan Shi8df9c002016-03-08 15:37:39 -0800193 @param board: Name of board to be tested, default is gandof.
194 @return: Build to be tested, e.g., gandof-release/R36-5881.0.0
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700195 """
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700196 build = None
Shuqian Zhao12861662016-08-31 19:23:17 -0700197 cmd = ('%s/cli/atest stable_version list --board=%s -w cautotest' %
198 (AUTOTEST_DIR, board))
199 result = subprocess.check_output(cmd, shell=True).strip()
200 build = re.search(BUILD_REGEX, result)
201 if build:
202 return '%s-release/%s' % (board, build.group(0))
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700203
Shuqian Zhao12861662016-08-31 19:23:17 -0700204 # If fail to get stable version from cautotest, use that defined in config
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700205 build = CONFIG.get_config_value('CROS', 'stable_cros_version')
206 return '%s-release/%s' % (board, build)
207
Dan Shi7e04fa82013-07-25 15:08:48 -0700208def parse_arguments():
209 """Parse arguments for test_push tool.
210
211 @return: Parsed arguments.
212
213 """
214 parser = argparse.ArgumentParser()
Dan Shi8df9c002016-03-08 15:37:39 -0800215 parser.add_argument('-b', '--board', dest='board', default='gandof',
216 help='Default is gandof.')
Jakob Juelich8f143912014-10-10 14:08:05 -0700217 parser.add_argument('-sb', '--shard_board', dest='shard_board',
218 default='quawks',
219 help='Default is quawks.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700220 parser.add_argument('-i', '--build', dest='build', default=None,
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700221 help='Default is the latest stale build of given '
222 'board. Must be a stable build, otherwise AU test '
223 'will fail. (ex: gandolf-release/R54-8743.25.0)')
Jakob Juelich8f143912014-10-10 14:08:05 -0700224 parser.add_argument('-si', '--shard_build', dest='shard_build', default=None,
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700225 help='Default is the latest stable build of given '
226 'board. Must be a stable build, otherwise AU test '
Jakob Juelich8f143912014-10-10 14:08:05 -0700227 'will fail.')
Dan Shi81ddc422016-09-09 13:58:31 -0700228 parser.add_argument('-ab', '--android_board', dest='android_board',
Shuqian Zhao8ac22e82016-09-22 14:26:18 -0700229 default='shamu-2', help='Android board to test.')
Dan Shi81ddc422016-09-09 13:58:31 -0700230 parser.add_argument('-ai', '--android_build', dest='android_build',
231 help='Android build to test.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700232 parser.add_argument('-p', '--pool', dest='pool', default='bvt')
233 parser.add_argument('-u', '--num', dest='num', type=int, default=3,
234 help='Run on at most NUM machines.')
Shuqian Zhao8ac22e82016-09-22 14:26:18 -0700235 parser.add_argument('-e', '--email', dest='email', default=DEFAULT_EMAIL,
Dan Shi7e04fa82013-07-25 15:08:48 -0700236 help='Email address for the notification to be sent to '
237 'after the script finished running.')
Shuqian Zhaod4864772015-08-06 09:46:22 -0700238 parser.add_argument('-t', '--timeout_min', dest='timeout_min', type=int,
xixuan2d668582016-06-10 14:02:32 -0700239 default=DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB,
Shuqian Zhaod4864772015-08-06 09:46:22 -0700240 help='Time in mins to wait before abort the jobs we '
241 'are waiting on. Only for the asynchronous suites '
242 'triggered by create_and_return flag.')
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700243 parser.add_argument('-ud', '--num_duts', dest='num_duts',
244 default=DEFAULT_NUM_DUTS,
245 help="String of dict that indicates the required number"
246 " of DUTs for each board. E.g {'gandof':4}")
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700247 parser.add_argument('-c', '--continue_on_failure', action='store_true',
248 dest='continue_on_failure',
249 help='All tests continue to run when there is failure')
Dan Shi7e04fa82013-07-25 15:08:48 -0700250
251 arguments = parser.parse_args(sys.argv[1:])
252
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700253 # Get latest stable build as default build.
Dan Shi7e04fa82013-07-25 15:08:48 -0700254 if not arguments.build:
Shuqian Zhao12861662016-08-31 19:23:17 -0700255 arguments.build = get_default_build(arguments.board)
Jakob Juelich8f143912014-10-10 14:08:05 -0700256 if not arguments.shard_build:
Shuqian Zhao12861662016-08-31 19:23:17 -0700257 arguments.shard_build = get_default_build(arguments.shard_board)
Dan Shi7e04fa82013-07-25 15:08:48 -0700258
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700259 arguments.num_duts = ast.literal_eval(arguments.num_duts)
260
Dan Shi7e04fa82013-07-25 15:08:48 -0700261 return arguments
262
263
Shuqian Zhaod4864772015-08-06 09:46:22 -0700264def do_run_suite(suite_name, arguments, use_shard=False,
Dan Shi81ddc422016-09-09 13:58:31 -0700265 create_and_return=False, testbed_test=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700266 """Call run_suite to run a suite job, and return the suite job id.
267
268 The script waits the suite job to finish before returning the suite job id.
269 Also it will echo the run_suite output to stdout.
270
271 @param suite_name: Name of a suite, e.g., dummy.
272 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700273 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700274 @param create_and_return: If True, run_suite just creates the suite, print
275 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700276 @param testbed_test: True to run testbed test. Default is False.
Jakob Juelich8f143912014-10-10 14:08:05 -0700277
Dan Shi7e04fa82013-07-25 15:08:48 -0700278 @return: Suite job ID.
279
280 """
Dan Shi81ddc422016-09-09 13:58:31 -0700281 if use_shard and not testbed_test:
Jakob Juelich8f143912014-10-10 14:08:05 -0700282 board = arguments.shard_board
283 build = arguments.shard_build
Dan Shi81ddc422016-09-09 13:58:31 -0700284 elif testbed_test:
285 board = arguments.android_board
286 build = arguments.android_build
287 else:
288 board = arguments.board
289 build = arguments.build
Jakob Juelich8f143912014-10-10 14:08:05 -0700290
Dan Shi47d32882014-12-22 16:25:05 -0800291 # Remove cros-version label to force provision.
Dan Shiefd403e2016-02-03 11:37:02 -0800292 hosts = AFE.get_hosts(label=constants.Labels.BOARD_PREFIX+board)
Dan Shi47d32882014-12-22 16:25:05 -0800293 for host in hosts:
Dan Shi81ddc422016-09-09 13:58:31 -0700294 labels_to_remove = [
295 l for l in host.labels
296 if (l.startswith(provision.CROS_VERSION_PREFIX) or
297 l.startswith(provision.TESTBED_BUILD_VERSION_PREFIX))]
298 if labels_to_remove:
299 AFE.run('host_remove_labels', id=host.id, labels=labels_to_remove)
Dan Shi47d32882014-12-22 16:25:05 -0800300
Shuqian Zhao327b6952016-09-12 10:42:03 -0700301 # Test repair work flow on shards
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800302 if use_shard and not create_and_return:
Shuqian Zhao327b6952016-09-12 10:42:03 -0700303 powerwash_dut_to_test_repair(host.hostname, timeout=300)
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800304
Dan Shief1a5c02015-04-07 17:37:09 -0700305 current_dir = os.path.dirname(os.path.realpath(__file__))
306 cmd = [os.path.join(current_dir, RUN_SUITE_COMMAND),
Dan Shi7e04fa82013-07-25 15:08:48 -0700307 '-s', suite_name,
Jakob Juelich8f143912014-10-10 14:08:05 -0700308 '-b', board,
309 '-i', build,
Dan Shi7e04fa82013-07-25 15:08:48 -0700310 '-p', arguments.pool,
Shuqian Zhao178ac012016-06-03 15:08:52 -0700311 '-u', str(arguments.num)]
Shuqian Zhaod4864772015-08-06 09:46:22 -0700312 if create_and_return:
313 cmd += ['-c']
Dan Shi81ddc422016-09-09 13:58:31 -0700314 if testbed_test:
315 cmd += ['--run_prod_code']
Dan Shi7e04fa82013-07-25 15:08:48 -0700316
317 suite_job_id = None
Dan Shi7e04fa82013-07-25 15:08:48 -0700318
319 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
320 stderr=subprocess.STDOUT)
321
322 while True:
323 line = proc.stdout.readline()
324
325 # Break when run_suite process completed.
326 if not line and proc.poll() != None:
327 break
328 print line.rstrip()
329 run_suite_output.append(line.rstrip())
330
331 if not suite_job_id:
332 m = re.match(SUITE_JOB_START_INFO_REGEX, line)
333 if m and m.group(1):
334 suite_job_id = int(m.group(1))
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700335 all_suite_ids.append(suite_job_id)
Dan Shi7e04fa82013-07-25 15:08:48 -0700336
337 if not suite_job_id:
338 raise TestPushException('Failed to retrieve suite job ID.')
Dan Shia8da7602014-05-09 15:18:15 -0700339
Shuqian Zhaod4864772015-08-06 09:46:22 -0700340 # If create_and_return specified, wait for the suite to finish.
341 if create_and_return:
342 end = time.time() + arguments.timeout_min * 60
Dan Shiefd403e2016-02-03 11:37:02 -0800343 while not AFE.get_jobs(id=suite_job_id, finished=True):
Shuqian Zhaod4864772015-08-06 09:46:22 -0700344 if time.time() < end:
345 time.sleep(10)
346 else:
Dan Shiefd403e2016-02-03 11:37:02 -0800347 AFE.run('abort_host_queue_entries', job=suite_job_id)
Shuqian Zhaod4864772015-08-06 09:46:22 -0700348 raise TestPushException(
349 'Asynchronous suite triggered by create_and_return '
350 'flag has timed out after %d mins. Aborting it.' %
351 arguments.timeout_min)
352
Dan Shia8da7602014-05-09 15:18:15 -0700353 print 'Suite job %s is completed.' % suite_job_id
Dan Shi7e04fa82013-07-25 15:08:48 -0700354 return suite_job_id
355
356
Dan Shia8da7602014-05-09 15:18:15 -0700357def check_dut_image(build, suite_job_id):
358 """Confirm all DUTs used for the suite are imaged to expected build.
359
360 @param build: Expected build to be imaged.
361 @param suite_job_id: job ID of the suite job.
362 @raise TestPushException: If a DUT does not have expected build imaged.
363 """
364 print 'Checking image installed in DUTs...'
365 job_ids = [job.id for job in
366 models.Job.objects.filter(parent_job_id=suite_job_id)]
367 hqes = [models.HostQueueEntry.objects.filter(job_id=job_id)[0]
368 for job_id in job_ids]
369 hostnames = set([hqe.host.hostname for hqe in hqes])
370 for hostname in hostnames:
Dan Shiefd403e2016-02-03 11:37:02 -0800371 found_build = site_utils.get_build_from_afe(hostname, AFE)
Dan Shia8da7602014-05-09 15:18:15 -0700372 if found_build != build:
373 raise TestPushException('DUT is not imaged properly. Host %s has '
374 'build %s, while build %s is expected.' %
375 (hostname, found_build, build))
376
377
Shuqian Zhaod4864772015-08-06 09:46:22 -0700378def test_suite(suite_name, expected_results, arguments, use_shard=False,
Dan Shi81ddc422016-09-09 13:58:31 -0700379 create_and_return=False, testbed_test=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700380 """Call run_suite to start a suite job and verify results.
381
382 @param suite_name: Name of a suite, e.g., dummy
383 @param expected_results: A dictionary of test name to test result.
384 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700385 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700386 @param create_and_return: If True, run_suite just creates the suite, print
387 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700388 @param testbed_test: True to run testbed test. Default is False.
Dan Shi7e04fa82013-07-25 15:08:48 -0700389 """
Shuqian Zhaod4864772015-08-06 09:46:22 -0700390 suite_job_id = do_run_suite(suite_name, arguments, use_shard,
Dan Shi81ddc422016-09-09 13:58:31 -0700391 create_and_return, testbed_test)
Dan Shi7e04fa82013-07-25 15:08:48 -0700392
Dan Shia8da7602014-05-09 15:18:15 -0700393 # Confirm all DUTs used for the suite are imaged to expected build.
Jakob Juelich8f143912014-10-10 14:08:05 -0700394 # hqe.host_id for jobs running in shard is not synced back to master db,
395 # therefore, skip verifying dut build for jobs running in shard.
Dan Shi81ddc422016-09-09 13:58:31 -0700396 build_expected = (arguments.android_build if testbed_test
397 else arguments.build)
398 if suite_name != AU_SUITE and not use_shard and not testbed_test:
399 check_dut_image(build_expected, suite_job_id)
Dan Shia8da7602014-05-09 15:18:15 -0700400
Shuqian Zhao327b6952016-09-12 10:42:03 -0700401 # Verify test results are the expected results.
402 verify_test_results(suite_job_id, expected_results)
403
404
405def verify_test_results(job_id, expected_results):
406 """Verify the test results with the expected results.
407
408 @param job_id: id of the running jobs. For suite job, it is suite_job_id.
409 @param expected_results: A dictionary of test name to test result.
410 @raise TestPushException: If verify fails.
411 """
Dan Shia8da7602014-05-09 15:18:15 -0700412 print 'Comparing test results...'
Shuqian Zhao327b6952016-09-12 10:42:03 -0700413 test_views = site_utils.get_test_views_from_tko(job_id, TKO)
Dan Shi7e04fa82013-07-25 15:08:48 -0700414
415 mismatch_errors = []
416 extra_test_errors = []
417
418 found_keys = set()
Shuqian Zhao327b6952016-09-12 10:42:03 -0700419 for test_name, test_status in test_views.items():
Dan Shi7e04fa82013-07-25 15:08:48 -0700420 print "%s%s" % (test_name.ljust(30), test_status)
Dan Shi80b6ec02016-07-21 15:49:18 -0700421 # platform_InstallTestImage test may exist in old builds.
422 if re.search('platform_InstallTestImage_SERVER_JOB$', test_name):
423 continue
Dan Shi7e04fa82013-07-25 15:08:48 -0700424 test_found = False
425 for key,val in expected_results.items():
426 if re.search(key, test_name):
427 test_found = True
428 found_keys.add(key)
Dan Shi7e04fa82013-07-25 15:08:48 -0700429 if val != test_status:
430 error = ('%s Expected: [%s], Actual: [%s]' %
431 (test_name, val, test_status))
432 mismatch_errors.append(error)
433 if not test_found:
434 extra_test_errors.append(test_name)
435
436 missing_test_errors = set(expected_results.keys()) - found_keys
Dan Shidc9eb172014-12-09 16:05:02 -0800437 for exception in IGNORE_MISSING_TESTS:
438 try:
439 missing_test_errors.remove(exception)
440 except KeyError:
441 pass
442
Dan Shi7e04fa82013-07-25 15:08:48 -0700443 summary = []
444 if mismatch_errors:
445 summary.append(('Results of %d test(s) do not match expected '
446 'values:') % len(mismatch_errors))
447 summary.extend(mismatch_errors)
448 summary.append('\n')
449
450 if extra_test_errors:
451 summary.append('%d test(s) are not expected to be run:' %
452 len(extra_test_errors))
453 summary.extend(extra_test_errors)
454 summary.append('\n')
455
456 if missing_test_errors:
457 summary.append('%d test(s) are missing from the results:' %
458 len(missing_test_errors))
459 summary.extend(missing_test_errors)
460 summary.append('\n')
461
462 # Test link to log can be loaded.
Shuqian Zhao327b6952016-09-12 10:42:03 -0700463 job_name = '%s-%s' % (job_id, getpass.getuser())
Dan Shi7e04fa82013-07-25 15:08:48 -0700464 log_link = URL_PATTERN % (URL_HOST, job_name)
465 try:
466 urllib2.urlopen(log_link).read()
467 except urllib2.URLError:
468 summary.append('Failed to load page for link to log: %s.' % log_link)
469
470 if summary:
471 raise TestPushException('\n'.join(summary))
472
473
Dan Shief1a5c02015-04-07 17:37:09 -0700474def test_suite_wrapper(queue, suite_name, expected_results, arguments,
Dan Shi81ddc422016-09-09 13:58:31 -0700475 use_shard=False, create_and_return=False,
476 testbed_test=False):
Dan Shief1a5c02015-04-07 17:37:09 -0700477 """Wrapper to call test_suite. Handle exception and pipe it to parent
478 process.
479
480 @param queue: Queue to save exception to be accessed by parent process.
481 @param suite_name: Name of a suite, e.g., dummy
482 @param expected_results: A dictionary of test name to test result.
483 @param arguments: Arguments for run_suite command.
484 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700485 @param create_and_return: If True, run_suite just creates the suite, print
486 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700487 @param testbed_test: True to run testbed test. Default is False.
Dan Shief1a5c02015-04-07 17:37:09 -0700488 """
489 try:
Shuqian Zhaod4864772015-08-06 09:46:22 -0700490 test_suite(suite_name, expected_results, arguments, use_shard,
Dan Shi81ddc422016-09-09 13:58:31 -0700491 create_and_return, testbed_test)
Dan Shief1a5c02015-04-07 17:37:09 -0700492 except:
493 # Store the whole exc_info leads to a PicklingError.
494 except_type, except_value, tb = sys.exc_info()
495 queue.put((except_type, except_value, traceback.extract_tb(tb)))
496
497
Dan Shief1a5c02015-04-07 17:37:09 -0700498def check_queue(queue):
499 """Check the queue for any exception being raised.
500
501 @param queue: Queue used to store exception for parent process to access.
502 @raise: Any exception found in the queue.
503 """
504 if queue.empty():
505 return
506 exc_info = queue.get()
507 # Raise the exception with original backtrace.
508 print 'Original stack trace of the exception:\n%s' % exc_info[2]
509 raise exc_info[0](exc_info[1])
510
511
Dan Shi7e04fa82013-07-25 15:08:48 -0700512def main():
513 """Entry point for test_push script."""
514 arguments = parse_arguments()
515
516 try:
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700517 # Use daemon flag will kill child processes when parent process fails.
518 use_daemon = not arguments.continue_on_failure
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700519 check_dut_inventory(arguments.num_duts)
Dan Shief1a5c02015-04-07 17:37:09 -0700520 queue = multiprocessing.Queue()
521
522 push_to_prod_suite = multiprocessing.Process(
523 target=test_suite_wrapper,
524 args=(queue, PUSH_TO_PROD_SUITE, EXPECTED_TEST_RESULTS,
525 arguments))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700526 push_to_prod_suite.daemon = use_daemon
Dan Shief1a5c02015-04-07 17:37:09 -0700527 push_to_prod_suite.start()
Jakob Juelich8f143912014-10-10 14:08:05 -0700528
Dan Shi7e04fa82013-07-25 15:08:48 -0700529 # TODO(dshi): Remove following line after crbug.com/267644 is fixed.
530 # Also, merge EXPECTED_TEST_RESULTS_AU to EXPECTED_TEST_RESULTS
Dan Shief1a5c02015-04-07 17:37:09 -0700531 au_suite = multiprocessing.Process(
532 target=test_suite_wrapper,
533 args=(queue, AU_SUITE, EXPECTED_TEST_RESULTS_AU,
534 arguments))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700535 au_suite.daemon = use_daemon
Dan Shief1a5c02015-04-07 17:37:09 -0700536 au_suite.start()
537
538 shard_suite = multiprocessing.Process(
539 target=test_suite_wrapper,
540 args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY,
541 arguments, True))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700542 shard_suite.daemon = use_daemon
Dan Shief1a5c02015-04-07 17:37:09 -0700543 shard_suite.start()
544
Shuqian Zhaod4864772015-08-06 09:46:22 -0700545 # suite test with --create_and_return flag
546 asynchronous_suite = multiprocessing.Process(
547 target=test_suite_wrapper,
548 args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY,
549 arguments, True, True))
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700550 asynchronous_suite.daemon = True
Shuqian Zhaod4864772015-08-06 09:46:22 -0700551 asynchronous_suite.start()
552
Dan Shi81ddc422016-09-09 13:58:31 -0700553 # Test suite for testbed
554 testbed_suite = multiprocessing.Process(
555 target=test_suite_wrapper,
556 args=(queue, TESTBED_SUITE, EXPECTED_TEST_RESULTS_TESTBED,
557 arguments, False, False, True))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700558 testbed_suite.daemon = use_daemon
Dan Shi81ddc422016-09-09 13:58:31 -0700559 testbed_suite.start()
560
Dan Shief1a5c02015-04-07 17:37:09 -0700561 while (push_to_prod_suite.is_alive() or au_suite.is_alive() or
Dan Shi81ddc422016-09-09 13:58:31 -0700562 shard_suite.is_alive() or asynchronous_suite.is_alive() or
563 testbed_suite.is_alive()):
Dan Shief1a5c02015-04-07 17:37:09 -0700564 check_queue(queue)
Dan Shief1a5c02015-04-07 17:37:09 -0700565 time.sleep(5)
566
567 check_queue(queue)
568
569 push_to_prod_suite.join()
570 au_suite.join()
571 shard_suite.join()
Shuqian Zhaod4864772015-08-06 09:46:22 -0700572 asynchronous_suite.join()
Dan Shi81ddc422016-09-09 13:58:31 -0700573 testbed_suite.join()
Dan Shi7e04fa82013-07-25 15:08:48 -0700574 except Exception as e:
575 print 'Test for pushing to prod failed:\n'
576 print str(e)
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700577 # Abort running jobs when choose not to continue when there is failure.
578 if not arguments.continue_on_failure:
579 for suite_id in all_suite_ids:
580 if AFE.get_jobs(id=suite_id, finished=False):
581 AFE.run('abort_host_queue_entries', job=suite_id)
Dan Shi7e04fa82013-07-25 15:08:48 -0700582 # Send out email about the test failure.
583 if arguments.email:
Dan Shi5fa602c2015-03-26 17:54:13 -0700584 gmail_lib.send_email(
585 arguments.email,
586 'Test for pushing to prod failed. Do NOT push!',
587 ('Errors occurred during the test:\n\n%s\n\n' % str(e) +
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700588 '\n'.join(run_suite_output)))
Dan Shi7e04fa82013-07-25 15:08:48 -0700589 raise
Shuqian Zhaod2a99f02016-09-22 13:31:30 -0700590 finally:
591 # Reverify all the hosts
592 reverify_all_push_duts(arguments.pool)
Dan Shi7e04fa82013-07-25 15:08:48 -0700593
594 message = ('\nAll tests are completed successfully, prod branch is ready to'
595 ' be pushed.')
596 print message
597 # Send out email about test completed successfully.
598 if arguments.email:
Dan Shi5fa602c2015-03-26 17:54:13 -0700599 gmail_lib.send_email(
600 arguments.email,
601 'Test for pushing to prod completed successfully',
602 message)
Dan Shi7e04fa82013-07-25 15:08:48 -0700603
604
605if __name__ == '__main__':
606 sys.exit(main())