blob: f2766883891270c303c026aadbc50c8531607620 [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
11import shutil
12import tempfile
13import time
14import unittest
15
Chris Masone5552dd72012-02-15 15:01:04 -080016from autotest_lib.client.common_lib import base_job, control_data, global_config
Chris Masone6fed6462011-10-20 16:36:43 -070017from autotest_lib.server.cros import control_file_getter, dynamic_suite
18from autotest_lib.server import frontend
19
20class FakeJob(object):
21 """Faked out RPC-client-side Job object."""
22 def __init__(self, id=0, statuses=[]):
23 self.id = id
Scott Zawalskie5bb1c52012-02-29 13:15:50 -050024 self.owner = 'tester'
Chris Masone6fed6462011-10-20 16:36:43 -070025 self.name = 'Fake Job %d' % self.id
26 self.statuses = statuses
27
28
Chris Masone5374c672012-03-05 15:11:39 -080029class FakeHost(object):
30 """Faked out RPC-client-side Host object."""
31 def __init__(self, status='Ready'):
32 self.status = status
33
34
Chris Masoneab3e7332012-02-29 18:54:58 -080035class DynamicSuiteTest(mox.MoxTestBase):
36 """Unit tests for dynamic_suite module methods.
37
38 @var _DARGS: default args to vet.
39 """
40
41
42 def setUp(self):
43 super(DynamicSuiteTest, self).setUp()
44 self._DARGS = {'name': 'name',
45 'build': 'build',
46 'board': 'board',
47 'job': self.mox.CreateMock(base_job.base_job),
48 'num': 1,
49 'pool': 'pool',
50 'skip_reimage': True,
51 'add_experimental': False}
52
53
54 def testVetRequiredReimageAndRunArgs(self):
55 """Should verify only that required args are present and correct."""
56 build, board, name, job, _, _, _, _ = \
57 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
58 self.assertEquals(build, self._DARGS['build'])
59 self.assertEquals(board, self._DARGS['board'])
60 self.assertEquals(name, self._DARGS['name'])
61 self.assertEquals(job, self._DARGS['job'])
62
63
64 def testVetReimageAndRunBuildArgFail(self):
65 """Should fail verification because |build| arg is bad."""
66 self._DARGS['build'] = None
67 self.assertRaises(dynamic_suite.SuiteArgumentException,
68 dynamic_suite._vet_reimage_and_run_args,
69 **self._DARGS)
70
71
72 def testVetReimageAndRunBoardArgFail(self):
73 """Should fail verification because |board| arg is bad."""
74 self._DARGS['board'] = None
75 self.assertRaises(dynamic_suite.SuiteArgumentException,
76 dynamic_suite._vet_reimage_and_run_args,
77 **self._DARGS)
78
79
80 def testVetReimageAndRunNameArgFail(self):
81 """Should fail verification because |name| arg is bad."""
82 self._DARGS['name'] = None
83 self.assertRaises(dynamic_suite.SuiteArgumentException,
84 dynamic_suite._vet_reimage_and_run_args,
85 **self._DARGS)
86
87
88 def testVetReimageAndRunJobArgFail(self):
89 """Should fail verification because |job| arg is bad."""
90 self._DARGS['job'] = None
91 self.assertRaises(dynamic_suite.SuiteArgumentException,
92 dynamic_suite._vet_reimage_and_run_args,
93 **self._DARGS)
94
95
96 def testOverrideOptionalReimageAndRunArgs(self):
97 """Should verify that optional args can be overridden."""
98 _, _, _, _, pool, num, skip, expr = \
99 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
100 self.assertEquals(pool, self._DARGS['pool'])
101 self.assertEquals(num, self._DARGS['num'])
102 self.assertEquals(skip, self._DARGS['skip_reimage'])
103 self.assertEquals(expr, self._DARGS['add_experimental'])
104
105
106 def testDefaultOptionalReimageAndRunArgs(self):
107 """Should verify that optional args get defaults."""
108 del(self._DARGS['pool'])
109 del(self._DARGS['skip_reimage'])
110 del(self._DARGS['add_experimental'])
111 del(self._DARGS['num'])
112 _, _, _, _, pool, num, skip, expr = \
113 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
114 self.assertEquals(pool, None)
115 self.assertEquals(num, None)
116 self.assertEquals(skip, False)
117 self.assertEquals(expr, True)
118
119
Chris Masone6fed6462011-10-20 16:36:43 -0700120class ReimagerTest(mox.MoxTestBase):
121 """Unit tests for dynamic_suite.Reimager.
122
123 @var _URL: fake image url
Chris Masone8b7cd422012-02-22 13:16:11 -0800124 @var _BUILD: fake build
Chris Masone6fed6462011-10-20 16:36:43 -0700125 @var _NUM: fake number of machines to run on
126 @var _BOARD: fake board to reimage
127 """
128
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800129 _URL = 'http://nothing/%s'
Chris Masone8b7cd422012-02-22 13:16:11 -0800130 _BUILD = 'build'
Chris Masone6fed6462011-10-20 16:36:43 -0700131 _NUM = 4
132 _BOARD = 'board'
Chris Masone5552dd72012-02-15 15:01:04 -0800133 _CONFIG = global_config.global_config
Chris Masone6fed6462011-10-20 16:36:43 -0700134
135
136 def setUp(self):
137 super(ReimagerTest, self).setUp()
138 self.afe = self.mox.CreateMock(frontend.AFE)
139 self.tko = self.mox.CreateMock(frontend.TKO)
140 self.reimager = dynamic_suite.Reimager('', afe=self.afe, tko=self.tko)
Chris Masone5552dd72012-02-15 15:01:04 -0800141 self._CONFIG.override_config_value('CROS',
142 'sharding_factor',
143 "%d" % self._NUM)
Chris Masone6fed6462011-10-20 16:36:43 -0700144
145
146 def testEnsureVersionLabelAlreadyExists(self):
147 """Should not create a label if it already exists."""
148 name = 'label'
149 self.afe.get_labels(name=name).AndReturn([name])
150 self.mox.ReplayAll()
151 self.reimager._ensure_version_label(name)
152
153
154 def testEnsureVersionLabel(self):
155 """Should create a label if it doesn't already exist."""
156 name = 'label'
157 self.afe.get_labels(name=name).AndReturn([])
158 self.afe.create_label(name=name)
159 self.mox.ReplayAll()
160 self.reimager._ensure_version_label(name)
161
162
Chris Masone5374c672012-03-05 15:11:39 -0800163 def testCountHostsByBoardAndPool(self):
164 """Should count available hosts by board and pool."""
165 spec = [self._BOARD, 'pool:bvt']
166 self.afe.get_hosts(multiple_labels=spec).AndReturn([FakeHost()])
167 self.mox.ReplayAll()
168 self.assertEquals(self.reimager._count_usable_hosts(spec), 1)
169
170
171 def testCountHostsByBoard(self):
172 """Should count available hosts by board."""
173 spec = [self._BOARD]
174 self.afe.get_hosts(multiple_labels=spec).AndReturn([FakeHost()] * 2)
175 self.mox.ReplayAll()
176 self.assertEquals(self.reimager._count_usable_hosts(spec), 2)
177
178
179 def testCountZeroHostsByBoard(self):
180 """Should count the available hosts, by board, getting zero."""
181 spec = [self._BOARD]
182 self.afe.get_hosts(multiple_labels=spec).AndReturn([])
183 self.mox.ReplayAll()
184 self.assertEquals(self.reimager._count_usable_hosts(spec), 0)
185
186
Chris Masone6fed6462011-10-20 16:36:43 -0700187 def testInjectVars(self):
188 """Should inject dict of varibles into provided strings."""
189 def find_all_in(d, s):
190 """Returns true if all key-value pairs in |d| are printed in |s|."""
Chris Masone6cb0d0d2012-03-05 15:37:49 -0800191 for k,v in d.iteritems():
192 if isinstance(v, str):
193 if "%s='%s'\n" % (k,v) not in s:
194 return False
195 else:
196 if "%s=%r\n" % (k,v) not in s:
197 return False
198 return True
Chris Masone6fed6462011-10-20 16:36:43 -0700199
Chris Masone6cb0d0d2012-03-05 15:37:49 -0800200 v = {'v1': 'one', 'v2': 'two', 'v3': None, 'v4': False, 'v5': 5}
Chris Masone8b764252012-01-17 11:12:51 -0800201 self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, '')))
202 self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, 'ctrl')))
Chris Masone6fed6462011-10-20 16:36:43 -0700203
204
205 def testReportResultsGood(self):
206 """Should report results in the case where all jobs passed."""
207 job = self.mox.CreateMock(frontend.Job)
208 job.name = 'RPC Client job'
209 job.result = True
210 recorder = self.mox.CreateMock(base_job.base_job)
211 recorder.record('GOOD', mox.IgnoreArg(), job.name)
212 self.mox.ReplayAll()
213 self.reimager._report_results(job, recorder.record)
214
215
216 def testReportResultsBad(self):
217 """Should report results in various job failure cases.
218
219 In this test scenario, there are five hosts, all the 'netbook' platform.
220
221 h1: Did not run
222 h2: Two failed tests
223 h3: Two aborted tests
224 h4: completed, GOOD
225 h5: completed, GOOD
226 """
227 H1 = 'host1'
228 H2 = 'host2'
229 H3 = 'host3'
230 H4 = 'host4'
231 H5 = 'host5'
232
233 class FakeResult(object):
234 def __init__(self, reason):
235 self.reason = reason
236
237
238 # The RPC-client-side Job object that is annotated with results.
239 job = FakeJob()
240 job.result = None # job failed, there are results to report.
241
242 # The semantics of |results_platform_map| and |test_results| are
243 # drawn from frontend.AFE.poll_all_jobs()
244 job.results_platform_map = {'netbook': {'Aborted' : [H3],
Chris Masone8b7cd422012-02-22 13:16:11 -0800245 'Completed' : [H1, H4, H5],
246 'Failed': [H2]
Chris Masone6fed6462011-10-20 16:36:43 -0700247 }
248 }
249 # Gin up fake results for H2 and H3 failure cases.
250 h2 = frontend.TestResults()
251 h2.fail = [FakeResult('a'), FakeResult('b')]
252 h3 = frontend.TestResults()
253 h3.fail = [FakeResult('a'), FakeResult('b')]
254 # Skipping H1 in |test_status| dict means that it did not get run.
255 job.test_status = {H2: h2, H3: h3, H4: {}, H5: {}}
256
257 # Set up recording expectations.
258 rjob = self.mox.CreateMock(base_job.base_job)
259 for res in h2.fail:
260 rjob.record('FAIL', mox.IgnoreArg(), H2, res.reason).InAnyOrder()
261 for res in h3.fail:
262 rjob.record('ABORT', mox.IgnoreArg(), H3, res.reason).InAnyOrder()
263 rjob.record('GOOD', mox.IgnoreArg(), H4).InAnyOrder()
264 rjob.record('GOOD', mox.IgnoreArg(), H5).InAnyOrder()
265 rjob.record(
266 'ERROR', mox.IgnoreArg(), H1, mox.IgnoreArg()).InAnyOrder()
267
268 self.mox.ReplayAll()
269 self.reimager._report_results(job, rjob.record)
270
271
272 def testScheduleJob(self):
273 """Should be able to create a job with the AFE."""
274 # Fake out getting the autoupdate control file contents.
275 cf_getter = self.mox.CreateMock(control_file_getter.ControlFileGetter)
276 cf_getter.get_control_file_contents_by_name('autoupdate').AndReturn('')
277 self.reimager._cf_getter = cf_getter
278
Chris Masone6dca10a2012-02-15 15:41:42 -0800279 self._CONFIG.override_config_value('CROS',
280 'image_url_pattern',
281 self._URL)
Chris Masone6fed6462011-10-20 16:36:43 -0700282 self.afe.create_job(
Chris Masone8b7cd422012-02-22 13:16:11 -0800283 control_file=mox.And(mox.StrContains(self._BUILD),
284 mox.StrContains(self._URL % self._BUILD)),
285 name=mox.StrContains(self._BUILD),
Chris Masone6fed6462011-10-20 16:36:43 -0700286 control_type='Server',
Chris Masone5374c672012-03-05 15:11:39 -0800287 meta_hosts=[self._BOARD] * self._NUM,
Chris Masone8b7cd422012-02-22 13:16:11 -0800288 dependencies=[])
Chris Masone6fed6462011-10-20 16:36:43 -0700289 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800290 self.reimager._schedule_reimage_job(self._BUILD, self._NUM, self._BOARD)
Chris Masone6fed6462011-10-20 16:36:43 -0700291
292
Chris Masone796fcf12012-02-22 16:53:31 -0800293 def expect_attempt(self, success, ex=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700294 """Sets up |self.reimager| to expect an attempt() that returns |success|
295
Chris Masone796fcf12012-02-22 16:53:31 -0800296 @param success: the value returned by poll_job_results()
297 @param ex: if not None, |ex| is raised by get_jobs()
Chris Masone6fed6462011-10-20 16:36:43 -0700298 @return a FakeJob configured with appropriate expectations
299 """
300 canary = FakeJob()
301 self.mox.StubOutWithMock(self.reimager, '_ensure_version_label')
Chris Masone8b7cd422012-02-22 13:16:11 -0800302 self.reimager._ensure_version_label(mox.StrContains(self._BUILD))
Chris Masone6fed6462011-10-20 16:36:43 -0700303
304 self.mox.StubOutWithMock(self.reimager, '_schedule_reimage_job')
Chris Masone8b7cd422012-02-22 13:16:11 -0800305 self.reimager._schedule_reimage_job(self._BUILD,
Chris Masone6fed6462011-10-20 16:36:43 -0700306 self._NUM,
307 self._BOARD).AndReturn(canary)
Chris Masone5374c672012-03-05 15:11:39 -0800308
309 self.mox.StubOutWithMock(self.reimager, '_count_usable_hosts')
310 self.reimager._count_usable_hosts(mox.IgnoreArg()).AndReturn(self._NUM)
311
Chris Masone6fed6462011-10-20 16:36:43 -0700312 if success is not None:
313 self.mox.StubOutWithMock(self.reimager, '_report_results')
314 self.reimager._report_results(canary, mox.IgnoreArg())
315
316 self.afe.get_jobs(id=canary.id, not_yet_run=True).AndReturn([])
Chris Masone796fcf12012-02-22 16:53:31 -0800317 if ex is not None:
318 self.afe.get_jobs(id=canary.id, finished=True).AndRaise(ex)
319 else:
320 self.afe.get_jobs(id=canary.id, finished=True).AndReturn([canary])
321 self.afe.poll_job_results(mox.IgnoreArg(),
322 canary, 0).AndReturn(success)
Chris Masone6fed6462011-10-20 16:36:43 -0700323
324 return canary
325
326
327 def testSuccessfulReimage(self):
328 """Should attempt a reimage and record success."""
329 canary = self.expect_attempt(True)
330
331 rjob = self.mox.CreateMock(base_job.base_job)
332 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
333 rjob.record('END GOOD', mox.IgnoreArg(), mox.IgnoreArg())
334 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800335 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masone6fed6462011-10-20 16:36:43 -0700336
337
338 def testFailedReimage(self):
339 """Should attempt a reimage and record failure."""
340 canary = self.expect_attempt(False)
341
342 rjob = self.mox.CreateMock(base_job.base_job)
343 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
344 rjob.record('END FAIL', mox.IgnoreArg(), mox.IgnoreArg())
345 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800346 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masone6fed6462011-10-20 16:36:43 -0700347
348
349 def testReimageThatNeverHappened(self):
350 """Should attempt a reimage and record that it didn't run."""
351 canary = self.expect_attempt(None)
352
353 rjob = self.mox.CreateMock(base_job.base_job)
354 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
355 rjob.record('FAIL', mox.IgnoreArg(), canary.name, mox.IgnoreArg())
356 rjob.record('END FAIL', mox.IgnoreArg(), mox.IgnoreArg())
357 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800358 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
Chris Masone6fed6462011-10-20 16:36:43 -0700359
360
Chris Masone796fcf12012-02-22 16:53:31 -0800361 def testReimageThatRaised(self):
362 """Should attempt a reimage that raises an exception and record that."""
363 ex_message = 'Oh no!'
364 canary = self.expect_attempt(None, Exception(ex_message))
365
366 rjob = self.mox.CreateMock(base_job.base_job)
367 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
368 rjob.record('END ERROR', mox.IgnoreArg(), mox.IgnoreArg(), ex_message)
369 self.mox.ReplayAll()
370 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
371
372
Chris Masone5374c672012-03-05 15:11:39 -0800373 def testReimageThatCouldNotSchedule(self):
374 """Should attempt a reimage that can't be scheduled."""
375 canary = FakeJob()
376
377 self.mox.StubOutWithMock(self.reimager, '_count_usable_hosts')
378 self.reimager._count_usable_hosts(mox.IgnoreArg()).AndReturn(0)
379
380 rjob = self.mox.CreateMock(base_job.base_job)
381 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
382 rjob.record('END WARN', mox.IgnoreArg(), mox.IgnoreArg(),
383 mox.StrContains('Too few hosts'))
384 self.mox.ReplayAll()
385 self.reimager.attempt(self._BUILD, self._BOARD, rjob.record)
386
387
Chris Masone6fed6462011-10-20 16:36:43 -0700388class SuiteTest(mox.MoxTestBase):
389 """Unit tests for dynamic_suite.Suite.
390
Chris Masone8b7cd422012-02-22 13:16:11 -0800391 @var _BUILD: fake build
Chris Masone6fed6462011-10-20 16:36:43 -0700392 @var _TAG: fake suite tag
393 """
394
Chris Masone8b7cd422012-02-22 13:16:11 -0800395 _BUILD = 'build'
396 _TAG = 'suite_tag'
Chris Masone6fed6462011-10-20 16:36:43 -0700397
398
399 def setUp(self):
400 class FakeControlData(object):
401 """A fake parsed control file data structure."""
402 def __init__(self, data, expr=False):
403 self.string = 'text-' + data
404 self.name = 'name-' + data
405 self.data = data
406 self.test_type = 'Client'
407 self.experimental = expr
408
409
410 super(SuiteTest, self).setUp()
411 self.afe = self.mox.CreateMock(frontend.AFE)
412 self.tko = self.mox.CreateMock(frontend.TKO)
413
414 self.tmpdir = tempfile.mkdtemp(suffix=type(self).__name__)
415
416 self.getter = self.mox.CreateMock(control_file_getter.ControlFileGetter)
417
418 self.files = {'one': FakeControlData('data_one', expr=True),
419 'two': FakeControlData('data_two'),
420 'three': FakeControlData('data_three')}
421
422
423 def tearDown(self):
424 super(SuiteTest, self).tearDown()
425 shutil.rmtree(self.tmpdir, ignore_errors=True)
426
427
428 def expect_control_file_parsing(self):
429 """Expect an attempt to parse the 'control files' in |self.files|."""
430 self.getter.get_control_file_list().AndReturn(self.files.keys())
431 self.mox.StubOutWithMock(control_data, 'parse_control_string')
432 for file, data in self.files.iteritems():
433 self.getter.get_control_file_contents(file).AndReturn(data.string)
434 control_data.parse_control_string(
435 data.string, raise_warnings=True).AndReturn(data)
436
437
438 def testFindAndParseStableTests(self):
439 """Should find only non-experimental tests that match a predicate."""
440 self.expect_control_file_parsing()
441 self.mox.ReplayAll()
442
443 predicate = lambda d: d.text == self.files['two'].string
444 tests = dynamic_suite.Suite.find_and_parse_tests(self.getter, predicate)
445 self.assertEquals(len(tests), 1)
446 self.assertEquals(tests[0], self.files['two'])
447
448
449 def testFindAndParseTests(self):
450 """Should find all tests that match a predicate."""
451 self.expect_control_file_parsing()
452 self.mox.ReplayAll()
453
454 predicate = lambda d: d.text != self.files['two'].string
455 tests = dynamic_suite.Suite.find_and_parse_tests(self.getter,
456 predicate,
457 add_experimental=True)
458 self.assertEquals(len(tests), 2)
459 self.assertTrue(self.files['one'] in tests)
460 self.assertTrue(self.files['three'] in tests)
461
462
463 def mock_control_file_parsing(self):
464 """Fake out find_and_parse_tests(), returning content from |self.files|.
465 """
466 for test in self.files.values():
467 test.text = test.string # mimic parsing.
468 self.mox.StubOutWithMock(dynamic_suite.Suite, 'find_and_parse_tests')
469 dynamic_suite.Suite.find_and_parse_tests(
470 mox.IgnoreArg(),
471 mox.IgnoreArg(),
472 add_experimental=True).AndReturn(self.files.values())
473
474
475 def testStableUnstableFilter(self):
476 """Should distinguish between experimental and stable tests."""
477 self.mock_control_file_parsing()
478 self.mox.ReplayAll()
479 suite = dynamic_suite.Suite.create_from_name(self._TAG, self.tmpdir,
Chris Masoned6f38c82012-02-22 14:53:42 -0800480 afe=self.afe, tko=self.tko)
Chris Masone6fed6462011-10-20 16:36:43 -0700481
482 self.assertTrue(self.files['one'] in suite.tests)
483 self.assertTrue(self.files['two'] in suite.tests)
484 self.assertTrue(self.files['one'] in suite.unstable_tests())
485 self.assertTrue(self.files['two'] in suite.stable_tests())
486 self.assertFalse(self.files['one'] in suite.stable_tests())
487 self.assertFalse(self.files['two'] in suite.unstable_tests())
488
489
490 def expect_job_scheduling(self, add_experimental):
491 """Expect jobs to be scheduled for 'tests' in |self.files|.
492
493 @param add_experimental: expect jobs for experimental tests as well.
494 """
495 for test in self.files.values():
496 if not add_experimental and test.experimental:
497 continue
498 self.afe.create_job(
499 control_file=test.text,
Chris Masone8b7cd422012-02-22 13:16:11 -0800500 name=mox.And(mox.StrContains(self._BUILD),
Chris Masone6fed6462011-10-20 16:36:43 -0700501 mox.StrContains(test.name)),
502 control_type=mox.IgnoreArg(),
Chris Masone8b7cd422012-02-22 13:16:11 -0800503 meta_hosts=[dynamic_suite.VERSION_PREFIX + self._BUILD],
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500504 dependencies=[]).AndReturn(FakeJob())
Chris Masone6fed6462011-10-20 16:36:43 -0700505
506
507 def testScheduleTests(self):
508 """Should schedule stable and experimental tests with the AFE."""
509 self.mock_control_file_parsing()
510 self.expect_job_scheduling(add_experimental=True)
511
512 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800513 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800514 afe=self.afe, tko=self.tko)
Chris Masone8b7cd422012-02-22 13:16:11 -0800515 suite.schedule()
Chris Masone6fed6462011-10-20 16:36:43 -0700516
517
Scott Zawalski9ece6532012-02-28 14:10:47 -0500518 def testScheduleTestsAndRecord(self):
519 """Should schedule stable and experimental tests with the AFE."""
520 self.mock_control_file_parsing()
521 self.mox.ReplayAll()
522 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
523 afe=self.afe, tko=self.tko,
524 results_dir=self.tmpdir)
525 self.mox.ResetAll()
526 self.expect_job_scheduling(add_experimental=True)
527 self.mox.StubOutWithMock(suite, '_record_scheduled_jobs')
528 suite._record_scheduled_jobs()
529 self.mox.ReplayAll()
530 suite.schedule()
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500531 for job in suite._jobs:
532 self.assertTrue(hasattr(job, 'test_name'))
Scott Zawalski9ece6532012-02-28 14:10:47 -0500533
534
Chris Masone6fed6462011-10-20 16:36:43 -0700535 def testScheduleStableTests(self):
536 """Should schedule only stable tests with the AFE."""
537 self.mock_control_file_parsing()
538 self.expect_job_scheduling(add_experimental=False)
539
540 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800541 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800542 afe=self.afe, tko=self.tko)
Chris Masone8b7cd422012-02-22 13:16:11 -0800543 suite.schedule(add_experimental=False)
Chris Masone6fed6462011-10-20 16:36:43 -0700544
545
546 def expect_result_gathering(self, job):
547 self.afe.get_jobs(id=job.id, finished=True).AndReturn(job)
548 entries = map(lambda s: s.entry, job.statuses)
549 self.afe.run('get_host_queue_entries',
550 job=job.id).AndReturn(entries)
551 if True not in map(lambda e: 'aborted' in e and e['aborted'], entries):
552 self.tko.get_status_counts(job=job.id).AndReturn(job.statuses)
553
554
Chris Masonefef21382012-01-17 11:16:32 -0800555 def _createSuiteWithMockedTestsAndControlFiles(self):
556 """Create a Suite, using mocked tests and control file contents.
557
558 @return Suite object, after mocking out behavior needed to create it.
559 """
Chris Masonefef21382012-01-17 11:16:32 -0800560 self.expect_control_file_parsing()
561 self.mox.ReplayAll()
Scott Zawalski9ece6532012-02-28 14:10:47 -0500562 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800563 self.getter, self.afe,
564 self.tko)
Chris Masonefef21382012-01-17 11:16:32 -0800565 self.mox.ResetAll()
566 return suite
567
568
Chris Masone6fed6462011-10-20 16:36:43 -0700569 def testWaitForResults(self):
570 """Should gather status and return records for job summaries."""
571 class FakeStatus(object):
572 """Fake replacement for server-side job status objects.
573
574 @var status: 'GOOD', 'FAIL', 'ERROR', etc.
575 @var test_name: name of the test this is status for
576 @var reason: reason for failure, if any
577 @var aborted: present and True if the job was aborted. Optional.
578 """
579 def __init__(self, code, name, reason, aborted=None):
580 self.status = code
581 self.test_name = name
582 self.reason = reason
583 self.entry = {}
584 if aborted:
585 self.entry['aborted'] = True
586
587 def equals_record(self, args):
588 """Compares this object to a recorded status."""
589 return self._equals_record(*args)
590
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800591 def _equals_record(self, status, subdir, name, reason=None):
Chris Masone6fed6462011-10-20 16:36:43 -0700592 """Compares this object and fields of recorded status."""
593 if 'aborted' in self.entry and self.entry['aborted']:
594 return status == 'ABORT'
595 return (self.status == status and
596 self.test_name == name and
597 self.reason == reason)
598
Chris Masonefef21382012-01-17 11:16:32 -0800599 suite = self._createSuiteWithMockedTestsAndControlFiles()
Chris Masone6fed6462011-10-20 16:36:43 -0700600
601 jobs = [FakeJob(0, [FakeStatus('GOOD', 'T0', ''),
602 FakeStatus('GOOD', 'T1', '')]),
603 FakeJob(1, [FakeStatus('ERROR', 'T0', 'err', False),
604 FakeStatus('GOOD', 'T1', '')]),
605 FakeJob(2, [FakeStatus('TEST_NA', 'T0', 'no')]),
606 FakeJob(2, [FakeStatus('FAIL', 'T0', 'broken')]),
607 FakeJob(3, [FakeStatus('ERROR', 'T0', 'gah', True)])]
608 # To simulate a job that isn't ready the first time we check.
609 self.afe.get_jobs(id=jobs[0].id, finished=True).AndReturn([])
610 # Expect all the rest of the jobs to be good to go the first time.
611 for job in jobs[1:]:
612 self.expect_result_gathering(job)
613 # Then, expect job[0] to be ready.
614 self.expect_result_gathering(jobs[0])
615 # Expect us to poll twice.
616 self.mox.StubOutWithMock(time, 'sleep')
617 time.sleep(5)
618 time.sleep(5)
619 self.mox.ReplayAll()
620
Chris Masone6fed6462011-10-20 16:36:43 -0700621 suite._jobs = list(jobs)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500622 results = [result for result in suite.wait_for_results()]
Chris Masone6fed6462011-10-20 16:36:43 -0700623 for job in jobs:
624 for status in job.statuses:
625 self.assertTrue(True in map(status.equals_record, results))
626
627
628 def testRunAndWaitSuccess(self):
629 """Should record successful results."""
Chris Masonefef21382012-01-17 11:16:32 -0800630 suite = self._createSuiteWithMockedTestsAndControlFiles()
Chris Masone6fed6462011-10-20 16:36:43 -0700631
Chris Masonefef21382012-01-17 11:16:32 -0800632 results = [('GOOD', None, 'good'), ('FAIL', None, 'bad', 'reason')]
Chris Masone6fed6462011-10-20 16:36:43 -0700633 recorder = self.mox.CreateMock(base_job.base_job)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500634 recorder.record('INFO', None, 'Start %s' % self._TAG)
Chris Masone6fed6462011-10-20 16:36:43 -0700635 for result in results:
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500636 status = result[0]
637 test_name = result[2]
638 recorder.record('START', None, test_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700639 recorder.record(*result).InAnyOrder('results')
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500640 recorder.record('END %s' % status, None, test_name)
Chris Masone6fed6462011-10-20 16:36:43 -0700641
Chris Masone6fed6462011-10-20 16:36:43 -0700642 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone8b7cd422012-02-22 13:16:11 -0800643 suite.schedule(True)
Chris Masone6fed6462011-10-20 16:36:43 -0700644 self.mox.StubOutWithMock(suite, 'wait_for_results')
645 suite.wait_for_results().AndReturn(results)
646 self.mox.ReplayAll()
647
Chris Masone8b7cd422012-02-22 13:16:11 -0800648 suite.run_and_wait(recorder.record, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700649
650
651 def testRunAndWaitFailure(self):
652 """Should record failure to gather results."""
Chris Masonefef21382012-01-17 11:16:32 -0800653 suite = self._createSuiteWithMockedTestsAndControlFiles()
654
Chris Masone6fed6462011-10-20 16:36:43 -0700655 recorder = self.mox.CreateMock(base_job.base_job)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500656 recorder.record('INFO', None, 'Start %s' % self._TAG)
657 recorder.record('FAIL', None, self._TAG,
658 mox.StrContains('waiting'))
Chris Masone6fed6462011-10-20 16:36:43 -0700659
Chris Masone6fed6462011-10-20 16:36:43 -0700660 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone8b7cd422012-02-22 13:16:11 -0800661 suite.schedule(True)
Chris Masone6fed6462011-10-20 16:36:43 -0700662 self.mox.StubOutWithMock(suite, 'wait_for_results')
663 suite.wait_for_results().AndRaise(Exception())
664 self.mox.ReplayAll()
665
Chris Masone8b7cd422012-02-22 13:16:11 -0800666 suite.run_and_wait(recorder.record, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700667
668
669 def testRunAndWaitScheduleFailure(self):
Chris Masonefef21382012-01-17 11:16:32 -0800670 """Should record failure to schedule jobs."""
671 suite = self._createSuiteWithMockedTestsAndControlFiles()
672
Chris Masone6fed6462011-10-20 16:36:43 -0700673 recorder = self.mox.CreateMock(base_job.base_job)
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500674 recorder.record('INFO', None, 'Start %s' % self._TAG)
675 recorder.record('FAIL', None, self._TAG,
676 mox.StrContains('scheduling'))
Chris Masone6fed6462011-10-20 16:36:43 -0700677
Chris Masone6fed6462011-10-20 16:36:43 -0700678 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone8b7cd422012-02-22 13:16:11 -0800679 suite.schedule(True).AndRaise(Exception())
Chris Masone6fed6462011-10-20 16:36:43 -0700680 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
685if __name__ == '__main__':
686 unittest.main()