blob: 81887b3f19c837a611366fe63a0c1468466f2bc9 [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 Millere1a2a292013-08-21 14:15:16 -07005import datetime, 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
Alex Miller7d658cf2013-09-04 16:00:35 -070010from autotest_lib.client.common_lib import priorities
Alex Millere1a2a292013-08-21 14:15:16 -070011from autotest_lib.client.common_lib import site_utils, utils, error
Chris Masone44e4d6c2012-08-15 14:25:53 -070012from autotest_lib.server.cros.dynamic_suite import constants
13from autotest_lib.server.cros.dynamic_suite import control_file_getter
14from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Alex Miller3a69adc2012-12-19 13:38:31 -080015from autotest_lib.server.cros.dynamic_suite import job_status
beeps1ccbbb82013-02-21 20:35:01 -080016from autotest_lib.server.cros.dynamic_suite import reporting
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -070017from autotest_lib.server.cros.dynamic_suite import tools
18from autotest_lib.server.cros.dynamic_suite.job_status import Status
Chris Masone44e4d6c2012-08-15 14:25:53 -070019
20class Suite(object):
21 """
22 A suite of tests, defined by some predicate over control file variables.
23
24 Given a place to search for control files a predicate to match the desired
25 tests, can gather tests and fire off jobs to run them, and then wait for
26 results.
27
28 @var _predicate: a function that should return True when run over a
29 ControlData representation of a control file that should be in
30 this Suite.
31 @var _tag: a string with which to tag jobs run in this suite.
32 @var _build: the build on which we're running this suite.
33 @var _afe: an instance of AFE as defined in server/frontend.py.
34 @var _tko: an instance of TKO as defined in server/frontend.py.
35 @var _jobs: currently scheduled jobs, if any.
36 @var _cf_getter: a control_file_getter.ControlFileGetter
37 """
38
39
40 @staticmethod
Chris Sosaaccb5ce2012-08-30 17:29:15 -070041 def create_ds_getter(build, devserver):
Chris Masone44e4d6c2012-08-15 14:25:53 -070042 """
43 @param build: the build on which we're running this suite.
Chris Sosaaccb5ce2012-08-30 17:29:15 -070044 @param devserver: the devserver which contains the build.
Chris Masone44e4d6c2012-08-15 14:25:53 -070045 @return a FileSystemGetter instance that looks under |autotest_dir|.
46 """
Chris Sosaaccb5ce2012-08-30 17:29:15 -070047 return control_file_getter.DevServerGetter(build, devserver)
Chris Masone44e4d6c2012-08-15 14:25:53 -070048
49
50 @staticmethod
51 def create_fs_getter(autotest_dir):
52 """
53 @param autotest_dir: the place to find autotests.
54 @return a FileSystemGetter instance that looks under |autotest_dir|.
55 """
56 # currently hard-coded places to look for tests.
57 subpaths = ['server/site_tests', 'client/site_tests',
58 'server/tests', 'client/tests']
59 directories = [os.path.join(autotest_dir, p) for p in subpaths]
60 return control_file_getter.FileSystemGetter(directories)
61
62
63 @staticmethod
64 def parse_tag(tag):
Aviv Keshet18308922013-02-19 17:49:49 -080065 """Splits a string on ',' optionally surrounded by whitespace.
66 @param tag: string to split.
67 """
Chris Masone44e4d6c2012-08-15 14:25:53 -070068 return map(lambda x: x.strip(), tag.split(','))
69
70
71 @staticmethod
72 def name_in_tag_predicate(name):
73 """Returns predicate that takes a control file and looks for |name|.
74
75 Builds a predicate that takes in a parsed control file (a ControlData)
76 and returns True if the SUITE tag is present and contains |name|.
77
78 @param name: the suite name to base the predicate on.
79 @return a callable that takes a ControlData and looks for |name| in that
80 ControlData object's suite member.
81 """
82 return lambda t: hasattr(t, 'suite') and \
83 name in Suite.parse_tag(t.suite)
84
85
86 @staticmethod
Aviv Keshet40222a42013-06-04 16:25:49 -070087 def not_in_blacklist_predicate(blacklist):
88 """Returns predicate that takes a control file and looks for its
89 path to not be in given blacklist.
90
91 @param blacklist: A list of strings both paths on control_files that
92 should be blacklisted.
93
94 @return a callable that takes a ControlData and looks for it to be
95 absent from blacklist.
96 """
97 return lambda t: hasattr(t, 'path') and \
98 not any(b.endswith(t.path) for b in blacklist)
99
100
101 @staticmethod
102 def test_name_equals_predicate(test_name):
103 """Returns predicate that matched based on a test's name.
104
105 Builds a predicate that takes in a parsed control file (a ControlData)
106 and returns True if the test name is equal to |test_name|.
107
108 @param test_name: the test name to base the predicate on.
109 @return a callable that takes a ControlData and looks for |test_name|
110 in that ControlData's name.
111 """
112 return lambda t: hasattr(t, 'name') and test_name == t.name
113
114
115 @staticmethod
Aviv Kesheta6adc7a2013-08-30 11:13:38 -0700116 def test_name_matches_pattern_predicate(test_name_pattern):
117 """Returns predicate that matches based on a test's name pattern.
118
119 Builds a predicate that takes in a parsed control file (a ControlData)
120 and returns True if the test name matches the given regular expression.
121
122 @param test_name_pattern: regular expression (string) to match against
123 test names.
124 @return a callable that takes a ControlData and returns
125 True if the name fields matches the pattern.
126 """
127 return lambda t: hasattr(t, 'name') and re.match(test_name_pattern,
128 t.name)
129
130
131 @staticmethod
132 def test_file_matches_pattern_predicate(test_file_pattern):
133 """Returns predicate that matches based on a test's file name pattern.
134
135 Builds a predicate that takes in a parsed control file (a ControlData)
136 and returns True if the test's control file name matches the given
137 regular expression.
138
139 @param test_file_pattern: regular expression (string) to match against
140 control file names.
141 @return a callable that takes a ControlData and and returns
142 True if control file name matches the pattern.
143 """
144 return lambda t: hasattr(t, 'path') and re.match(test_file_pattern,
145 t.path)
146
147
148 @staticmethod
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700149 def list_all_suites(build, devserver, cf_getter=None):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700150 """
151 Parses all ControlData objects with a SUITE tag and extracts all
152 defined suite names.
153
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700154 @param build: the build on which we're running this suite.
155 @param devserver: the devserver which contains the build.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700156 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
157 using DevServerGetter.
158
159 @return list of suites
160 """
161 if cf_getter is None:
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700162 cf_getter = Suite.create_ds_getter(build, devserver)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700163
164 suites = set()
165 predicate = lambda t: hasattr(t, 'suite')
166 for test in Suite.find_and_parse_tests(cf_getter, predicate,
167 add_experimental=True):
168 suites.update(Suite.parse_tag(test.suite))
169 return list(suites)
170
171
172 @staticmethod
Alex Millera0913072013-06-12 10:01:51 -0700173 def create_from_predicates(predicates, build, board, devserver,
174 cf_getter=None, name='ad_hoc_suite', **dargs):
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700175 """
176 Create a Suite using a given predicate test filters.
177
178 Uses supplied predicate(s) to instantiate a Suite. Looks for tests in
179 |autotest_dir| and will schedule them using |afe|. Pulls control files
180 from the default dev server. Results will be pulled from |tko| upon
181 completion.
182
183 @param predicates: A list of callables that accept ControlData
184 representations of control files. A test will be
Aviv Keshet938a6772013-07-25 14:05:45 -0700185 included in suite if all callables in this list
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700186 return True on the given control file.
187 @param build: the build on which we're running this suite.
Alex Millera0913072013-06-12 10:01:51 -0700188 @param board: the board on which we're running this suite.
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700189 @param devserver: the devserver which contains the build.
190 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
191 using DevServerGetter.
192 @param name: name of suite. Defaults to 'ad_hoc_suite'
193 @param **dargs: Any other Suite constructor parameters, as described
194 in Suite.__init__ docstring.
195 @return a Suite instance.
196 """
197 if cf_getter is None:
198 cf_getter = Suite.create_ds_getter(build, devserver)
199
200 return Suite(predicates,
Alex Millera0913072013-06-12 10:01:51 -0700201 name, build, board, cf_getter, **dargs)
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700202
203
204 @staticmethod
Alex Millera0913072013-06-12 10:01:51 -0700205 def create_from_name(name, build, board, devserver, cf_getter=None,
206 **dargs):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700207 """
208 Create a Suite using a predicate based on the SUITE control file var.
209
210 Makes a predicate based on |name| and uses it to instantiate a Suite
211 that looks for tests in |autotest_dir| and will schedule them using
212 |afe|. Pulls control files from the default dev server.
213 Results will be pulled from |tko| upon completion.
214
215 @param name: a value of the SUITE control file variable to search for.
216 @param build: the build on which we're running this suite.
Alex Millera0913072013-06-12 10:01:51 -0700217 @param board: the board on which we're running this suite.
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700218 @param devserver: the devserver which contains the build.
Aviv Keshet813d6782013-06-04 17:11:03 -0700219 @param cf_getter: control_file_getter.ControlFileGetter. Defaults to
220 using DevServerGetter.
221 @param **dargs: Any other Suite constructor parameters, as described
222 in Suite.__init__ docstring.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700223 @return a Suite instance.
224 """
225 if cf_getter is None:
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700226 cf_getter = Suite.create_ds_getter(build, devserver)
227
Aviv Keshet40222a42013-06-04 16:25:49 -0700228 return Suite([Suite.name_in_tag_predicate(name)],
Alex Millera0913072013-06-12 10:01:51 -0700229 name, build, board, cf_getter, **dargs)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700230
231
Alex Millera0913072013-06-12 10:01:51 -0700232 def __init__(self, predicates, tag, build, board, cf_getter, afe=None,
233 tko=None, pool=None, results_dir=None, max_runtime_mins=24*60,
Simran Basi8705d672013-11-19 15:56:58 -0800234 timeout_mins=24*60, file_bugs=False,
235 file_experimental_bugs=False, suite_job_id=None,
236 ignore_deps=False, extra_deps=[],
Dan Shi95122412013-11-12 16:20:33 -0800237 priority=priorities.Priority.DEFAULT, forgiving_parser=True,
238 wait_for_results=True):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700239 """
240 Constructor
241
Aviv Keshet40222a42013-06-04 16:25:49 -0700242 @param predicates: A list of callables that accept ControlData
243 representations of control files. A test will be
244 included in suite is all callables in this list
245 return True on the given control file.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700246 @param tag: a string with which to tag jobs run in this suite.
247 @param build: the build on which we're running this suite.
Alex Millera0913072013-06-12 10:01:51 -0700248 @param board: the board on which we're running this suite.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700249 @param cf_getter: a control_file_getter.ControlFileGetter
250 @param afe: an instance of AFE as defined in server/frontend.py.
251 @param tko: an instance of TKO as defined in server/frontend.py.
252 @param pool: Specify the pool of machines to use for scheduling
253 purposes.
254 @param results_dir: The directory where the job can write results to.
255 This must be set if you want job_id of sub-jobs
256 list in the job keyvals.
Aviv Keshet18308922013-02-19 17:49:49 -0800257 @param max_runtime_mins: Maximum suite runtime, in minutes.
Alex Miller028b0312013-09-07 15:25:45 -0700258 @param timeout: Maximum job lifetime, in hours.
Aviv Keshet18308922013-02-19 17:49:49 -0800259 @param suite_job_id: Job id that will act as parent id to all sub jobs.
260 Default: None
Aviv Keshetd7959f32013-05-17 15:58:43 -0700261 @param ignore_deps: True if jobs should ignore the DEPENDENCIES
262 attribute and skip applying of dependency labels.
263 (Default:False)
Alex Miller47a03672013-08-27 09:09:53 -0700264 @param extra_deps: A list of strings which are the extra DEPENDENCIES
265 to add to each test being scheduled.
Alex Miller7d658cf2013-09-04 16:00:35 -0700266 @param priority: Integer priority level. Higher is more important.
Dan Shi95122412013-11-12 16:20:33 -0800267 @param wait_for_results: Set to False to run the suite job without
268 waiting for test jobs to finish. Default is
269 True.
Alex Miller7d658cf2013-09-04 16:00:35 -0700270
Chris Masone44e4d6c2012-08-15 14:25:53 -0700271 """
Aviv Keshet40222a42013-06-04 16:25:49 -0700272 def combined_predicate(test):
273 #pylint: disable-msg=C0111
274 return all((f(test) for f in predicates))
275 self._predicate = combined_predicate
276
Chris Masone44e4d6c2012-08-15 14:25:53 -0700277 self._tag = tag
278 self._build = build
Alex Millera0913072013-06-12 10:01:51 -0700279 self._board = board
Chris Masone44e4d6c2012-08-15 14:25:53 -0700280 self._cf_getter = cf_getter
281 self._results_dir = results_dir
282 self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
283 delay_sec=10,
284 debug=False)
285 self._tko = tko or frontend_wrappers.RetryingTKO(timeout_min=30,
286 delay_sec=10,
287 debug=False)
288 self._pool = pool
289 self._jobs = []
290 self._tests = Suite.find_and_parse_tests(self._cf_getter,
beeps89f1e062013-09-18 12:00:17 -0700291 self._predicate, self._tag, add_experimental=True,
292 forgiving_parser=forgiving_parser)
293
Simran Basic68cda42012-11-19 17:03:18 -0800294 self._max_runtime_mins = max_runtime_mins
Simran Basi8705d672013-11-19 15:56:58 -0800295 self._timeout_mins = timeout_mins
Alex Millera3a4fe72013-01-22 09:57:47 -0800296 self._file_bugs = file_bugs
beepsda5b7112013-05-30 11:34:14 -0700297 self._file_experimental_bugs = file_experimental_bugs
Aviv Keshet18308922013-02-19 17:49:49 -0800298 self._suite_job_id = suite_job_id
Aviv Keshetd7959f32013-05-17 15:58:43 -0700299 self._ignore_deps = ignore_deps
Alex Miller47a03672013-08-27 09:09:53 -0700300 self._extra_deps = extra_deps
Alex Miller7d658cf2013-09-04 16:00:35 -0700301 self._priority = priority
Dan Shi95122412013-11-12 16:20:33 -0800302 self.wait_for_results = wait_for_results
Alex Millera3a4fe72013-01-22 09:57:47 -0800303
Chris Masone44e4d6c2012-08-15 14:25:53 -0700304
305 @property
306 def tests(self):
307 """
308 A list of ControlData objects in the suite, with added |text| attr.
309 """
310 return self._tests
311
312
313 def stable_tests(self):
314 """
315 |self.tests|, filtered for non-experimental tests.
316 """
317 return filter(lambda t: not t.experimental, self.tests)
318
319
320 def unstable_tests(self):
321 """
322 |self.tests|, filtered for experimental tests.
323 """
324 return filter(lambda t: t.experimental, self.tests)
325
326
327 def _create_job(self, test):
328 """
329 Thin wrapper around frontend.AFE.create_job().
330
331 @param test: ControlData object for a test to run.
332 @return a frontend.Job object with an added test_name member.
333 test_name is used to preserve the higher level TEST_NAME
334 name of the job.
335 """
Aviv Keshetd7959f32013-05-17 15:58:43 -0700336 if self._ignore_deps:
337 job_deps = []
338 else:
339 job_deps = list(test.dependencies)
Alex Miller47a03672013-08-27 09:09:53 -0700340 if self._extra_deps:
341 job_deps.extend(self._extra_deps)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700342 if self._pool:
Alex Millera0913072013-06-12 10:01:51 -0700343 job_deps.append(self._pool)
344
beeps7d8273b2013-11-06 09:44:34 -0800345 # TODO(beeps): Comletely remove the concept of a metahost.
346 # Currently we use this to distinguis a job scheduled through
347 # the afe from a suite job, as only the latter will get requeued
348 # when a special task fails.
349 job_deps.append(self._board)
350
Chris Masone44e4d6c2012-08-15 14:25:53 -0700351 test_obj = self._afe.create_job(
352 control_file=test.text,
Dan Shi605f7642013-11-04 16:32:54 -0800353 name=tools.create_job_name(self._build, self._tag, test.name),
Chris Masone44e4d6c2012-08-15 14:25:53 -0700354 control_type=test.test_type.capitalize(),
Dan Shi6eedadb2013-12-04 15:38:01 -0800355 meta_hosts=[self._board]*test.sync_count,
Chris Masone44e4d6c2012-08-15 14:25:53 -0700356 dependencies=job_deps,
357 keyvals={constants.JOB_BUILD_KEY: self._build,
Fang Deng2db96762013-10-03 16:45:31 -0700358 constants.JOB_SUITE_KEY: self._tag,
359 constants.JOB_EXPERIMENTAL_KEY: test.experimental},
Aviv Keshet18308922013-02-19 17:49:49 -0800360 max_runtime_mins=self._max_runtime_mins,
Simran Basi8705d672013-11-19 15:56:58 -0800361 timeout_mins=self._timeout_mins,
Aviv Keshet6f455262013-03-01 16:02:29 -0800362 parent_job_id=self._suite_job_id,
Alex Miller7d658cf2013-09-04 16:00:35 -0700363 test_retry=test.retries,
Dan Shi6eedadb2013-12-04 15:38:01 -0800364 priority=self._priority,
365 synch_count=test.sync_count)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700366
367 setattr(test_obj, 'test_name', test.name)
368
369 return test_obj
370
371
Alex Miller3a69adc2012-12-19 13:38:31 -0800372 def schedule(self, record, add_experimental=True):
Aviv Keshet18308922013-02-19 17:49:49 -0800373 #pylint: disable-msg=C0111
Chris Masone44e4d6c2012-08-15 14:25:53 -0700374 """
375 Schedule jobs using |self._afe|.
376
377 frontend.Job objects representing each scheduled job will be put in
378 |self._jobs|.
379
380 @param add_experimental: schedule experimental tests as well, or not.
Aviv Keshete9170d92013-07-19 11:20:45 -0700381 @returns: The number of tests that were scheduled.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700382 """
Alex Miller3a69adc2012-12-19 13:38:31 -0800383 logging.debug('Discovered %d stable tests.', len(self.stable_tests()))
384 logging.debug('Discovered %d unstable tests.',
385 len(self.unstable_tests()))
Aviv Keshete9170d92013-07-19 11:20:45 -0700386 n_scheduled = 0
Chris Masone44e4d6c2012-08-15 14:25:53 -0700387
Alex Millere1a2a292013-08-21 14:15:16 -0700388 begin_time_str = datetime.datetime.now().strftime(job_status.TIME_FMT)
Alex Miller3a69adc2012-12-19 13:38:31 -0800389 Status('INFO', 'Start %s' % self._tag).record_result(record)
390 try:
Alex Millere1a2a292013-08-21 14:15:16 -0700391 tests = self.stable_tests()
Alex Miller3a69adc2012-12-19 13:38:31 -0800392 if add_experimental:
393 for test in self.unstable_tests():
Alex Miller3a69adc2012-12-19 13:38:31 -0800394 test.name = constants.EXPERIMENTAL_PREFIX + test.name
Alex Millere1a2a292013-08-21 14:15:16 -0700395 tests.append(test)
396
397 for test in tests:
398 logging.debug('Scheduling %s', test.name)
399 try:
400 job = self._create_job(test)
401 except error.NoEligibleHostException:
402 logging.debug('%s not applicable for this board/pool. '
403 'Emitting TEST_NA.', test.name)
404 Status('TEST_NA', test.name, 'Unsatisfiable DEPENDENCIES',
405 begin_time_str=begin_time_str).record_all(record)
406 else:
407 self._jobs.append(job)
Aviv Keshete9170d92013-07-19 11:20:45 -0700408 n_scheduled += 1
Alex Miller3a69adc2012-12-19 13:38:31 -0800409
410 if self._results_dir:
411 self._remember_scheduled_job_ids()
412 except Exception: # pylint: disable=W0703
413 logging.error(traceback.format_exc())
414 Status('FAIL', self._tag,
415 'Exception while scheduling suite').record_result(record)
416
Aviv Keshete9170d92013-07-19 11:20:45 -0700417 return n_scheduled
418
Alex Miller3a69adc2012-12-19 13:38:31 -0800419
beepsda5b7112013-05-30 11:34:14 -0700420 def should_file_bug(self, result):
421 """
422 Returns True if this failure requires a bug.
423
424 @param result: A result, encapsulating the status of the failed job.
425 @return: True if we should file bugs for this failure.
426 """
beepsbeefc062013-08-02 11:17:09 -0700427 is_not_experimental = (
428 constants.EXPERIMENTAL_PREFIX not in result._test_name and
429 constants.EXPERIMENTAL_PREFIX not in result._job_name)
430
Alex Millerfcc119b2014-01-15 13:54:58 -0800431 return (self._file_bugs and result.test_executed and
beepsbeefc062013-08-02 11:17:09 -0700432 (is_not_experimental or self._file_experimental_bugs) and
beepsda5b7112013-05-30 11:34:14 -0700433 result.is_worse_than(job_status.Status('WARN', '', 'reason')))
434
435
beepsc8a875b2013-03-25 10:20:38 -0700436 def wait(self, record, bug_template={}):
Alex Miller3a69adc2012-12-19 13:38:31 -0800437 """
438 Polls for the job statuses, using |record| to print status when each
439 completes.
440
441 @param record: callable that records job status.
442 prototype:
443 record(base_job.status_log_entry)
beepsc8a875b2013-03-25 10:20:38 -0700444 @param bug_template: A template dictionary specifying the default bug
445 filing options for failures in this suite.
Alex Miller3a69adc2012-12-19 13:38:31 -0800446 """
Alex Millera3a4fe72013-01-22 09:57:47 -0800447 if self._file_bugs:
448 bug_reporter = reporting.Reporter()
Alex Miller3a69adc2012-12-19 13:38:31 -0800449 try:
Aviv Keshet133beb12013-08-20 14:37:13 -0700450 if self._suite_job_id:
451 results_generator = job_status.wait_for_child_results(
452 self._afe, self._tko, self._suite_job_id)
453 else:
454 logging.warn('Unknown suite_job_id, falling back to less '
455 'efficient results_generator.')
456 results_generator = job_status.wait_for_results(self._afe,
457 self._tko,
458 self._jobs)
459 for result in results_generator:
Alex Miller3a69adc2012-12-19 13:38:31 -0800460 result.record_all(record)
461 if (self._results_dir and
462 job_status.is_for_infrastructure_fail(result)):
463 self._remember_provided_job_id(result)
Dan Shid1521802013-05-24 13:08:37 -0700464 elif (self._results_dir and isinstance(result, Status)):
465 self._remember_test_status_job_id(result)
Alex Millera3a4fe72013-01-22 09:57:47 -0800466
beepsda5b7112013-05-30 11:34:14 -0700467 if self.should_file_bug(result):
beepsc4fb1472013-05-08 21:49:48 -0700468 job_views = self._tko.run('get_detailed_test_views',
469 afe_job_id=result.id)
470
beepsc208bf32013-04-16 14:36:35 -0700471 failure = reporting.TestFailure(self._build,
beepsc4fb1472013-05-08 21:49:48 -0700472 site_utils.get_chrome_version(job_views),
473 self._tag,
474 result)
beeps1ccbbb82013-02-21 20:35:01 -0800475
beeps66377332013-11-04 10:26:53 -0800476 bug_id, bug_count = bug_reporter.report(failure,
477 bug_template)
478
479 # We use keyvals to communicate bugs filed with run_suite.
480 if bug_id is not None:
481 bug_keyvals = tools.create_bug_keyvals(
482 result.test_name, (bug_id, bug_count))
483 try:
484 utils.write_keyval(self._results_dir, bug_keyvals)
485 except ValueError:
486 logging.error('Unable to log bug keyval for:%s ',
487 result.test_name)
beeps8ead53c2013-04-26 19:12:46 -0700488
Alex Miller3a69adc2012-12-19 13:38:31 -0800489 except Exception: # pylint: disable=W0703
490 logging.error(traceback.format_exc())
491 Status('FAIL', self._tag,
492 'Exception waiting for results').record_result(record)
493
494
495 def abort(self):
496 """
497 Abort all scheduled test jobs.
498 """
499 if self._jobs:
500 job_ids = [job.id for job in self._jobs]
501 self._afe.run('abort_host_queue_entries', job__id__in=job_ids)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700502
503
Chris Masoned9f13c52012-08-29 10:37:08 -0700504 def _remember_scheduled_job_ids(self):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700505 """
506 Record scheduled job ids as keyvals, so they can be referenced later.
507 """
508 for job in self._jobs:
Chris Masoned9f13c52012-08-29 10:37:08 -0700509 self._remember_provided_job_id(job)
510
511
512 def _remember_provided_job_id(self, job):
513 """
514 Record provided job as a suite job keyval, for later referencing.
515
516 @param job: some representation of a job, including id, test_name
517 and owner
518 """
519 if job.id and job.owner and job.test_name:
Chris Masone44e4d6c2012-08-15 14:25:53 -0700520 job_id_owner = '%s-%s' % (job.id, job.owner)
Chris Masoned9f13c52012-08-29 10:37:08 -0700521 logging.debug('Adding job keyval for %s=%s',
Chris Sosaaccb5ce2012-08-30 17:29:15 -0700522 job.test_name, job_id_owner)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700523 utils.write_keyval(
524 self._results_dir,
525 {hashlib.md5(job.test_name).hexdigest(): job_id_owner})
526
527
Dan Shid1521802013-05-24 13:08:37 -0700528 def _remember_test_status_job_id(self, status):
529 """
530 Record provided status as a test status keyval, for later referencing.
531
532 @param status: Test status, including properties such as id, test_name
533 and owner.
534 """
535 if status.id and status.owner and status.test_name:
536 test_id_owner = '%s-%s' % (status.id, status.owner)
537 logging.debug('Adding status keyval for %s=%s',
538 status.test_name, test_id_owner)
539 utils.write_keyval(
540 self._results_dir,
541 {hashlib.md5(status.test_name).hexdigest(): test_id_owner})
542
543
Chris Masone44e4d6c2012-08-15 14:25:53 -0700544 @staticmethod
beepsc594c1c2013-07-09 22:33:18 -0700545 def find_and_parse_tests(cf_getter, predicate, suite_name='',
beeps89f1e062013-09-18 12:00:17 -0700546 add_experimental=False, forgiving_parser=True):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700547 """
548 Function to scan through all tests and find eligible tests.
549
550 Looks at control files returned by _cf_getter.get_control_file_list()
beepsc594c1c2013-07-09 22:33:18 -0700551 for tests that pass self._predicate(). When this method is called
552 with a file system ControlFileGetter, it performs a full parse of the
553 root directory associated with the getter. This is the case when it's
554 invoked from suite_preprocessor. When it's invoked with a devserver
555 getter it looks up the suite_name in a suite to control file map
556 generated at build time, and parses the relevant control files alone.
557 This lookup happens on the devserver, so as far as this method is
558 concerned, both cases are equivalent.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700559
560 @param cf_getter: a control_file_getter.ControlFileGetter used to list
561 and fetch the content of control files
562 @param predicate: a function that should return True when run over a
563 ControlData representation of a control file that should be in
564 this Suite.
beepsc594c1c2013-07-09 22:33:18 -0700565 @param suite_name: If specified, this method will attempt to restrain
566 the search space to just this suite's control files.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700567 @param add_experimental: add tests with experimental attribute set.
beeps89f1e062013-09-18 12:00:17 -0700568 @param forgiving_parser: If False, will raise ControlVariableExceptions
569 if any are encountered when parsing control
570 files. Note that this can raise an exception
571 for syntax errors in unrelated files, because
572 we parse them before applying the predicate.
573
574 @raises ControlVariableException: If forgiving_parser is False and there
575 is a syntax error in a control file.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700576
577 @return list of ControlData objects that should be run, with control
Dan Shief5b53f2013-01-22 10:22:01 -0800578 file text added in |text| attribute. Results are sorted based
579 on the TIME setting in control file, slowest test comes first.
Chris Masone44e4d6c2012-08-15 14:25:53 -0700580 """
581 tests = {}
beepsc594c1c2013-07-09 22:33:18 -0700582 files = cf_getter.get_control_file_list(suite_name=suite_name)
583
Chris Masone44e4d6c2012-08-15 14:25:53 -0700584 matcher = re.compile(r'[^/]+/(deps|profilers)/.+')
Aviv Keshete8765c02013-06-04 14:33:43 -0700585 parsed_count = 0
Chris Masone44e4d6c2012-08-15 14:25:53 -0700586 for file in filter(lambda f: not matcher.match(f), files):
Chris Masone44e4d6c2012-08-15 14:25:53 -0700587 text = cf_getter.get_control_file_contents(file)
588 try:
589 found_test = control_data.parse_control_string(
590 text, raise_warnings=True)
Aviv Keshete8765c02013-06-04 14:33:43 -0700591 parsed_count += 1
Chris Masone44e4d6c2012-08-15 14:25:53 -0700592 if not add_experimental and found_test.experimental:
593 continue
Chris Masone44e4d6c2012-08-15 14:25:53 -0700594 found_test.text = text
595 found_test.path = file
596 tests[file] = found_test
597 except control_data.ControlVariableException, e:
beeps89f1e062013-09-18 12:00:17 -0700598 if not forgiving_parser:
beeps389188b2013-12-06 13:03:38 -0800599 msg = "Failed parsing %s\n%s" % (file, e)
600 raise control_data.ControlVariableException(msg)
Chris Masone44e4d6c2012-08-15 14:25:53 -0700601 logging.warn("Skipping %s\n%s", file, e)
602 except Exception, e:
603 logging.error("Bad %s\n%s", file, e)
Aviv Keshete8765c02013-06-04 14:33:43 -0700604 logging.debug('Parsed %s control files.', parsed_count)
Dan Shief5b53f2013-01-22 10:22:01 -0800605 tests = [test for test in tests.itervalues() if predicate(test)]
606 tests.sort(key=lambda t:
607 control_data.ControlData.get_test_time_index(t.time),
608 reverse=True)
609 return tests