blob: b93f361b58bb4f591e17218442bb4448087fef16 [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
17The script uses latest stumpy canary build as test build by default.
18
19"""
20
21import argparse
22import getpass
Dan Shief1a5c02015-04-07 17:37:09 -070023import multiprocessing
Dan Shi7e04fa82013-07-25 15:08:48 -070024import os
25import re
26import subprocess
27import sys
Dan Shief1a5c02015-04-07 17:37:09 -070028import time
29import traceback
Dan Shi7e04fa82013-07-25 15:08:48 -070030import urllib2
31
32import common
Dan Shia8da7602014-05-09 15:18:15 -070033try:
34 from autotest_lib.frontend import setup_django_environment
35 from autotest_lib.frontend.afe import models
36except ImportError:
37 # Unittest may not have Django database configured and will fail to import.
38 pass
Dan Shi5fa602c2015-03-26 17:54:13 -070039from autotest_lib.client.common_lib import global_config
Dan Shi7e04fa82013-07-25 15:08:48 -070040from autotest_lib.server import site_utils
Dan Shi47d32882014-12-22 16:25:05 -080041from autotest_lib.server.cros import provision
Dan Shi7e04fa82013-07-25 15:08:48 -070042from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
43from autotest_lib.server.cros.dynamic_suite import reporting
Dan Shia8da7602014-05-09 15:18:15 -070044from autotest_lib.server.hosts import cros_host
Dan Shi5fa602c2015-03-26 17:54:13 -070045from autotest_lib.site_utils import gmail_lib
Dan Shi47d32882014-12-22 16:25:05 -080046from autotest_lib.site_utils.suite_scheduler import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070047
48CONFIG = global_config.global_config
49
50MAIL_FROM = 'chromeos-test@google.com'
Dan Shi5ba5d2e2014-05-09 13:47:00 -070051DEVSERVERS = CONFIG.get_config_value('CROS', 'dev_server', type=list,
52 default=[])
53BUILD_REGEX = '^R[\d]+-[\d]+\.[\d]+\.[\d]+$'
Dan Shi7e04fa82013-07-25 15:08:48 -070054RUN_SUITE_COMMAND = 'run_suite.py'
55PUSH_TO_PROD_SUITE = 'push_to_prod'
Jakob Juelich8f143912014-10-10 14:08:05 -070056DUMMY_SUITE = 'dummy'
Dan Shi7e04fa82013-07-25 15:08:48 -070057AU_SUITE = 'paygen_au_canary'
58
Fang Deng6dddf602014-04-17 17:01:47 -070059SUITE_JOB_START_INFO_REGEX = ('^.*Created suite job:.*'
60 'tab_id=view_job&object_id=(\d+)$')
Dan Shi7e04fa82013-07-25 15:08:48 -070061
62# Dictionary of test results keyed by test name regular expression.
63EXPECTED_TEST_RESULTS = {'^SERVER_JOB$': 'GOOD',
64 # This is related to dummy_Fail/control.dependency.
65 'dummy_Fail.dependency$': 'TEST_NA',
Dan Shidc9eb172014-12-09 16:05:02 -080066 'login_LoginSuccess.*': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -070067 'platform_InstallTestImage_SERVER_JOB$': 'GOOD',
Dan Shi47d32882014-12-22 16:25:05 -080068 'provision_AutoUpdate.double': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -070069 'dummy_Pass.*': 'GOOD',
70 'dummy_Fail.Fail$': 'FAIL',
71 'dummy_Fail.RetryFail$': 'FAIL',
72 'dummy_Fail.RetrySuccess': 'GOOD',
73 'dummy_Fail.Error$': 'ERROR',
74 'dummy_Fail.Warn$': 'WARN',
75 'dummy_Fail.NAError$': 'TEST_NA',
76 'dummy_Fail.Crash$': 'GOOD',
77 }
78
Jakob Juelich8f143912014-10-10 14:08:05 -070079EXPECTED_TEST_RESULTS_DUMMY = {'^SERVER_JOB$': 'GOOD',
80 'dummy_Pass.*': 'GOOD',
81 'dummy_Fail.Fail': 'FAIL',
82 'dummy_Fail.Warn': 'WARN',
83 'dummy_Fail.Crash': 'GOOD',
84 'dummy_Fail.Error': 'ERROR',
85 'dummy_Fail.NAError': 'TEST_NA',}
86
Dan Shi7e04fa82013-07-25 15:08:48 -070087EXPECTED_TEST_RESULTS_AU = {'SERVER_JOB$': 'GOOD',
Dan Shi15e6d722015-09-29 14:20:47 -070088 'autoupdate_EndToEndTest.paygen_au_canary_delta.*': 'GOOD',
89 'autoupdate_EndToEndTest.paygen_au_canary_full.*': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -070090 }
91
92# Anchor for the auto-filed bug for dummy_Fail tests.
93BUG_ANCHOR = 'TestFailure(push_to_prod,dummy_Fail.Fail,always fail)'
94
95URL_HOST = CONFIG.get_config_value('SERVER', 'hostname', type=str)
96URL_PATTERN = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str)
97
Dan Shidc9eb172014-12-09 16:05:02 -080098# Some test could be missing from the test results for various reasons. Add
99# such test in this list and explain the reason.
100IGNORE_MISSING_TESTS = [
101 # For latest build, npo_test_delta does not exist.
102 'autoupdate_EndToEndTest.npo_test_delta.*',
103 # For trybot build, nmo_test_delta does not exist.
104 'autoupdate_EndToEndTest.nmo_test_delta.*',
105 # Older build does not have login_LoginSuccess test in push_to_prod suite.
106 # TODO(dshi): Remove following lines after R41 is stable.
107 'login_LoginSuccess']
108
Dan Shi7e04fa82013-07-25 15:08:48 -0700109# Save all run_suite command output.
110run_suite_output = []
111
112class TestPushException(Exception):
113 """Exception to be raised when the test to push to prod failed."""
114 pass
115
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700116
117def get_default_build(devserver=None, board='stumpy'):
118 """Get the default build to be used for test.
119
120 @param devserver: devserver used to look for latest staged build. If value
121 is None, all devservers in config will be tried.
122 @param board: Name of board to be tested, default is stumpy.
123 @return: Build to be tested, e.g., stumpy-release/R36-5881.0.0
124 """
125 LATEST_BUILD_URL_PATTERN = '%s/latestbuild?target=%s-release'
126 build = None
127 if not devserver:
128 for server in DEVSERVERS:
Dan Shief1a5c02015-04-07 17:37:09 -0700129 url = LATEST_BUILD_URL_PATTERN % (server, board)
130 build = urllib2.urlopen(url).read()
131 if build and re.match(BUILD_REGEX, build):
132 return '%s-release/%s' % (board, build)
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700133
134 # If no devserver has any build staged for the given board, use the stable
135 # build in config.
136 build = CONFIG.get_config_value('CROS', 'stable_cros_version')
137 return '%s-release/%s' % (board, build)
138
139
Dan Shi7e04fa82013-07-25 15:08:48 -0700140def parse_arguments():
141 """Parse arguments for test_push tool.
142
143 @return: Parsed arguments.
144
145 """
146 parser = argparse.ArgumentParser()
147 parser.add_argument('-b', '--board', dest='board', default='stumpy',
148 help='Default is stumpy.')
Jakob Juelich8f143912014-10-10 14:08:05 -0700149 parser.add_argument('-sb', '--shard_board', dest='shard_board',
150 default='quawks',
151 help='Default is quawks.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700152 parser.add_argument('-i', '--build', dest='build', default=None,
153 help='Default is the latest canary build of given '
154 'board. Must be a canary build, otherwise AU test '
155 'will fail.')
Jakob Juelich8f143912014-10-10 14:08:05 -0700156 parser.add_argument('-si', '--shard_build', dest='shard_build', default=None,
157 help='Default is the latest canary build of given '
158 'board. Must be a canary build, otherwise AU test '
159 'will fail.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700160 parser.add_argument('-p', '--pool', dest='pool', default='bvt')
161 parser.add_argument('-u', '--num', dest='num', type=int, default=3,
162 help='Run on at most NUM machines.')
163 parser.add_argument('-f', '--file_bugs', dest='file_bugs', default='True',
164 help='File bugs on test failures. Must pass "True" or '
165 '"False" if used.')
166 parser.add_argument('-e', '--email', dest='email', default=None,
167 help='Email address for the notification to be sent to '
168 'after the script finished running.')
169 parser.add_argument('-d', '--devserver', dest='devserver',
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700170 default=None,
Dan Shi7e04fa82013-07-25 15:08:48 -0700171 help='devserver to find what\'s the latest build.')
Shuqian Zhaod4864772015-08-06 09:46:22 -0700172 parser.add_argument('-t', '--timeout_min', dest='timeout_min', type=int,
173 default=24,
174 help='Time in mins to wait before abort the jobs we '
175 'are waiting on. Only for the asynchronous suites '
176 'triggered by create_and_return flag.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700177
178 arguments = parser.parse_args(sys.argv[1:])
179
180 # Get latest canary build as default build.
181 if not arguments.build:
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700182 arguments.build = get_default_build(arguments.devserver,
183 arguments.board)
Jakob Juelich8f143912014-10-10 14:08:05 -0700184 if not arguments.shard_build:
185 arguments.shard_build = get_default_build(arguments.devserver,
186 arguments.shard_board)
Dan Shi7e04fa82013-07-25 15:08:48 -0700187
188 return arguments
189
190
Shuqian Zhaod4864772015-08-06 09:46:22 -0700191def do_run_suite(suite_name, arguments, use_shard=False,
192 create_and_return=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700193 """Call run_suite to run a suite job, and return the suite job id.
194
195 The script waits the suite job to finish before returning the suite job id.
196 Also it will echo the run_suite output to stdout.
197
198 @param suite_name: Name of a suite, e.g., dummy.
199 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700200 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700201 @param create_and_return: If True, run_suite just creates the suite, print
202 the job id, then finish immediately.
Jakob Juelich8f143912014-10-10 14:08:05 -0700203
Dan Shi7e04fa82013-07-25 15:08:48 -0700204 @return: Suite job ID.
205
206 """
Jakob Juelich8f143912014-10-10 14:08:05 -0700207 if not use_shard:
208 board = arguments.board
209 build = arguments.build
210 else:
211 board = arguments.shard_board
212 build = arguments.shard_build
213
Dan Shi47d32882014-12-22 16:25:05 -0800214 # Remove cros-version label to force provision.
215 afe = frontend_wrappers.RetryingAFE(timeout_min=0.1, delay_sec=10)
216 hosts = afe.get_hosts(label=constants.Labels.BOARD_PREFIX+board)
217 for host in hosts:
218 for label in [l for l in host.labels
219 if l.startswith(provision.CROS_VERSION_PREFIX)]:
220 afe.run('host_remove_labels', id=host.id, labels=[label])
221
Dan Shief1a5c02015-04-07 17:37:09 -0700222 current_dir = os.path.dirname(os.path.realpath(__file__))
223 cmd = [os.path.join(current_dir, RUN_SUITE_COMMAND),
Dan Shi7e04fa82013-07-25 15:08:48 -0700224 '-s', suite_name,
Jakob Juelich8f143912014-10-10 14:08:05 -0700225 '-b', board,
226 '-i', build,
Dan Shi7e04fa82013-07-25 15:08:48 -0700227 '-p', arguments.pool,
228 '-u', str(arguments.num),
229 '-f', arguments.file_bugs]
Shuqian Zhaod4864772015-08-06 09:46:22 -0700230 if create_and_return:
231 cmd += ['-c']
Dan Shi7e04fa82013-07-25 15:08:48 -0700232
233 suite_job_id = None
Dan Shi7e04fa82013-07-25 15:08:48 -0700234
235 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
236 stderr=subprocess.STDOUT)
237
238 while True:
239 line = proc.stdout.readline()
240
241 # Break when run_suite process completed.
242 if not line and proc.poll() != None:
243 break
244 print line.rstrip()
245 run_suite_output.append(line.rstrip())
246
247 if not suite_job_id:
248 m = re.match(SUITE_JOB_START_INFO_REGEX, line)
249 if m and m.group(1):
250 suite_job_id = int(m.group(1))
251
252 if not suite_job_id:
253 raise TestPushException('Failed to retrieve suite job ID.')
Dan Shia8da7602014-05-09 15:18:15 -0700254
Shuqian Zhaod4864772015-08-06 09:46:22 -0700255 # If create_and_return specified, wait for the suite to finish.
256 if create_and_return:
257 end = time.time() + arguments.timeout_min * 60
258 while not afe.get_jobs(id=suite_job_id, finished=True):
259 if time.time() < end:
260 time.sleep(10)
261 else:
262 afe.run('abort_host_queue_entries', job=suite_job_id)
263 raise TestPushException(
264 'Asynchronous suite triggered by create_and_return '
265 'flag has timed out after %d mins. Aborting it.' %
266 arguments.timeout_min)
267
Dan Shia8da7602014-05-09 15:18:15 -0700268 print 'Suite job %s is completed.' % suite_job_id
Dan Shi7e04fa82013-07-25 15:08:48 -0700269 return suite_job_id
270
271
Dan Shia8da7602014-05-09 15:18:15 -0700272def check_dut_image(build, suite_job_id):
273 """Confirm all DUTs used for the suite are imaged to expected build.
274
275 @param build: Expected build to be imaged.
276 @param suite_job_id: job ID of the suite job.
277 @raise TestPushException: If a DUT does not have expected build imaged.
278 """
279 print 'Checking image installed in DUTs...'
280 job_ids = [job.id for job in
281 models.Job.objects.filter(parent_job_id=suite_job_id)]
282 hqes = [models.HostQueueEntry.objects.filter(job_id=job_id)[0]
283 for job_id in job_ids]
284 hostnames = set([hqe.host.hostname for hqe in hqes])
285 for hostname in hostnames:
286 host = cros_host.CrosHost(hostname)
287 found_build = host.get_build()
288 if found_build != build:
289 raise TestPushException('DUT is not imaged properly. Host %s has '
290 'build %s, while build %s is expected.' %
291 (hostname, found_build, build))
292
293
Shuqian Zhaod4864772015-08-06 09:46:22 -0700294def test_suite(suite_name, expected_results, arguments, use_shard=False,
295 create_and_return=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700296 """Call run_suite to start a suite job and verify results.
297
298 @param suite_name: Name of a suite, e.g., dummy
299 @param expected_results: A dictionary of test name to test result.
300 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700301 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700302 @param create_and_return: If True, run_suite just creates the suite, print
303 the job id, then finish immediately.
Dan Shi7e04fa82013-07-25 15:08:48 -0700304 """
Shuqian Zhaod4864772015-08-06 09:46:22 -0700305 suite_job_id = do_run_suite(suite_name, arguments, use_shard,
306 create_and_return)
Dan Shi7e04fa82013-07-25 15:08:48 -0700307
Dan Shia8da7602014-05-09 15:18:15 -0700308 # Confirm all DUTs used for the suite are imaged to expected build.
Jakob Juelich8f143912014-10-10 14:08:05 -0700309 # hqe.host_id for jobs running in shard is not synced back to master db,
310 # therefore, skip verifying dut build for jobs running in shard.
311 if suite_name != AU_SUITE and not use_shard:
Dan Shia8da7602014-05-09 15:18:15 -0700312 check_dut_image(arguments.build, suite_job_id)
313
Dan Shi7e04fa82013-07-25 15:08:48 -0700314 # Find all tests and their status
Dan Shia8da7602014-05-09 15:18:15 -0700315 print 'Comparing test results...'
Dan Shi7e04fa82013-07-25 15:08:48 -0700316 TKO = frontend_wrappers.RetryingTKO(timeout_min=0.1, delay_sec=10)
317 test_views = site_utils.get_test_views_from_tko(suite_job_id, TKO)
318
319 mismatch_errors = []
320 extra_test_errors = []
321
322 found_keys = set()
323 for test_name,test_status in test_views.items():
324 print "%s%s" % (test_name.ljust(30), test_status)
325 test_found = False
326 for key,val in expected_results.items():
327 if re.search(key, test_name):
328 test_found = True
329 found_keys.add(key)
330 # TODO(dshi): result for this test is ignored until servo is
331 # added to a host accessible by cbf server (crbug.com/277109).
332 if key == 'platform_InstallTestImage_SERVER_JOB$':
333 continue
Dan Shi7e04fa82013-07-25 15:08:48 -0700334 if val != test_status:
335 error = ('%s Expected: [%s], Actual: [%s]' %
336 (test_name, val, test_status))
337 mismatch_errors.append(error)
338 if not test_found:
339 extra_test_errors.append(test_name)
340
341 missing_test_errors = set(expected_results.keys()) - found_keys
Dan Shidc9eb172014-12-09 16:05:02 -0800342 for exception in IGNORE_MISSING_TESTS:
343 try:
344 missing_test_errors.remove(exception)
345 except KeyError:
346 pass
347
Dan Shi7e04fa82013-07-25 15:08:48 -0700348 summary = []
349 if mismatch_errors:
350 summary.append(('Results of %d test(s) do not match expected '
351 'values:') % len(mismatch_errors))
352 summary.extend(mismatch_errors)
353 summary.append('\n')
354
355 if extra_test_errors:
356 summary.append('%d test(s) are not expected to be run:' %
357 len(extra_test_errors))
358 summary.extend(extra_test_errors)
359 summary.append('\n')
360
361 if missing_test_errors:
362 summary.append('%d test(s) are missing from the results:' %
363 len(missing_test_errors))
364 summary.extend(missing_test_errors)
365 summary.append('\n')
366
367 # Test link to log can be loaded.
368 job_name = '%s-%s' % (suite_job_id, getpass.getuser())
369 log_link = URL_PATTERN % (URL_HOST, job_name)
370 try:
371 urllib2.urlopen(log_link).read()
372 except urllib2.URLError:
373 summary.append('Failed to load page for link to log: %s.' % log_link)
374
375 if summary:
376 raise TestPushException('\n'.join(summary))
377
378
Dan Shief1a5c02015-04-07 17:37:09 -0700379def test_suite_wrapper(queue, suite_name, expected_results, arguments,
Shuqian Zhaod4864772015-08-06 09:46:22 -0700380 use_shard=False, create_and_return=False):
Dan Shief1a5c02015-04-07 17:37:09 -0700381 """Wrapper to call test_suite. Handle exception and pipe it to parent
382 process.
383
384 @param queue: Queue to save exception to be accessed by parent process.
385 @param suite_name: Name of a suite, e.g., dummy
386 @param expected_results: A dictionary of test name to test result.
387 @param arguments: Arguments for run_suite command.
388 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700389 @param create_and_return: If True, run_suite just creates the suite, print
390 the job id, then finish immediately.
Dan Shief1a5c02015-04-07 17:37:09 -0700391 """
392 try:
Shuqian Zhaod4864772015-08-06 09:46:22 -0700393 test_suite(suite_name, expected_results, arguments, use_shard,
394 create_and_return)
Dan Shief1a5c02015-04-07 17:37:09 -0700395 except:
396 # Store the whole exc_info leads to a PicklingError.
397 except_type, except_value, tb = sys.exc_info()
398 queue.put((except_type, except_value, traceback.extract_tb(tb)))
399
400
Dan Shi7e04fa82013-07-25 15:08:48 -0700401def close_bug():
402 """Close all existing bugs filed for dummy_Fail.
403
404 @return: A list of issue ids to be used in check_bug_filed_and_deduped.
405 """
406 old_issue_ids = []
407 reporter = reporting.Reporter()
408 while True:
409 issue = reporter.find_issue_by_marker(BUG_ANCHOR)
410 if not issue:
411 return old_issue_ids
412 if issue.id in old_issue_ids:
413 raise TestPushException('Failed to close issue %d' % issue.id)
414 old_issue_ids.append(issue.id)
415 reporter.modify_bug_report(issue.id,
416 comment='Issue closed by test_push script.',
417 label_update='',
418 status='WontFix')
419
420
421def check_bug_filed_and_deduped(old_issue_ids):
422 """Confirm bug related to dummy_Fail was filed and deduped.
423
424 @param old_issue_ids: A list of issue ids that was closed earlier. id of the
425 new issue must be not in this list.
426 @raise TestPushException: If auto bug file failed to create a new issue or
427 dedupe multiple failures.
428 """
429 reporter = reporting.Reporter()
430 issue = reporter.find_issue_by_marker(BUG_ANCHOR)
431 if not issue:
432 raise TestPushException('Auto bug file failed. Unable to locate bug '
433 'with marker %s' % BUG_ANCHOR)
434 if old_issue_ids and issue.id in old_issue_ids:
435 raise TestPushException('Auto bug file failed to create a new issue. '
436 'id of the old issue found is %d.' % issue.id)
437 if not ('%s2' % reporter.AUTOFILED_COUNT) in issue.labels:
438 raise TestPushException(('Auto bug file failed to dedupe for issue %d '
439 'with labels of %s.') %
440 (issue.id, issue.labels))
441 # Close the bug, and do the search again, which should return None.
442 reporter.modify_bug_report(issue.id,
443 comment='Issue closed by test_push script.',
444 label_update='',
445 status='WontFix')
446 second_issue = reporter.find_issue_by_marker(BUG_ANCHOR)
447 if second_issue:
448 ids = '%d, %d' % (issue.id, second_issue.id)
449 raise TestPushException(('Auto bug file failed. Multiple issues (%s) '
450 'filed with marker %s') % (ids, BUG_ANCHOR))
451 print 'Issue %d was filed and deduped successfully.' % issue.id
452
453
Dan Shief1a5c02015-04-07 17:37:09 -0700454def check_queue(queue):
455 """Check the queue for any exception being raised.
456
457 @param queue: Queue used to store exception for parent process to access.
458 @raise: Any exception found in the queue.
459 """
460 if queue.empty():
461 return
462 exc_info = queue.get()
463 # Raise the exception with original backtrace.
464 print 'Original stack trace of the exception:\n%s' % exc_info[2]
465 raise exc_info[0](exc_info[1])
466
467
Dan Shi7e04fa82013-07-25 15:08:48 -0700468def main():
469 """Entry point for test_push script."""
470 arguments = parse_arguments()
471
472 try:
473 # Close existing bugs. New bug should be filed in dummy_Fail test.
474 old_issue_ids = close_bug()
Dan Shi7e04fa82013-07-25 15:08:48 -0700475
Dan Shief1a5c02015-04-07 17:37:09 -0700476 queue = multiprocessing.Queue()
477
478 push_to_prod_suite = multiprocessing.Process(
479 target=test_suite_wrapper,
480 args=(queue, PUSH_TO_PROD_SUITE, EXPECTED_TEST_RESULTS,
481 arguments))
482 push_to_prod_suite.start()
Jakob Juelich8f143912014-10-10 14:08:05 -0700483
Dan Shi7e04fa82013-07-25 15:08:48 -0700484 # TODO(dshi): Remove following line after crbug.com/267644 is fixed.
485 # Also, merge EXPECTED_TEST_RESULTS_AU to EXPECTED_TEST_RESULTS
Dan Shief1a5c02015-04-07 17:37:09 -0700486 au_suite = multiprocessing.Process(
487 target=test_suite_wrapper,
488 args=(queue, AU_SUITE, EXPECTED_TEST_RESULTS_AU,
489 arguments))
490 au_suite.start()
491
492 shard_suite = multiprocessing.Process(
493 target=test_suite_wrapper,
494 args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY,
495 arguments, True))
496 shard_suite.start()
497
Shuqian Zhaod4864772015-08-06 09:46:22 -0700498 # suite test with --create_and_return flag
499 asynchronous_suite = multiprocessing.Process(
500 target=test_suite_wrapper,
501 args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY,
502 arguments, True, True))
503 asynchronous_suite.start()
504
Dan Shief1a5c02015-04-07 17:37:09 -0700505 bug_filing_checked = False
506 while (push_to_prod_suite.is_alive() or au_suite.is_alive() or
Shuqian Zhaod4864772015-08-06 09:46:22 -0700507 shard_suite.is_alive() or asynchronous_suite.is_alive()):
Dan Shief1a5c02015-04-07 17:37:09 -0700508 check_queue(queue)
509 # Check bug filing results to fail early if bug filing failed.
510 if not bug_filing_checked and not push_to_prod_suite.is_alive():
511 check_bug_filed_and_deduped(old_issue_ids)
512 bug_filing_checked = True
513 time.sleep(5)
514
515 check_queue(queue)
516
517 push_to_prod_suite.join()
518 au_suite.join()
519 shard_suite.join()
Shuqian Zhaod4864772015-08-06 09:46:22 -0700520 asynchronous_suite.join()
Dan Shi7e04fa82013-07-25 15:08:48 -0700521 except Exception as e:
522 print 'Test for pushing to prod failed:\n'
523 print str(e)
524 # Send out email about the test failure.
525 if arguments.email:
Dan Shi5fa602c2015-03-26 17:54:13 -0700526 gmail_lib.send_email(
527 arguments.email,
528 'Test for pushing to prod failed. Do NOT push!',
529 ('Errors occurred during the test:\n\n%s\n\n' % str(e) +
530 'run_suite output:\n\n%s' % '\n'.join(run_suite_output)))
Dan Shi7e04fa82013-07-25 15:08:48 -0700531 raise
532
533 message = ('\nAll tests are completed successfully, prod branch is ready to'
534 ' be pushed.')
535 print message
536 # Send out email about test completed successfully.
537 if arguments.email:
Dan Shi5fa602c2015-03-26 17:54:13 -0700538 gmail_lib.send_email(
539 arguments.email,
540 'Test for pushing to prod completed successfully',
541 message)
Dan Shi7e04fa82013-07-25 15:08:48 -0700542
543
544if __name__ == '__main__':
545 sys.exit(main())