Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 1 | #!/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 | |
| 9 | The desired test suite will be scheduled with autotest, and then |
| 10 | this tool will block until the job is complete, printing a summary |
| 11 | at the end. Error conditions result in exceptions. |
| 12 | |
| 13 | This is intended for use only with Chrome OS test suits that leverage the |
| 14 | dynamic suite infrastructure in server/cros/dynamic_suite.py. |
| 15 | """ |
| 16 | |
Chris Masone | cfa7efc | 2012-09-06 16:00:07 -0700 | [diff] [blame] | 17 | import getpass, hashlib, logging, optparse, os, time, sys |
| 18 | from datetime import datetime |
| 19 | |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 20 | import common |
Chris Masone | cfa7efc | 2012-09-06 16:00:07 -0700 | [diff] [blame] | 21 | |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 22 | from autotest_lib.client.common_lib import global_config, error, utils |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 23 | from autotest_lib.server.cros.dynamic_suite import constants |
Chris Masone | b493555 | 2012-08-14 12:05:54 -0700 | [diff] [blame] | 24 | from autotest_lib.server.cros.dynamic_suite import frontend_wrappers |
| 25 | from autotest_lib.server.cros.dynamic_suite import job_status |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 26 | from autotest_lib.server.cros.dynamic_suite.reimager import Reimager |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 27 | |
Chris Masone | 1120cdf | 2012-02-27 17:35:07 -0800 | [diff] [blame] | 28 | CONFIG = global_config.global_config |
| 29 | |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 30 | # Return code that will be sent back to autotest_rpc_server.py |
| 31 | OK = 0 |
| 32 | ERROR = 1 |
| 33 | WARNING = 2 |
| 34 | |
Chris Masone | dfa0beba | 2012-03-19 11:41:47 -0700 | [diff] [blame] | 35 | |
Scott Zawalski | 94457b7 | 2012-07-02 18:45:07 -0400 | [diff] [blame] | 36 | def setup_logging(logfile=None): |
| 37 | """Setup basic logging with all logging info stripped. |
| 38 | |
| 39 | Calls to logging will only show the message. No severity is logged. |
| 40 | |
| 41 | @param logfile: If specified dump output to a file as well. |
| 42 | """ |
| 43 | screen_handler = logging.StreamHandler() |
| 44 | screen_handler.setFormatter(logging.Formatter('%(message)s')) |
| 45 | logging.getLogger().addHandler(screen_handler) |
| 46 | logging.getLogger().setLevel(logging.INFO) |
| 47 | if logfile: |
| 48 | file_handler = logging.FileHandler(logfile) |
| 49 | file_handler.setLevel(logging.DEBUG) |
| 50 | logging.getLogger().addHandler(file_handler) |
Chris Masone | dfa0beba | 2012-03-19 11:41:47 -0700 | [diff] [blame] | 51 | |
| 52 | |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 53 | def parse_options(): |
Zdenek Behan | 77290c3 | 2012-06-26 17:39:47 +0200 | [diff] [blame] | 54 | usage = "usage: %prog [options]" |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 55 | parser = optparse.OptionParser(usage=usage) |
| 56 | parser.add_option("-b", "--board", dest="board") |
| 57 | parser.add_option("-i", "--build", dest="build") |
Chris Masone | 359c0fd | 2012-03-13 15:18:59 -0700 | [diff] [blame] | 58 | # This should just be a boolean flag, but the autotest "proxy" code |
| 59 | # can't handle flags that don't take arguments. |
Alex Miller | ab33ddb | 2012-10-03 12:56:02 -0700 | [diff] [blame] | 60 | parser.add_option("-n", "--no_wait", dest="no_wait", default="False", |
| 61 | help='Must pass "True" or "False" if used.') |
Scott Zawalski | 6565017 | 2012-02-16 11:48:26 -0500 | [diff] [blame] | 62 | parser.add_option("-p", "--pool", dest="pool", default=None) |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 63 | parser.add_option("-s", "--suite_name", dest="name") |
Chris Masone | 8ac6671 | 2012-02-15 14:21:02 -0800 | [diff] [blame] | 64 | parser.add_option("-t", "--timeout_min", dest="timeout_min", default=30) |
| 65 | parser.add_option("-d", "--delay_sec", dest="delay_sec", default=10) |
Chris Masone | 986459e | 2012-04-11 11:36:48 -0700 | [diff] [blame] | 66 | parser.add_option("-m", "--mock_job_id", dest="mock_job_id", |
| 67 | help="Skips running suite; creates report for given ID.") |
Alex Miller | 05a2fff | 2012-09-10 10:14:34 -0700 | [diff] [blame] | 68 | parser.add_option("-u", "--num", dest="num", type="int", default=None, |
Chris Masone | 8906ab1 | 2012-07-23 15:37:56 -0700 | [diff] [blame] | 69 | help="Run on at most NUM machines.") |
Alex Miller | f43d0eb | 2012-10-01 13:43:13 -0700 | [diff] [blame] | 70 | # Same boolean flag issue applies here. |
Alex Miller | ab33ddb | 2012-10-03 12:56:02 -0700 | [diff] [blame] | 71 | parser.add_option("-f", "--file_bugs", dest="file_bugs", default='False', |
| 72 | help='File bugs on test failures. Must pass "True" or ' |
| 73 | '"False" if used.') |
Alex Miller | f43d0eb | 2012-10-01 13:43:13 -0700 | [diff] [blame] | 74 | |
| 75 | |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 76 | options, args = parser.parse_args() |
| 77 | return parser, options, args |
| 78 | |
| 79 | |
| 80 | def get_pretty_status(status): |
| 81 | if status == 'GOOD': |
| 82 | return '[ PASSED ]' |
Chris Masone | 8906ab1 | 2012-07-23 15:37:56 -0700 | [diff] [blame] | 83 | elif status == 'TEST_NA': |
| 84 | return '[ INFO ]' |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 85 | return '[ FAILED ]' |
| 86 | |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 87 | def is_fail_status(status): |
| 88 | # All the statuses tests can have when they fail. |
Chris Masone | 8906ab1 | 2012-07-23 15:37:56 -0700 | [diff] [blame] | 89 | if status in ['FAIL', 'ERROR']: |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 90 | return True |
| 91 | return False |
| 92 | |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 93 | |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 94 | def get_view_info(suite_job_id, view): |
| 95 | """ |
| 96 | Parse a view for the slave job name and job_id. |
| 97 | |
| 98 | @param suite_job_id: The job id of our master suite job. |
| 99 | @param view: Test result view. |
| 100 | @return A tuple job_name, experimental of the slave test run |
| 101 | described by view. |
| 102 | """ |
| 103 | # By default, we are the main suite job since there is no |
| 104 | # keyval entry for our job_name. |
| 105 | job_name = '%s-%s' % (suite_job_id, getpass.getuser()) |
| 106 | experimental = False |
| 107 | if 'job_keyvals' in view: |
| 108 | # The job name depends on whether it's experimental or not. |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 109 | if view['test_name'].startswith(Reimager.JOB_NAME): |
| 110 | std_job_name = Reimager.JOB_NAME |
| 111 | elif job_status.view_is_for_infrastructure_fail(view): |
| 112 | std_job_name = view['test_name'] |
| 113 | else: |
| 114 | std_job_name = view['test_name'].split('.')[0] |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 115 | exp_job_name = constants.EXPERIMENTAL_PREFIX + std_job_name |
Chris Masone | 11aae45 | 2012-05-21 16:08:39 -0700 | [diff] [blame] | 116 | |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 117 | std_job_hash = hashlib.md5(std_job_name).hexdigest() |
| 118 | exp_job_hash = hashlib.md5(exp_job_name).hexdigest() |
Chris Masone | 11aae45 | 2012-05-21 16:08:39 -0700 | [diff] [blame] | 119 | |
| 120 | if std_job_hash in view['job_keyvals']: |
| 121 | job_name = view['job_keyvals'][std_job_hash] |
| 122 | elif exp_job_hash in view['job_keyvals']: |
| 123 | experimental = True |
| 124 | job_name = view['job_keyvals'][exp_job_hash] |
| 125 | |
| 126 | # For backward compatibility. |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 127 | if std_job_name in view['job_keyvals']: |
| 128 | job_name = view['job_keyvals'][std_job_name] |
| 129 | elif exp_job_name in view['job_keyvals']: |
| 130 | experimental = True |
| 131 | job_name = view['job_keyvals'][exp_job_name] |
Chris Masone | 11aae45 | 2012-05-21 16:08:39 -0700 | [diff] [blame] | 132 | |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 133 | return job_name, experimental |
| 134 | |
| 135 | |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 136 | class LogLink(object): |
| 137 | """ |
| 138 | Link to a log. |
| 139 | |
| 140 | @var anchor: the link text. |
| 141 | @var url: the link url. |
| 142 | """ |
| 143 | def __init__(self, anchor, job_string): |
| 144 | """ |
| 145 | Initialize the LogLink by generating the log URL. |
| 146 | |
| 147 | @param anchor: the link text. |
| 148 | @param job_string: the job whose logs we'd like to link to. |
| 149 | """ |
| 150 | self.anchor = anchor |
| 151 | host = CONFIG.get_config_value('SERVER', 'hostname', type=str) |
| 152 | pattern = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str) |
| 153 | self.url = pattern % (host, job_string) |
| 154 | |
| 155 | |
| 156 | def GenerateBuildbotLink(self): |
| 157 | """ |
| 158 | Generate a link to the job's logs, for consumption by buildbot. |
| 159 | |
| 160 | @return A link formatted for the buildbot log annotator. |
| 161 | """ |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 162 | return "@@@STEP_LINK@%s@%s@@@" % (self.anchor.strip(), self.url) |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 163 | |
| 164 | |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 165 | def GenerateTextLink(self): |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 166 | """ |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 167 | Generate a link to the job's logs, for consumption by a human. |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 168 | |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 169 | @return A link formatted for human readability. |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 170 | """ |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 171 | return "%s%s" % (self.anchor, self.url) |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 172 | |
| 173 | |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 174 | class Timings(object): |
| 175 | """Timings for important events during a suite. |
| 176 | |
| 177 | All timestamps are datetime.datetime objects. |
| 178 | |
| 179 | @var suite_start_time: the time the suite started. |
| 180 | @var reimage_start_time: the time we started reimaging devices. |
| 181 | @var reimage_end_time: the time we finished reimaging devices. |
| 182 | @var tests_start_time: the time the first test started running. |
| 183 | """ |
Chris Masone | a8066a9 | 2012-05-01 16:52:31 -0700 | [diff] [blame] | 184 | download_start_time = None |
| 185 | payload_end_time = None |
| 186 | artifact_end_time = None |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 187 | suite_start_time = None |
Chris Masone | 604baf3 | 2012-06-28 08:45:30 -0700 | [diff] [blame] | 188 | reimage_times = {} # {'hostname': (start_time, end_time)} |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 189 | tests_start_time = None |
| 190 | tests_end_time = None |
| 191 | |
| 192 | |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 193 | def RecordTiming(self, view): |
| 194 | """Given a test report view, extract and record pertinent time info. |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 195 | |
| 196 | get_detailed_test_views() returns a list of entries that provide |
| 197 | info about the various parts of a suite run. This method can take |
| 198 | any one of these entries and look up timestamp info we might want |
| 199 | and record it. |
| 200 | |
Chris Masone | cfa7efc | 2012-09-06 16:00:07 -0700 | [diff] [blame] | 201 | If timestamps are unavailable, datetime.datetime.min/max will be used. |
| 202 | |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 203 | @param view: a view dict, as returned by get_detailed_test_views(). |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 204 | """ |
Chris Masone | cfa7efc | 2012-09-06 16:00:07 -0700 | [diff] [blame] | 205 | start_candidate = datetime.min |
| 206 | end_candidate = datetime.max |
| 207 | if view['test_started_time']: |
| 208 | start_candidate = datetime.strptime(view['test_started_time'], |
| 209 | job_status.TIME_FMT) |
| 210 | if view['test_finished_time']: |
| 211 | end_candidate = datetime.strptime(view['test_finished_time'], |
| 212 | job_status.TIME_FMT) |
| 213 | |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 214 | if job_status.view_is_for_suite_prep(view): |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 215 | self.suite_start_time = start_candidate |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 216 | elif view['test_name'].startswith(Reimager.JOB_NAME): |
| 217 | if '-' in view['test_name']: |
| 218 | hostname = view['test_name'].split('-', 1)[1] |
Chris Masone | f76ebba | 2012-06-28 14:56:22 -0700 | [diff] [blame] | 219 | else: |
| 220 | hostname = '' |
Chris Masone | 604baf3 | 2012-06-28 08:45:30 -0700 | [diff] [blame] | 221 | self.reimage_times[hostname] = (start_candidate, end_candidate) |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 222 | else: |
| 223 | self._UpdateFirstTestStartTime(start_candidate) |
| 224 | self._UpdateLastTestEndTime(end_candidate) |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 225 | if 'job_keyvals' in view: |
| 226 | keyvals = view['job_keyvals'] |
Chris Masone | aa10f8e | 2012-05-15 13:34:21 -0700 | [diff] [blame] | 227 | self.download_start_time = keyvals.get( |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 228 | constants.DOWNLOAD_STARTED_TIME) |
Chris Masone | aa10f8e | 2012-05-15 13:34:21 -0700 | [diff] [blame] | 229 | self.payload_end_time = keyvals.get( |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 230 | constants.PAYLOAD_FINISHED_TIME) |
Chris Masone | aa10f8e | 2012-05-15 13:34:21 -0700 | [diff] [blame] | 231 | self.artifact_end_time = keyvals.get( |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 232 | constants.ARTIFACT_FINISHED_TIME) |
| 233 | |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 234 | |
| 235 | def _UpdateFirstTestStartTime(self, candidate): |
| 236 | """Update self.tests_start_time, iff candidate is an earlier time. |
| 237 | |
| 238 | @param candidate: a datetime.datetime object. |
| 239 | """ |
| 240 | if not self.tests_start_time or candidate < self.tests_start_time: |
| 241 | self.tests_start_time = candidate |
| 242 | |
| 243 | |
| 244 | def _UpdateLastTestEndTime(self, candidate): |
| 245 | """Update self.tests_end_time, iff candidate is a later time. |
| 246 | |
| 247 | @param candidate: a datetime.datetime object. |
| 248 | """ |
| 249 | if not self.tests_end_time or candidate > self.tests_end_time: |
| 250 | self.tests_end_time = candidate |
| 251 | |
| 252 | |
| 253 | def __str__(self): |
Chris Masone | 604baf3 | 2012-06-28 08:45:30 -0700 | [diff] [blame] | 254 | reimaging_info = '' |
| 255 | for host, (start, end) in self.reimage_times.iteritems(): |
| 256 | reimaging_info += ('Reimaging %s started at %s\n' |
| 257 | 'Reimaging %s ended at %s\n' % (host, start, |
| 258 | host, end)) |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 259 | return ('\n' |
| 260 | 'Suite timings:\n' |
Chris Masone | a8066a9 | 2012-05-01 16:52:31 -0700 | [diff] [blame] | 261 | 'Downloads started at %s\n' |
| 262 | 'Payload downloads ended at %s\n' |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 263 | 'Suite started at %s\n' |
Chris Masone | 604baf3 | 2012-06-28 08:45:30 -0700 | [diff] [blame] | 264 | '%s' |
Chris Masone | a8066a9 | 2012-05-01 16:52:31 -0700 | [diff] [blame] | 265 | 'Artifact downloads ended (at latest) at %s\n' |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 266 | 'Testing started at %s\n' |
Chris Masone | a8066a9 | 2012-05-01 16:52:31 -0700 | [diff] [blame] | 267 | 'Testing ended at %s\n' % (self.download_start_time, |
| 268 | self.payload_end_time, |
| 269 | self.suite_start_time, |
Chris Masone | 604baf3 | 2012-06-28 08:45:30 -0700 | [diff] [blame] | 270 | reimaging_info, |
Chris Masone | a8066a9 | 2012-05-01 16:52:31 -0700 | [diff] [blame] | 271 | self.artifact_end_time, |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 272 | self.tests_start_time, |
| 273 | self.tests_end_time)) |
| 274 | |
| 275 | |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 276 | def _full_test_name(job_id, view): |
| 277 | """Generates the full test name for printing to logs. |
| 278 | |
| 279 | @param job_id: the job id. |
| 280 | @param view: the view for which we are generating the name. |
| 281 | @return The test name, possibly with a descriptive prefix appended. |
| 282 | """ |
| 283 | job_name, experimental = get_view_info(job_id, view) |
| 284 | prefix = constants.EXPERIMENTAL_PREFIX if experimental else '' |
| 285 | return prefix + view['test_name'] |
| 286 | |
| 287 | |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 288 | def main(): |
| 289 | parser, options, args = parse_options() |
Chris Masone | 3a85064 | 2012-07-11 11:11:18 -0700 | [diff] [blame] | 290 | log_name = 'run_suite-default.log' |
Chris Masone | 986459e | 2012-04-11 11:36:48 -0700 | [diff] [blame] | 291 | if not options.mock_job_id: |
Zdenek Behan | 77290c3 | 2012-06-26 17:39:47 +0200 | [diff] [blame] | 292 | if args: |
| 293 | print 'Unknown arguments: ' + str(args) |
| 294 | parser.print_help() |
| 295 | return |
| 296 | if not options.build: |
| 297 | print 'Need to specify which build to use' |
| 298 | parser.print_help() |
| 299 | return |
| 300 | if not options.board: |
| 301 | print 'Need to specify board' |
| 302 | parser.print_help() |
| 303 | return |
| 304 | if not options.name: |
| 305 | print 'Need to specify suite name' |
Chris Masone | 986459e | 2012-04-11 11:36:48 -0700 | [diff] [blame] | 306 | parser.print_help() |
| 307 | return |
Chris Masone | 3a85064 | 2012-07-11 11:11:18 -0700 | [diff] [blame] | 308 | # convert build name from containing / to containing only _ |
| 309 | log_name = 'run_suite-%s.log' % options.build.replace('/', '_') |
| 310 | log_dir = os.path.join(common.autotest_dir, 'logs') |
| 311 | if os.path.exists(log_dir): |
| 312 | log_name = os.path.join(log_dir, log_name) |
Alex Miller | 8e75d0d | 2012-07-31 15:13:32 -0700 | [diff] [blame] | 313 | if options.num is not None and options.num < 1: |
| 314 | print 'Number of machines must be more than 0, if specified.' |
| 315 | parser.print_help() |
| 316 | return |
Alex Miller | ab33ddb | 2012-10-03 12:56:02 -0700 | [diff] [blame] | 317 | if options.no_wait != 'True' and options.no_wait != 'False': |
| 318 | print 'Please specify "True" or "False" for --no_wait.' |
| 319 | parser.print_help() |
| 320 | return |
| 321 | if options.file_bugs != 'True' and options.file_bugs != 'False': |
| 322 | print 'Please specify "True" or "False" for --file_bugs.' |
| 323 | parser.print_help() |
| 324 | return |
Scott Zawalski | 94457b7 | 2012-07-02 18:45:07 -0400 | [diff] [blame] | 325 | setup_logging(logfile=log_name) |
Chris Masone | dfa0beba | 2012-03-19 11:41:47 -0700 | [diff] [blame] | 326 | |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 327 | try: |
| 328 | utils.check_lab_status() |
| 329 | except error.LabIsDownException as e: |
| 330 | # Lab is not up, return WARNING. |
| 331 | logging.debug('Lab is not up. Error message: %s', e) |
| 332 | print str(e) |
| 333 | return WARNING |
| 334 | |
Chris Masone | 8ac6671 | 2012-02-15 14:21:02 -0800 | [diff] [blame] | 335 | afe = frontend_wrappers.RetryingAFE(timeout_min=options.timeout_min, |
| 336 | delay_sec=options.delay_sec) |
Chris Masone | 359c0fd | 2012-03-13 15:18:59 -0700 | [diff] [blame] | 337 | |
Alex Miller | ab33ddb | 2012-10-03 12:56:02 -0700 | [diff] [blame] | 338 | wait = (options.no_wait == 'False') |
| 339 | file_bugs = (options.file_bugs == 'True') |
Chris Masone | 986459e | 2012-04-11 11:36:48 -0700 | [diff] [blame] | 340 | if options.mock_job_id: |
| 341 | job_id = int(options.mock_job_id) |
| 342 | else: |
Alex Miller | 8e75d0d | 2012-07-31 15:13:32 -0700 | [diff] [blame] | 343 | job_id = afe.run('create_suite_job', suite_name=options.name, |
| 344 | board=options.board, build=options.build, |
Alex Miller | f43d0eb | 2012-10-01 13:43:13 -0700 | [diff] [blame] | 345 | check_hosts=wait, pool=options.pool, num=options.num, |
| 346 | file_bugs=file_bugs) |
Chris Masone | 8ac6671 | 2012-02-15 14:21:02 -0800 | [diff] [blame] | 347 | TKO = frontend_wrappers.RetryingTKO(timeout_min=options.timeout_min, |
| 348 | delay_sec=options.delay_sec) |
Scott Zawalski | 94457b7 | 2012-07-02 18:45:07 -0400 | [diff] [blame] | 349 | logging.info('Started suite job: %s', job_id) |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 350 | |
| 351 | code = OK |
Chris Masone | 359c0fd | 2012-03-13 15:18:59 -0700 | [diff] [blame] | 352 | while wait and True: |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 353 | if not afe.get_jobs(id=job_id, finished=True): |
| 354 | time.sleep(1) |
| 355 | continue |
Scott Zawalski | 0acfe11 | 2012-03-06 09:21:44 -0500 | [diff] [blame] | 356 | views = TKO.run('get_detailed_test_views', afe_job_id=job_id) |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 357 | width = max((len(_full_test_name(job_id, view)) for view in views)) + 3 |
Scott Zawalski | 0acfe11 | 2012-03-06 09:21:44 -0500 | [diff] [blame] | 358 | |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 359 | relevant_views = filter(job_status.view_is_relevant, views) |
Scott Zawalski | 0acfe11 | 2012-03-06 09:21:44 -0500 | [diff] [blame] | 360 | if not relevant_views: |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 361 | # The main suite job most likely failed in SERVER_JOB. |
| 362 | relevant_views = views |
Scott Zawalski | 0acfe11 | 2012-03-06 09:21:44 -0500 | [diff] [blame] | 363 | |
Chris Masone | b61b405 | 2012-04-30 14:35:28 -0700 | [diff] [blame] | 364 | timings = Timings() |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 365 | web_links = [] |
| 366 | buildbot_links = [] |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 367 | for view in relevant_views: |
| 368 | timings.RecordTiming(view) |
| 369 | if job_status.view_is_for_suite_prep(view): |
| 370 | view['test_name'] = 'Suite prep' |
Chris Masone | 3a85064 | 2012-07-11 11:11:18 -0700 | [diff] [blame] | 371 | |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 372 | job_name, experimental = get_view_info(job_id, view) |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 373 | test_view = _full_test_name(job_id, view).ljust(width) |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 374 | logging.info("%s%s", test_view, get_pretty_status(view['status'])) |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 375 | link = LogLink(test_view, job_name) |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 376 | web_links.append(link) |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 377 | |
| 378 | if view['status'] != 'GOOD': |
| 379 | logging.info("%s %s: %s", test_view, view['status'], |
| 380 | view['reason']) |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 381 | # Don't show links on the buildbot waterfall for tests with |
| 382 | # GOOD status. |
| 383 | buildbot_links.append(link) |
Chris Masone | 8906ab1 | 2012-07-23 15:37:56 -0700 | [diff] [blame] | 384 | if view['status'] == 'TEST_NA': |
| 385 | # Didn't run; nothing to do here! |
| 386 | continue |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 387 | if code == ERROR: |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 388 | # Failed already, no need to worry further. |
| 389 | continue |
Chris Masone | d9f13c5 | 2012-08-29 10:37:08 -0700 | [diff] [blame] | 390 | if (view['status'] == 'WARN' or |
| 391 | (is_fail_status(view['status']) and experimental)): |
Zdenek Behan | 150fbd6 | 2012-04-06 17:20:01 +0200 | [diff] [blame] | 392 | # Failures that produce a warning. Either a test with WARN |
| 393 | # status or any experimental test failure. |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 394 | code = WARNING |
Chris Masone | 5374c67 | 2012-03-05 15:11:39 -0800 | [diff] [blame] | 395 | else: |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 396 | code = ERROR |
Scott Zawalski | 94457b7 | 2012-07-02 18:45:07 -0400 | [diff] [blame] | 397 | logging.info(timings) |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 398 | logging.info('\n' |
| 399 | 'Links to test logs:') |
| 400 | for link in web_links: |
Craig Harrison | d845157 | 2012-08-31 10:29:33 -0700 | [diff] [blame] | 401 | logging.info(link.GenerateTextLink()) |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 402 | logging.info('\n' |
| 403 | 'Output below this line is for buildbot consumption:') |
| 404 | for link in buildbot_links: |
| 405 | logging.info(link.GenerateBuildbotLink()) |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 406 | break |
Chris Masone | d5939fe | 2012-03-13 10:11:06 -0700 | [diff] [blame] | 407 | else: |
Scott Zawalski | 94457b7 | 2012-07-02 18:45:07 -0400 | [diff] [blame] | 408 | logging.info('Created suite job: %r', job_id) |
Craig Harrison | 25eb0f3 | 2012-08-23 16:48:49 -0700 | [diff] [blame] | 409 | link = LogLink(options.name, '%s-%s' % (job_id, getpass.getuser())) |
| 410 | logging.info(link.GenerateBuildbotLink()) |
Scott Zawalski | 94457b7 | 2012-07-02 18:45:07 -0400 | [diff] [blame] | 411 | logging.info('--no_wait specified; Exiting.') |
Chris Masone | 24b80f1 | 2012-02-14 14:18:01 -0800 | [diff] [blame] | 412 | return code |
| 413 | |
| 414 | if __name__ == "__main__": |
| 415 | sys.exit(main()) |