blob: 0746cb3e6900cb539ee332197a78c87dc16efa3d [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'
xixuan2d668582016-06-10 14:02:32 -070063DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB = 30
Shuqian Zhao12861662016-08-31 19:23:17 -070064IMAGE_BUCKET = CONFIG.get_config_value('CROS', 'image_storage_server')
Shuqian Zhao1f311c02016-09-01 19:30:54 -070065DEFAULT_NUM_DUTS = "{'board:gandof': 4, 'board:quawks': 2}"
Dan Shi7e04fa82013-07-25 15:08:48 -070066
Fang Deng6dddf602014-04-17 17:01:47 -070067SUITE_JOB_START_INFO_REGEX = ('^.*Created suite job:.*'
68 'tab_id=view_job&object_id=(\d+)$')
Dan Shi7e04fa82013-07-25 15:08:48 -070069
70# Dictionary of test results keyed by test name regular expression.
71EXPECTED_TEST_RESULTS = {'^SERVER_JOB$': 'GOOD',
72 # This is related to dummy_Fail/control.dependency.
73 'dummy_Fail.dependency$': 'TEST_NA',
Dan Shidc9eb172014-12-09 16:05:02 -080074 'login_LoginSuccess.*': 'GOOD',
Dan Shi47d32882014-12-22 16:25:05 -080075 'provision_AutoUpdate.double': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -070076 'dummy_Pass.*': 'GOOD',
77 'dummy_Fail.Fail$': 'FAIL',
78 'dummy_Fail.RetryFail$': 'FAIL',
79 'dummy_Fail.RetrySuccess': 'GOOD',
80 'dummy_Fail.Error$': 'ERROR',
81 'dummy_Fail.Warn$': 'WARN',
82 'dummy_Fail.NAError$': 'TEST_NA',
83 'dummy_Fail.Crash$': 'GOOD',
84 }
85
Jakob Juelich8f143912014-10-10 14:08:05 -070086EXPECTED_TEST_RESULTS_DUMMY = {'^SERVER_JOB$': 'GOOD',
87 'dummy_Pass.*': 'GOOD',
88 'dummy_Fail.Fail': 'FAIL',
89 'dummy_Fail.Warn': 'WARN',
90 'dummy_Fail.Crash': 'GOOD',
91 'dummy_Fail.Error': 'ERROR',
92 'dummy_Fail.NAError': 'TEST_NA',}
93
Dan Shi7e04fa82013-07-25 15:08:48 -070094EXPECTED_TEST_RESULTS_AU = {'SERVER_JOB$': 'GOOD',
Shuqian Zhaof3a114c2016-09-21 11:02:15 -070095 'autoupdate_EndToEndTest.paygen_au_beta_delta.*': 'GOOD',
96 'autoupdate_EndToEndTest.paygen_au_beta_full.*': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -070097 }
98
Dan Shi81ddc422016-09-09 13:58:31 -070099EXPECTED_TEST_RESULTS_TESTBED = {'^SERVER_JOB$': 'GOOD',
100 'testbed_DummyTest': 'GOOD',}
101
Shuqian Zhao327b6952016-09-12 10:42:03 -0700102EXPECTED_TEST_RESULTS_POWERWASH = {'platform_Powerwash': 'GOOD',
103 'SERVER_JOB': 'GOOD'}
104
Dan Shi7e04fa82013-07-25 15:08:48 -0700105URL_HOST = CONFIG.get_config_value('SERVER', 'hostname', type=str)
106URL_PATTERN = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str)
107
Dan Shidc9eb172014-12-09 16:05:02 -0800108# Some test could be missing from the test results for various reasons. Add
109# such test in this list and explain the reason.
110IGNORE_MISSING_TESTS = [
111 # For latest build, npo_test_delta does not exist.
112 'autoupdate_EndToEndTest.npo_test_delta.*',
113 # For trybot build, nmo_test_delta does not exist.
114 'autoupdate_EndToEndTest.nmo_test_delta.*',
115 # Older build does not have login_LoginSuccess test in push_to_prod suite.
116 # TODO(dshi): Remove following lines after R41 is stable.
117 'login_LoginSuccess']
118
Dan Shi7e04fa82013-07-25 15:08:48 -0700119# Save all run_suite command output.
Shuqian Zhao7b682192016-09-16 14:38:41 -0700120manager = multiprocessing.Manager()
121run_suite_output = manager.list()
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700122all_suite_ids = manager.list()
Dan Shi7e04fa82013-07-25 15:08:48 -0700123
124class TestPushException(Exception):
125 """Exception to be raised when the test to push to prod failed."""
126 pass
127
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700128
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700129def check_dut_inventory(required_num_duts):
130 """Check DUT inventory for each board.
131
132 @param required_num_duts: a dict specified the number of DUT each board
133 requires in order to finish push tests.
134 @raise TestPushException: if number of DUTs are less than the requirement.
135 """
136 hosts = AFE.run('get_hosts', status='Ready', locked=False)
137 boards = [[l for l in host['labels'] if l.startswith('board:')][0]
138 for host in hosts]
139 current_inventory = {b:boards.count(b) for b in boards}
140 error_msg = ''
141 for board, req_num in required_num_duts.items():
142 curr_num = current_inventory.get(board, 0)
143 if curr_num < req_num:
144 error_msg += ('\nRequire %d %s DUTs, only %d are Ready now' %
145 (req_num, board, curr_num))
146 if error_msg:
147 raise TestPushException('Not enough DUTs to run push tests. %s' %
148 error_msg)
149
150
Shuqian Zhao327b6952016-09-12 10:42:03 -0700151def powerwash_dut_to_test_repair(hostname, timeout):
152 """Powerwash dut to test repair workflow.
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800153
154 @param hostname: hostname of the dut.
Shuqian Zhao327b6952016-09-12 10:42:03 -0700155 @param timeout: seconds of the powerwash test to hit timeout.
156 @raise TestPushException: if DUT fail to run the test.
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800157 """
Shuqian Zhao327b6952016-09-12 10:42:03 -0700158 t = models.Test.objects.get(name='platform_Powerwash')
159 c = utils.read_file(os.path.join(common.autotest_dir, t.path))
160 job_id = rpc_utils.create_job_common(
161 'powerwash', priority=priorities.Priority.SUPER,
162 control_type='Server', control_file=c, hosts=[hostname])
163
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700164 end = time.time() + timeout
Shuqian Zhao327b6952016-09-12 10:42:03 -0700165 while not TKO.get_job_test_statuses_from_db(job_id):
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700166 if time.time() >= end:
167 AFE.run('abort_host_queue_entries', job=job_id)
Shuqian Zhao327b6952016-09-12 10:42:03 -0700168 raise TestPushException(
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700169 'Powerwash test on %s timeout after %ds, abort it.' %
170 (hostname, timeout))
Shuqian Zhao327b6952016-09-12 10:42:03 -0700171 time.sleep(10)
172 verify_test_results(job_id, EXPECTED_TEST_RESULTS_POWERWASH)
173 # Kick off verify, verify will fail and a repair should be triggered.
174 AFE.reverify_hosts(hostnames=[hostname])
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800175
176
Shuqian Zhaod2a99f02016-09-22 13:31:30 -0700177def reverify_all_push_duts(pool):
178 """Reverify all the push DUTs.
179
180 @param pool: Name of the pool used by test_push.
181 """
182 pool_label = constants.Labels.POOL_PREFIX + pool
183 hosts = [h.hostname for h in AFE.get_hosts(label=pool_label)]
184 AFE.reverify_hosts(hostnames=hosts)
185
186
Shuqian Zhao12861662016-08-31 19:23:17 -0700187def get_default_build(board='gandof'):
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700188 """Get the default build to be used for test.
189
Dan Shi8df9c002016-03-08 15:37:39 -0800190 @param board: Name of board to be tested, default is gandof.
191 @return: Build to be tested, e.g., gandof-release/R36-5881.0.0
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700192 """
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700193 build = None
Shuqian Zhao12861662016-08-31 19:23:17 -0700194 cmd = ('%s/cli/atest stable_version list --board=%s -w cautotest' %
195 (AUTOTEST_DIR, board))
196 result = subprocess.check_output(cmd, shell=True).strip()
197 build = re.search(BUILD_REGEX, result)
198 if build:
199 return '%s-release/%s' % (board, build.group(0))
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700200
Shuqian Zhao12861662016-08-31 19:23:17 -0700201 # If fail to get stable version from cautotest, use that defined in config
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700202 build = CONFIG.get_config_value('CROS', 'stable_cros_version')
203 return '%s-release/%s' % (board, build)
204
Dan Shi7e04fa82013-07-25 15:08:48 -0700205def parse_arguments():
206 """Parse arguments for test_push tool.
207
208 @return: Parsed arguments.
209
210 """
211 parser = argparse.ArgumentParser()
Dan Shi8df9c002016-03-08 15:37:39 -0800212 parser.add_argument('-b', '--board', dest='board', default='gandof',
213 help='Default is gandof.')
Jakob Juelich8f143912014-10-10 14:08:05 -0700214 parser.add_argument('-sb', '--shard_board', dest='shard_board',
215 default='quawks',
216 help='Default is quawks.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700217 parser.add_argument('-i', '--build', dest='build', default=None,
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700218 help='Default is the latest stale build of given '
219 'board. Must be a stable build, otherwise AU test '
220 'will fail. (ex: gandolf-release/R54-8743.25.0)')
Jakob Juelich8f143912014-10-10 14:08:05 -0700221 parser.add_argument('-si', '--shard_build', dest='shard_build', default=None,
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700222 help='Default is the latest stable build of given '
223 'board. Must be a stable build, otherwise AU test '
Jakob Juelich8f143912014-10-10 14:08:05 -0700224 'will fail.')
Dan Shi81ddc422016-09-09 13:58:31 -0700225 parser.add_argument('-ab', '--android_board', dest='android_board',
226 help='Android board to test.')
227 parser.add_argument('-ai', '--android_build', dest='android_build',
228 help='Android build to test.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700229 parser.add_argument('-p', '--pool', dest='pool', default='bvt')
230 parser.add_argument('-u', '--num', dest='num', type=int, default=3,
231 help='Run on at most NUM machines.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700232 parser.add_argument('-e', '--email', dest='email', default=None,
233 help='Email address for the notification to be sent to '
234 'after the script finished running.')
Shuqian Zhaod4864772015-08-06 09:46:22 -0700235 parser.add_argument('-t', '--timeout_min', dest='timeout_min', type=int,
xixuan2d668582016-06-10 14:02:32 -0700236 default=DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB,
Shuqian Zhaod4864772015-08-06 09:46:22 -0700237 help='Time in mins to wait before abort the jobs we '
238 'are waiting on. Only for the asynchronous suites '
239 'triggered by create_and_return flag.')
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700240 parser.add_argument('-ud', '--num_duts', dest='num_duts',
241 default=DEFAULT_NUM_DUTS,
242 help="String of dict that indicates the required number"
243 " of DUTs for each board. E.g {'gandof':4}")
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700244 parser.add_argument('-c', '--continue_on_failure', action='store_true',
245 dest='continue_on_failure',
246 help='All tests continue to run when there is failure')
Dan Shi7e04fa82013-07-25 15:08:48 -0700247
248 arguments = parser.parse_args(sys.argv[1:])
249
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700250 # Get latest stable build as default build.
Dan Shi7e04fa82013-07-25 15:08:48 -0700251 if not arguments.build:
Shuqian Zhao12861662016-08-31 19:23:17 -0700252 arguments.build = get_default_build(arguments.board)
Jakob Juelich8f143912014-10-10 14:08:05 -0700253 if not arguments.shard_build:
Shuqian Zhao12861662016-08-31 19:23:17 -0700254 arguments.shard_build = get_default_build(arguments.shard_board)
Dan Shi7e04fa82013-07-25 15:08:48 -0700255
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700256 arguments.num_duts = ast.literal_eval(arguments.num_duts)
257
Dan Shi7e04fa82013-07-25 15:08:48 -0700258 return arguments
259
260
Shuqian Zhaod4864772015-08-06 09:46:22 -0700261def do_run_suite(suite_name, arguments, use_shard=False,
Dan Shi81ddc422016-09-09 13:58:31 -0700262 create_and_return=False, testbed_test=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700263 """Call run_suite to run a suite job, and return the suite job id.
264
265 The script waits the suite job to finish before returning the suite job id.
266 Also it will echo the run_suite output to stdout.
267
268 @param suite_name: Name of a suite, e.g., dummy.
269 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700270 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700271 @param create_and_return: If True, run_suite just creates the suite, print
272 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700273 @param testbed_test: True to run testbed test. Default is False.
Jakob Juelich8f143912014-10-10 14:08:05 -0700274
Dan Shi7e04fa82013-07-25 15:08:48 -0700275 @return: Suite job ID.
276
277 """
Dan Shi81ddc422016-09-09 13:58:31 -0700278 if use_shard and not testbed_test:
Jakob Juelich8f143912014-10-10 14:08:05 -0700279 board = arguments.shard_board
280 build = arguments.shard_build
Dan Shi81ddc422016-09-09 13:58:31 -0700281 elif testbed_test:
282 board = arguments.android_board
283 build = arguments.android_build
284 else:
285 board = arguments.board
286 build = arguments.build
Jakob Juelich8f143912014-10-10 14:08:05 -0700287
Dan Shi47d32882014-12-22 16:25:05 -0800288 # Remove cros-version label to force provision.
Dan Shiefd403e2016-02-03 11:37:02 -0800289 hosts = AFE.get_hosts(label=constants.Labels.BOARD_PREFIX+board)
Dan Shi47d32882014-12-22 16:25:05 -0800290 for host in hosts:
Dan Shi81ddc422016-09-09 13:58:31 -0700291 labels_to_remove = [
292 l for l in host.labels
293 if (l.startswith(provision.CROS_VERSION_PREFIX) or
294 l.startswith(provision.TESTBED_BUILD_VERSION_PREFIX))]
295 if labels_to_remove:
296 AFE.run('host_remove_labels', id=host.id, labels=labels_to_remove)
Dan Shi47d32882014-12-22 16:25:05 -0800297
Shuqian Zhao327b6952016-09-12 10:42:03 -0700298 # Test repair work flow on shards
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800299 if use_shard and not create_and_return:
Shuqian Zhao327b6952016-09-12 10:42:03 -0700300 powerwash_dut_to_test_repair(host.hostname, timeout=300)
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800301
Dan Shief1a5c02015-04-07 17:37:09 -0700302 current_dir = os.path.dirname(os.path.realpath(__file__))
303 cmd = [os.path.join(current_dir, RUN_SUITE_COMMAND),
Dan Shi7e04fa82013-07-25 15:08:48 -0700304 '-s', suite_name,
Jakob Juelich8f143912014-10-10 14:08:05 -0700305 '-b', board,
306 '-i', build,
Dan Shi7e04fa82013-07-25 15:08:48 -0700307 '-p', arguments.pool,
Shuqian Zhao178ac012016-06-03 15:08:52 -0700308 '-u', str(arguments.num)]
Shuqian Zhaod4864772015-08-06 09:46:22 -0700309 if create_and_return:
310 cmd += ['-c']
Dan Shi81ddc422016-09-09 13:58:31 -0700311 if testbed_test:
312 cmd += ['--run_prod_code']
Dan Shi7e04fa82013-07-25 15:08:48 -0700313
314 suite_job_id = None
Dan Shi7e04fa82013-07-25 15:08:48 -0700315
316 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
317 stderr=subprocess.STDOUT)
318
319 while True:
320 line = proc.stdout.readline()
321
322 # Break when run_suite process completed.
323 if not line and proc.poll() != None:
324 break
325 print line.rstrip()
326 run_suite_output.append(line.rstrip())
327
328 if not suite_job_id:
329 m = re.match(SUITE_JOB_START_INFO_REGEX, line)
330 if m and m.group(1):
331 suite_job_id = int(m.group(1))
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700332 all_suite_ids.append(suite_job_id)
Dan Shi7e04fa82013-07-25 15:08:48 -0700333
334 if not suite_job_id:
335 raise TestPushException('Failed to retrieve suite job ID.')
Dan Shia8da7602014-05-09 15:18:15 -0700336
Shuqian Zhaod4864772015-08-06 09:46:22 -0700337 # If create_and_return specified, wait for the suite to finish.
338 if create_and_return:
339 end = time.time() + arguments.timeout_min * 60
Dan Shiefd403e2016-02-03 11:37:02 -0800340 while not AFE.get_jobs(id=suite_job_id, finished=True):
Shuqian Zhaod4864772015-08-06 09:46:22 -0700341 if time.time() < end:
342 time.sleep(10)
343 else:
Dan Shiefd403e2016-02-03 11:37:02 -0800344 AFE.run('abort_host_queue_entries', job=suite_job_id)
Shuqian Zhaod4864772015-08-06 09:46:22 -0700345 raise TestPushException(
346 'Asynchronous suite triggered by create_and_return '
347 'flag has timed out after %d mins. Aborting it.' %
348 arguments.timeout_min)
349
Dan Shia8da7602014-05-09 15:18:15 -0700350 print 'Suite job %s is completed.' % suite_job_id
Dan Shi7e04fa82013-07-25 15:08:48 -0700351 return suite_job_id
352
353
Dan Shia8da7602014-05-09 15:18:15 -0700354def check_dut_image(build, suite_job_id):
355 """Confirm all DUTs used for the suite are imaged to expected build.
356
357 @param build: Expected build to be imaged.
358 @param suite_job_id: job ID of the suite job.
359 @raise TestPushException: If a DUT does not have expected build imaged.
360 """
361 print 'Checking image installed in DUTs...'
362 job_ids = [job.id for job in
363 models.Job.objects.filter(parent_job_id=suite_job_id)]
364 hqes = [models.HostQueueEntry.objects.filter(job_id=job_id)[0]
365 for job_id in job_ids]
366 hostnames = set([hqe.host.hostname for hqe in hqes])
367 for hostname in hostnames:
Dan Shiefd403e2016-02-03 11:37:02 -0800368 found_build = site_utils.get_build_from_afe(hostname, AFE)
Dan Shia8da7602014-05-09 15:18:15 -0700369 if found_build != build:
370 raise TestPushException('DUT is not imaged properly. Host %s has '
371 'build %s, while build %s is expected.' %
372 (hostname, found_build, build))
373
374
Shuqian Zhaod4864772015-08-06 09:46:22 -0700375def test_suite(suite_name, expected_results, arguments, use_shard=False,
Dan Shi81ddc422016-09-09 13:58:31 -0700376 create_and_return=False, testbed_test=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700377 """Call run_suite to start a suite job and verify results.
378
379 @param suite_name: Name of a suite, e.g., dummy
380 @param expected_results: A dictionary of test name to test result.
381 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700382 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700383 @param create_and_return: If True, run_suite just creates the suite, print
384 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700385 @param testbed_test: True to run testbed test. Default is False.
Dan Shi7e04fa82013-07-25 15:08:48 -0700386 """
Shuqian Zhaod4864772015-08-06 09:46:22 -0700387 suite_job_id = do_run_suite(suite_name, arguments, use_shard,
Dan Shi81ddc422016-09-09 13:58:31 -0700388 create_and_return, testbed_test)
Dan Shi7e04fa82013-07-25 15:08:48 -0700389
Dan Shia8da7602014-05-09 15:18:15 -0700390 # Confirm all DUTs used for the suite are imaged to expected build.
Jakob Juelich8f143912014-10-10 14:08:05 -0700391 # hqe.host_id for jobs running in shard is not synced back to master db,
392 # therefore, skip verifying dut build for jobs running in shard.
Dan Shi81ddc422016-09-09 13:58:31 -0700393 build_expected = (arguments.android_build if testbed_test
394 else arguments.build)
395 if suite_name != AU_SUITE and not use_shard and not testbed_test:
396 check_dut_image(build_expected, suite_job_id)
Dan Shia8da7602014-05-09 15:18:15 -0700397
Shuqian Zhao327b6952016-09-12 10:42:03 -0700398 # Verify test results are the expected results.
399 verify_test_results(suite_job_id, expected_results)
400
401
402def verify_test_results(job_id, expected_results):
403 """Verify the test results with the expected results.
404
405 @param job_id: id of the running jobs. For suite job, it is suite_job_id.
406 @param expected_results: A dictionary of test name to test result.
407 @raise TestPushException: If verify fails.
408 """
Dan Shia8da7602014-05-09 15:18:15 -0700409 print 'Comparing test results...'
Shuqian Zhao327b6952016-09-12 10:42:03 -0700410 test_views = site_utils.get_test_views_from_tko(job_id, TKO)
Dan Shi7e04fa82013-07-25 15:08:48 -0700411
412 mismatch_errors = []
413 extra_test_errors = []
414
415 found_keys = set()
Shuqian Zhao327b6952016-09-12 10:42:03 -0700416 for test_name, test_status in test_views.items():
Dan Shi7e04fa82013-07-25 15:08:48 -0700417 print "%s%s" % (test_name.ljust(30), test_status)
Dan Shi80b6ec02016-07-21 15:49:18 -0700418 # platform_InstallTestImage test may exist in old builds.
419 if re.search('platform_InstallTestImage_SERVER_JOB$', test_name):
420 continue
Dan Shi7e04fa82013-07-25 15:08:48 -0700421 test_found = False
422 for key,val in expected_results.items():
423 if re.search(key, test_name):
424 test_found = True
425 found_keys.add(key)
Dan Shi7e04fa82013-07-25 15:08:48 -0700426 if val != test_status:
427 error = ('%s Expected: [%s], Actual: [%s]' %
428 (test_name, val, test_status))
429 mismatch_errors.append(error)
430 if not test_found:
431 extra_test_errors.append(test_name)
432
433 missing_test_errors = set(expected_results.keys()) - found_keys
Dan Shidc9eb172014-12-09 16:05:02 -0800434 for exception in IGNORE_MISSING_TESTS:
435 try:
436 missing_test_errors.remove(exception)
437 except KeyError:
438 pass
439
Dan Shi7e04fa82013-07-25 15:08:48 -0700440 summary = []
441 if mismatch_errors:
442 summary.append(('Results of %d test(s) do not match expected '
443 'values:') % len(mismatch_errors))
444 summary.extend(mismatch_errors)
445 summary.append('\n')
446
447 if extra_test_errors:
448 summary.append('%d test(s) are not expected to be run:' %
449 len(extra_test_errors))
450 summary.extend(extra_test_errors)
451 summary.append('\n')
452
453 if missing_test_errors:
454 summary.append('%d test(s) are missing from the results:' %
455 len(missing_test_errors))
456 summary.extend(missing_test_errors)
457 summary.append('\n')
458
459 # Test link to log can be loaded.
Shuqian Zhao327b6952016-09-12 10:42:03 -0700460 job_name = '%s-%s' % (job_id, getpass.getuser())
Dan Shi7e04fa82013-07-25 15:08:48 -0700461 log_link = URL_PATTERN % (URL_HOST, job_name)
462 try:
463 urllib2.urlopen(log_link).read()
464 except urllib2.URLError:
465 summary.append('Failed to load page for link to log: %s.' % log_link)
466
467 if summary:
468 raise TestPushException('\n'.join(summary))
469
470
Dan Shief1a5c02015-04-07 17:37:09 -0700471def test_suite_wrapper(queue, suite_name, expected_results, arguments,
Dan Shi81ddc422016-09-09 13:58:31 -0700472 use_shard=False, create_and_return=False,
473 testbed_test=False):
Dan Shief1a5c02015-04-07 17:37:09 -0700474 """Wrapper to call test_suite. Handle exception and pipe it to parent
475 process.
476
477 @param queue: Queue to save exception to be accessed by parent process.
478 @param suite_name: Name of a suite, e.g., dummy
479 @param expected_results: A dictionary of test name to test result.
480 @param arguments: Arguments for run_suite command.
481 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700482 @param create_and_return: If True, run_suite just creates the suite, print
483 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700484 @param testbed_test: True to run testbed test. Default is False.
Dan Shief1a5c02015-04-07 17:37:09 -0700485 """
486 try:
Shuqian Zhaod4864772015-08-06 09:46:22 -0700487 test_suite(suite_name, expected_results, arguments, use_shard,
Dan Shi81ddc422016-09-09 13:58:31 -0700488 create_and_return, testbed_test)
Dan Shief1a5c02015-04-07 17:37:09 -0700489 except:
490 # Store the whole exc_info leads to a PicklingError.
491 except_type, except_value, tb = sys.exc_info()
492 queue.put((except_type, except_value, traceback.extract_tb(tb)))
493
494
Dan Shief1a5c02015-04-07 17:37:09 -0700495def check_queue(queue):
496 """Check the queue for any exception being raised.
497
498 @param queue: Queue used to store exception for parent process to access.
499 @raise: Any exception found in the queue.
500 """
501 if queue.empty():
502 return
503 exc_info = queue.get()
504 # Raise the exception with original backtrace.
505 print 'Original stack trace of the exception:\n%s' % exc_info[2]
506 raise exc_info[0](exc_info[1])
507
508
Dan Shi7e04fa82013-07-25 15:08:48 -0700509def main():
510 """Entry point for test_push script."""
511 arguments = parse_arguments()
512
513 try:
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700514 # Use daemon flag will kill child processes when parent process fails.
515 use_daemon = not arguments.continue_on_failure
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700516 check_dut_inventory(arguments.num_duts)
Dan Shief1a5c02015-04-07 17:37:09 -0700517 queue = multiprocessing.Queue()
518
519 push_to_prod_suite = multiprocessing.Process(
520 target=test_suite_wrapper,
521 args=(queue, PUSH_TO_PROD_SUITE, EXPECTED_TEST_RESULTS,
522 arguments))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700523 push_to_prod_suite.daemon = use_daemon
Dan Shief1a5c02015-04-07 17:37:09 -0700524 push_to_prod_suite.start()
Jakob Juelich8f143912014-10-10 14:08:05 -0700525
Dan Shi7e04fa82013-07-25 15:08:48 -0700526 # TODO(dshi): Remove following line after crbug.com/267644 is fixed.
527 # Also, merge EXPECTED_TEST_RESULTS_AU to EXPECTED_TEST_RESULTS
Dan Shief1a5c02015-04-07 17:37:09 -0700528 au_suite = multiprocessing.Process(
529 target=test_suite_wrapper,
530 args=(queue, AU_SUITE, EXPECTED_TEST_RESULTS_AU,
531 arguments))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700532 au_suite.daemon = use_daemon
Dan Shief1a5c02015-04-07 17:37:09 -0700533 au_suite.start()
534
535 shard_suite = multiprocessing.Process(
536 target=test_suite_wrapper,
537 args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY,
538 arguments, True))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700539 shard_suite.daemon = use_daemon
Dan Shief1a5c02015-04-07 17:37:09 -0700540 shard_suite.start()
541
Shuqian Zhaod4864772015-08-06 09:46:22 -0700542 # suite test with --create_and_return flag
543 asynchronous_suite = multiprocessing.Process(
544 target=test_suite_wrapper,
545 args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY,
546 arguments, True, True))
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700547 asynchronous_suite.daemon = True
Shuqian Zhaod4864772015-08-06 09:46:22 -0700548 asynchronous_suite.start()
549
Dan Shi81ddc422016-09-09 13:58:31 -0700550 # Test suite for testbed
551 testbed_suite = multiprocessing.Process(
552 target=test_suite_wrapper,
553 args=(queue, TESTBED_SUITE, EXPECTED_TEST_RESULTS_TESTBED,
554 arguments, False, False, True))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700555 testbed_suite.daemon = use_daemon
Dan Shi81ddc422016-09-09 13:58:31 -0700556 testbed_suite.start()
557
Dan Shief1a5c02015-04-07 17:37:09 -0700558 while (push_to_prod_suite.is_alive() or au_suite.is_alive() or
Dan Shi81ddc422016-09-09 13:58:31 -0700559 shard_suite.is_alive() or asynchronous_suite.is_alive() or
560 testbed_suite.is_alive()):
Dan Shief1a5c02015-04-07 17:37:09 -0700561 check_queue(queue)
Dan Shief1a5c02015-04-07 17:37:09 -0700562 time.sleep(5)
563
564 check_queue(queue)
565
566 push_to_prod_suite.join()
567 au_suite.join()
568 shard_suite.join()
Shuqian Zhaod4864772015-08-06 09:46:22 -0700569 asynchronous_suite.join()
Dan Shi81ddc422016-09-09 13:58:31 -0700570 testbed_suite.join()
Dan Shi7e04fa82013-07-25 15:08:48 -0700571 except Exception as e:
572 print 'Test for pushing to prod failed:\n'
573 print str(e)
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700574 # Abort running jobs when choose not to continue when there is failure.
575 if not arguments.continue_on_failure:
576 for suite_id in all_suite_ids:
577 if AFE.get_jobs(id=suite_id, finished=False):
578 AFE.run('abort_host_queue_entries', job=suite_id)
Dan Shi7e04fa82013-07-25 15:08:48 -0700579 # Send out email about the test failure.
580 if arguments.email:
Dan Shi5fa602c2015-03-26 17:54:13 -0700581 gmail_lib.send_email(
582 arguments.email,
583 'Test for pushing to prod failed. Do NOT push!',
584 ('Errors occurred during the test:\n\n%s\n\n' % str(e) +
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700585 '\n'.join(run_suite_output)))
Dan Shi7e04fa82013-07-25 15:08:48 -0700586 raise
Shuqian Zhaod2a99f02016-09-22 13:31:30 -0700587 finally:
588 # Reverify all the hosts
589 reverify_all_push_duts(arguments.pool)
Dan Shi7e04fa82013-07-25 15:08:48 -0700590
591 message = ('\nAll tests are completed successfully, prod branch is ready to'
592 ' be pushed.')
593 print message
594 # Send out email about test completed successfully.
595 if arguments.email:
Dan Shi5fa602c2015-03-26 17:54:13 -0700596 gmail_lib.send_email(
597 arguments.email,
598 'Test for pushing to prod completed successfully',
599 message)
Dan Shi7e04fa82013-07-25 15:08:48 -0700600
601
602if __name__ == '__main__':
603 sys.exit(main())