blob: 1dcec0d6bbbac64cf3a39729ab53369710809ccd [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
7"""Tool for running suites of tests and waiting for completion.
8
9The desired test suite will be scheduled with autotest, and then
10this tool will block until the job is complete, printing a summary
11at the end. Error conditions result in exceptions.
12
13This is intended for use only with Chrome OS test suits that leverage the
14dynamic suite infrastructure in server/cros/dynamic_suite.py.
15"""
16
Scott Zawalski0acfe112012-03-06 09:21:44 -050017import getpass, optparse, time, sys
Chris Masone24b80f12012-02-14 14:18:01 -080018import common
Chris Masone1120cdf2012-02-27 17:35:07 -080019from autotest_lib.client.common_lib import global_config
Chris Masone8ac66712012-02-15 14:21:02 -080020from autotest_lib.server.cros import frontend_wrappers
Chris Masone24b80f12012-02-14 14:18:01 -080021
Chris Masone1120cdf2012-02-27 17:35:07 -080022CONFIG = global_config.global_config
23
Chris Masone24b80f12012-02-14 14:18:01 -080024def parse_options():
25 usage = "usage: %prog [options] control_file"
26 parser = optparse.OptionParser(usage=usage)
27 parser.add_option("-b", "--board", dest="board")
28 parser.add_option("-i", "--build", dest="build")
Scott Zawalski65650172012-02-16 11:48:26 -050029 parser.add_option("-p", "--pool", dest="pool", default=None)
Chris Masone24b80f12012-02-14 14:18:01 -080030 parser.add_option("-s", "--suite_name", dest="name")
Chris Masone8ac66712012-02-15 14:21:02 -080031 parser.add_option("-t", "--timeout_min", dest="timeout_min", default=30)
32 parser.add_option("-d", "--delay_sec", dest="delay_sec", default=10)
Chris Masone24b80f12012-02-14 14:18:01 -080033 options, args = parser.parse_args()
34 return parser, options, args
35
36
37def get_pretty_status(status):
38 if status == 'GOOD':
39 return '[ PASSED ]'
40 return '[ FAILED ]'
41
42
43def status_is_relevant(status):
44 """
45 Indicates whether the status of a given test is meaningful or not.
46
47 @param status: frontend.TestStatus object to look at.
48 @return True if this is a test result worth looking at further.
49 """
50 return not (status['test_name'].startswith('SERVER_JOB') or
51 status['test_name'].startswith('CLIENT_JOB'))
52
53
Chris Masone1120cdf2012-02-27 17:35:07 -080054def generate_log_link(anchor, job_string):
55 """
56 Generate a link to this job's logs, for consumption by buildbot.
57
58 @param anchor: Link anchor text.
59 @param job_id: the job whose logs we'd like to link to.
60 @return A link formatted for the buildbot log annotator.
61 """
62 host = CONFIG.get_config_value('SERVER', 'hostname', type=str)
63 pattern = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str)
64 return "@@@STEP_LINK@%s@%s@@@" % (anchor, pattern % (host, job_string))
65
66
Chris Masone24b80f12012-02-14 14:18:01 -080067def main():
68 parser, options, args = parse_options()
69 if args or not options.build or not options.board or not options.name:
70 parser.print_help()
71 return
Chris Masone8ac66712012-02-15 14:21:02 -080072 afe = frontend_wrappers.RetryingAFE(timeout_min=options.timeout_min,
73 delay_sec=options.delay_sec)
Chris Masone24b80f12012-02-14 14:18:01 -080074 job_id = afe.run('create_suite_job',
75 suite_name=options.name,
76 board=options.board,
Scott Zawalski65650172012-02-16 11:48:26 -050077 build=options.build,
78 pool=options.pool)
Chris Masone8ac66712012-02-15 14:21:02 -080079 TKO = frontend_wrappers.RetryingTKO(timeout_min=options.timeout_min,
80 delay_sec=options.delay_sec)
Chris Masone24b80f12012-02-14 14:18:01 -080081 # Return code that will be sent back to autotest_rpc_server.py
82 code = 0
83 while True:
84 if not afe.get_jobs(id=job_id, finished=True):
85 time.sleep(1)
86 continue
Scott Zawalski0acfe112012-03-06 09:21:44 -050087 views = TKO.run('get_detailed_test_views', afe_job_id=job_id)
Chris Masone24b80f12012-02-14 14:18:01 -080088 width = len(max(map(lambda x: x['test_name'], views), key=len)) + 3
Scott Zawalski0acfe112012-03-06 09:21:44 -050089
90 relevant_views = filter(status_is_relevant, views)
91 if not relevant_views:
92 # The main suite job most likely failed in SERVER_JOB.
93 relevant_views = views
94
Chris Masone1120cdf2012-02-27 17:35:07 -080095 log_links = []
Scott Zawalski0acfe112012-03-06 09:21:44 -050096 for entry in relevant_views:
Chris Masone24b80f12012-02-14 14:18:01 -080097 test_entry = entry['test_name'].ljust(width)
98 print "%s%s" % (test_entry, get_pretty_status(entry['status']))
99 if entry['status'] != 'GOOD':
100 print "%s %s: %s" % (test_entry,
101 entry['status'],
102 entry['reason'])
Chris Masone1120cdf2012-02-27 17:35:07 -0800103 job_name = entry['test_name'].split('.')[0]
104 if 'job_keyvals' in entry and job_name in entry['job_keyvals']:
Scott Zawalski0acfe112012-03-06 09:21:44 -0500105 job_name = entry['job_keyvals'][job_name]
106 else:
107 # We are the main suite job since there is no keyval entry
108 # for our job_name.
109 job_name = '%s-%s' % (job_id, getpass.getuser())
110
111 log_links.append(generate_log_link(entry['test_name'],
112 job_name))
Chris Masone5374c672012-03-05 15:11:39 -0800113 if code == 0 and entry['status'] == 'WARN':
114 # IFF this is the only error, and it's a warning...
115 code = 2
116 else:
117 code = 1
Chris Masone1120cdf2012-02-27 17:35:07 -0800118 for link in log_links:
119 print link
Chris Masone24b80f12012-02-14 14:18:01 -0800120 break
121 return code
122
123if __name__ == "__main__":
124 sys.exit(main())