blob: 2f35ed013d7e38bc3a8194af9e016f040cff8231 [file] [log] [blame]
Chris Masone44e4d6c2012-08-15 14:25:53 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Alex Miller3a69adc2012-12-19 13:38:31 -08005import hashlib, logging, os, re, traceback
Chris Masone44e4d6c2012-08-15 14:25:53 -07006
7import common
8
Alex Miller3a69adc2012-12-19 13:38:31 -08009from autotest_lib.client.common_lib import control_data
beepsc4fb1472013-05-08 21:49:48 -070010from autotest_lib.client.common_lib import site_utils, utils
Chris Masone44e4d6c2012-08-15 14:25:53 -070011from autotest_lib.server.cros.dynamic_suite import constants
12from autotest_lib.server.cros.dynamic_suite import control_file_getter
13from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Alex Miller3a69adc2012-12-19 13:38:31 -080014from autotest_lib.server.cros.dynamic_suite import job_status
Chris Masone44e4d6c2012-08-15 14:25:53 -070015from autotest_lib.server.cros.dynamic_suite.job_status import Status
beeps1ccbbb82013-02-21 20:35:01 -080016from autotest_lib.server.cros.dynamic_suite import reporting
Chris Masone44e4d6c2012-08-15 14:25:53 -070017
18class Suite(object):
19 """
20 A suite of tests, defined by some predicate over control file variables.
21
22 Given a place to search for control files a predicate to match the desired
23 tests, can gather tests and fire off jobs to run them, and then wait for
24 results.
25
26 @var _predicate: a function that should return True when run over a
27 ControlData representation of a control file that should be in
28 this Suite.
29 @var _tag: a string with which to tag jobs run in this suite.
30 @var _build: the build on which we're running this suite.
31 @var _afe: an instance of AFE as defined in server/frontend.py.
32 @var _tko: an instance of TKO as defined in server/frontend.py.
33 @var _jobs: currently scheduled jobs, if any.
34 @var _cf_getter: a control_file_getter.ControlFileGetter
35 """
36
37
38 @staticmethod
Chris Sosaaccb5ce2012-08-30 17:29:15 -070039 def create_ds_getter(build, devserver):
Chris Masone44e4d6c2012-08-15 14:25:53 -070040 """
41 @param build: the build on which we're running this suite.
Chris Sosaaccb5ce2012-08-30 17:29:15 -070042 @param devserver: the devserver which contains the build.
Chris Masone44e4d6c2012-08-15 14:25:53 -070043 @return a FileSystemGetter instance that looks under |autotest_dir|.
44 """
Chris Sosaaccb5ce2012-08-30 17:29:15 -070045 return control_file_getter.DevServerGetter(build, devserver)
Chris Masone44e4d6c2012-08-15 14:25:53 -070046
47
48 @staticmethod
49 def create_fs_getter(autotest_dir):
50 """
51 @param autotest_dir: the place to find autotests.
52 @return a FileSystemGetter instance that looks under |autotest_dir|.
53 """
54 # currently hard-coded places to look for tests.
55 subpaths = ['server/site_tests', 'client/site_tests',
56 'server/tests', 'client/tests']
57 directories = [os.path.join(autotest_dir, p) for p in subpaths]
58 return control_file_getter.FileSystemGetter(directories)
59
60
61 @staticmethod
62 def parse_tag(tag):
Aviv Keshet18308922013-02-19 17:49:49 -080063 """Splits a string on ',' optionally surrounded by whitespace.
64 @param tag: string to split.
65 """
Chris Masone44e4d6c2012-08-15 14:25:53 -070066 return map(lambda x: x.strip(), tag.split(','))
67
68
69 @staticmethod
70 def name_in_tag_predicate(name):
71 """Returns predicate that takes a control file and looks for |name|.
72
73 Builds a predicate that takes in a parsed control file (a ControlData)
74 and returns True if the SUITE tag is present and contains |name|.
75
76 @param name: the suite name to base the predicate on.
77 @return a callable that takes a ControlData and looks for |name| in that
78 ControlData object's suite member.
79 """
80 return lambda t: hasattr(t, 'suite') and \
81 name in Suite.parse_tag(t.suite)
82
83
84 @staticmethod
Aviv Keshet40222a42013-06-04 16:25:49 -070085 def not_in_blacklist_predicate(blacklist):
86 """Returns predicate that takes a control file and looks for its
87 path to not be in given blacklist.
88
89 @param blacklist: A list of strings both paths on control_files that
90 should be blacklisted.
91
92 @return a callable that takes a ControlData and looks for it to be
93 absent from blacklist.
94 """
95 return lambda t: hasattr(t, 'path') and \
96 not any(b.endswith(t.path) for b in blacklist)
97
98
99 @staticmethod
100 def test_name_equals_predicate(test_name):
101 """Returns predicate that matched based on a test's name.
102
103 Builds a predicate that takes in a parsed control file (a ControlData)
104 and returns True if the test name is equal to |test_name|.
105
106 @param test_name: the test name to base the predicate on.
107 @return a callable that takes a ControlData and looks for |test_name|
108 in that ControlData's name.
109 """
110 return lambda t: hasattr(t, 'name') and test_name == t.name
111
112
113 @staticmethod
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700114 def list_all_suites(build, devserver, cf_getter=None):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700115 """
116 Parses all ControlData objects with a SUITE tag and extracts all
117 defined suite names.
118
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700119 @param build: the build on which we're running this suite.
120 @param devserver: the devserver which contains the build.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700121 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
122 using DevServerGetter.
123
124 @return list of suites
125 """
126 if cf_getter is None:
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700127 cf_getter = Suite.create_ds_getter(build, devserver)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700128
129 suites = set()
130 predicate = lambda t: hasattr(t, 'suite')
131 for test in Suite.find_and_parse_tests(cf_getter, predicate,
132 add_experimental=True):
133 suites.update(Suite.parse_tag(test.suite))
134 return list(suites)
135
136
137 @staticmethod
Alex Millera0913072013-06-12 10:01:51 -0700138 def create_from_predicates(predicates, build, board, devserver,
139 cf_getter=None, name='ad_hoc_suite', **dargs):
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700140 """
141 Create a Suite using a given predicate test filters.
142
143 Uses supplied predicate(s) to instantiate a Suite. Looks for tests in
144 |autotest_dir| and will schedule them using |afe|. Pulls control files
145 from the default dev server. Results will be pulled from |tko| upon
146 completion.
147
148 @param predicates: A list of callables that accept ControlData
149 representations of control files. A test will be
Aviv Keshet938a6772013-07-25 14:05:45 -0700150 included in suite if all callables in this list
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700151 return True on the given control file.
152 @param build: the build on which we're running this suite.
Alex Millera0913072013-06-12 10:01:51 -0700153 @param board: the board on which we're running this suite.
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700154 @param devserver: the devserver which contains the build.
155 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
156 using DevServerGetter.
157 @param name: name of suite. Defaults to 'ad_hoc_suite'
158 @param **dargs: Any other Suite constructor parameters, as described
159 in Suite.__init__ docstring.
160 @return a Suite instance.
161 """
162 if cf_getter is None:
163 cf_getter = Suite.create_ds_getter(build, devserver)
164
165 return Suite(predicates,
Alex Millera0913072013-06-12 10:01:51 -0700166 name, build, board, cf_getter, **dargs)
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700167
168
169 @staticmethod
Alex Millera0913072013-06-12 10:01:51 -0700170 def create_from_name(name, build, board, devserver, cf_getter=None,
171 **dargs):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700172 """
173 Create a Suite using a predicate based on the SUITE control file var.
174
175 Makes a predicate based on |name| and uses it to instantiate a Suite
176 that looks for tests in |autotest_dir| and will schedule them using
177 |afe|. Pulls control files from the default dev server.
178 Results will be pulled from |tko| upon completion.
179
180 @param name: a value of the SUITE control file variable to search for.
181 @param build: the build on which we're running this suite.
Alex Millera0913072013-06-12 10:01:51 -0700182 @param board: the board on which we're running this suite.
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700183 @param devserver: the devserver which contains the build.
Aviv Keshet813d6782013-06-04 17:11:03 -0700184 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
185 using DevServerGetter.
186 @param **dargs: Any other Suite constructor parameters, as described
187 in Suite.__init__ docstring.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700188 @return a Suite instance.
189 """
190 if cf_getter is None:
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700191 cf_getter = Suite.create_ds_getter(build, devserver)
192
Aviv Keshet40222a42013-06-04 16:25:49 -0700193 return Suite([Suite.name_in_tag_predicate(name)],
Alex Millera0913072013-06-12 10:01:51 -0700194 name, build, board, cf_getter, **dargs)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700195
196
Chris Masone8906ab12012-07-23 15:37:56 -0700197 @staticmethod
Alex Millera0913072013-06-12 10:01:51 -0700198 def create_from_name_and_blacklist(name, blacklist, build, board, devserver,
Aviv Keshet813d6782013-06-04 17:11:03 -0700199 cf_getter=None, **dargs):
Chris Masone8906ab12012-07-23 15:37:56 -0700200 """
201 Create a Suite using a predicate based on the SUITE control file var.
202
203 Makes a predicate based on |name| and uses it to instantiate a Suite
204 that looks for tests in |autotest_dir| and will schedule them using
205 |afe|. Pulls control files from the default dev server.
206 Results will be pulled from |tko| upon completion.
207
208 @param name: a value of the SUITE control file variable to search for.
209 @param blacklist: iterable of control file paths to skip.
210 @param build: the build on which we're running this suite.
Alex Millera0913072013-06-12 10:01:51 -0700211 @param board: the board on which we're running this suite.
Chris Masone8906ab12012-07-23 15:37:56 -0700212 @param devserver: the devserver which contains the build.
Aviv Keshet813d6782013-06-04 17:11:03 -0700213 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
214 using DevServerGetter.
215 @param **dargs: Any other Suite constructor parameters, as described
216 in Suite.__init__ docstring.
Chris Masone8906ab12012-07-23 15:37:56 -0700217 @return a Suite instance.
218 """
219 if cf_getter is None:
220 cf_getter = Suite.create_ds_getter(build, devserver)
221
Aviv Keshet40222a42013-06-04 16:25:49 -0700222 predicates = [Suite.name_in_tag_predicate(name),
223 Suite.not_in_blacklist_predicate(blacklist)]
Chris Masone8906ab12012-07-23 15:37:56 -0700224
Alex Millera0913072013-06-12 10:01:51 -0700225 return Suite(predicates, name, build, board, cf_getter, **dargs)
Chris Masone8906ab12012-07-23 15:37:56 -0700226
227
Alex Millera0913072013-06-12 10:01:51 -0700228 def __init__(self, predicates, tag, build, board, cf_getter, afe=None,
229 tko=None, pool=None, results_dir=None, max_runtime_mins=24*60,
Alex Millera3a4fe72013-01-22 09:57:47 -0800230 version_prefix=constants.VERSION_PREFIX,
beepsda5b7112013-05-30 11:34:14 -0700231 file_bugs=False, file_experimental_bugs=False,
232 suite_job_id=None, ignore_deps=False):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700233 """
234 Constructor
235
Aviv Keshet40222a42013-06-04 16:25:49 -0700236 @param predicates: A list of callables that accept ControlData
237 representations of control files. A test will be
238 included in suite is all callables in this list
239 return True on the given control file.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700240 @param tag: a string with which to tag jobs run in this suite.
241 @param build: the build on which we're running this suite.
Alex Millera0913072013-06-12 10:01:51 -0700242 @param board: the board on which we're running this suite.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700243 @param cf_getter: a control_file_getter.ControlFileGetter
244 @param afe: an instance of AFE as defined in server/frontend.py.
245 @param tko: an instance of TKO as defined in server/frontend.py.
246 @param pool: Specify the pool of machines to use for scheduling
247 purposes.
248 @param results_dir: The directory where the job can write results to.
249 This must be set if you want job_id of sub-jobs
250 list in the job keyvals.
Aviv Keshet18308922013-02-19 17:49:49 -0800251 @param max_runtime_mins: Maximum suite runtime, in minutes.
Vadim Bendeburyab14bf12012-12-28 13:51:46 -0800252 @param version_prefix: a string, prefix for the database label
253 associated with the build
Aviv Keshet18308922013-02-19 17:49:49 -0800254 @param suite_job_id: Job id that will act as parent id to all sub jobs.
255 Default: None
Aviv Keshetd7959f32013-05-17 15:58:43 -0700256 @param ignore_deps: True if jobs should ignore the DEPENDENCIES
257 attribute and skip applying of dependency labels.
258 (Default:False)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700259 """
Aviv Keshet40222a42013-06-04 16:25:49 -0700260 def combined_predicate(test):
261 #pylint: disable-msg=C0111
262 return all((f(test) for f in predicates))
263 self._predicate = combined_predicate
264
Chris Masone44e4d6c2012-08-15 14:25:53 -0700265 self._tag = tag
266 self._build = build
Alex Millera0913072013-06-12 10:01:51 -0700267 self._board = board
Chris Masone44e4d6c2012-08-15 14:25:53 -0700268 self._cf_getter = cf_getter
269 self._results_dir = results_dir
270 self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
271 delay_sec=10,
272 debug=False)
273 self._tko = tko or frontend_wrappers.RetryingTKO(timeout_min=30,
274 delay_sec=10,
275 debug=False)
276 self._pool = pool
277 self._jobs = []
278 self._tests = Suite.find_and_parse_tests(self._cf_getter,
279 self._predicate,
beepsc594c1c2013-07-09 22:33:18 -0700280 self._tag,
Chris Masone44e4d6c2012-08-15 14:25:53 -0700281 add_experimental=True)
Simran Basic68cda42012-11-19 17:03:18 -0800282 self._max_runtime_mins = max_runtime_mins
Vadim Bendeburyab14bf12012-12-28 13:51:46 -0800283 self._version_prefix = version_prefix
Alex Millera3a4fe72013-01-22 09:57:47 -0800284 self._file_bugs = file_bugs
beepsda5b7112013-05-30 11:34:14 -0700285 self._file_experimental_bugs = file_experimental_bugs
Aviv Keshet18308922013-02-19 17:49:49 -0800286 self._suite_job_id = suite_job_id
Aviv Keshetd7959f32013-05-17 15:58:43 -0700287 self._ignore_deps = ignore_deps
Alex Millera3a4fe72013-01-22 09:57:47 -0800288
Chris Masone44e4d6c2012-08-15 14:25:53 -0700289
290 @property
291 def tests(self):
292 """
293 A list of ControlData objects in the suite, with added |text| attr.
294 """
295 return self._tests
296
297
298 def stable_tests(self):
299 """
300 |self.tests|, filtered for non-experimental tests.
301 """
302 return filter(lambda t: not t.experimental, self.tests)
303
304
305 def unstable_tests(self):
306 """
307 |self.tests|, filtered for experimental tests.
308 """
309 return filter(lambda t: t.experimental, self.tests)
310
311
312 def _create_job(self, test):
313 """
314 Thin wrapper around frontend.AFE.create_job().
315
316 @param test: ControlData object for a test to run.
317 @return a frontend.Job object with an added test_name member.
318 test_name is used to preserve the higher level TEST_NAME
319 name of the job.
320 """
Aviv Keshetd7959f32013-05-17 15:58:43 -0700321 if self._ignore_deps:
322 job_deps = []
323 else:
324 job_deps = list(test.dependencies)
Alex Millera0913072013-06-12 10:01:51 -0700325
326 cros_label = self._version_prefix + self._build
327 job_deps.append(cros_label)
328
Chris Masone44e4d6c2012-08-15 14:25:53 -0700329 if self._pool:
Alex Millera0913072013-06-12 10:01:51 -0700330 job_deps.append(self._pool)
331
Chris Masone44e4d6c2012-08-15 14:25:53 -0700332 test_obj = self._afe.create_job(
333 control_file=test.text,
334 name='/'.join([self._build, self._tag, test.name]),
335 control_type=test.test_type.capitalize(),
Alex Millera0913072013-06-12 10:01:51 -0700336 meta_hosts=[self._board],
Chris Masone44e4d6c2012-08-15 14:25:53 -0700337 dependencies=job_deps,
338 keyvals={constants.JOB_BUILD_KEY: self._build,
Simran Basic68cda42012-11-19 17:03:18 -0800339 constants.JOB_SUITE_KEY: self._tag},
Aviv Keshet18308922013-02-19 17:49:49 -0800340 max_runtime_mins=self._max_runtime_mins,
Aviv Keshet6f455262013-03-01 16:02:29 -0800341 parent_job_id=self._suite_job_id,
342 test_retry=test.retries)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700343
344 setattr(test_obj, 'test_name', test.name)
345
346 return test_obj
347
348
Alex Miller3a69adc2012-12-19 13:38:31 -0800349 def schedule_and_wait(self, record, add_experimental=True):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700350 """
351 Synchronously run tests in |self.tests|.
352
Alex Miller3a69adc2012-12-19 13:38:31 -0800353 See |schedule| and |wait| for more information.
354
Chris Masone44e4d6c2012-08-15 14:25:53 -0700355 Schedules tests against a device running image |self._build|, and
356 then polls for status, using |record| to print status when each
357 completes.
358
359 Tests returned by self.stable_tests() will always be run, while tests
360 in self.unstable_tests() will only be run if |add_experimental| is true.
361
362 @param record: callable that records job status.
363 prototype:
364 record(base_job.status_log_entry)
Chris Masone8906ab12012-07-23 15:37:56 -0700365 @param manager: a populated HostLockManager instance to handle
366 unlocking DUTs that we already reimaged.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700367 @param add_experimental: schedule experimental tests as well, or not.
368 """
Alex Miller3a69adc2012-12-19 13:38:31 -0800369 # This method still exists for unittesting convenience.
370 self.schedule(record, add_experimental)
371 self.wait(record)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700372
373
Alex Miller3a69adc2012-12-19 13:38:31 -0800374 def schedule(self, record, add_experimental=True):
Aviv Keshet18308922013-02-19 17:49:49 -0800375 #pylint: disable-msg=C0111
Chris Masone44e4d6c2012-08-15 14:25:53 -0700376 """
377 Schedule jobs using |self._afe|.
378
379 frontend.Job objects representing each scheduled job will be put in
380 |self._jobs|.
381
382 @param add_experimental: schedule experimental tests as well, or not.
Aviv Keshete9170d92013-07-19 11:20:45 -0700383 @returns: The number of tests that were scheduled.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700384 """
Alex Miller3a69adc2012-12-19 13:38:31 -0800385 logging.debug('Discovered %d stable tests.', len(self.stable_tests()))
386 logging.debug('Discovered %d unstable tests.',
387 len(self.unstable_tests()))
Aviv Keshete9170d92013-07-19 11:20:45 -0700388 n_scheduled = 0
Chris Masone44e4d6c2012-08-15 14:25:53 -0700389
Alex Miller3a69adc2012-12-19 13:38:31 -0800390 Status('INFO', 'Start %s' % self._tag).record_result(record)
391 try:
392 for test in self.stable_tests():
393 logging.debug('Scheduling %s', test.name)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700394 self._jobs.append(self._create_job(test))
Aviv Keshete9170d92013-07-19 11:20:45 -0700395 n_scheduled += 1
Alex Miller3a69adc2012-12-19 13:38:31 -0800396
397 if add_experimental:
398 for test in self.unstable_tests():
399 logging.debug('Scheduling experimental %s', test.name)
400 test.name = constants.EXPERIMENTAL_PREFIX + test.name
401 self._jobs.append(self._create_job(test))
Aviv Keshete9170d92013-07-19 11:20:45 -0700402 n_scheduled += 1
Alex Miller3a69adc2012-12-19 13:38:31 -0800403
404 if self._results_dir:
405 self._remember_scheduled_job_ids()
406 except Exception: # pylint: disable=W0703
407 logging.error(traceback.format_exc())
408 Status('FAIL', self._tag,
409 'Exception while scheduling suite').record_result(record)
410
Aviv Keshete9170d92013-07-19 11:20:45 -0700411 return n_scheduled
412
Alex Miller3a69adc2012-12-19 13:38:31 -0800413
beepsda5b7112013-05-30 11:34:14 -0700414 def should_file_bug(self, result):
415 """
416 Returns True if this failure requires a bug.
417
418 @param result: A result, encapsulating the status of the failed job.
419 @return: True if we should file bugs for this failure.
420 """
beepsbeefc062013-08-02 11:17:09 -0700421 is_not_experimental = (
422 constants.EXPERIMENTAL_PREFIX not in result._test_name and
423 constants.EXPERIMENTAL_PREFIX not in result._job_name)
424
beepsda5b7112013-05-30 11:34:14 -0700425 return (self._file_bugs and
beepsbeefc062013-08-02 11:17:09 -0700426 (is_not_experimental or self._file_experimental_bugs) and
beepsda5b7112013-05-30 11:34:14 -0700427 result.is_worse_than(job_status.Status('WARN', '', 'reason')))
428
429
beepsc8a875b2013-03-25 10:20:38 -0700430 def wait(self, record, bug_template={}):
Alex Miller3a69adc2012-12-19 13:38:31 -0800431 """
432 Polls for the job statuses, using |record| to print status when each
433 completes.
434
435 @param record: callable that records job status.
436 prototype:
437 record(base_job.status_log_entry)
beepsc8a875b2013-03-25 10:20:38 -0700438 @param bug_template: A template dictionary specifying the default bug
439 filing options for failures in this suite.
Alex Miller3a69adc2012-12-19 13:38:31 -0800440 """
Alex Millera3a4fe72013-01-22 09:57:47 -0800441 if self._file_bugs:
442 bug_reporter = reporting.Reporter()
Alex Miller3a69adc2012-12-19 13:38:31 -0800443 try:
444 for result in job_status.wait_for_results(self._afe,
445 self._tko,
446 self._jobs):
447 result.record_all(record)
448 if (self._results_dir and
449 job_status.is_for_infrastructure_fail(result)):
450 self._remember_provided_job_id(result)
Dan Shid1521802013-05-24 13:08:37 -0700451 elif (self._results_dir and isinstance(result, Status)):
452 self._remember_test_status_job_id(result)
Alex Millera3a4fe72013-01-22 09:57:47 -0800453
beepsda5b7112013-05-30 11:34:14 -0700454 if self.should_file_bug(result):
beepsc4fb1472013-05-08 21:49:48 -0700455 job_views = self._tko.run('get_detailed_test_views',
456 afe_job_id=result.id)
457
beepsc208bf32013-04-16 14:36:35 -0700458 failure = reporting.TestFailure(self._build,
beepsc4fb1472013-05-08 21:49:48 -0700459 site_utils.get_chrome_version(job_views),
460 self._tag,
461 result)
beeps1ccbbb82013-02-21 20:35:01 -0800462
beeps8ead53c2013-04-26 19:12:46 -0700463 bug_id = bug_reporter.report(failure, bug_template)
464 try:
465 # Attempting to use the name of a job with special
466 # characters as a keyval will throw a ValueError. One
467 # such case is with aborted jobs. Luckily, we don't
468 # really care about the name, since the same name we
469 # have here is inserted into the results database we can
470 # use it as a key to retrieve the bug id. An example key
471 # for an aborted job after replacing the '/' with '_':
472 # lumpy-release_R28-3947.0.0_dummy_experimental_dummy_\
473 # Pass-Bug_Id=xxxx, where xxxx is the id of the bug.
474 utils.write_keyval(self._results_dir, {
475 (result.test_name.replace('/', '_')+
476 constants.BUG_KEYVAL): bug_id})
477 except ValueError:
478 logging.error('Unable to log keyval for test:%s '
479 'bugid: %s', result.test_name, bug_id)
480
Alex Miller3a69adc2012-12-19 13:38:31 -0800481 except Exception: # pylint: disable=W0703
482 logging.error(traceback.format_exc())
483 Status('FAIL', self._tag,
484 'Exception waiting for results').record_result(record)
485
486
487 def abort(self):
488 """
489 Abort all scheduled test jobs.
490 """
491 if self._jobs:
492 job_ids = [job.id for job in self._jobs]
493 self._afe.run('abort_host_queue_entries', job__id__in=job_ids)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700494
495
Chris Masoned9f13c52012-08-29 10:37:08 -0700496 def _remember_scheduled_job_ids(self):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700497 """
498 Record scheduled job ids as keyvals, so they can be referenced later.
499 """
500 for job in self._jobs:
Chris Masoned9f13c52012-08-29 10:37:08 -0700501 self._remember_provided_job_id(job)
502
503
504 def _remember_provided_job_id(self, job):
505 """
506 Record provided job as a suite job keyval, for later referencing.
507
508 @param job: some representation of a job, including id, test_name
509 and owner
510 """
511 if job.id and job.owner and job.test_name:
Chris Masone44e4d6c2012-08-15 14:25:53 -0700512 job_id_owner = '%s-%s' % (job.id, job.owner)
Chris Masoned9f13c52012-08-29 10:37:08 -0700513 logging.debug('Adding job keyval for %s=%s',
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700514 job.test_name, job_id_owner)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700515 utils.write_keyval(
516 self._results_dir,
517 {hashlib.md5(job.test_name).hexdigest(): job_id_owner})
518
519
Dan Shid1521802013-05-24 13:08:37 -0700520 def _remember_test_status_job_id(self, status):
521 """
522 Record provided status as a test status keyval, for later referencing.
523
524 @param status: Test status, including properties such as id, test_name
525 and owner.
526 """
527 if status.id and status.owner and status.test_name:
528 test_id_owner = '%s-%s' % (status.id, status.owner)
529 logging.debug('Adding status keyval for %s=%s',
530 status.test_name, test_id_owner)
531 utils.write_keyval(
532 self._results_dir,
533 {hashlib.md5(status.test_name).hexdigest(): test_id_owner})
534
535
Chris Masone44e4d6c2012-08-15 14:25:53 -0700536 @staticmethod
beepsc594c1c2013-07-09 22:33:18 -0700537 def find_and_parse_tests(cf_getter, predicate, suite_name='',
538 add_experimental=False):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700539 """
540 Function to scan through all tests and find eligible tests.
541
542 Looks at control files returned by _cf_getter.get_control_file_list()
beepsc594c1c2013-07-09 22:33:18 -0700543 for tests that pass self._predicate(). When this method is called
544 with a file system ControlFileGetter, it performs a full parse of the
545 root directory associated with the getter. This is the case when it's
546 invoked from suite_preprocessor. When it's invoked with a devserver
547 getter it looks up the suite_name in a suite to control file map
548 generated at build time, and parses the relevant control files alone.
549 This lookup happens on the devserver, so as far as this method is
550 concerned, both cases are equivalent.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700551
552 @param cf_getter: a control_file_getter.ControlFileGetter used to list
553 and fetch the content of control files
554 @param predicate: a function that should return True when run over a
555 ControlData representation of a control file that should be in
556 this Suite.
beepsc594c1c2013-07-09 22:33:18 -0700557 @param suite_name: If specified, this method will attempt to restrain
558 the search space to just this suite's control files.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700559 @param add_experimental: add tests with experimental attribute set.
560
561 @return list of ControlData objects that should be run, with control
Dan Shief5b53f2013-01-22 10:22:01 -0800562 file text added in |text| attribute. Results are sorted based
563 on the TIME setting in control file, slowest test comes first.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700564 """
565 tests = {}
beepsc594c1c2013-07-09 22:33:18 -0700566 files = cf_getter.get_control_file_list(suite_name=suite_name)
567
Chris Masone44e4d6c2012-08-15 14:25:53 -0700568 matcher = re.compile(r'[^/]+/(deps|profilers)/.+')
Aviv Keshete8765c02013-06-04 14:33:43 -0700569 parsed_count = 0
Chris Masone44e4d6c2012-08-15 14:25:53 -0700570 for file in filter(lambda f: not matcher.match(f), files):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700571 text = cf_getter.get_control_file_contents(file)
572 try:
573 found_test = control_data.parse_control_string(
574 text, raise_warnings=True)
Aviv Keshete8765c02013-06-04 14:33:43 -0700575 parsed_count += 1
Chris Masone44e4d6c2012-08-15 14:25:53 -0700576 if not add_experimental and found_test.experimental:
577 continue
Chris Masone44e4d6c2012-08-15 14:25:53 -0700578 found_test.text = text
579 found_test.path = file
580 tests[file] = found_test
581 except control_data.ControlVariableException, e:
582 logging.warn("Skipping %s\n%s", file, e)
583 except Exception, e:
584 logging.error("Bad %s\n%s", file, e)
Aviv Keshete8765c02013-06-04 14:33:43 -0700585 logging.debug('Parsed %s control files.', parsed_count)
Dan Shief5b53f2013-01-22 10:22:01 -0800586 tests = [test for test in tests.itervalues() if predicate(test)]
587 tests.sort(key=lambda t:
588 control_data.ControlData.get_test_time_index(t.time),
589 reverse=True)
590 return tests