blob: 311f6788ba6f35c950f1e82420912aa1209c742b [file] [log] [blame]
Chris Masone6fed6462011-10-20 16:36:43 -07001#!/usr/bin/python
2#
Chris Masoneab3e7332012-02-29 18:54:58 -08003# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Masone6fed6462011-10-20 16:36:43 -07004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Unit tests for server/cros/dynamic_suite.py."""
8
9import logging
10import mox
Chris Masoned368cc42012-03-07 15:16:59 -080011import random
Chris Masone6fed6462011-10-20 16:36:43 -070012import shutil
13import tempfile
14import time
15import unittest
16
Chris Masone5552dd72012-02-15 15:01:04 -080017from autotest_lib.client.common_lib import base_job, control_data, global_config
Chris Masone6fed6462011-10-20 16:36:43 -070018from autotest_lib.server.cros import control_file_getter, dynamic_suite
19from autotest_lib.server import frontend
20
21class FakeJob(object):
22 """Faked out RPC-client-side Job object."""
23 def __init__(self, id=0, statuses=[]):
24 self.id = id
Chris Masoned368cc42012-03-07 15:16:59 -080025 self.hostname = 'host%d' % id
Scott Zawalskie5bb1c52012-02-29 13:15:50 -050026 self.owner = 'tester'
Chris Masone6fed6462011-10-20 16:36:43 -070027 self.name = 'Fake Job %d' % self.id
28 self.statuses = statuses
29
30
Chris Masone5374c672012-03-05 15:11:39 -080031class FakeHost(object):
32 """Faked out RPC-client-side Host object."""
33 def __init__(self, status='Ready'):
34 self.status = status
35
Chris Masoned368cc42012-03-07 15:16:59 -080036class FakeLabel(object):
37 """Faked out RPC-client-side Label object."""
38 def __init__(self, id=0):
39 self.id = id
40
Chris Masone5374c672012-03-05 15:11:39 -080041
Chris Masoneab3e7332012-02-29 18:54:58 -080042class DynamicSuiteTest(mox.MoxTestBase):
43 """Unit tests for dynamic_suite module methods.
44
45 @var _DARGS: default args to vet.
46 """
47
48
49 def setUp(self):
50 super(DynamicSuiteTest, self).setUp()
51 self._DARGS = {'name': 'name',
52 'build': 'build',
53 'board': 'board',
54 'job': self.mox.CreateMock(base_job.base_job),
55 'num': 1,
56 'pool': 'pool',
57 'skip_reimage': True,
58 'add_experimental': False}
59
60
61 def testVetRequiredReimageAndRunArgs(self):
62 """Should verify only that required args are present and correct."""
63 build, board, name, job, _, _, _, _ = \
64 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
65 self.assertEquals(build, self._DARGS['build'])
66 self.assertEquals(board, self._DARGS['board'])
67 self.assertEquals(name, self._DARGS['name'])
68 self.assertEquals(job, self._DARGS['job'])
69
70
71 def testVetReimageAndRunBuildArgFail(self):
72 """Should fail verification because |build| arg is bad."""
73 self._DARGS['build'] = None
74 self.assertRaises(dynamic_suite.SuiteArgumentException,
75 dynamic_suite._vet_reimage_and_run_args,
76 **self._DARGS)
77
78
79 def testVetReimageAndRunBoardArgFail(self):
80 """Should fail verification because |board| arg is bad."""
81 self._DARGS['board'] = None
82 self.assertRaises(dynamic_suite.SuiteArgumentException,
83 dynamic_suite._vet_reimage_and_run_args,
84 **self._DARGS)
85
86
87 def testVetReimageAndRunNameArgFail(self):
88 """Should fail verification because |name| arg is bad."""
89 self._DARGS['name'] = None
90 self.assertRaises(dynamic_suite.SuiteArgumentException,
91 dynamic_suite._vet_reimage_and_run_args,
92 **self._DARGS)
93
94
95 def testVetReimageAndRunJobArgFail(self):
96 """Should fail verification because |job| arg is bad."""
97 self._DARGS['job'] = None
98 self.assertRaises(dynamic_suite.SuiteArgumentException,
99 dynamic_suite._vet_reimage_and_run_args,
100 **self._DARGS)
101
102
103 def testOverrideOptionalReimageAndRunArgs(self):
104 """Should verify that optional args can be overridden."""
105 _, _, _, _, pool, num, skip, expr = \
106 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
107 self.assertEquals(pool, self._DARGS['pool'])
108 self.assertEquals(num, self._DARGS['num'])
109 self.assertEquals(skip, self._DARGS['skip_reimage'])
110 self.assertEquals(expr, self._DARGS['add_experimental'])
111
112
113 def testDefaultOptionalReimageAndRunArgs(self):
114 """Should verify that optional args get defaults."""
115 del(self._DARGS['pool'])
116 del(self._DARGS['skip_reimage'])
117 del(self._DARGS['add_experimental'])
118 del(self._DARGS['num'])
119 _, _, _, _, pool, num, skip, expr = \
120 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
121 self.assertEquals(pool, None)
122 self.assertEquals(num, None)
123 self.assertEquals(skip, False)
124 self.assertEquals(expr, True)
125
126
Chris Masone6fed6462011-10-20 16:36:43 -0700127class ReimagerTest(mox.MoxTestBase):
128 """Unit tests for dynamic_suite.Reimager.
129
130 @var _URL: fake image url
Chris Masone8b7cd422012-02-22 13:16:11 -0800131 @var _BUILD: fake build
Chris Masone6fed6462011-10-20 16:36:43 -0700132 @var _NUM: fake number of machines to run on
133 @var _BOARD: fake board to reimage
134 """
135
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800136 _URL = 'http://nothing/%s'
Chris Masone8b7cd422012-02-22 13:16:11 -0800137 _BUILD = 'build'
Chris Masone6fed6462011-10-20 16:36:43 -0700138 _NUM = 4
139 _BOARD = 'board'
Chris Masone5552dd72012-02-15 15:01:04 -0800140 _CONFIG = global_config.global_config
Chris Masone6fed6462011-10-20 16:36:43 -0700141
142
143 def setUp(self):
144 super(ReimagerTest, self).setUp()
145 self.afe = self.mox.CreateMock(frontend.AFE)
146 self.tko = self.mox.CreateMock(frontend.TKO)
147 self.reimager = dynamic_suite.Reimager('', afe=self.afe, tko=self.tko)
Chris Masone5552dd72012-02-15 15:01:04 -0800148 self._CONFIG.override_config_value('CROS',
149 'sharding_factor',
150 "%d" % self._NUM)
Chris Masone6fed6462011-10-20 16:36:43 -0700151
152
153 def testEnsureVersionLabelAlreadyExists(self):
154 """Should not create a label if it already exists."""
155 name = 'label'
156 self.afe.get_labels(name=name).AndReturn([name])
157 self.mox.ReplayAll()
158 self.reimager._ensure_version_label(name)
159
160
161 def testEnsureVersionLabel(self):
162 """Should create a label if it doesn't already exist."""
163 name = 'label'
164 self.afe.get_labels(name=name).AndReturn([])
165 self.afe.create_label(name=name)
166 self.mox.ReplayAll()
167 self.reimager._ensure_version_label(name)
168
169
Chris Masone5374c672012-03-05 15:11:39 -0800170 def testCountHostsByBoardAndPool(self):
171 """Should count available hosts by board and pool."""
172 spec = [self._BOARD, 'pool:bvt']
173 self.afe.get_hosts(multiple_labels=spec).AndReturn([FakeHost()])
174 self.mox.ReplayAll()
175 self.assertEquals(self.reimager._count_usable_hosts(spec), 1)
176
177
178 def testCountHostsByBoard(self):
179 """Should count available hosts by board."""
180 spec = [self._BOARD]
181 self.afe.get_hosts(multiple_labels=spec).AndReturn([FakeHost()] * 2)
182 self.mox.ReplayAll()
183 self.assertEquals(self.reimager._count_usable_hosts(spec), 2)
184
185
186 def testCountZeroHostsByBoard(self):
187 """Should count the available hosts, by board, getting zero."""
188 spec = [self._BOARD]
189 self.afe.get_hosts(multiple_labels=spec).AndReturn([])
190 self.mox.ReplayAll()
191 self.assertEquals(self.reimager._count_usable_hosts(spec), 0)
192
193
Chris Masone6fed6462011-10-20 16:36:43 -0700194 def testInjectVars(self):
195 """Should inject dict of varibles into provided strings."""
196 def find_all_in(d, s):
197 """Returns true if all key-value pairs in |d| are printed in |s|."""
Chris Masone6cb0d0d2012-03-05 15:37:49 -0800198 for k,v in d.iteritems():
199 if isinstance(v, str):
200 if "%s='%s'\n" % (k,v) not in s:
201 return False
202 else:
203 if "%s=%r\n" % (k,v) not in s:
204 return False
205 return True
Chris Masone6fed6462011-10-20 16:36:43 -0700206
Chris Masone6cb0d0d2012-03-05 15:37:49 -0800207 v = {'v1': 'one', 'v2': 'two', 'v3': None, 'v4': False, 'v5': 5}
Chris Masone8b764252012-01-17 11:12:51 -0800208 self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, '')))
209 self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, 'ctrl')))
Chris Masone6fed6462011-10-20 16:36:43 -0700210
211
212 def testReportResultsGood(self):
213 """Should report results in the case where all jobs passed."""
214 job = self.mox.CreateMock(frontend.Job)
215 job.name = 'RPC Client job'
216 job.result = True
217 recorder = self.mox.CreateMock(base_job.base_job)
218 recorder.record('GOOD', mox.IgnoreArg(), job.name)
219 self.mox.ReplayAll()
220 self.reimager._report_results(job, recorder.record)
221
222
223 def testReportResultsBad(self):
224 """Should report results in various job failure cases.
225
226 In this test scenario, there are five hosts, all the 'netbook' platform.
227
228 h1: Did not run
229 h2: Two failed tests
230 h3: Two aborted tests
231 h4: completed, GOOD
232 h5: completed, GOOD
233 """
234 H1 = 'host1'
235 H2 = 'host2'
236 H3 = 'host3'
237 H4 = 'host4'
238 H5 = 'host5'
239
240 class FakeResult(object):
241 def __init__(self, reason):
242 self.reason = reason
243
244
245 # The RPC-client-side Job object that is annotated with results.
246 job = FakeJob()
247 job.result = None # job failed, there are results to report.
248
249 # The semantics of |results_platform_map| and |test_results| are
250 # drawn from frontend.AFE.poll_all_jobs()
251 job.results_platform_map = {'netbook': {'Aborted' : [H3],
Chris Masone8b7cd422012-02-22 13:16:11 -0800252 'Completed' : [H1, H4, H5],
253 'Failed': [H2]
Chris Masone6fed6462011-10-20 16:36:43 -0700254 }
255 }
256 # Gin up fake results for H2 and H3 failure cases.
257 h2 = frontend.TestResults()
258 h2.fail = [FakeResult('a'), FakeResult('b')]
259 h3 = frontend.TestResults()
260 h3.fail = [FakeResult('a'), FakeResult('b')]
261 # Skipping H1 in |test_status| dict means that it did not get run.
262 job.test_status = {H2: h2, H3: h3, H4: {}, H5: {}}
263
264 # Set up recording expectations.
265 rjob = self.mox.CreateMock(base_job.base_job)
266 for res in h2.fail:
267 rjob.record('FAIL', mox.IgnoreArg(), H2, res.reason).InAnyOrder()
268 for res in h3.fail:
269 rjob.record('ABORT', mox.IgnoreArg(), H3, res.reason).InAnyOrder()
270 rjob.record('GOOD', mox.IgnoreArg(), H4).InAnyOrder()
271 rjob.record('GOOD', mox.IgnoreArg(), H5).InAnyOrder()
272 rjob.record(
273 'ERROR', mox.IgnoreArg(), H1, mox.IgnoreArg()).InAnyOrder()
274
275 self.mox.ReplayAll()
276 self.reimager._report_results(job, rjob.record)
277
278
279 def testScheduleJob(self):
280 """Should be able to create a job with the AFE."""
281 # Fake out getting the autoupdate control file contents.
282 cf_getter = self.mox.CreateMock(control_file_getter.ControlFileGetter)
283 cf_getter.get_control_file_contents_by_name('autoupdate').AndReturn('')
284 self.reimager._cf_getter = cf_getter
285
Chris Masone6dca10a2012-02-15 15:41:42 -0800286 self._CONFIG.override_config_value('CROS',
287 'image_url_pattern',
288 self._URL)
Chris Masone6fed6462011-10-20 16:36:43 -0700289 self.afe.create_job(
Chris Masone8b7cd422012-02-22 13:16:11 -0800290 control_file=mox.And(mox.StrContains(self._BUILD),
291 mox.StrContains(self._URL % self._BUILD)),
292 name=mox.StrContains(self._BUILD),
Chris Masone6fed6462011-10-20 16:36:43 -0700293 control_type='Server',
Chris Masone5374c672012-03-05 15:11:39 -0800294 meta_hosts=[self._BOARD] * self._NUM,
Chris Masone8b7cd422012-02-22 13:16:11 -0800295 dependencies=[])
Chris Masone6fed6462011-10-20 16:36:43 -0700296 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800297 self.reimager._schedule_reimage_job(self._BUILD, self._NUM, self._BOARD)
Chris Masone6fed6462011-10-20 16:36:43 -0700298
299
Chris Masoned368cc42012-03-07 15:16:59 -0800300 def expect_label_cleanup(self, build):
301 """Sets up |self.afe| to expect deletion of the version label.
302
303 @param build: the build the label is named after.
304 """
305 label = FakeLabel(id=random.randrange(0, 5))
306 self.afe.get_labels(
307 name__startswith=mox.StrContains(build)).AndReturn([label])
308 self.afe.run('delete_label', id=label.id)
309
310
Chris Masone796fcf12012-02-22 16:53:31 -0800311 def expect_attempt(self, success, ex=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700312 """Sets up |self.reimager| to expect an attempt() that returns |success|
313
Chris Masoned368cc42012-03-07 15:16:59 -0800314 Also stubs out Reimger._clear_build_state(), should the caller wish
315 to set an expectation there as well.
316
Chris Masone796fcf12012-02-22 16:53:31 -0800317 @param success: the value returned by poll_job_results()
318 @param ex: if not None, |ex| is raised by get_jobs()
Chris Masone6fed6462011-10-20 16:36:43 -0700319 @return a FakeJob configured with appropriate expectations
320 """
321 canary = FakeJob()
322 self.mox.StubOutWithMock(self.reimager, '_ensure_version_label')
Chris Masone8b7cd422012-02-22 13:16:11 -0800323 self.reimager._ensure_version_label(mox.StrContains(self._BUILD))
Chris Masone6fed6462011-10-20 16:36:43 -0700324
325 self.mox.StubOutWithMock(self.reimager, '_schedule_reimage_job')
Chris Masone8b7cd422012-02-22 13:16:11 -0800326 self.reimager._schedule_reimage_job(self._BUILD,
Chris Masone6fed6462011-10-20 16:36:43 -0700327 self._NUM,
328 self._BOARD).AndReturn(canary)
Chris Masone5374c672012-03-05 15:11:39 -0800329
330 self.mox.StubOutWithMock(self.reimager, '_count_usable_hosts')
331 self.reimager._count_usable_hosts(mox.IgnoreArg()).AndReturn(self._NUM)
332
Chris Masone6fed6462011-10-20 16:36:43 -0700333 if success is not None:
334 self.mox.StubOutWithMock(self.reimager, '_report_results')
335 self.reimager._report_results(canary, mox.IgnoreArg())
Chris Masoned368cc42012-03-07 15:16:59 -0800336 canary.results_platform_map = {None: {'Total': [canary.hostname]}}
337
Chris Masone6fed6462011-10-20 16:36:43 -0700338
339 self.afe.get_jobs(id=canary.id, not_yet_run=True).AndReturn([])
Chris Masone796fcf12012-02-22 16:53:31 -0800340 if ex is not None:
341 self.afe.get_jobs(id=canary.id, finished=True).AndRaise(ex)
342 else:
343 self.afe.get_jobs(id=canary.id, finished=True).AndReturn([canary])
344 self.afe.poll_job_results(mox.IgnoreArg(),
345 canary, 0).AndReturn(success)
Chris Masone6fed6462011-10-20 16:36:43 -0700346
Chris Masoned368cc42012-03-07 15:16:59 -0800347 self.expect_label_cleanup(self._BUILD)
348 self.mox.StubOutWithMock(self.reimager, '_clear_build_state')
349
Chris Masone6fed6462011-10-20 16:36:43 -0700350 return canary
351
352
353 def testSuccessfulReimage(self):
354 """Should attempt a reimage and record success."""
355 canary = self.expect_attempt(True)
356
357 rjob = self.mox.CreateMock(base_job.base_job)
358 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
359 rjob.record('END GOOD', mox.IgnoreArg(), mox.IgnoreArg())
Chris Masoned368cc42012-03-07 15:16:59 -0800360 self.reimager._clear_build_state(mox.StrContains(canary.hostname))
Chris Masone6fed6462011-10-20 16:36:43 -0700361 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800362 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masoned368cc42012-03-07 15:16:59 -0800363 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone6fed6462011-10-20 16:36:43 -0700364
365
366 def testFailedReimage(self):
367 """Should attempt a reimage and record failure."""
368 canary = self.expect_attempt(False)
369
370 rjob = self.mox.CreateMock(base_job.base_job)
371 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
372 rjob.record('END FAIL', mox.IgnoreArg(), mox.IgnoreArg())
Chris Masoned368cc42012-03-07 15:16:59 -0800373 self.reimager._clear_build_state(mox.StrContains(canary.hostname))
Chris Masone6fed6462011-10-20 16:36:43 -0700374 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800375 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masoned368cc42012-03-07 15:16:59 -0800376 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone6fed6462011-10-20 16:36:43 -0700377
378
379 def testReimageThatNeverHappened(self):
380 """Should attempt a reimage and record that it didn't run."""
381 canary = self.expect_attempt(None)
382
383 rjob = self.mox.CreateMock(base_job.base_job)
384 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
385 rjob.record('FAIL', mox.IgnoreArg(), canary.name, mox.IgnoreArg())
386 rjob.record('END FAIL', mox.IgnoreArg(), mox.IgnoreArg())
387 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800388 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masoned368cc42012-03-07 15:16:59 -0800389 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone6fed6462011-10-20 16:36:43 -0700390
391
Chris Masone796fcf12012-02-22 16:53:31 -0800392 def testReimageThatRaised(self):
393 """Should attempt a reimage that raises an exception and record that."""
394 ex_message = 'Oh no!'
395 canary = self.expect_attempt(None, Exception(ex_message))
396
397 rjob = self.mox.CreateMock(base_job.base_job)
398 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
399 rjob.record('END ERROR', mox.IgnoreArg(), mox.IgnoreArg(), ex_message)
400 self.mox.ReplayAll()
401 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masoned368cc42012-03-07 15:16:59 -0800402 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone796fcf12012-02-22 16:53:31 -0800403
404
Chris Masone5374c672012-03-05 15:11:39 -0800405 def testReimageThatCouldNotSchedule(self):
406 """Should attempt a reimage that can't be scheduled."""
407 canary = FakeJob()
408
409 self.mox.StubOutWithMock(self.reimager, '_count_usable_hosts')
410 self.reimager._count_usable_hosts(mox.IgnoreArg()).AndReturn(0)
411
412 rjob = self.mox.CreateMock(base_job.base_job)
413 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
414 rjob.record('END WARN', mox.IgnoreArg(), mox.IgnoreArg(),
415 mox.StrContains('Too few hosts'))
Chris Masoned368cc42012-03-07 15:16:59 -0800416 self.expect_label_cleanup(self._BUILD)
Chris Masone5374c672012-03-05 15:11:39 -0800417 self.mox.ReplayAll()
418 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masoned368cc42012-03-07 15:16:59 -0800419 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone5374c672012-03-05 15:11:39 -0800420
421
Chris Masone6fed6462011-10-20 16:36:43 -0700422class SuiteTest(mox.MoxTestBase):
423 """Unit tests for dynamic_suite.Suite.
424
Chris Masone8b7cd422012-02-22 13:16:11 -0800425 @var _BUILD: fake build
Chris Masone6fed6462011-10-20 16:36:43 -0700426 @var _TAG: fake suite tag
427 """
428
Chris Masone8b7cd422012-02-22 13:16:11 -0800429 _BUILD = 'build'
430 _TAG = 'suite_tag'
Chris Masone6fed6462011-10-20 16:36:43 -0700431
432
433 def setUp(self):
434 class FakeControlData(object):
435 """A fake parsed control file data structure."""
436 def __init__(self, data, expr=False):
437 self.string = 'text-' + data
438 self.name = 'name-' + data
439 self.data = data
440 self.test_type = 'Client'
441 self.experimental = expr
442
443
444 super(SuiteTest, self).setUp()
445 self.afe = self.mox.CreateMock(frontend.AFE)
446 self.tko = self.mox.CreateMock(frontend.TKO)
447
448 self.tmpdir = tempfile.mkdtemp(suffix=type(self).__name__)
449
450 self.getter = self.mox.CreateMock(control_file_getter.ControlFileGetter)
451
452 self.files = {'one': FakeControlData('data_one', expr=True),
453 'two': FakeControlData('data_two'),
454 'three': FakeControlData('data_three')}
455
456
457 def tearDown(self):
458 super(SuiteTest, self).tearDown()
459 shutil.rmtree(self.tmpdir, ignore_errors=True)
460
461
462 def expect_control_file_parsing(self):
463 """Expect an attempt to parse the 'control files' in |self.files|."""
464 self.getter.get_control_file_list().AndReturn(self.files.keys())
465 self.mox.StubOutWithMock(control_data, 'parse_control_string')
466 for file, data in self.files.iteritems():
467 self.getter.get_control_file_contents(file).AndReturn(data.string)
468 control_data.parse_control_string(
469 data.string, raise_warnings=True).AndReturn(data)
470
471
472 def testFindAndParseStableTests(self):
473 """Should find only non-experimental tests that match a predicate."""
474 self.expect_control_file_parsing()
475 self.mox.ReplayAll()
476
477 predicate = lambda d: d.text == self.files['two'].string
478 tests = dynamic_suite.Suite.find_and_parse_tests(self.getter, predicate)
479 self.assertEquals(len(tests), 1)
480 self.assertEquals(tests[0], self.files['two'])
481
482
483 def testFindAndParseTests(self):
484 """Should find all tests that match a predicate."""
485 self.expect_control_file_parsing()
486 self.mox.ReplayAll()
487
488 predicate = lambda d: d.text != self.files['two'].string
489 tests = dynamic_suite.Suite.find_and_parse_tests(self.getter,
490 predicate,
491 add_experimental=True)
492 self.assertEquals(len(tests), 2)
493 self.assertTrue(self.files['one'] in tests)
494 self.assertTrue(self.files['three'] in tests)
495
496
497 def mock_control_file_parsing(self):
498 """Fake out find_and_parse_tests(), returning content from |self.files|.
499 """
500 for test in self.files.values():
501 test.text = test.string # mimic parsing.
502 self.mox.StubOutWithMock(dynamic_suite.Suite, 'find_and_parse_tests')
503 dynamic_suite.Suite.find_and_parse_tests(
504 mox.IgnoreArg(),
505 mox.IgnoreArg(),
506 add_experimental=True).AndReturn(self.files.values())
507
508
509 def testStableUnstableFilter(self):
510 """Should distinguish between experimental and stable tests."""
511 self.mock_control_file_parsing()
512 self.mox.ReplayAll()
513 suite = dynamic_suite.Suite.create_from_name(self._TAG, self.tmpdir,
Chris Masoned6f38c82012-02-22 14:53:42 -0800514 afe=self.afe, tko=self.tko)
Chris Masone6fed6462011-10-20 16:36:43 -0700515
516 self.assertTrue(self.files['one'] in suite.tests)
517 self.assertTrue(self.files['two'] in suite.tests)
518 self.assertTrue(self.files['one'] in suite.unstable_tests())
519 self.assertTrue(self.files['two'] in suite.stable_tests())
520 self.assertFalse(self.files['one'] in suite.stable_tests())
521 self.assertFalse(self.files['two'] in suite.unstable_tests())
522
523
524 def expect_job_scheduling(self, add_experimental):
525 """Expect jobs to be scheduled for 'tests' in |self.files|.
526
527 @param add_experimental: expect jobs for experimental tests as well.
528 """
529 for test in self.files.values():
530 if not add_experimental and test.experimental:
531 continue
532 self.afe.create_job(
533 control_file=test.text,
Chris Masone8b7cd422012-02-22 13:16:11 -0800534 name=mox.And(mox.StrContains(self._BUILD),
Chris Masone6fed6462011-10-20 16:36:43 -0700535 mox.StrContains(test.name)),
536 control_type=mox.IgnoreArg(),
Chris Masone8b7cd422012-02-22 13:16:11 -0800537 meta_hosts=[dynamic_suite.VERSION_PREFIX + self._BUILD],
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500538 dependencies=[]).AndReturn(FakeJob())
Chris Masone6fed6462011-10-20 16:36:43 -0700539
540
541 def testScheduleTests(self):
542 """Should schedule stable and experimental tests with the AFE."""
543 self.mock_control_file_parsing()
544 self.expect_job_scheduling(add_experimental=True)
545
546 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800547 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800548 afe=self.afe, tko=self.tko)
Chris Masone8b7cd422012-02-22 13:16:11 -0800549 suite.schedule()
Chris Masone6fed6462011-10-20 16:36:43 -0700550
551
Scott Zawalski9ece6532012-02-28 14:10:47 -0500552 def testScheduleTestsAndRecord(self):
553 """Should schedule stable and experimental tests with the AFE."""
554 self.mock_control_file_parsing()
555 self.mox.ReplayAll()
556 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
557 afe=self.afe, tko=self.tko,
558 results_dir=self.tmpdir)
559 self.mox.ResetAll()
560 self.expect_job_scheduling(add_experimental=True)
561 self.mox.StubOutWithMock(suite, '_record_scheduled_jobs')
562 suite._record_scheduled_jobs()
563 self.mox.ReplayAll()
564 suite.schedule()
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500565 for job in suite._jobs:
566 self.assertTrue(hasattr(job, 'test_name'))
Scott Zawalski9ece6532012-02-28 14:10:47 -0500567
568
Chris Masone6fed6462011-10-20 16:36:43 -0700569 def testScheduleStableTests(self):
570 """Should schedule only stable tests with the AFE."""
571 self.mock_control_file_parsing()
572 self.expect_job_scheduling(add_experimental=False)
573
574 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800575 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800576 afe=self.afe, tko=self.tko)
Chris Masone8b7cd422012-02-22 13:16:11 -0800577 suite.schedule(add_experimental=False)
Chris Masone6fed6462011-10-20 16:36:43 -0700578
579
580 def expect_result_gathering(self, job):
581 self.afe.get_jobs(id=job.id, finished=True).AndReturn(job)
582 entries = map(lambda s: s.entry, job.statuses)
583 self.afe.run('get_host_queue_entries',
584 job=job.id).AndReturn(entries)
585 if True not in map(lambda e: 'aborted' in e and e['aborted'], entries):
586 self.tko.get_status_counts(job=job.id).AndReturn(job.statuses)
587
588
Chris Masonefef21382012-01-17 11:16:32 -0800589 def _createSuiteWithMockedTestsAndControlFiles(self):
590 """Create a Suite, using mocked tests and control file contents.
591
592 @return Suite object, after mocking out behavior needed to create it.
593 """
Chris Masonefef21382012-01-17 11:16:32 -0800594 self.expect_control_file_parsing()
595 self.mox.ReplayAll()
Scott Zawalski9ece6532012-02-28 14:10:47 -0500596 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800597 self.getter, self.afe,
598 self.tko)
Chris Masonefef21382012-01-17 11:16:32 -0800599 self.mox.ResetAll()
600 return suite
601
602
Chris Masone6fed6462011-10-20 16:36:43 -0700603 def testWaitForResults(self):
604 """Should gather status and return records for job summaries."""
605 class FakeStatus(object):
606 """Fake replacement for server-side job status objects.
607
608 @var status: 'GOOD', 'FAIL', 'ERROR', etc.
609 @var test_name: name of the test this is status for
610 @var reason: reason for failure, if any
611 @var aborted: present and True if the job was aborted. Optional.
612 """
613 def __init__(self, code, name, reason, aborted=None):
614 self.status = code
615 self.test_name = name
616 self.reason = reason
617 self.entry = {}
618 if aborted:
619 self.entry['aborted'] = True
620
621 def equals_record(self, args):
622 """Compares this object to a recorded status."""
623 return self._equals_record(*args)
624
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800625 def _equals_record(self, status, subdir, name, reason=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700626 """Compares this object and fields of recorded status."""
627 if 'aborted' in self.entry and self.entry['aborted']:
628 return status == 'ABORT'
629 return (self.status == status and
630 self.test_name == name and
631 self.reason == reason)
632
Chris Masonefef21382012-01-17 11:16:32 -0800633 suite = self._createSuiteWithMockedTestsAndControlFiles()
Chris Masone6fed6462011-10-20 16:36:43 -0700634
635 jobs = [FakeJob(0, [FakeStatus('GOOD', 'T0', ''),
636 FakeStatus('GOOD', 'T1', '')]),
637 FakeJob(1, [FakeStatus('ERROR', 'T0', 'err', False),
638 FakeStatus('GOOD', 'T1', '')]),
639 FakeJob(2, [FakeStatus('TEST_NA', 'T0', 'no')]),
640 FakeJob(2, [FakeStatus('FAIL', 'T0', 'broken')]),
641 FakeJob(3, [FakeStatus('ERROR', 'T0', 'gah', True)])]
642 # To simulate a job that isn't ready the first time we check.
643 self.afe.get_jobs(id=jobs[0].id, finished=True).AndReturn([])
644 # Expect all the rest of the jobs to be good to go the first time.
645 for job in jobs[1:]:
646 self.expect_result_gathering(job)
647 # Then, expect job[0] to be ready.
648 self.expect_result_gathering(jobs[0])
649 # Expect us to poll twice.
650 self.mox.StubOutWithMock(time, 'sleep')
651 time.sleep(5)
652 time.sleep(5)
653 self.mox.ReplayAll()
654
Chris Masone6fed6462011-10-20 16:36:43 -0700655 suite._jobs = list(jobs)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500656 results = [result for result in suite.wait_for_results()]
Chris Masone6fed6462011-10-20 16:36:43 -0700657 for job in jobs:
658 for status in job.statuses:
659 self.assertTrue(True in map(status.equals_record, results))
660
661
662 def testRunAndWaitSuccess(self):
663 """Should record successful results."""
Chris Masonefef21382012-01-17 11:16:32 -0800664 suite = self._createSuiteWithMockedTestsAndControlFiles()
Chris Masone6fed6462011-10-20 16:36:43 -0700665
Chris Masonefef21382012-01-17 11:16:32 -0800666 results = [('GOOD', None, 'good'), ('FAIL', None, 'bad', 'reason')]
Chris Masone6fed6462011-10-20 16:36:43 -0700667 recorder = self.mox.CreateMock(base_job.base_job)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500668 recorder.record('INFO', None, 'Start %s' % self._TAG)
Chris Masone6fed6462011-10-20 16:36:43 -0700669 for result in results:
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500670 status = result[0]
671 test_name = result[2]
672 recorder.record('START', None, test_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700673 recorder.record(*result).InAnyOrder('results')
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500674 recorder.record('END %s' % status, None, test_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700675
Chris Masone6fed6462011-10-20 16:36:43 -0700676 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone8b7cd422012-02-22 13:16:11 -0800677 suite.schedule(True)
Chris Masone6fed6462011-10-20 16:36:43 -0700678 self.mox.StubOutWithMock(suite, 'wait_for_results')
679 suite.wait_for_results().AndReturn(results)
680 self.mox.ReplayAll()
681
Chris Masone8b7cd422012-02-22 13:16:11 -0800682 suite.run_and_wait(recorder.record, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700683
684
685 def testRunAndWaitFailure(self):
686 """Should record failure to gather results."""
Chris Masonefef21382012-01-17 11:16:32 -0800687 suite = self._createSuiteWithMockedTestsAndControlFiles()
688
Chris Masone6fed6462011-10-20 16:36:43 -0700689 recorder = self.mox.CreateMock(base_job.base_job)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500690 recorder.record('INFO', None, 'Start %s' % self._TAG)
691 recorder.record('FAIL', None, self._TAG,
692 mox.StrContains('waiting'))
Chris Masone6fed6462011-10-20 16:36:43 -0700693
Chris Masone6fed6462011-10-20 16:36:43 -0700694 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone8b7cd422012-02-22 13:16:11 -0800695 suite.schedule(True)
Chris Masone6fed6462011-10-20 16:36:43 -0700696 self.mox.StubOutWithMock(suite, 'wait_for_results')
697 suite.wait_for_results().AndRaise(Exception())
698 self.mox.ReplayAll()
699
Chris Masone8b7cd422012-02-22 13:16:11 -0800700 suite.run_and_wait(recorder.record, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700701
702
703 def testRunAndWaitScheduleFailure(self):
Chris Masonefef21382012-01-17 11:16:32 -0800704 """Should record failure to schedule jobs."""
705 suite = self._createSuiteWithMockedTestsAndControlFiles()
706
Chris Masone6fed6462011-10-20 16:36:43 -0700707 recorder = self.mox.CreateMock(base_job.base_job)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500708 recorder.record('INFO', None, 'Start %s' % self._TAG)
709 recorder.record('FAIL', None, self._TAG,
710 mox.StrContains('scheduling'))
Chris Masone6fed6462011-10-20 16:36:43 -0700711
Chris Masone6fed6462011-10-20 16:36:43 -0700712 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone8b7cd422012-02-22 13:16:11 -0800713 suite.schedule(True).AndRaise(Exception())
Chris Masone6fed6462011-10-20 16:36:43 -0700714 self.mox.ReplayAll()
715
Chris Masone8b7cd422012-02-22 13:16:11 -0800716 suite.run_and_wait(recorder.record, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700717
718
719if __name__ == '__main__':
720 unittest.main()