blob: 803d350fa19251d3249b7a6b9997e4f4772f9245 [file] [log] [blame]
Chris Masone8ac66712012-02-15 14:21:02 -08001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Masone6fed6462011-10-20 16:36:43 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import common
6import compiler, logging, os, random, re, time
Chris Masone2ef1d4e2011-12-20 11:06:53 -08007from autotest_lib.client.common_lib import control_data, global_config, error
8from autotest_lib.client.common_lib import utils
Chris Masone8b7cd422012-02-22 13:16:11 -08009from autotest_lib.client.common_lib.cros import dev_server
Chris Masone8ac66712012-02-15 14:21:02 -080010from autotest_lib.server.cros import control_file_getter, frontend_wrappers
Chris Masone6fed6462011-10-20 16:36:43 -070011from autotest_lib.server import frontend
12
13
Scott Zawalski65650172012-02-16 11:48:26 -050014VERSION_PREFIX = 'cros-version:'
Chris Masone2ef1d4e2011-12-20 11:06:53 -080015CONFIG = global_config.global_config
16
17
Chris Masone8b764252012-01-17 11:12:51 -080018def inject_vars(vars, control_file_in):
19 """
20 Inject the contents of |vars| into |control_file_in|
21
22 @param vars: a dict to shoehorn into the provided control file string.
23 @param control_file_in: the contents of a control file to munge.
24 @return the modified control file string.
25 """
26 control_file = ''
27 for key, value in vars.iteritems():
28 control_file += "%s='%s'\n" % (key, value)
29 return control_file + control_file_in
30
31
Chris Masone2ef1d4e2011-12-20 11:06:53 -080032def _image_url_pattern():
33 return CONFIG.get_config_value('CROS', 'image_url_pattern', type=str)
34
35
36def _package_url_pattern():
37 return CONFIG.get_config_value('CROS', 'package_url_pattern', type=str)
38
Chris Masone6fed6462011-10-20 16:36:43 -070039
40class Reimager(object):
41 """
42 A class that can run jobs to reimage devices.
43
44 @var _afe: a frontend.AFE instance used to talk to autotest.
45 @var _tko: a frontend.TKO instance used to query the autotest results db.
46 @var _cf_getter: a ControlFileGetter used to get the AU control file.
47 """
48
49
Scott Zawalski65650172012-02-16 11:48:26 -050050 def __init__(self, autotest_dir, afe=None, tko=None, pool=None):
Chris Masone6fed6462011-10-20 16:36:43 -070051 """
52 Constructor
53
54 @param autotest_dir: the place to find autotests.
55 @param afe: an instance of AFE as defined in server/frontend.py.
56 @param tko: an instance of TKO as defined in server/frontend.py.
Scott Zawalski65650172012-02-16 11:48:26 -050057 @param pool: Specify the pool of machines to use for scheduling
58 purposes.
Chris Masone6fed6462011-10-20 16:36:43 -070059 """
Chris Masone8ac66712012-02-15 14:21:02 -080060 self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
61 delay_sec=10,
62 debug=False)
63 self._tko = tko or frontend_wrappers.RetryingTKO(timeout_min=30,
64 delay_sec=10,
65 debug=False)
Scott Zawalski65650172012-02-16 11:48:26 -050066 self._pool = pool
Chris Masone6fed6462011-10-20 16:36:43 -070067 self._cf_getter = control_file_getter.FileSystemGetter(
68 [os.path.join(autotest_dir, 'server/site_tests')])
69
70
Chris Masone2ef1d4e2011-12-20 11:06:53 -080071 def skip(self, g):
72 return 'SKIP_IMAGE' in g and g['SKIP_IMAGE']
73
74
Scott Zawalski65650172012-02-16 11:48:26 -050075 def attempt(self, build, board, record, num=None, pool=None):
Chris Masone6fed6462011-10-20 16:36:43 -070076 """
77 Synchronously attempt to reimage some machines.
78
79 Fire off attempts to reimage |num| machines of type |board|, using an
Chris Masone8abb6fc2012-01-31 09:27:36 -080080 image at |url| called |build|. Wait for completion, polling every
Chris Masone6fed6462011-10-20 16:36:43 -070081 10s, and log results with |record| upon completion.
82
Chris Masone8abb6fc2012-01-31 09:27:36 -080083 @param build: the build to install e.g.
84 x86-alex-release/R18-1655.0.0-a1-b1584.
Chris Masone6fed6462011-10-20 16:36:43 -070085 @param board: which kind of devices to reimage.
86 @param record: callable that records job status.
Chris Masone796fcf12012-02-22 16:53:31 -080087 prototype:
88 record(status, subdir, name, reason)
Chris Masone5552dd72012-02-15 15:01:04 -080089 @param num: how many devices to reimage.
Scott Zawalski65650172012-02-16 11:48:26 -050090 @param pool: Specify the pool of machines to use for scheduling
Chris Masone796fcf12012-02-22 16:53:31 -080091 purposes.
Chris Masone6fed6462011-10-20 16:36:43 -070092 @return True if all reimaging jobs succeed, false otherwise.
93 """
Chris Masone5552dd72012-02-15 15:01:04 -080094 if not num:
95 num = CONFIG.get_config_value('CROS', 'sharding_factor', type=int)
Scott Zawalski65650172012-02-16 11:48:26 -050096 if pool:
97 self._pool = pool
98 logging.debug("scheduling reimaging across %d machines", num)
Chris Masone73f65022012-01-31 14:00:43 -080099 wrapper_job_name = 'try new image'
100 record('START', None, wrapper_job_name)
Chris Masone796fcf12012-02-22 16:53:31 -0800101 try:
102 self._ensure_version_label(VERSION_PREFIX + build)
103 canary = self._schedule_reimage_job(build, num, board)
104 logging.debug('Created re-imaging job: %d', canary.id)
105 while len(self._afe.get_jobs(id=canary.id, not_yet_run=True)) > 0:
106 time.sleep(10)
107 logging.debug('Re-imaging job running.')
108 while len(self._afe.get_jobs(id=canary.id, finished=True)) == 0:
109 time.sleep(10)
110 logging.debug('Re-imaging job finished.')
111 canary.result = self._afe.poll_job_results(self._tko, canary, 0)
112 except Exception as e:
113 # catch Exception so we record the job as terminated no matter what.
114 logging.error(e)
115 record('END ERROR', None, wrapper_job_name, str(e))
116 return False
Chris Masone6fed6462011-10-20 16:36:43 -0700117
118 if canary.result is True:
119 self._report_results(canary, record)
Chris Masone73f65022012-01-31 14:00:43 -0800120 record('END GOOD', None, wrapper_job_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700121 return True
122
123 if canary.result is None:
124 record('FAIL', None, canary.name, 're-imaging tasks did not run')
125 else: # canary.result is False
126 self._report_results(canary, record)
127
Chris Masone73f65022012-01-31 14:00:43 -0800128 record('END FAIL', None, wrapper_job_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700129 return False
130
131
132 def _ensure_version_label(self, name):
133 """
134 Ensure that a label called |name| exists in the autotest DB.
135
136 @param name: the label to check for/create.
137 """
138 labels = self._afe.get_labels(name=name)
139 if len(labels) == 0:
140 self._afe.create_label(name=name)
141
142
Chris Masone8abb6fc2012-01-31 09:27:36 -0800143 def _schedule_reimage_job(self, build, num_machines, board):
Chris Masone6fed6462011-10-20 16:36:43 -0700144 """
145 Schedules the reimaging of |num_machines| |board| devices with |image|.
146
147 Sends an RPC to the autotest frontend to enqueue reimaging jobs on
148 |num_machines| devices of type |board|
149
Chris Masone8abb6fc2012-01-31 09:27:36 -0800150 @param build: the build to install (must be unique).
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800151 @param num_machines: how many devices to reimage.
Chris Masone6fed6462011-10-20 16:36:43 -0700152 @param board: which kind of devices to reimage.
153 @return a frontend.Job object for the reimaging job we scheduled.
154 """
Chris Masone8b764252012-01-17 11:12:51 -0800155 control_file = inject_vars(
Chris Masone8abb6fc2012-01-31 09:27:36 -0800156 {'image_url': _image_url_pattern() % build, 'image_name': build},
Chris Masone6fed6462011-10-20 16:36:43 -0700157 self._cf_getter.get_control_file_contents_by_name('autoupdate'))
Scott Zawalski65650172012-02-16 11:48:26 -0500158 job_deps = []
159 if self._pool:
160 meta_host = 'pool:%s' % self._pool
161 board_label = 'board:%s' % board
162 job_deps.append(board_label)
163 else:
164 # No pool specified use board.
165 meta_host = 'board:%s' % board
Chris Masone6fed6462011-10-20 16:36:43 -0700166
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800167 return self._afe.create_job(control_file=control_file,
Chris Masone8abb6fc2012-01-31 09:27:36 -0800168 name=build + '-try',
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800169 control_type='Server',
Scott Zawalski65650172012-02-16 11:48:26 -0500170 meta_hosts=[meta_host] * num_machines,
171 dependencies=job_deps)
Chris Masone6fed6462011-10-20 16:36:43 -0700172
173
174 def _report_results(self, job, record):
175 """
176 Record results from a completed frontend.Job object.
177
178 @param job: a completed frontend.Job object populated by
179 frontend.AFE.poll_job_results.
180 @param record: callable that records job status.
181 prototype:
182 record(status, subdir, name, reason)
183 """
184 if job.result == True:
185 record('GOOD', None, job.name)
186 return
187
188 for platform in job.results_platform_map:
189 for status in job.results_platform_map[platform]:
190 if status == 'Total':
191 continue
192 for host in job.results_platform_map[platform][status]:
193 if host not in job.test_status:
194 record('ERROR', None, host, 'Job failed to run.')
195 elif status == 'Failed':
196 for test_status in job.test_status[host].fail:
197 record('FAIL', None, host, test_status.reason)
198 elif status == 'Aborted':
199 for test_status in job.test_status[host].fail:
200 record('ABORT', None, host, test_status.reason)
201 elif status == 'Completed':
202 record('GOOD', None, host)
203
204
205class Suite(object):
206 """
207 A suite of tests, defined by some predicate over control file variables.
208
209 Given a place to search for control files a predicate to match the desired
210 tests, can gather tests and fire off jobs to run them, and then wait for
211 results.
212
213 @var _predicate: a function that should return True when run over a
214 ControlData representation of a control file that should be in
215 this Suite.
216 @var _tag: a string with which to tag jobs run in this suite.
Chris Masone8b7cd422012-02-22 13:16:11 -0800217 @var _build: the build on which we're running this suite.
Chris Masone6fed6462011-10-20 16:36:43 -0700218 @var _afe: an instance of AFE as defined in server/frontend.py.
219 @var _tko: an instance of TKO as defined in server/frontend.py.
220 @var _jobs: currently scheduled jobs, if any.
221 @var _cf_getter: a control_file_getter.ControlFileGetter
222 """
223
224
Chris Masonefef21382012-01-17 11:16:32 -0800225 @staticmethod
Chris Masoned6f38c82012-02-22 14:53:42 -0800226 def create_ds_getter(build):
Chris Masonefef21382012-01-17 11:16:32 -0800227 """
Chris Masone8b7cd422012-02-22 13:16:11 -0800228 @param build: the build on which we're running this suite.
Chris Masonefef21382012-01-17 11:16:32 -0800229 @return a FileSystemGetter instance that looks under |autotest_dir|.
230 """
Chris Masone8b7cd422012-02-22 13:16:11 -0800231 return control_file_getter.DevServerGetter(
232 build, dev_server.DevServer.create())
Chris Masonefef21382012-01-17 11:16:32 -0800233
234
235 @staticmethod
Chris Masoned6f38c82012-02-22 14:53:42 -0800236 def create_fs_getter(autotest_dir):
237 """
238 @param autotest_dir: the place to find autotests.
239 @return a FileSystemGetter instance that looks under |autotest_dir|.
240 """
241 # currently hard-coded places to look for tests.
242 subpaths = ['server/site_tests', 'client/site_tests',
243 'server/tests', 'client/tests']
244 directories = [os.path.join(autotest_dir, p) for p in subpaths]
245 return control_file_getter.FileSystemGetter(directories)
246
247
248 @staticmethod
Chris Masone84564792012-02-23 10:52:42 -0800249 def name_in_tag_predicate(name):
250 """Returns predicate that takes a control file and looks for |name|.
251
252 Builds a predicate that takes in a parsed control file (a ControlData)
253 and returns True if the SUITE tag is present and contains |name|.
254
255 @param name: the suite name to base the predicate on.
256 @return a callable that takes a ControlData and looks for |name| in that
257 ControlData object's suite member.
258 """
259 def parse(suite):
260 """Splits a string on ',' optionally surrounded by whitespace."""
261 return map(lambda x: x.strip(), suite.split(','))
262
263 return lambda t: hasattr(t, 'suite') and name in parse(t.suite)
264
265
266 @staticmethod
Chris Masoned6f38c82012-02-22 14:53:42 -0800267 def create_from_name(name, build, cf_getter=None,
268 afe=None, tko=None, pool=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700269 """
270 Create a Suite using a predicate based on the SUITE control file var.
271
272 Makes a predicate based on |name| and uses it to instantiate a Suite
273 that looks for tests in |autotest_dir| and will schedule them using
Chris Masoned6f38c82012-02-22 14:53:42 -0800274 |afe|. Pulls control files from the default dev server.
275 Results will be pulled from |tko| upon completion.
Chris Masone6fed6462011-10-20 16:36:43 -0700276
277 @param name: a value of the SUITE control file variable to search for.
Chris Masone8b7cd422012-02-22 13:16:11 -0800278 @param build: the build on which we're running this suite.
Chris Masoned6f38c82012-02-22 14:53:42 -0800279 @param cf_getter: a control_file_getter.ControlFileGetter.
280 If None, default to using a DevServerGetter.
Chris Masone6fed6462011-10-20 16:36:43 -0700281 @param afe: an instance of AFE as defined in server/frontend.py.
282 @param tko: an instance of TKO as defined in server/frontend.py.
Scott Zawalski65650172012-02-16 11:48:26 -0500283 @param pool: Specify the pool of machines to use for scheduling
Chris Masoned6f38c82012-02-22 14:53:42 -0800284 purposes.
Chris Masone6fed6462011-10-20 16:36:43 -0700285 @return a Suite instance.
286 """
Chris Masoned6f38c82012-02-22 14:53:42 -0800287 if cf_getter is None:
288 cf_getter = Suite.create_ds_getter(build)
Chris Masone84564792012-02-23 10:52:42 -0800289 return Suite(Suite.name_in_tag_predicate(name),
Chris Masoned6f38c82012-02-22 14:53:42 -0800290 name, build, cf_getter, afe, tko, pool)
Chris Masone6fed6462011-10-20 16:36:43 -0700291
292
Chris Masoned6f38c82012-02-22 14:53:42 -0800293 def __init__(self, predicate, tag, build, cf_getter, afe=None, tko=None,
Scott Zawalski65650172012-02-16 11:48:26 -0500294 pool=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700295 """
296 Constructor
297
298 @param predicate: a function that should return True when run over a
299 ControlData representation of a control file that should be in
300 this Suite.
301 @param tag: a string with which to tag jobs run in this suite.
Chris Masone8b7cd422012-02-22 13:16:11 -0800302 @param build: the build on which we're running this suite.
Chris Masoned6f38c82012-02-22 14:53:42 -0800303 @param cf_getter: a control_file_getter.ControlFileGetter
Chris Masone6fed6462011-10-20 16:36:43 -0700304 @param afe: an instance of AFE as defined in server/frontend.py.
305 @param tko: an instance of TKO as defined in server/frontend.py.
Scott Zawalski65650172012-02-16 11:48:26 -0500306 @param pool: Specify the pool of machines to use for scheduling
307 purposes.
Chris Masone6fed6462011-10-20 16:36:43 -0700308 """
309 self._predicate = predicate
310 self._tag = tag
Chris Masone8b7cd422012-02-22 13:16:11 -0800311 self._build = build
Chris Masoned6f38c82012-02-22 14:53:42 -0800312 self._cf_getter = cf_getter
Chris Masone8ac66712012-02-15 14:21:02 -0800313 self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
314 delay_sec=10,
315 debug=False)
316 self._tko = tko or frontend_wrappers.RetryingTKO(timeout_min=30,
317 delay_sec=10,
318 debug=False)
Scott Zawalski65650172012-02-16 11:48:26 -0500319 self._pool = pool
Chris Masone6fed6462011-10-20 16:36:43 -0700320 self._jobs = []
Chris Masone6fed6462011-10-20 16:36:43 -0700321 self._tests = Suite.find_and_parse_tests(self._cf_getter,
322 self._predicate,
323 add_experimental=True)
324
325
326 @property
327 def tests(self):
328 """
329 A list of ControlData objects in the suite, with added |text| attr.
330 """
331 return self._tests
332
333
334 def stable_tests(self):
335 """
336 |self.tests|, filtered for non-experimental tests.
337 """
338 return filter(lambda t: not t.experimental, self.tests)
339
340
341 def unstable_tests(self):
342 """
343 |self.tests|, filtered for experimental tests.
344 """
345 return filter(lambda t: t.experimental, self.tests)
346
347
Chris Masone8b7cd422012-02-22 13:16:11 -0800348 def _create_job(self, test):
Chris Masone6fed6462011-10-20 16:36:43 -0700349 """
350 Thin wrapper around frontend.AFE.create_job().
351
352 @param test: ControlData object for a test to run.
Chris Masone6fed6462011-10-20 16:36:43 -0700353 @return frontend.Job object for the job just scheduled.
354 """
Scott Zawalski65650172012-02-16 11:48:26 -0500355 job_deps = []
356 if self._pool:
357 meta_hosts = 'pool:%s' % self._pool
Chris Masone8b7cd422012-02-22 13:16:11 -0800358 cros_label = VERSION_PREFIX + self._build
Scott Zawalski65650172012-02-16 11:48:26 -0500359 job_deps.append(cros_label)
360 else:
361 # No pool specified use any machines with the following label.
Chris Masone8b7cd422012-02-22 13:16:11 -0800362 meta_hosts = VERSION_PREFIX + self._build
Scott Zawalski65650172012-02-16 11:48:26 -0500363
Chris Masone6fed6462011-10-20 16:36:43 -0700364 return self._afe.create_job(
365 control_file=test.text,
Chris Masone8b7cd422012-02-22 13:16:11 -0800366 name='/'.join([self._build, self._tag, test.name]),
Chris Masone6fed6462011-10-20 16:36:43 -0700367 control_type=test.test_type.capitalize(),
Scott Zawalski65650172012-02-16 11:48:26 -0500368 meta_hosts=[meta_hosts],
369 dependencies=job_deps)
Chris Masone6fed6462011-10-20 16:36:43 -0700370
371
Chris Masone8b7cd422012-02-22 13:16:11 -0800372 def run_and_wait(self, record, add_experimental=True):
Chris Masone6fed6462011-10-20 16:36:43 -0700373 """
374 Synchronously run tests in |self.tests|.
375
Chris Masone8b7cd422012-02-22 13:16:11 -0800376 Schedules tests against a device running image |self._build|, and
Chris Masone6fed6462011-10-20 16:36:43 -0700377 then polls for status, using |record| to print status when each
378 completes.
379
380 Tests returned by self.stable_tests() will always be run, while tests
381 in self.unstable_tests() will only be run if |add_experimental| is true.
382
Chris Masone6fed6462011-10-20 16:36:43 -0700383 @param record: callable that records job status.
384 prototype:
385 record(status, subdir, name, reason)
386 @param add_experimental: schedule experimental tests as well, or not.
387 """
388 try:
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500389 record('INFO', None, 'Start %s' % self._tag)
Chris Masone8b7cd422012-02-22 13:16:11 -0800390 self.schedule(add_experimental)
Chris Masone6fed6462011-10-20 16:36:43 -0700391 try:
392 for result in self.wait_for_results():
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500393 # |result| will be a tuple of a maximum of 4 entries and a
394 # minimum of 3. We use the first 3 for START and END
395 # entries so we separate those variables out for legible
396 # variable names, nothing more.
397 status = result[0]
398 test_name = result[2]
399 record('START', None, test_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700400 record(*result)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500401 record('END %s' % status, None, test_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700402 except Exception as e:
403 logging.error(e)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500404 record('FAIL', None, self._tag,
405 'Exception waiting for results')
Chris Masone6fed6462011-10-20 16:36:43 -0700406 except Exception as e:
407 logging.error(e)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500408 record('FAIL', None, self._tag,
409 'Exception while scheduling suite')
Chris Masone6fed6462011-10-20 16:36:43 -0700410
411
Chris Masone8b7cd422012-02-22 13:16:11 -0800412 def schedule(self, add_experimental=True):
Chris Masone6fed6462011-10-20 16:36:43 -0700413 """
414 Schedule jobs using |self._afe|.
415
416 frontend.Job objects representing each scheduled job will be put in
417 |self._jobs|.
418
Chris Masone6fed6462011-10-20 16:36:43 -0700419 @param add_experimental: schedule experimental tests as well, or not.
420 """
421 for test in self.stable_tests():
422 logging.debug('Scheduling %s', test.name)
Chris Masone8b7cd422012-02-22 13:16:11 -0800423 self._jobs.append(self._create_job(test))
Chris Masone6fed6462011-10-20 16:36:43 -0700424
425 if add_experimental:
426 # TODO(cmasone): ensure I can log results from these differently.
427 for test in self.unstable_tests():
428 logging.debug('Scheduling %s', test.name)
Chris Masone8b7cd422012-02-22 13:16:11 -0800429 self._jobs.append(self._create_job(test))
Chris Masone6fed6462011-10-20 16:36:43 -0700430
431
432 def _status_is_relevant(self, status):
433 """
434 Indicates whether the status of a given test is meaningful or not.
435
436 @param status: frontend.TestStatus object to look at.
437 @return True if this is a test result worth looking at further.
438 """
439 return not (status.test_name.startswith('SERVER_JOB') or
440 status.test_name.startswith('CLIENT_JOB'))
441
442
443 def _collate_aborted(self, current_value, entry):
444 """
445 reduce() over a list of HostQueueEntries for a job; True if any aborted.
446
447 Functor that can be reduced()ed over a list of
448 HostQueueEntries for a job. If any were aborted
449 (|entry.aborted| exists and is True), then the reduce() will
450 return True.
451
452 Ex:
453 entries = self._afe.run('get_host_queue_entries', job=job.id)
454 reduce(self._collate_aborted, entries, False)
455
456 @param current_value: the current accumulator (a boolean).
457 @param entry: the current entry under consideration.
458 @return the value of |entry.aborted| if it exists, False if not.
459 """
460 return current_value or ('aborted' in entry and entry['aborted'])
461
462
463 def wait_for_results(self):
464 """
465 Wait for results of all tests in all jobs in |self._jobs|.
466
467 Currently polls for results every 5s. When all results are available,
468 @return a list of tuples, one per test: (status, subdir, name, reason)
469 """
Chris Masone6fed6462011-10-20 16:36:43 -0700470 while self._jobs:
471 for job in list(self._jobs):
472 if not self._afe.get_jobs(id=job.id, finished=True):
473 continue
474
475 self._jobs.remove(job)
476
477 entries = self._afe.run('get_host_queue_entries', job=job.id)
478 if reduce(self._collate_aborted, entries, False):
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500479 yield('ABORT', None, job.name)
Chris Masone6fed6462011-10-20 16:36:43 -0700480 else:
481 statuses = self._tko.get_status_counts(job=job.id)
482 for s in filter(self._status_is_relevant, statuses):
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500483 yield(s.status, None, s.test_name, s.reason)
Chris Masone6fed6462011-10-20 16:36:43 -0700484 time.sleep(5)
485
Chris Masone6fed6462011-10-20 16:36:43 -0700486
Chris Masonefef21382012-01-17 11:16:32 -0800487 @staticmethod
488 def find_and_parse_tests(cf_getter, predicate, add_experimental=False):
Chris Masone6fed6462011-10-20 16:36:43 -0700489 """
490 Function to scan through all tests and find eligible tests.
491
492 Looks at control files returned by _cf_getter.get_control_file_list()
493 for tests that pass self._predicate().
494
495 @param cf_getter: a control_file_getter.ControlFileGetter used to list
496 and fetch the content of control files
497 @param predicate: a function that should return True when run over a
498 ControlData representation of a control file that should be in
499 this Suite.
500 @param add_experimental: add tests with experimental attribute set.
501
502 @return list of ControlData objects that should be run, with control
503 file text added in |text| attribute.
504 """
505 tests = {}
506 files = cf_getter.get_control_file_list()
507 for file in files:
508 text = cf_getter.get_control_file_contents(file)
509 try:
510 found_test = control_data.parse_control_string(text,
511 raise_warnings=True)
512 if not add_experimental and found_test.experimental:
513 continue
514
515 found_test.text = text
516 tests[file] = found_test
517 except control_data.ControlVariableException, e:
518 logging.warn("Skipping %s\n%s", file, e)
519 except Exception, e:
520 logging.error("Bad %s\n%s", file, e)
521
522 return [test for test in tests.itervalues() if predicate(test)]