blob: fa4a7faea9f187fe123110d243ca63eae464e461 [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
Chris Masone11aae452012-05-21 16:08:39 -07006import compiler, datetime, hashlib, logging, os, random, re, time, traceback
Chris Masoneab3e7332012-02-29 18:54:58 -08007from autotest_lib.client.common_lib import base_job, control_data, global_config
8from autotest_lib.client.common_lib import error, 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 Masone275ec902012-07-10 15:28:34 -070011from autotest_lib.server.cros import host_lock_manager, job_status
Chris Masone604baf32012-06-28 08:45:30 -070012from autotest_lib.server.cros.job_status import Status
Chris Masone6fed6462011-10-20 16:36:43 -070013from autotest_lib.server import frontend
Chris Masonef8b53062012-05-08 22:14:18 -070014from autotest_lib.frontend.afe.json_rpc import proxy
Chris Masone6fed6462011-10-20 16:36:43 -070015
Chris Masone6cfb7122012-05-02 11:36:28 -070016"""CrOS dynamic test suite generation and execution module.
17
18This module implements runtime-generated test suites for CrOS.
19Design doc: http://goto.google.com/suitesv2
20
21Individual tests can declare themselves as a part of one or more
22suites, and the code here enables control files to be written
23that can refer to these "dynamic suites" by name. We also provide
24support for reimaging devices with a given build and running a
25dynamic suite across all reimaged devices.
26
27The public API for defining a suite includes one method: reimage_and_run().
28A suite control file can be written by importing this module and making
29an appropriate call to this single method. In normal usage, this control
30file will be run in a 'hostless' server-side autotest job, scheduling
31sub-jobs to do the needed reimaging and test running.
32
33Example control file:
34
35import common
36from autotest_lib.server.cros import dynamic_suite
37
38dynamic_suite.reimage_and_run(
39 build=build, board=board, name='bvt', job=job, pool=pool,
40 check_hosts=check_hosts, add_experimental=True, num=4,
41 skip_reimage=dynamic_suite.skip_reimage(globals()))
42
43This will -- at runtime -- find all control files that contain "bvt"
44in their "SUITE=" clause, schedule jobs to reimage 4 devices in the
45specified pool of the specified board with the specified build and,
46upon completion of those jobs, schedule and wait for jobs that run all
47the tests it discovered across those 4 machines.
48
49Suites can be run by using the atest command-line tool:
50 atest suite create -b <board> -i <build/name> <suite>
51e.g.
52 atest suite create -b x86-mario -i x86-mario/R20-2203.0.0 bvt
53
54-------------------------------------------------------------------------
55Implementation details
56
57In addition to the create_suite_job() RPC defined in the autotest frontend,
58there are two main classes defined here: Suite and Reimager.
59
60A Suite instance represents a single test suite, defined by some predicate
61run over all known control files. The simplest example is creating a Suite
62by 'name'.
63
64The Reimager class provides support for reimaging a heterogenous set
65of devices with an appropriate build, in preparation for a test run.
66One could use a single Reimager, followed by the instantiation and use
67of multiple Suite objects.
68
69create_suite_job() takes the parameters needed to define a suite run (board,
70build to test, machine pool, and which suite to run), ensures important
71preconditions are met, finds the appropraite suite control file, and then
72schedules the hostless job that will do the rest of the work.
73
74reimage_and_run() works by creating a Reimager, using it to perform the
75requested installs, and then instantiating a Suite and running it on the
76machines that were just reimaged. We'll go through this process in stages.
77
78- create_suite_job()
79The primary role of create_suite_job() is to ensure that the required
80artifacts for the build to be tested are staged on the dev server. This
81includes payloads required to autoupdate machines to the desired build, as
82well as the autotest control files appropriate for that build. Then, the
83RPC pulls the control file for the suite to be run from the dev server and
84uses it to create the suite job with the autotest frontend.
85
86 +----------------+
87 | Google Storage | Client
88 +----------------+ |
89 | ^ | create_suite_job()
90 payloads/ | | |
91 control files | | request |
92 V | V
93 +-------------+ download request +--------------------------+
94 | |<----------------------| |
95 | Dev Server | | Autotest Frontend (AFE) |
96 | |---------------------->| |
97 +-------------+ suite control file +--------------------------+
98 |
99 V
100 Suite Job (hostless)
101
102- The Reimaging process
103In short, the Reimager schedules and waits for a number of autoupdate 'test'
104jobs that perform image installation and make sure the device comes back up.
105It labels the machines that it reimages with the newly-installed CrOS version,
106so that later steps in the can refer to the machines by version and board,
107instead of having to keep track of hostnames or some such.
108
109The number of machines to use is called the 'sharding_factor', and the default
110is defined in the [CROS] section of global_config.ini. This can be overridden
111by passing a 'num=N' parameter to reimage_and_run() as shown in the example
112above.
113
114Step by step:
1151) Schedule autoupdate 'tests' across N devices of the appropriate board.
116 - Technically, one job that has N tests across N hosts.
117 - This 'test' is in server/site_tests/autoupdate/
118 - The control file is modified at runtime to inject the name of the build
119 to install, and the URL to get said build from.
120 - This is the _TOT_ version of the autoupdate test; it must be able to run
121 successfully on all currently supported branches at all times.
1222) Wait for this job to get kicked off and run to completion.
1233) Label successfully reimaged devices with a 'cros-version' label
124 - This is actually done by the autoupdate 'test' control file.
1254) Add a host attribute ('job_repo_url') to each reimaged host indicating
126 the URL where packages should be downloaded for subsequent tests
127 - This is actually done by the autoupdate 'test' control file
128 - This information is consumed in server/site_autotest.py
129 - job_repo_url points to some location on the dev server, where build
130 artifacts are staged -- including autotest packages.
1315) Return success or failure.
132
133 +------------+ +--------------------------+
134 | | | |
135 | Dev Server | | Autotest Frontend (AFE) |
136 | | | [Suite Job] |
137 +------------+ +--------------------------+
138 | payloads | | | |
139 V V autoupdate test | | |
140 +--------+ +--------+ <-----+----------------+ | |
141 | Host 1 |<------| Host 2 |-------+ | |
142 +--------+ +--------+ label | |
143 VersLabel VersLabel <-----------------------+ |
144 job_repo_url job_repo_url <-----------------------------+
145 host-attribute
146
147To sum up, after re-imaging, we have the following assumptions:
148- |num| devices of type |board| have |build| installed.
149- These devices are labeled appropriately
150- They have a host attribute called 'job_repo_url' dictating where autotest
151 packages can be downloaded for test runs.
152
153
154- Running Suites
155A Suite instance uses the labels created by the Reimager to schedule test jobs
156across all the hosts that were just reimaged. It then waits for all these jobs.
157
158Step by step:
1591) At instantiation time, find all appropriate control files for this suite
160 that were included in the build to be tested. To do this, we consult the
161 Dev Server, where all these control files are staged.
162
163 +------------+ control files? +--------------------------+
164 | |<----------------------| |
165 | Dev Server | | Autotest Frontend (AFE) |
166 | |---------------------->| [Suite Job] |
167 +------------+ control files! +--------------------------+
168
1692) Now that the Suite instance exists, it schedules jobs for every control
170 file it deemed appropriate, to be run on the hosts that were labeled
171 by the Reimager. We stuff keyvals into these jobs, indicating what
172 build they were testing and which suite they were for.
173
174 +--------------------------+ Job for VersLabel +--------+
175 | |------------------------>| Host 1 | VersLabel
176 | Autotest Frontend (AFE) | +--------+ +--------+
177 | [Suite Job] |----------->| Host 2 |
178 +--------------------------+ Job for +--------+
179 | ^ VersLabel VersLabel
180 | |
181 +----------------+
182 One job per test
183 {'build': build/name,
184 'suite': suite_name}
185
1863) Now that all jobs are scheduled, they'll be doled out as labeled hosts
187 finish their assigned work and become available again.
1884) As we clean up each job, we check to see if any crashes occurred. If they
189 did, we look at the 'build' keyval in the job to see which build's debug
190 symbols we'll need to symbolicate the crash dump we just found.
1915) Using this info, we tell the Dev Server to stage the required debug symbols.
192 Once that's done, we ask the dev server to use those symbols to symbolicate
193 the crash dump in question.
194
195 +----------------+
196 | Google Storage |
197 +----------------+
198 | ^
199 symbols! | | symbols?
200 V |
201 +------------+ stage symbols for build +--------------------------+
202 | |<--------------------------| |
203 | | | |
204 | Dev Server | dump to symbolicate | Autotest Frontend (AFE) |
205 | |<--------------------------| [Suite Job] |
206 | |-------------------------->| |
207 +------------+ symbolicated dump +--------------------------+
208
2096) As jobs finish, we record their success or failure in the status of the suite
210 job. We also record a 'job keyval' in the suite job for each test, noting
211 the job ID and job owner. This can be used to refer to test logs later.
2127) Once all jobs are complete, status is recorded for the suite job, and the
213 job_repo_url host attribute is removed from all hosts used by the suite.
214
215"""
216
Chris Masone6fed6462011-10-20 16:36:43 -0700217
Chris Masoneaa10f8e2012-05-15 13:34:21 -0700218# Job keyvals for finding debug symbols when processing crash dumps.
219JOB_BUILD_KEY = 'build'
220JOB_SUITE_KEY = 'suite'
221
222# Job attribute and label names
223JOB_REPO_URL = 'job_repo_url'
Scott Zawalski65650172012-02-16 11:48:26 -0500224VERSION_PREFIX = 'cros-version:'
Chris Masoneaa10f8e2012-05-15 13:34:21 -0700225EXPERIMENTAL_PREFIX = 'experimental_'
226REIMAGE_JOB_NAME = 'try_new_image'
227
228# Timings
229ARTIFACT_FINISHED_TIME = 'artifact_finished_time'
230DOWNLOAD_STARTED_TIME = 'download_started_time'
231PAYLOAD_FINISHED_TIME = 'payload_finished_time'
Chris Masoneaa10f8e2012-05-15 13:34:21 -0700232
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800233CONFIG = global_config.global_config
234
235
Chris Masonef8b53062012-05-08 22:14:18 -0700236# Relevant CrosDynamicSuiteExceptions are defined in client/common_lib/error.py.
Chris Masone502b71e2012-04-10 10:41:35 -0700237
238
Chris Masoneab3e7332012-02-29 18:54:58 -0800239def reimage_and_run(**dargs):
240 """
241 Backward-compatible API for dynamic_suite.
242
243 Will re-image a number of devices (of the specified board) with the
244 provided build, and then run the indicated test suite on them.
245 Guaranteed to be compatible with any build from stable to dev.
246
247 Currently required args:
248 @param build: the build to install e.g.
249 x86-alex-release/R18-1655.0.0-a1-b1584.
250 @param board: which kind of devices to reimage.
251 @param name: a value of the SUITE control file variable to search for.
252 @param job: an instance of client.common_lib.base_job representing the
253 currently running suite job.
254
255 Currently supported optional args:
256 @param pool: specify the pool of machines to use for scheduling purposes.
257 Default: None
258 @param num: how many devices to reimage.
259 Default in global_config
Chris Masone62579122012-03-08 15:18:43 -0800260 @param check_hosts: require appropriate hosts to be available now.
Chris Masoneab3e7332012-02-29 18:54:58 -0800261 @param skip_reimage: skip reimaging, used for testing purposes.
262 Default: False
263 @param add_experimental: schedule experimental tests as well, or not.
264 Default: True
Chris Sosa6b288c82012-03-29 15:31:06 -0700265 @raises AsynchronousBuildFailure: if there was an issue finishing staging
266 from the devserver.
Chris Masoneab3e7332012-02-29 18:54:58 -0800267 """
Chris Masone62579122012-03-08 15:18:43 -0800268 (build, board, name, job, pool, num, check_hosts, skip_reimage,
269 add_experimental) = _vet_reimage_and_run_args(**dargs)
Chris Masone5374c672012-03-05 15:11:39 -0800270 board = 'board:%s' % board
271 if pool:
272 pool = 'pool:%s' % pool
Chris Masoned368cc42012-03-07 15:16:59 -0800273
Chris Masone275ec902012-07-10 15:28:34 -0700274 afe = frontend_wrappers.RetryingAFE(timeout_min=30, delay_sec=10,
275 debug=False)
276 tko = frontend_wrappers.RetryingTKO(timeout_min=30, delay_sec=10,
277 debug=False)
278 manager = host_lock_manager.HostLockManager(afe=afe)
279 reimager = Reimager(job.autodir, afe, tko, results_dir=job.resultdir)
Chris Sosa5ca9d1b2012-07-30 14:41:18 -0700280
Chris Masone275ec902012-07-10 15:28:34 -0700281 try:
282 if skip_reimage or reimager.attempt(build, board, pool,
283 job.record_entry, check_hosts,
284 manager, num=num):
285 # Ensure that the image's artifacts have completed downloading.
286 try:
287 ds = dev_server.DevServer.create()
288 ds.finish_download(build)
289 except dev_server.DevServerException as e:
290 raise error.AsynchronousBuildFailure(e)
Chris Sosa6b288c82012-03-29 15:31:06 -0700291
Chris Masone275ec902012-07-10 15:28:34 -0700292 timestamp = datetime.datetime.now().strftime(job_status.TIME_FMT)
293 utils.write_keyval(job.resultdir,
294 {ARTIFACT_FINISHED_TIME: timestamp})
295
296 suite = Suite.create_from_name(name, build, afe=afe, tko=tko,
297 pool=pool, results_dir=job.resultdir)
298 suite.run_and_wait(job.record_entry, manager, add_experimental)
299 finally:
300 manager.unlock()
Chris Masoneab3e7332012-02-29 18:54:58 -0800301
Chris Masoned368cc42012-03-07 15:16:59 -0800302 reimager.clear_reimaged_host_state(build)
303
Chris Masoneab3e7332012-02-29 18:54:58 -0800304
305def _vet_reimage_and_run_args(build=None, board=None, name=None, job=None,
Chris Masone62579122012-03-08 15:18:43 -0800306 pool=None, num=None, check_hosts=True,
307 skip_reimage=False, add_experimental=True,
308 **dargs):
Chris Masoneab3e7332012-02-29 18:54:58 -0800309 """
310 Vets arguments for reimage_and_run().
311
312 Currently required args:
313 @param build: the build to install e.g.
314 x86-alex-release/R18-1655.0.0-a1-b1584.
315 @param board: which kind of devices to reimage.
316 @param name: a value of the SUITE control file variable to search for.
317 @param job: an instance of client.common_lib.base_job representing the
318 currently running suite job.
319
320 Currently supported optional args:
321 @param pool: specify the pool of machines to use for scheduling purposes.
322 Default: None
323 @param num: how many devices to reimage.
324 Default in global_config
Chris Masone62579122012-03-08 15:18:43 -0800325 @param check_hosts: require appropriate hosts to be available now.
Chris Masoneab3e7332012-02-29 18:54:58 -0800326 @param skip_reimage: skip reimaging, used for testing purposes.
327 Default: False
328 @param add_experimental: schedule experimental tests as well, or not.
329 Default: True
330 @return a tuple of args set to provided (or default) values.
331 """
332 required_keywords = {'build': str,
333 'board': str,
334 'name': str,
335 'job': base_job.base_job}
336 for key, expected in required_keywords.iteritems():
337 value = locals().get(key)
338 if not value or not isinstance(value, expected):
Chris Masonef8b53062012-05-08 22:14:18 -0700339 raise error.SuiteArgumentException(
340 "reimage_and_run() needs %s=<%r>" % (key, expected))
Chris Masone62579122012-03-08 15:18:43 -0800341 return (build, board, name, job, pool, num, check_hosts, skip_reimage,
342 add_experimental)
Chris Masoneab3e7332012-02-29 18:54:58 -0800343
344
Chris Masone8b764252012-01-17 11:12:51 -0800345def inject_vars(vars, control_file_in):
346 """
Chris Masoneab3e7332012-02-29 18:54:58 -0800347 Inject the contents of |vars| into |control_file_in|.
Chris Masone8b764252012-01-17 11:12:51 -0800348
349 @param vars: a dict to shoehorn into the provided control file string.
350 @param control_file_in: the contents of a control file to munge.
351 @return the modified control file string.
352 """
353 control_file = ''
354 for key, value in vars.iteritems():
Chris Masone6cb0d0d2012-03-05 15:37:49 -0800355 # None gets injected as 'None' without this check; same for digits.
356 if isinstance(value, str):
357 control_file += "%s='%s'\n" % (key, value)
358 else:
359 control_file += "%s=%r\n" % (key, value)
Chris Masone8b764252012-01-17 11:12:51 -0800360 return control_file + control_file_in
361
362
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800363def _image_url_pattern():
364 return CONFIG.get_config_value('CROS', 'image_url_pattern', type=str)
365
366
367def _package_url_pattern():
368 return CONFIG.get_config_value('CROS', 'package_url_pattern', type=str)
369
Chris Masone6fed6462011-10-20 16:36:43 -0700370
Chris Masoneab3e7332012-02-29 18:54:58 -0800371def skip_reimage(g):
372 return g.get('SKIP_IMAGE')
373
374
Chris Masone6fed6462011-10-20 16:36:43 -0700375class Reimager(object):
376 """
377 A class that can run jobs to reimage devices.
378
379 @var _afe: a frontend.AFE instance used to talk to autotest.
380 @var _tko: a frontend.TKO instance used to query the autotest results db.
381 @var _cf_getter: a ControlFileGetter used to get the AU control file.
382 """
383
384
Chris Masonec43448f2012-05-31 12:55:59 -0700385 def __init__(self, autotest_dir, afe=None, tko=None, results_dir=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700386 """
387 Constructor
388
389 @param autotest_dir: the place to find autotests.
390 @param afe: an instance of AFE as defined in server/frontend.py.
391 @param tko: an instance of TKO as defined in server/frontend.py.
Chris Masone9f13ff22012-03-05 13:45:25 -0800392 @param results_dir: The directory where the job can write results to.
393 This must be set if you want job_id of sub-jobs
394 list in the job keyvals.
Chris Masone6fed6462011-10-20 16:36:43 -0700395 """
Chris Masone8ac66712012-02-15 14:21:02 -0800396 self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
397 delay_sec=10,
398 debug=False)
399 self._tko = tko or frontend_wrappers.RetryingTKO(timeout_min=30,
400 delay_sec=10,
401 debug=False)
Chris Masone9f13ff22012-03-05 13:45:25 -0800402 self._results_dir = results_dir
Chris Masoned368cc42012-03-07 15:16:59 -0800403 self._reimaged_hosts = {}
Chris Masone6fed6462011-10-20 16:36:43 -0700404 self._cf_getter = control_file_getter.FileSystemGetter(
405 [os.path.join(autotest_dir, 'server/site_tests')])
406
407
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800408 def skip(self, g):
Chris Masoneab3e7332012-02-29 18:54:58 -0800409 """Deprecated in favor of dynamic_suite.skip_reimage()."""
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800410 return 'SKIP_IMAGE' in g and g['SKIP_IMAGE']
411
412
Chris Masone275ec902012-07-10 15:28:34 -0700413 def attempt(self, build, board, pool, record, check_hosts,
414 manager, num=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700415 """
416 Synchronously attempt to reimage some machines.
417
418 Fire off attempts to reimage |num| machines of type |board|, using an
Chris Masone8abb6fc2012-01-31 09:27:36 -0800419 image at |url| called |build|. Wait for completion, polling every
Chris Masone6fed6462011-10-20 16:36:43 -0700420 10s, and log results with |record| upon completion.
421
Chris Masone8abb6fc2012-01-31 09:27:36 -0800422 @param build: the build to install e.g.
423 x86-alex-release/R18-1655.0.0-a1-b1584.
Chris Masone6fed6462011-10-20 16:36:43 -0700424 @param board: which kind of devices to reimage.
Chris Masonec43448f2012-05-31 12:55:59 -0700425 @param pool: Specify the pool of machines to use for scheduling
426 purposes.
Chris Masone6fed6462011-10-20 16:36:43 -0700427 @param record: callable that records job status.
Chris Masone604baf32012-06-28 08:45:30 -0700428 prototype:
429 record(base_job.status_log_entry)
Chris Masone62579122012-03-08 15:18:43 -0800430 @param check_hosts: require appropriate hosts to be available now.
Chris Masone275ec902012-07-10 15:28:34 -0700431 @param manager: an as-yet-unused HostLockManager instance to handle
432 locking DUTs that we decide to reimage.
Chris Masone5552dd72012-02-15 15:01:04 -0800433 @param num: how many devices to reimage.
Chris Masone6fed6462011-10-20 16:36:43 -0700434 @return True if all reimaging jobs succeed, false otherwise.
435 """
Chris Masone5552dd72012-02-15 15:01:04 -0800436 if not num:
437 num = CONFIG.get_config_value('CROS', 'sharding_factor', type=int)
Scott Zawalski65650172012-02-16 11:48:26 -0500438 logging.debug("scheduling reimaging across %d machines", num)
Chris Masone604baf32012-06-28 08:45:30 -0700439 begin_time_str = datetime.datetime.now().strftime(job_status.TIME_FMT)
Chris Masone796fcf12012-02-22 16:53:31 -0800440 try:
Chris Masone62579122012-03-08 15:18:43 -0800441 self._ensure_version_label(VERSION_PREFIX + build)
442
443 if check_hosts:
Chris Masonec43448f2012-05-31 12:55:59 -0700444 # TODO make DEPENDENCIES-aware
445 self._ensure_enough_hosts(board, pool, num)
Chris Masone5374c672012-03-05 15:11:39 -0800446
Chris Masoned368cc42012-03-07 15:16:59 -0800447 # Schedule job and record job metadata.
Chris Masonec43448f2012-05-31 12:55:59 -0700448 # TODO make DEPENDENCIES-aware
449 canary_job = self._schedule_reimage_job(build, board, pool, num)
Chris Masoneaa10f8e2012-05-15 13:34:21 -0700450 self._record_job_if_possible(REIMAGE_JOB_NAME, canary_job)
Chris Masonee1056d72012-07-24 15:22:40 -0700451 logging.info('Created re-imaging job: %d', canary_job.id)
Chris Masoned368cc42012-03-07 15:16:59 -0800452
Chris Masone517ef482012-07-23 15:36:36 -0700453 job_status.wait_for_jobs_to_start(self._afe, [canary_job])
Chris Masone275ec902012-07-10 15:28:34 -0700454 logging.debug('Re-imaging job running.')
455
456 hosts = job_status.wait_for_and_lock_job_hosts(self._afe,
Chris Masone517ef482012-07-23 15:36:36 -0700457 [canary_job],
458 manager)
Chris Masonee1056d72012-07-24 15:22:40 -0700459 logging.info('%r locked for reimaging.', hosts)
Chris Masone275ec902012-07-10 15:28:34 -0700460
Chris Masone517ef482012-07-23 15:36:36 -0700461 job_status.wait_for_jobs_to_finish(self._afe, [canary_job])
Chris Masone275ec902012-07-10 15:28:34 -0700462 logging.debug('Re-imaging job finished.')
Chris Masoned368cc42012-03-07 15:16:59 -0800463
464 # Gather job results.
Chris Masone6ea0cad2012-07-02 09:43:36 -0700465 results = self.get_results(canary_job)
Chris Masone275ec902012-07-10 15:28:34 -0700466 self._reimaged_hosts[build] = results.keys()
Chris Masone6ea0cad2012-07-02 09:43:36 -0700467
Chris Masonef8b53062012-05-08 22:14:18 -0700468 except error.InadequateHostsException as e:
Chris Masone5374c672012-03-05 15:11:39 -0800469 logging.warning(e)
Chris Masone604baf32012-06-28 08:45:30 -0700470 Status('WARN', REIMAGE_JOB_NAME, str(e),
471 begin_time_str=begin_time_str).record_all(record)
Chris Masone5374c672012-03-05 15:11:39 -0800472 return False
Chris Masone796fcf12012-02-22 16:53:31 -0800473 except Exception as e:
474 # catch Exception so we record the job as terminated no matter what.
475 logging.error(e)
Chris Masone604baf32012-06-28 08:45:30 -0700476 Status('ERROR', REIMAGE_JOB_NAME, str(e),
477 begin_time_str=begin_time_str).record_all(record)
Chris Masone796fcf12012-02-22 16:53:31 -0800478 return False
Chris Masone6fed6462011-10-20 16:36:43 -0700479
Chris Masone6ea0cad2012-07-02 09:43:36 -0700480 return job_status.record_and_report_results(results.values(), record)
Chris Masone6fed6462011-10-20 16:36:43 -0700481
Chris Masone604baf32012-06-28 08:45:30 -0700482
Chris Masone6ea0cad2012-07-02 09:43:36 -0700483 def get_results(self, canary_job):
484 """
485 Gather results for |canary_job|, in a map of Statuses indexed by host.
Chris Masone6fed6462011-10-20 16:36:43 -0700486
Chris Masone6ea0cad2012-07-02 09:43:36 -0700487 A host's results will be named REIMAGE_JOB_NAME-<host> in the map, e.g.
488 {'chromeos2-rack1': Status('GOOD', 'try_new_image-chromeos2-rack1')}
489
490 @param canary_job: a completed frontend.Job
491 @return a map of hostname: job_status.Status objects.
492 """
493 return job_status.gather_per_host_results(self._afe,
494 self._tko,
495 [canary_job],
Chris Sosa5ca9d1b2012-07-30 14:41:18 -0700496 REIMAGE_JOB_NAME+'-')
Chris Masone6fed6462011-10-20 16:36:43 -0700497
498
Chris Masone62579122012-03-08 15:18:43 -0800499 def _ensure_enough_hosts(self, board, pool, num):
500 """
501 Determine if there are enough working hosts to run on.
502
503 Raises exception if there are not enough hosts.
504
505 @param board: which kind of devices to reimage.
506 @param pool: the pool of machines to use for scheduling purposes.
507 @param num: how many devices to reimage.
Chris Masonef8b53062012-05-08 22:14:18 -0700508 @raises NoHostsException: if no working hosts.
Chris Masone62579122012-03-08 15:18:43 -0800509 @raises InadequateHostsException: if too few working hosts.
510 """
511 labels = [l for l in [board, pool] if l is not None]
Chris Masone502b71e2012-04-10 10:41:35 -0700512 available = self._count_usable_hosts(labels)
513 if available == 0:
Chris Masonef8b53062012-05-08 22:14:18 -0700514 raise error.NoHostsException('All hosts with %r are dead!' % labels)
Chris Masone502b71e2012-04-10 10:41:35 -0700515 elif num > available:
Chris Masonef8b53062012-05-08 22:14:18 -0700516 raise error.InadequateHostsException(
517 'Too few hosts with %r' % labels)
Chris Masone62579122012-03-08 15:18:43 -0800518
519
Chris Masoned368cc42012-03-07 15:16:59 -0800520 def clear_reimaged_host_state(self, build):
521 """
522 Clear per-host state created in the autotest DB for this job.
523
524 After reimaging a host, we label it and set some host attributes on it
525 that are then used by the suite scheduling code. This call cleans
526 that up.
527
528 @param build: the build whose hosts we want to clean up e.g.
529 x86-alex-release/R18-1655.0.0-a1-b1584.
530 """
Chris Masoned368cc42012-03-07 15:16:59 -0800531 for host in self._reimaged_hosts.get('build', []):
Chris Masone6ea0cad2012-07-02 09:43:36 -0700532 if not host.startswith('hostless'):
533 self._clear_build_state(host)
Chris Masoned368cc42012-03-07 15:16:59 -0800534
535
536 def _clear_build_state(self, machine):
537 """
538 Clear all build-specific labels, attributes from the target.
539
540 @param machine: the host to clear labels, attributes from.
541 """
Chris Masoneaa10f8e2012-05-15 13:34:21 -0700542 self._afe.set_host_attribute(JOB_REPO_URL, None, hostname=machine)
Chris Masoned368cc42012-03-07 15:16:59 -0800543
544
Chris Masone9f13ff22012-03-05 13:45:25 -0800545 def _record_job_if_possible(self, test_name, job):
546 """
547 Record job id as keyval, if possible, so it can be referenced later.
548
549 If |self._results_dir| is None, then this is a NOOP.
Chris Masone5374c672012-03-05 15:11:39 -0800550
551 @param test_name: the test to record id/owner for.
552 @param job: the job object to pull info from.
Chris Masone9f13ff22012-03-05 13:45:25 -0800553 """
554 if self._results_dir:
555 job_id_owner = '%s-%s' % (job.id, job.owner)
Chris Masone11aae452012-05-21 16:08:39 -0700556 utils.write_keyval(
557 self._results_dir,
558 {hashlib.md5(test_name).hexdigest(): job_id_owner})
Chris Masone9f13ff22012-03-05 13:45:25 -0800559
560
Chris Masone5374c672012-03-05 15:11:39 -0800561 def _count_usable_hosts(self, host_spec):
562 """
563 Given a set of host labels, count the live hosts that have them all.
564
565 @param host_spec: list of labels specifying a set of hosts.
566 @return the number of live hosts that satisfy |host_spec|.
567 """
568 count = 0
569 for h in self._afe.get_hosts(multiple_labels=host_spec):
570 if h.status not in ['Repair Failed', 'Repairing']:
571 count += 1
572 return count
573
574
Chris Masone6fed6462011-10-20 16:36:43 -0700575 def _ensure_version_label(self, name):
576 """
577 Ensure that a label called |name| exists in the autotest DB.
578
579 @param name: the label to check for/create.
580 """
Chris Masone47c9e642012-04-25 14:22:18 -0700581 try:
Chris Masone6fed6462011-10-20 16:36:43 -0700582 self._afe.create_label(name=name)
Chris Masone47c9e642012-04-25 14:22:18 -0700583 except proxy.ValidationError as ve:
584 if ('name' in ve.problem_keys and
585 'This value must be unique' in ve.problem_keys['name']):
586 logging.debug('Version label %s already exists', name)
587 else:
588 raise ve
Chris Masone6fed6462011-10-20 16:36:43 -0700589
590
Chris Masonec43448f2012-05-31 12:55:59 -0700591 def _schedule_reimage_job(self, build, board, pool, num_machines):
Chris Masone6fed6462011-10-20 16:36:43 -0700592 """
593 Schedules the reimaging of |num_machines| |board| devices with |image|.
594
595 Sends an RPC to the autotest frontend to enqueue reimaging jobs on
596 |num_machines| devices of type |board|
597
Chris Masone8abb6fc2012-01-31 09:27:36 -0800598 @param build: the build to install (must be unique).
Chris Masone6fed6462011-10-20 16:36:43 -0700599 @param board: which kind of devices to reimage.
Chris Masonec43448f2012-05-31 12:55:59 -0700600 @param pool: the pool of machines to use for scheduling purposes.
601 @param num_machines: how many devices to reimage.
Chris Masone6fed6462011-10-20 16:36:43 -0700602 @return a frontend.Job object for the reimaging job we scheduled.
603 """
Chris Masone8b764252012-01-17 11:12:51 -0800604 control_file = inject_vars(
Chris Sosa5ca9d1b2012-07-30 14:41:18 -0700605 {'image_url': _image_url_pattern() % build, 'image_name': build},
Chris Masone6fed6462011-10-20 16:36:43 -0700606 self._cf_getter.get_control_file_contents_by_name('autoupdate'))
Scott Zawalski65650172012-02-16 11:48:26 -0500607 job_deps = []
Chris Masonec43448f2012-05-31 12:55:59 -0700608 if pool:
609 meta_host = pool
Chris Masone5374c672012-03-05 15:11:39 -0800610 board_label = board
Scott Zawalski65650172012-02-16 11:48:26 -0500611 job_deps.append(board_label)
612 else:
613 # No pool specified use board.
Chris Masone5374c672012-03-05 15:11:39 -0800614 meta_host = board
Chris Masone6fed6462011-10-20 16:36:43 -0700615
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800616 return self._afe.create_job(control_file=control_file,
Chris Masone8abb6fc2012-01-31 09:27:36 -0800617 name=build + '-try',
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800618 control_type='Server',
Chris Masone97325362012-04-26 16:19:13 -0700619 priority='Low',
Scott Zawalski65650172012-02-16 11:48:26 -0500620 meta_hosts=[meta_host] * num_machines,
621 dependencies=job_deps)
Chris Masone6fed6462011-10-20 16:36:43 -0700622
623
Chris Masone6fed6462011-10-20 16:36:43 -0700624class Suite(object):
625 """
626 A suite of tests, defined by some predicate over control file variables.
627
628 Given a place to search for control files a predicate to match the desired
629 tests, can gather tests and fire off jobs to run them, and then wait for
630 results.
631
632 @var _predicate: a function that should return True when run over a
633 ControlData representation of a control file that should be in
634 this Suite.
635 @var _tag: a string with which to tag jobs run in this suite.
Chris Masone8b7cd422012-02-22 13:16:11 -0800636 @var _build: the build on which we're running this suite.
Chris Masone6fed6462011-10-20 16:36:43 -0700637 @var _afe: an instance of AFE as defined in server/frontend.py.
638 @var _tko: an instance of TKO as defined in server/frontend.py.
639 @var _jobs: currently scheduled jobs, if any.
640 @var _cf_getter: a control_file_getter.ControlFileGetter
641 """
642
643
Chris Masonefef21382012-01-17 11:16:32 -0800644 @staticmethod
Chris Masoned6f38c82012-02-22 14:53:42 -0800645 def create_ds_getter(build):
Chris Masonefef21382012-01-17 11:16:32 -0800646 """
Chris Masone8b7cd422012-02-22 13:16:11 -0800647 @param build: the build on which we're running this suite.
Chris Masonefef21382012-01-17 11:16:32 -0800648 @return a FileSystemGetter instance that looks under |autotest_dir|.
649 """
Chris Masone8b7cd422012-02-22 13:16:11 -0800650 return control_file_getter.DevServerGetter(
651 build, dev_server.DevServer.create())
Chris Masonefef21382012-01-17 11:16:32 -0800652
653
654 @staticmethod
Chris Masoned6f38c82012-02-22 14:53:42 -0800655 def create_fs_getter(autotest_dir):
656 """
657 @param autotest_dir: the place to find autotests.
658 @return a FileSystemGetter instance that looks under |autotest_dir|.
659 """
660 # currently hard-coded places to look for tests.
661 subpaths = ['server/site_tests', 'client/site_tests',
662 'server/tests', 'client/tests']
663 directories = [os.path.join(autotest_dir, p) for p in subpaths]
664 return control_file_getter.FileSystemGetter(directories)
665
666
667 @staticmethod
Zdenek Behan849db052012-02-29 19:16:28 +0100668 def parse_tag(tag):
669 """Splits a string on ',' optionally surrounded by whitespace."""
670 return map(lambda x: x.strip(), tag.split(','))
671
672
673 @staticmethod
Chris Masone84564792012-02-23 10:52:42 -0800674 def name_in_tag_predicate(name):
675 """Returns predicate that takes a control file and looks for |name|.
676
677 Builds a predicate that takes in a parsed control file (a ControlData)
678 and returns True if the SUITE tag is present and contains |name|.
679
680 @param name: the suite name to base the predicate on.
681 @return a callable that takes a ControlData and looks for |name| in that
682 ControlData object's suite member.
683 """
Zdenek Behan849db052012-02-29 19:16:28 +0100684 return lambda t: hasattr(t, 'suite') and \
685 name in Suite.parse_tag(t.suite)
Chris Masone84564792012-02-23 10:52:42 -0800686
Zdenek Behan849db052012-02-29 19:16:28 +0100687
688 @staticmethod
689 def list_all_suites(build, cf_getter=None):
690 """
691 Parses all ControlData objects with a SUITE tag and extracts all
692 defined suite names.
693
694 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
695 using DevServerGetter.
696
697 @return list of suites
698 """
699 if cf_getter is None:
700 cf_getter = Suite.create_ds_getter(build)
701
702 suites = set()
703 predicate = lambda t: hasattr(t, 'suite')
Scott Zawalskif22b75d2012-05-10 16:54:37 -0400704 for test in Suite.find_and_parse_tests(cf_getter, predicate,
705 add_experimental=True):
Zdenek Behan849db052012-02-29 19:16:28 +0100706 suites.update(Suite.parse_tag(test.suite))
707 return list(suites)
Chris Masone84564792012-02-23 10:52:42 -0800708
709
710 @staticmethod
Scott Zawalski9ece6532012-02-28 14:10:47 -0500711 def create_from_name(name, build, cf_getter=None, afe=None, tko=None,
712 pool=None, results_dir=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700713 """
714 Create a Suite using a predicate based on the SUITE control file var.
715
716 Makes a predicate based on |name| and uses it to instantiate a Suite
717 that looks for tests in |autotest_dir| and will schedule them using
Chris Masoned6f38c82012-02-22 14:53:42 -0800718 |afe|. Pulls control files from the default dev server.
719 Results will be pulled from |tko| upon completion.
Chris Masone6fed6462011-10-20 16:36:43 -0700720
721 @param name: a value of the SUITE control file variable to search for.
Chris Masone8b7cd422012-02-22 13:16:11 -0800722 @param build: the build on which we're running this suite.
Chris Masoned6f38c82012-02-22 14:53:42 -0800723 @param cf_getter: a control_file_getter.ControlFileGetter.
724 If None, default to using a DevServerGetter.
Chris Masone6fed6462011-10-20 16:36:43 -0700725 @param afe: an instance of AFE as defined in server/frontend.py.
726 @param tko: an instance of TKO as defined in server/frontend.py.
Scott Zawalski65650172012-02-16 11:48:26 -0500727 @param pool: Specify the pool of machines to use for scheduling
Chris Masoned6f38c82012-02-22 14:53:42 -0800728 purposes.
Scott Zawalski9ece6532012-02-28 14:10:47 -0500729 @param results_dir: The directory where the job can write results to.
730 This must be set if you want job_id of sub-jobs
731 list in the job keyvals.
Chris Masone6fed6462011-10-20 16:36:43 -0700732 @return a Suite instance.
733 """
Chris Masoned6f38c82012-02-22 14:53:42 -0800734 if cf_getter is None:
735 cf_getter = Suite.create_ds_getter(build)
Chris Masone84564792012-02-23 10:52:42 -0800736 return Suite(Suite.name_in_tag_predicate(name),
Scott Zawalski9ece6532012-02-28 14:10:47 -0500737 name, build, cf_getter, afe, tko, pool, results_dir)
Chris Masone6fed6462011-10-20 16:36:43 -0700738
739
Chris Masoned6f38c82012-02-22 14:53:42 -0800740 def __init__(self, predicate, tag, build, cf_getter, afe=None, tko=None,
Scott Zawalski9ece6532012-02-28 14:10:47 -0500741 pool=None, results_dir=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700742 """
743 Constructor
744
745 @param predicate: a function that should return True when run over a
746 ControlData representation of a control file that should be in
747 this Suite.
748 @param tag: a string with which to tag jobs run in this suite.
Chris Masone8b7cd422012-02-22 13:16:11 -0800749 @param build: the build on which we're running this suite.
Chris Masoned6f38c82012-02-22 14:53:42 -0800750 @param cf_getter: a control_file_getter.ControlFileGetter
Chris Masone6fed6462011-10-20 16:36:43 -0700751 @param afe: an instance of AFE as defined in server/frontend.py.
752 @param tko: an instance of TKO as defined in server/frontend.py.
Scott Zawalski65650172012-02-16 11:48:26 -0500753 @param pool: Specify the pool of machines to use for scheduling
754 purposes.
Scott Zawalski9ece6532012-02-28 14:10:47 -0500755 @param results_dir: The directory where the job can write results to.
756 This must be set if you want job_id of sub-jobs
757 list in the job keyvals.
Chris Masone6fed6462011-10-20 16:36:43 -0700758 """
759 self._predicate = predicate
760 self._tag = tag
Chris Masone8b7cd422012-02-22 13:16:11 -0800761 self._build = build
Chris Masoned6f38c82012-02-22 14:53:42 -0800762 self._cf_getter = cf_getter
Scott Zawalski9ece6532012-02-28 14:10:47 -0500763 self._results_dir = results_dir
Chris Masone8ac66712012-02-15 14:21:02 -0800764 self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
765 delay_sec=10,
766 debug=False)
767 self._tko = tko or frontend_wrappers.RetryingTKO(timeout_min=30,
768 delay_sec=10,
769 debug=False)
Scott Zawalski65650172012-02-16 11:48:26 -0500770 self._pool = pool
Chris Masone6fed6462011-10-20 16:36:43 -0700771 self._jobs = []
Chris Masone6fed6462011-10-20 16:36:43 -0700772 self._tests = Suite.find_and_parse_tests(self._cf_getter,
773 self._predicate,
774 add_experimental=True)
775
776
777 @property
778 def tests(self):
779 """
780 A list of ControlData objects in the suite, with added |text| attr.
781 """
782 return self._tests
783
784
785 def stable_tests(self):
786 """
787 |self.tests|, filtered for non-experimental tests.
788 """
789 return filter(lambda t: not t.experimental, self.tests)
790
791
792 def unstable_tests(self):
793 """
794 |self.tests|, filtered for experimental tests.
795 """
796 return filter(lambda t: t.experimental, self.tests)
797
798
Chris Masone8b7cd422012-02-22 13:16:11 -0800799 def _create_job(self, test):
Chris Masone6fed6462011-10-20 16:36:43 -0700800 """
801 Thin wrapper around frontend.AFE.create_job().
802
803 @param test: ControlData object for a test to run.
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500804 @return a frontend.Job object with an added test_name member.
805 test_name is used to preserve the higher level TEST_NAME
806 name of the job.
Chris Masone6fed6462011-10-20 16:36:43 -0700807 """
Chris Masonec43448f2012-05-31 12:55:59 -0700808 job_deps = [] # TODO(cmasone): init from test.dependencies.
Scott Zawalski65650172012-02-16 11:48:26 -0500809 if self._pool:
Chris Masone5374c672012-03-05 15:11:39 -0800810 meta_hosts = self._pool
Chris Masone8b7cd422012-02-22 13:16:11 -0800811 cros_label = VERSION_PREFIX + self._build
Scott Zawalski65650172012-02-16 11:48:26 -0500812 job_deps.append(cros_label)
813 else:
814 # No pool specified use any machines with the following label.
Chris Masone8b7cd422012-02-22 13:16:11 -0800815 meta_hosts = VERSION_PREFIX + self._build
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500816 test_obj = self._afe.create_job(
Chris Masone6fed6462011-10-20 16:36:43 -0700817 control_file=test.text,
Chris Masone8b7cd422012-02-22 13:16:11 -0800818 name='/'.join([self._build, self._tag, test.name]),
Chris Masone6fed6462011-10-20 16:36:43 -0700819 control_type=test.test_type.capitalize(),
Scott Zawalski65650172012-02-16 11:48:26 -0500820 meta_hosts=[meta_hosts],
Chris Masonebafbbb02012-05-16 13:41:36 -0700821 dependencies=job_deps,
Chris Masoneaa10f8e2012-05-15 13:34:21 -0700822 keyvals={JOB_BUILD_KEY: self._build, JOB_SUITE_KEY: self._tag})
Chris Masone6fed6462011-10-20 16:36:43 -0700823
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500824 setattr(test_obj, 'test_name', test.name)
825
826 return test_obj
827
Chris Masone6fed6462011-10-20 16:36:43 -0700828
Chris Masone275ec902012-07-10 15:28:34 -0700829 def run_and_wait(self, record, manager, add_experimental=True):
Chris Masone6fed6462011-10-20 16:36:43 -0700830 """
831 Synchronously run tests in |self.tests|.
832
Chris Masone8b7cd422012-02-22 13:16:11 -0800833 Schedules tests against a device running image |self._build|, and
Chris Masone6fed6462011-10-20 16:36:43 -0700834 then polls for status, using |record| to print status when each
835 completes.
836
837 Tests returned by self.stable_tests() will always be run, while tests
838 in self.unstable_tests() will only be run if |add_experimental| is true.
839
Chris Masone6fed6462011-10-20 16:36:43 -0700840 @param record: callable that records job status.
841 prototype:
Chris Masone604baf32012-06-28 08:45:30 -0700842 record(base_job.status_log_entry)
Chris Masone6fed6462011-10-20 16:36:43 -0700843 @param add_experimental: schedule experimental tests as well, or not.
844 """
Chris Masoneed356392012-05-08 14:07:13 -0700845 logging.debug('Discovered %d stable tests.', len(self.stable_tests()))
846 logging.debug('Discovered %d unstable tests.',
847 len(self.unstable_tests()))
Chris Masone6fed6462011-10-20 16:36:43 -0700848 try:
Chris Masone604baf32012-06-28 08:45:30 -0700849 Status('INFO', 'Start %s' % self._tag).record_result(record)
Chris Masone8b7cd422012-02-22 13:16:11 -0800850 self.schedule(add_experimental)
Chris Masone275ec902012-07-10 15:28:34 -0700851 # Unlock all hosts, so test jobs can be run on them.
852 manager.unlock()
Chris Masone6fed6462011-10-20 16:36:43 -0700853 try:
Chris Masone8d6e6412012-06-28 11:20:56 -0700854 for result in job_status.wait_for_results(self._afe,
855 self._tko,
856 self._jobs):
Chris Masone604baf32012-06-28 08:45:30 -0700857 result.record_all(record)
858
Chris Masone6fed6462011-10-20 16:36:43 -0700859 except Exception as e:
Chris Masone99378582012-04-30 13:10:58 -0700860 logging.error(traceback.format_exc())
Chris Masone604baf32012-06-28 08:45:30 -0700861 Status('FAIL', self._tag,
Chris Masone99378582012-04-30 13:10:58 -0700862 'Exception waiting for results').record_result(record)
Chris Masone6fed6462011-10-20 16:36:43 -0700863 except Exception as e:
Chris Masone99378582012-04-30 13:10:58 -0700864 logging.error(traceback.format_exc())
Chris Masone604baf32012-06-28 08:45:30 -0700865 Status('FAIL', self._tag,
Chris Masone99378582012-04-30 13:10:58 -0700866 'Exception while scheduling suite').record_result(record)
Chris Masoneed356392012-05-08 14:07:13 -0700867 # Sanity check
868 tests_at_end = self.find_and_parse_tests(self._cf_getter,
869 self._predicate,
870 add_experimental=True)
871 if len(self.tests) != len(tests_at_end):
872 msg = 'Dev Server enumerated %d tests at start, %d at end.' % (
873 len(self.tests), len(tests_at_end))
Chris Masone604baf32012-06-28 08:45:30 -0700874 Status('FAIL', self._tag, msg).record_result(record)
Chris Masone6fed6462011-10-20 16:36:43 -0700875
876
Chris Masone8b7cd422012-02-22 13:16:11 -0800877 def schedule(self, add_experimental=True):
Chris Masone6fed6462011-10-20 16:36:43 -0700878 """
879 Schedule jobs using |self._afe|.
880
881 frontend.Job objects representing each scheduled job will be put in
882 |self._jobs|.
883
Chris Masone6fed6462011-10-20 16:36:43 -0700884 @param add_experimental: schedule experimental tests as well, or not.
885 """
886 for test in self.stable_tests():
887 logging.debug('Scheduling %s', test.name)
Chris Masone8b7cd422012-02-22 13:16:11 -0800888 self._jobs.append(self._create_job(test))
Chris Masone6fed6462011-10-20 16:36:43 -0700889
890 if add_experimental:
Chris Masone6fed6462011-10-20 16:36:43 -0700891 for test in self.unstable_tests():
Zdenek Behan150fbd62012-04-06 17:20:01 +0200892 logging.debug('Scheduling experimental %s', test.name)
Chris Masoneaa10f8e2012-05-15 13:34:21 -0700893 test.name = EXPERIMENTAL_PREFIX + test.name
Chris Masone8b7cd422012-02-22 13:16:11 -0800894 self._jobs.append(self._create_job(test))
Scott Zawalski9ece6532012-02-28 14:10:47 -0500895 if self._results_dir:
896 self._record_scheduled_jobs()
897
898
899 def _record_scheduled_jobs(self):
900 """
901 Record scheduled job ids as keyvals, so they can be referenced later.
Scott Zawalski9ece6532012-02-28 14:10:47 -0500902 """
903 for job in self._jobs:
904 job_id_owner = '%s-%s' % (job.id, job.owner)
Chris Masone11aae452012-05-21 16:08:39 -0700905 utils.write_keyval(
906 self._results_dir,
907 {hashlib.md5(job.test_name).hexdigest(): job_id_owner})
Chris Masone6fed6462011-10-20 16:36:43 -0700908
909
Chris Masonefef21382012-01-17 11:16:32 -0800910 @staticmethod
911 def find_and_parse_tests(cf_getter, predicate, add_experimental=False):
Chris Masone6fed6462011-10-20 16:36:43 -0700912 """
913 Function to scan through all tests and find eligible tests.
914
915 Looks at control files returned by _cf_getter.get_control_file_list()
916 for tests that pass self._predicate().
917
918 @param cf_getter: a control_file_getter.ControlFileGetter used to list
919 and fetch the content of control files
920 @param predicate: a function that should return True when run over a
921 ControlData representation of a control file that should be in
922 this Suite.
923 @param add_experimental: add tests with experimental attribute set.
924
925 @return list of ControlData objects that should be run, with control
926 file text added in |text| attribute.
927 """
928 tests = {}
929 files = cf_getter.get_control_file_list()
Chris Masone75a20612012-05-08 12:37:31 -0700930 matcher = re.compile(r'[^/]+/(deps|profilers)/.+')
931 for file in filter(lambda f: not matcher.match(f), files):
Chris Masoneed356392012-05-08 14:07:13 -0700932 logging.debug('Considering %s', file)
Chris Masone6fed6462011-10-20 16:36:43 -0700933 text = cf_getter.get_control_file_contents(file)
934 try:
Chris Masoneed356392012-05-08 14:07:13 -0700935 found_test = control_data.parse_control_string(
936 text, raise_warnings=True)
Chris Masone6fed6462011-10-20 16:36:43 -0700937 if not add_experimental and found_test.experimental:
938 continue
939
940 found_test.text = text
Chris Masonee8a4eff2012-02-28 16:33:43 -0800941 found_test.path = file
Chris Masone6fed6462011-10-20 16:36:43 -0700942 tests[file] = found_test
943 except control_data.ControlVariableException, e:
944 logging.warn("Skipping %s\n%s", file, e)
945 except Exception, e:
946 logging.error("Bad %s\n%s", file, e)
947
948 return [test for test in tests.itervalues() if predicate(test)]