blob: d891c16736424ab1599878ee2a9456e91422723e [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 Masonef8b53062012-05-08 22:14:18 -070017from autotest_lib.client.common_lib import base_job, control_data, error
18from autotest_lib.client.common_lib import global_config
Chris Masone47c9e642012-04-25 14:22:18 -070019from autotest_lib.frontend.afe.json_rpc import proxy
Chris Masone6fed6462011-10-20 16:36:43 -070020from autotest_lib.server.cros import control_file_getter, dynamic_suite
Chris Masone8d6e6412012-06-28 11:20:56 -070021from autotest_lib.server.cros import job_status
22from autotest_lib.server.cros.dynamic_suite_fakes import FakeControlData
23from autotest_lib.server.cros.dynamic_suite_fakes import FakeHost, FakeJob
24from autotest_lib.server.cros.dynamic_suite_fakes import FakeLabel
Chris Masone6fed6462011-10-20 16:36:43 -070025from autotest_lib.server import frontend
26
Chris Masone5374c672012-03-05 15:11:39 -080027
Chris Masoneab3e7332012-02-29 18:54:58 -080028class DynamicSuiteTest(mox.MoxTestBase):
29 """Unit tests for dynamic_suite module methods.
30
31 @var _DARGS: default args to vet.
32 """
33
34
35 def setUp(self):
36 super(DynamicSuiteTest, self).setUp()
37 self._DARGS = {'name': 'name',
38 'build': 'build',
39 'board': 'board',
40 'job': self.mox.CreateMock(base_job.base_job),
41 'num': 1,
42 'pool': 'pool',
43 'skip_reimage': True,
Chris Masone62579122012-03-08 15:18:43 -080044 'check_hosts': False,
Chris Masoneab3e7332012-02-29 18:54:58 -080045 'add_experimental': False}
46
47
48 def testVetRequiredReimageAndRunArgs(self):
49 """Should verify only that required args are present and correct."""
Chris Masone62579122012-03-08 15:18:43 -080050 build, board, name, job, _, _, _, _,_ = \
Chris Masoneab3e7332012-02-29 18:54:58 -080051 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
52 self.assertEquals(build, self._DARGS['build'])
53 self.assertEquals(board, self._DARGS['board'])
54 self.assertEquals(name, self._DARGS['name'])
55 self.assertEquals(job, self._DARGS['job'])
56
57
58 def testVetReimageAndRunBuildArgFail(self):
59 """Should fail verification because |build| arg is bad."""
60 self._DARGS['build'] = None
Chris Masonef8b53062012-05-08 22:14:18 -070061 self.assertRaises(error.SuiteArgumentException,
Chris Masoneab3e7332012-02-29 18:54:58 -080062 dynamic_suite._vet_reimage_and_run_args,
63 **self._DARGS)
64
65
66 def testVetReimageAndRunBoardArgFail(self):
67 """Should fail verification because |board| arg is bad."""
68 self._DARGS['board'] = None
Chris Masonef8b53062012-05-08 22:14:18 -070069 self.assertRaises(error.SuiteArgumentException,
Chris Masoneab3e7332012-02-29 18:54:58 -080070 dynamic_suite._vet_reimage_and_run_args,
71 **self._DARGS)
72
73
74 def testVetReimageAndRunNameArgFail(self):
75 """Should fail verification because |name| arg is bad."""
76 self._DARGS['name'] = None
Chris Masonef8b53062012-05-08 22:14:18 -070077 self.assertRaises(error.SuiteArgumentException,
Chris Masoneab3e7332012-02-29 18:54:58 -080078 dynamic_suite._vet_reimage_and_run_args,
79 **self._DARGS)
80
81
82 def testVetReimageAndRunJobArgFail(self):
83 """Should fail verification because |job| arg is bad."""
84 self._DARGS['job'] = None
Chris Masonef8b53062012-05-08 22:14:18 -070085 self.assertRaises(error.SuiteArgumentException,
Chris Masoneab3e7332012-02-29 18:54:58 -080086 dynamic_suite._vet_reimage_and_run_args,
87 **self._DARGS)
88
89
90 def testOverrideOptionalReimageAndRunArgs(self):
91 """Should verify that optional args can be overridden."""
Chris Masone62579122012-03-08 15:18:43 -080092 _, _, _, _, pool, num, check, skip, expr = \
Chris Masoneab3e7332012-02-29 18:54:58 -080093 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
94 self.assertEquals(pool, self._DARGS['pool'])
95 self.assertEquals(num, self._DARGS['num'])
Chris Masone62579122012-03-08 15:18:43 -080096 self.assertEquals(check, self._DARGS['check_hosts'])
Chris Masoneab3e7332012-02-29 18:54:58 -080097 self.assertEquals(skip, self._DARGS['skip_reimage'])
98 self.assertEquals(expr, self._DARGS['add_experimental'])
99
100
101 def testDefaultOptionalReimageAndRunArgs(self):
102 """Should verify that optional args get defaults."""
103 del(self._DARGS['pool'])
104 del(self._DARGS['skip_reimage'])
Chris Masone62579122012-03-08 15:18:43 -0800105 del(self._DARGS['check_hosts'])
Chris Masoneab3e7332012-02-29 18:54:58 -0800106 del(self._DARGS['add_experimental'])
107 del(self._DARGS['num'])
Chris Masone62579122012-03-08 15:18:43 -0800108 _, _, _, _, pool, num, check, skip, expr = \
Chris Masoneab3e7332012-02-29 18:54:58 -0800109 dynamic_suite._vet_reimage_and_run_args(**self._DARGS)
110 self.assertEquals(pool, None)
111 self.assertEquals(num, None)
Chris Masone62579122012-03-08 15:18:43 -0800112 self.assertEquals(check, True)
Chris Masoneab3e7332012-02-29 18:54:58 -0800113 self.assertEquals(skip, False)
114 self.assertEquals(expr, True)
115
116
Chris Masone6fed6462011-10-20 16:36:43 -0700117class ReimagerTest(mox.MoxTestBase):
118 """Unit tests for dynamic_suite.Reimager.
119
120 @var _URL: fake image url
Chris Masone8b7cd422012-02-22 13:16:11 -0800121 @var _BUILD: fake build
Chris Masone6fed6462011-10-20 16:36:43 -0700122 @var _NUM: fake number of machines to run on
123 @var _BOARD: fake board to reimage
124 """
125
Chris Masone2ef1d4e2011-12-20 11:06:53 -0800126 _URL = 'http://nothing/%s'
Chris Masone8b7cd422012-02-22 13:16:11 -0800127 _BUILD = 'build'
Chris Masone6fed6462011-10-20 16:36:43 -0700128 _NUM = 4
129 _BOARD = 'board'
Chris Masone5552dd72012-02-15 15:01:04 -0800130 _CONFIG = global_config.global_config
Chris Masone6fed6462011-10-20 16:36:43 -0700131
132
133 def setUp(self):
134 super(ReimagerTest, self).setUp()
135 self.afe = self.mox.CreateMock(frontend.AFE)
136 self.tko = self.mox.CreateMock(frontend.TKO)
137 self.reimager = dynamic_suite.Reimager('', afe=self.afe, tko=self.tko)
Chris Masone5552dd72012-02-15 15:01:04 -0800138 self._CONFIG.override_config_value('CROS',
139 'sharding_factor',
140 "%d" % self._NUM)
Chris Masone6fed6462011-10-20 16:36:43 -0700141
142
143 def testEnsureVersionLabelAlreadyExists(self):
Chris Masone47c9e642012-04-25 14:22:18 -0700144 """Should tolerate a label that already exists."""
Chris Masone6fed6462011-10-20 16:36:43 -0700145 name = 'label'
Chris Masone47c9e642012-04-25 14:22:18 -0700146 error = proxy.ValidationError(
147 {'name': 'ValidationError',
148 'message': '{"name": "This value must be unique"}',
149 'traceback': ''},
150 'BAD')
151 self.afe.create_label(name=name).AndRaise(error)
Chris Masone6fed6462011-10-20 16:36:43 -0700152 self.mox.ReplayAll()
153 self.reimager._ensure_version_label(name)
154
155
156 def testEnsureVersionLabel(self):
157 """Should create a label if it doesn't already exist."""
158 name = 'label'
Chris Masone6fed6462011-10-20 16:36:43 -0700159 self.afe.create_label(name=name)
160 self.mox.ReplayAll()
161 self.reimager._ensure_version_label(name)
162
163
Chris Masone5374c672012-03-05 15:11:39 -0800164 def testCountHostsByBoardAndPool(self):
165 """Should count available hosts by board and pool."""
166 spec = [self._BOARD, 'pool:bvt']
167 self.afe.get_hosts(multiple_labels=spec).AndReturn([FakeHost()])
168 self.mox.ReplayAll()
169 self.assertEquals(self.reimager._count_usable_hosts(spec), 1)
170
171
172 def testCountHostsByBoard(self):
173 """Should count available hosts by board."""
174 spec = [self._BOARD]
175 self.afe.get_hosts(multiple_labels=spec).AndReturn([FakeHost()] * 2)
176 self.mox.ReplayAll()
177 self.assertEquals(self.reimager._count_usable_hosts(spec), 2)
178
179
180 def testCountZeroHostsByBoard(self):
181 """Should count the available hosts, by board, getting zero."""
182 spec = [self._BOARD]
183 self.afe.get_hosts(multiple_labels=spec).AndReturn([])
184 self.mox.ReplayAll()
185 self.assertEquals(self.reimager._count_usable_hosts(spec), 0)
186
187
Chris Masone6fed6462011-10-20 16:36:43 -0700188 def testInjectVars(self):
189 """Should inject dict of varibles into provided strings."""
190 def find_all_in(d, s):
191 """Returns true if all key-value pairs in |d| are printed in |s|."""
Chris Masone6cb0d0d2012-03-05 15:37:49 -0800192 for k,v in d.iteritems():
193 if isinstance(v, str):
194 if "%s='%s'\n" % (k,v) not in s:
195 return False
196 else:
197 if "%s=%r\n" % (k,v) not in s:
198 return False
199 return True
Chris Masone6fed6462011-10-20 16:36:43 -0700200
Chris Masone6cb0d0d2012-03-05 15:37:49 -0800201 v = {'v1': 'one', 'v2': 'two', 'v3': None, 'v4': False, 'v5': 5}
Chris Masone8b764252012-01-17 11:12:51 -0800202 self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, '')))
203 self.assertTrue(find_all_in(v, dynamic_suite.inject_vars(v, 'ctrl')))
Chris Masone6fed6462011-10-20 16:36:43 -0700204
205
206 def testReportResultsGood(self):
207 """Should report results in the case where all jobs passed."""
208 job = self.mox.CreateMock(frontend.Job)
209 job.name = 'RPC Client job'
210 job.result = True
211 recorder = self.mox.CreateMock(base_job.base_job)
212 recorder.record('GOOD', mox.IgnoreArg(), job.name)
213 self.mox.ReplayAll()
214 self.reimager._report_results(job, recorder.record)
215
216
217 def testReportResultsBad(self):
218 """Should report results in various job failure cases.
219
220 In this test scenario, there are five hosts, all the 'netbook' platform.
221
222 h1: Did not run
223 h2: Two failed tests
224 h3: Two aborted tests
225 h4: completed, GOOD
226 h5: completed, GOOD
227 """
228 H1 = 'host1'
229 H2 = 'host2'
230 H3 = 'host3'
231 H4 = 'host4'
232 H5 = 'host5'
233
234 class FakeResult(object):
235 def __init__(self, reason):
236 self.reason = reason
237
238
239 # The RPC-client-side Job object that is annotated with results.
240 job = FakeJob()
241 job.result = None # job failed, there are results to report.
242
243 # The semantics of |results_platform_map| and |test_results| are
244 # drawn from frontend.AFE.poll_all_jobs()
245 job.results_platform_map = {'netbook': {'Aborted' : [H3],
Chris Masone8b7cd422012-02-22 13:16:11 -0800246 'Completed' : [H1, H4, H5],
247 'Failed': [H2]
Chris Masone6fed6462011-10-20 16:36:43 -0700248 }
249 }
250 # Gin up fake results for H2 and H3 failure cases.
251 h2 = frontend.TestResults()
252 h2.fail = [FakeResult('a'), FakeResult('b')]
253 h3 = frontend.TestResults()
254 h3.fail = [FakeResult('a'), FakeResult('b')]
255 # Skipping H1 in |test_status| dict means that it did not get run.
256 job.test_status = {H2: h2, H3: h3, H4: {}, H5: {}}
257
258 # Set up recording expectations.
259 rjob = self.mox.CreateMock(base_job.base_job)
260 for res in h2.fail:
261 rjob.record('FAIL', mox.IgnoreArg(), H2, res.reason).InAnyOrder()
262 for res in h3.fail:
263 rjob.record('ABORT', mox.IgnoreArg(), H3, res.reason).InAnyOrder()
264 rjob.record('GOOD', mox.IgnoreArg(), H4).InAnyOrder()
265 rjob.record('GOOD', mox.IgnoreArg(), H5).InAnyOrder()
266 rjob.record(
267 'ERROR', mox.IgnoreArg(), H1, mox.IgnoreArg()).InAnyOrder()
268
269 self.mox.ReplayAll()
270 self.reimager._report_results(job, rjob.record)
271
272
273 def testScheduleJob(self):
274 """Should be able to create a job with the AFE."""
275 # Fake out getting the autoupdate control file contents.
276 cf_getter = self.mox.CreateMock(control_file_getter.ControlFileGetter)
277 cf_getter.get_control_file_contents_by_name('autoupdate').AndReturn('')
278 self.reimager._cf_getter = cf_getter
279
Chris Masone6dca10a2012-02-15 15:41:42 -0800280 self._CONFIG.override_config_value('CROS',
281 'image_url_pattern',
282 self._URL)
Chris Masone6fed6462011-10-20 16:36:43 -0700283 self.afe.create_job(
Chris Masone8b7cd422012-02-22 13:16:11 -0800284 control_file=mox.And(mox.StrContains(self._BUILD),
285 mox.StrContains(self._URL % self._BUILD)),
286 name=mox.StrContains(self._BUILD),
Chris Masone6fed6462011-10-20 16:36:43 -0700287 control_type='Server',
Chris Masone5374c672012-03-05 15:11:39 -0800288 meta_hosts=[self._BOARD] * self._NUM,
Chris Masone99378582012-04-30 13:10:58 -0700289 dependencies=[],
290 priority='Low')
Chris Masone6fed6462011-10-20 16:36:43 -0700291 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700292 self.reimager._schedule_reimage_job(self._BUILD, self._BOARD, None,
293 self._NUM)
Chris Masone6fed6462011-10-20 16:36:43 -0700294
295
Chris Masone62579122012-03-08 15:18:43 -0800296 def expect_attempt(self, success, ex=None, check_hosts=True):
Chris Masone6fed6462011-10-20 16:36:43 -0700297 """Sets up |self.reimager| to expect an attempt() that returns |success|
298
Chris Masoned368cc42012-03-07 15:16:59 -0800299 Also stubs out Reimger._clear_build_state(), should the caller wish
300 to set an expectation there as well.
301
Chris Masone796fcf12012-02-22 16:53:31 -0800302 @param success: the value returned by poll_job_results()
303 @param ex: if not None, |ex| is raised by get_jobs()
Chris Masone6fed6462011-10-20 16:36:43 -0700304 @return a FakeJob configured with appropriate expectations
305 """
306 canary = FakeJob()
307 self.mox.StubOutWithMock(self.reimager, '_ensure_version_label')
Chris Masone8b7cd422012-02-22 13:16:11 -0800308 self.reimager._ensure_version_label(mox.StrContains(self._BUILD))
Chris Masone6fed6462011-10-20 16:36:43 -0700309
310 self.mox.StubOutWithMock(self.reimager, '_schedule_reimage_job')
Chris Masone8b7cd422012-02-22 13:16:11 -0800311 self.reimager._schedule_reimage_job(self._BUILD,
Chris Masonec43448f2012-05-31 12:55:59 -0700312 self._BOARD,
313 None,
314 self._NUM).AndReturn(canary)
Chris Masone62579122012-03-08 15:18:43 -0800315 if check_hosts:
316 self.mox.StubOutWithMock(self.reimager, '_count_usable_hosts')
317 self.reimager._count_usable_hosts(
318 mox.IgnoreArg()).AndReturn(self._NUM)
Chris Masone5374c672012-03-05 15:11:39 -0800319
Chris Masone6fed6462011-10-20 16:36:43 -0700320 if success is not None:
321 self.mox.StubOutWithMock(self.reimager, '_report_results')
322 self.reimager._report_results(canary, mox.IgnoreArg())
Chris Masoned368cc42012-03-07 15:16:59 -0800323 canary.results_platform_map = {None: {'Total': [canary.hostname]}}
324
Chris Masone6fed6462011-10-20 16:36:43 -0700325
326 self.afe.get_jobs(id=canary.id, not_yet_run=True).AndReturn([])
Chris Masone796fcf12012-02-22 16:53:31 -0800327 if ex is not None:
328 self.afe.get_jobs(id=canary.id, finished=True).AndRaise(ex)
329 else:
330 self.afe.get_jobs(id=canary.id, finished=True).AndReturn([canary])
331 self.afe.poll_job_results(mox.IgnoreArg(),
332 canary, 0).AndReturn(success)
Chris Masone6fed6462011-10-20 16:36:43 -0700333
Chris Masoned368cc42012-03-07 15:16:59 -0800334 self.mox.StubOutWithMock(self.reimager, '_clear_build_state')
335
Chris Masone6fed6462011-10-20 16:36:43 -0700336 return canary
337
338
339 def testSuccessfulReimage(self):
340 """Should attempt a reimage and record success."""
Chris Masone62579122012-03-08 15:18:43 -0800341 canary = self.expect_attempt(success=True)
Chris Masone6fed6462011-10-20 16:36:43 -0700342
343 rjob = self.mox.CreateMock(base_job.base_job)
344 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
345 rjob.record('END GOOD', mox.IgnoreArg(), mox.IgnoreArg())
Chris Masoned368cc42012-03-07 15:16:59 -0800346 self.reimager._clear_build_state(mox.StrContains(canary.hostname))
Chris Masone6fed6462011-10-20 16:36:43 -0700347 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700348 self.reimager.attempt(self._BUILD, self._BOARD, None, rjob.record, True)
Chris Masoned368cc42012-03-07 15:16:59 -0800349 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone6fed6462011-10-20 16:36:43 -0700350
351
352 def testFailedReimage(self):
353 """Should attempt a reimage and record failure."""
Chris Masone62579122012-03-08 15:18:43 -0800354 canary = self.expect_attempt(success=False)
Chris Masone6fed6462011-10-20 16:36:43 -0700355
356 rjob = self.mox.CreateMock(base_job.base_job)
357 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
358 rjob.record('END FAIL', mox.IgnoreArg(), mox.IgnoreArg())
Chris Masoned368cc42012-03-07 15:16:59 -0800359 self.reimager._clear_build_state(mox.StrContains(canary.hostname))
Chris Masone6fed6462011-10-20 16:36:43 -0700360 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700361 self.reimager.attempt(self._BUILD, self._BOARD, None, rjob.record, True)
Chris Masoned368cc42012-03-07 15:16:59 -0800362 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone6fed6462011-10-20 16:36:43 -0700363
364
365 def testReimageThatNeverHappened(self):
366 """Should attempt a reimage and record that it didn't run."""
Chris Masone62579122012-03-08 15:18:43 -0800367 canary = self.expect_attempt(success=None)
Chris Masone6fed6462011-10-20 16:36:43 -0700368
369 rjob = self.mox.CreateMock(base_job.base_job)
370 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
371 rjob.record('FAIL', mox.IgnoreArg(), canary.name, mox.IgnoreArg())
372 rjob.record('END FAIL', mox.IgnoreArg(), mox.IgnoreArg())
373 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700374 self.reimager.attempt(self._BUILD, self._BOARD, None, rjob.record, True)
Chris Masoned368cc42012-03-07 15:16:59 -0800375 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone6fed6462011-10-20 16:36:43 -0700376
377
Chris Masone796fcf12012-02-22 16:53:31 -0800378 def testReimageThatRaised(self):
379 """Should attempt a reimage that raises an exception and record that."""
380 ex_message = 'Oh no!'
Chris Masone62579122012-03-08 15:18:43 -0800381 canary = self.expect_attempt(success=None, ex=Exception(ex_message))
Chris Masone796fcf12012-02-22 16:53:31 -0800382
383 rjob = self.mox.CreateMock(base_job.base_job)
384 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
385 rjob.record('END ERROR', mox.IgnoreArg(), mox.IgnoreArg(), ex_message)
386 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700387 self.reimager.attempt(self._BUILD, self._BOARD, None, rjob.record, True)
Chris Masone62579122012-03-08 15:18:43 -0800388 self.reimager.clear_reimaged_host_state(self._BUILD)
389
390
391 def testSuccessfulReimageThatCouldNotScheduleRightAway(self):
Chris Masone99378582012-04-30 13:10:58 -0700392 """Should attempt reimage, ignoring host availability; record success.
Chris Masone62579122012-03-08 15:18:43 -0800393 """
394 canary = self.expect_attempt(success=True, check_hosts=False)
395
396 rjob = self.mox.CreateMock(base_job.base_job)
397 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
398 rjob.record('END GOOD', mox.IgnoreArg(), mox.IgnoreArg())
399 self.reimager._clear_build_state(mox.StrContains(canary.hostname))
400 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700401 self.reimager.attempt(self._BUILD, self._BOARD, None, rjob.record,
402 False)
Chris Masoned368cc42012-03-07 15:16:59 -0800403 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone796fcf12012-02-22 16:53:31 -0800404
405
Chris Masone5374c672012-03-05 15:11:39 -0800406 def testReimageThatCouldNotSchedule(self):
407 """Should attempt a reimage that can't be scheduled."""
Chris Masone502b71e2012-04-10 10:41:35 -0700408 self.mox.StubOutWithMock(self.reimager, '_ensure_version_label')
409 self.reimager._ensure_version_label(mox.StrContains(self._BUILD))
410
411 self.mox.StubOutWithMock(self.reimager, '_count_usable_hosts')
412 self.reimager._count_usable_hosts(mox.IgnoreArg()).AndReturn(1)
413
414 rjob = self.mox.CreateMock(base_job.base_job)
415 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
416 rjob.record('END WARN', mox.IgnoreArg(), mox.IgnoreArg(),
417 mox.StrContains('Too few hosts'))
Chris Masone502b71e2012-04-10 10:41:35 -0700418 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700419 self.reimager.attempt(self._BUILD, self._BOARD, None, rjob.record, True)
Chris Masone502b71e2012-04-10 10:41:35 -0700420 self.reimager.clear_reimaged_host_state(self._BUILD)
421
422
423 def testReimageWithNoAvailableHosts(self):
424 """Should attempt a reimage while all hosts are dead."""
Chris Masone62579122012-03-08 15:18:43 -0800425 self.mox.StubOutWithMock(self.reimager, '_ensure_version_label')
426 self.reimager._ensure_version_label(mox.StrContains(self._BUILD))
Chris Masone5374c672012-03-05 15:11:39 -0800427
428 self.mox.StubOutWithMock(self.reimager, '_count_usable_hosts')
429 self.reimager._count_usable_hosts(mox.IgnoreArg()).AndReturn(0)
430
431 rjob = self.mox.CreateMock(base_job.base_job)
432 rjob.record('START', mox.IgnoreArg(), mox.IgnoreArg())
Chris Masone502b71e2012-04-10 10:41:35 -0700433 rjob.record('END ERROR', mox.IgnoreArg(), mox.IgnoreArg(),
434 mox.StrContains('All hosts'))
Chris Masone5374c672012-03-05 15:11:39 -0800435 self.mox.ReplayAll()
Chris Masonec43448f2012-05-31 12:55:59 -0700436 self.reimager.attempt(self._BUILD, self._BOARD, None, rjob.record, True)
Chris Masoned368cc42012-03-07 15:16:59 -0800437 self.reimager.clear_reimaged_host_state(self._BUILD)
Chris Masone5374c672012-03-05 15:11:39 -0800438
439
Chris Masone99378582012-04-30 13:10:58 -0700440class StatusContains(mox.Comparator):
441 @staticmethod
442 def CreateFromStrings(status=None, test_name=None, reason=None):
443 status_comp = mox.StrContains(status) if status else mox.IgnoreArg()
444 name_comp = mox.StrContains(test_name) if test_name else mox.IgnoreArg()
445 reason_comp = mox.StrContains(reason) if reason else mox.IgnoreArg()
446 return StatusContains(status_comp, name_comp, reason_comp)
447
448
449 def __init__(self, status=mox.IgnoreArg(), test_name=mox.IgnoreArg(),
450 reason=mox.IgnoreArg()):
451 """Initialize.
452
453 Takes mox.Comparator objects to apply to dynamic_suite.Status
454 member variables.
455
456 @param status: status code, e.g. 'INFO', 'START', etc.
457 @param test_name: expected test name.
458 @param reason: expected reason
459 """
460 self._status = status
461 self._test_name = test_name
462 self._reason = reason
463
464
465 def equals(self, rhs):
Chris Masoneb0a95612012-05-31 14:25:03 -0700466 """Check to see if fields match base_job.status_log_entry obj in rhs.
Chris Masone99378582012-04-30 13:10:58 -0700467
Chris Masoneb0a95612012-05-31 14:25:03 -0700468 @param rhs: base_job.status_log_entry object to match.
Chris Masone99378582012-04-30 13:10:58 -0700469 @return boolean
470 """
471 return (self._status.equals(rhs.status_code) and
472 self._test_name.equals(rhs.operation) and
473 self._reason.equals(rhs.message))
474
475
476 def __repr__(self):
477 return '<Status containing \'%s\t%s\t%s\'>' % (self._status,
478 self._test_name,
479 self._reason)
480
481
Chris Masone6fed6462011-10-20 16:36:43 -0700482class SuiteTest(mox.MoxTestBase):
483 """Unit tests for dynamic_suite.Suite.
484
Chris Masone8b7cd422012-02-22 13:16:11 -0800485 @var _BUILD: fake build
Chris Masone6fed6462011-10-20 16:36:43 -0700486 @var _TAG: fake suite tag
487 """
488
Chris Masone8b7cd422012-02-22 13:16:11 -0800489 _BUILD = 'build'
490 _TAG = 'suite_tag'
Chris Masone6fed6462011-10-20 16:36:43 -0700491
492
493 def setUp(self):
Chris Masone6fed6462011-10-20 16:36:43 -0700494 super(SuiteTest, self).setUp()
495 self.afe = self.mox.CreateMock(frontend.AFE)
496 self.tko = self.mox.CreateMock(frontend.TKO)
497
498 self.tmpdir = tempfile.mkdtemp(suffix=type(self).__name__)
499
500 self.getter = self.mox.CreateMock(control_file_getter.ControlFileGetter)
501
Chris Masone8d6e6412012-06-28 11:20:56 -0700502 self.files = {'one': FakeControlData(self._TAG, 'data_one', expr=True),
503 'two': FakeControlData(self._TAG, 'data_two'),
504 'three': FakeControlData(self._TAG, 'data_three')}
Chris Masone6fed6462011-10-20 16:36:43 -0700505
Chris Masone75a20612012-05-08 12:37:31 -0700506 self.files_to_filter = {
Chris Masone8d6e6412012-06-28 11:20:56 -0700507 'with/deps/...': FakeControlData(self._TAG, 'gets filtered'),
508 'with/profilers/...': FakeControlData(self._TAG, 'gets filtered')}
Chris Masone75a20612012-05-08 12:37:31 -0700509
Chris Masone6fed6462011-10-20 16:36:43 -0700510
511 def tearDown(self):
512 super(SuiteTest, self).tearDown()
513 shutil.rmtree(self.tmpdir, ignore_errors=True)
514
515
516 def expect_control_file_parsing(self):
517 """Expect an attempt to parse the 'control files' in |self.files|."""
Chris Masone75a20612012-05-08 12:37:31 -0700518 all_files = self.files.keys() + self.files_to_filter.keys()
Chris Masoneed356392012-05-08 14:07:13 -0700519 self._set_control_file_parsing_expectations(False, all_files,
520 self.files.iteritems())
521
522
523 def expect_control_file_reparsing(self):
524 """Expect re-parsing the 'control files' in |self.files|."""
525 all_files = self.files.keys() + self.files_to_filter.keys()
526 self._set_control_file_parsing_expectations(True, all_files,
527 self.files.iteritems())
528
529
530 def expect_racy_control_file_reparsing(self, new_files):
531 """Expect re-fetching and parsing of control files to return extra.
532
533 @param new_files: extra control files that showed up during scheduling.
534 """
535 all_files = (self.files.keys() + self.files_to_filter.keys() +
536 new_files.keys())
537 new_files.update(self.files)
538 self._set_control_file_parsing_expectations(True, all_files,
539 new_files.iteritems())
540
541
542 def _set_control_file_parsing_expectations(self, already_stubbed,
543 file_list, files_to_parse):
544 """Expect an attempt to parse the 'control files' in |files|.
545
546 @param already_stubbed: parse_control_string already stubbed out.
547 @param file_list: the files the dev server returns
548 @param files_to_parse: the {'name': FakeControlData} dict of files we
549 expect to get parsed.
550 """
551 if not already_stubbed:
552 self.mox.StubOutWithMock(control_data, 'parse_control_string')
553
554 self.getter.get_control_file_list().AndReturn(file_list)
555 for file, data in files_to_parse:
556 self.getter.get_control_file_contents(
557 file).InAnyOrder().AndReturn(data.string)
Chris Masone6fed6462011-10-20 16:36:43 -0700558 control_data.parse_control_string(
Chris Masoneed356392012-05-08 14:07:13 -0700559 data.string, raise_warnings=True).InAnyOrder().AndReturn(data)
Chris Masone6fed6462011-10-20 16:36:43 -0700560
561
562 def testFindAndParseStableTests(self):
563 """Should find only non-experimental tests that match a predicate."""
564 self.expect_control_file_parsing()
565 self.mox.ReplayAll()
566
567 predicate = lambda d: d.text == self.files['two'].string
568 tests = dynamic_suite.Suite.find_and_parse_tests(self.getter, predicate)
569 self.assertEquals(len(tests), 1)
570 self.assertEquals(tests[0], self.files['two'])
571
572
573 def testFindAndParseTests(self):
574 """Should find all tests that match a predicate."""
575 self.expect_control_file_parsing()
576 self.mox.ReplayAll()
577
578 predicate = lambda d: d.text != self.files['two'].string
579 tests = dynamic_suite.Suite.find_and_parse_tests(self.getter,
580 predicate,
581 add_experimental=True)
582 self.assertEquals(len(tests), 2)
583 self.assertTrue(self.files['one'] in tests)
584 self.assertTrue(self.files['three'] in tests)
585
586
587 def mock_control_file_parsing(self):
588 """Fake out find_and_parse_tests(), returning content from |self.files|.
589 """
590 for test in self.files.values():
591 test.text = test.string # mimic parsing.
592 self.mox.StubOutWithMock(dynamic_suite.Suite, 'find_and_parse_tests')
593 dynamic_suite.Suite.find_and_parse_tests(
594 mox.IgnoreArg(),
595 mox.IgnoreArg(),
596 add_experimental=True).AndReturn(self.files.values())
597
598
599 def testStableUnstableFilter(self):
600 """Should distinguish between experimental and stable tests."""
601 self.mock_control_file_parsing()
602 self.mox.ReplayAll()
603 suite = dynamic_suite.Suite.create_from_name(self._TAG, self.tmpdir,
Chris Masoned6f38c82012-02-22 14:53:42 -0800604 afe=self.afe, tko=self.tko)
Chris Masone6fed6462011-10-20 16:36:43 -0700605
606 self.assertTrue(self.files['one'] in suite.tests)
607 self.assertTrue(self.files['two'] in suite.tests)
608 self.assertTrue(self.files['one'] in suite.unstable_tests())
609 self.assertTrue(self.files['two'] in suite.stable_tests())
610 self.assertFalse(self.files['one'] in suite.stable_tests())
611 self.assertFalse(self.files['two'] in suite.unstable_tests())
612
613
614 def expect_job_scheduling(self, add_experimental):
615 """Expect jobs to be scheduled for 'tests' in |self.files|.
616
617 @param add_experimental: expect jobs for experimental tests as well.
618 """
619 for test in self.files.values():
620 if not add_experimental and test.experimental:
621 continue
622 self.afe.create_job(
623 control_file=test.text,
Chris Masone8b7cd422012-02-22 13:16:11 -0800624 name=mox.And(mox.StrContains(self._BUILD),
Chris Masone6fed6462011-10-20 16:36:43 -0700625 mox.StrContains(test.name)),
626 control_type=mox.IgnoreArg(),
Chris Masone8b7cd422012-02-22 13:16:11 -0800627 meta_hosts=[dynamic_suite.VERSION_PREFIX + self._BUILD],
Chris Masonebafbbb02012-05-16 13:41:36 -0700628 dependencies=[],
629 keyvals={'build': self._BUILD, 'suite': self._TAG}
630 ).AndReturn(FakeJob())
Chris Masone6fed6462011-10-20 16:36:43 -0700631
632
633 def testScheduleTests(self):
634 """Should schedule stable and experimental tests with the AFE."""
635 self.mock_control_file_parsing()
636 self.expect_job_scheduling(add_experimental=True)
637
638 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800639 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800640 afe=self.afe, tko=self.tko)
Chris Masone8b7cd422012-02-22 13:16:11 -0800641 suite.schedule()
Chris Masone6fed6462011-10-20 16:36:43 -0700642
643
Scott Zawalski9ece6532012-02-28 14:10:47 -0500644 def testScheduleTestsAndRecord(self):
645 """Should schedule stable and experimental tests with the AFE."""
646 self.mock_control_file_parsing()
647 self.mox.ReplayAll()
648 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
649 afe=self.afe, tko=self.tko,
650 results_dir=self.tmpdir)
651 self.mox.ResetAll()
652 self.expect_job_scheduling(add_experimental=True)
653 self.mox.StubOutWithMock(suite, '_record_scheduled_jobs')
654 suite._record_scheduled_jobs()
655 self.mox.ReplayAll()
656 suite.schedule()
Scott Zawalskie5bb1c52012-02-29 13:15:50 -0500657 for job in suite._jobs:
658 self.assertTrue(hasattr(job, 'test_name'))
Scott Zawalski9ece6532012-02-28 14:10:47 -0500659
660
Chris Masone6fed6462011-10-20 16:36:43 -0700661 def testScheduleStableTests(self):
662 """Should schedule only stable tests with the AFE."""
663 self.mock_control_file_parsing()
664 self.expect_job_scheduling(add_experimental=False)
665
666 self.mox.ReplayAll()
Chris Masone8b7cd422012-02-22 13:16:11 -0800667 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800668 afe=self.afe, tko=self.tko)
Chris Masone8b7cd422012-02-22 13:16:11 -0800669 suite.schedule(add_experimental=False)
Chris Masone6fed6462011-10-20 16:36:43 -0700670
671
Chris Masonefef21382012-01-17 11:16:32 -0800672 def _createSuiteWithMockedTestsAndControlFiles(self):
673 """Create a Suite, using mocked tests and control file contents.
674
675 @return Suite object, after mocking out behavior needed to create it.
676 """
Chris Masonefef21382012-01-17 11:16:32 -0800677 self.expect_control_file_parsing()
678 self.mox.ReplayAll()
Scott Zawalski9ece6532012-02-28 14:10:47 -0500679 suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
Chris Masoned6f38c82012-02-22 14:53:42 -0800680 self.getter, self.afe,
681 self.tko)
Chris Masonefef21382012-01-17 11:16:32 -0800682 self.mox.ResetAll()
683 return suite
684
685
Chris Masoneed356392012-05-08 14:07:13 -0700686 def schedule_and_expect_these_results(self, suite, results, recorder):
687 self.mox.StubOutWithMock(suite, 'schedule')
688 suite.schedule(True)
Chris Masone6fed6462011-10-20 16:36:43 -0700689 for result in results:
Scott Zawalskiab25bd62012-02-10 18:29:12 -0500690 status = result[0]
Chris Masone99378582012-04-30 13:10:58 -0700691 test_name = result[1]
692 recorder.record_entry(
693 StatusContains.CreateFromStrings('START', test_name))
694 recorder.record_entry(
695 StatusContains.CreateFromStrings(*result)).InAnyOrder('results')
696 recorder.record_entry(
697 StatusContains.CreateFromStrings('END %s' % status, test_name))
Chris Masone8d6e6412012-06-28 11:20:56 -0700698 self.mox.StubOutWithMock(job_status, 'wait_for_results')
699 job_status.wait_for_results(self.afe, self.tko, suite._jobs).AndReturn(
700 map(lambda r: job_status.Status(*r), results))
Chris Masoneed356392012-05-08 14:07:13 -0700701
702
703 def testRunAndWaitSuccess(self):
704 """Should record successful results."""
705 suite = self._createSuiteWithMockedTestsAndControlFiles()
706
707 recorder = self.mox.CreateMock(base_job.base_job)
708 recorder.record_entry(
709 StatusContains.CreateFromStrings('INFO', 'Start %s' % self._TAG))
710
711 results = [('GOOD', 'good'), ('FAIL', 'bad', 'reason')]
712 self.schedule_and_expect_these_results(suite, results, recorder)
713 self.expect_control_file_reparsing()
Chris Masone6fed6462011-10-20 16:36:43 -0700714 self.mox.ReplayAll()
715
Chris Masone99378582012-04-30 13:10:58 -0700716 suite.run_and_wait(recorder.record_entry, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700717
718
719 def testRunAndWaitFailure(self):
720 """Should record failure to gather results."""
Chris Masonefef21382012-01-17 11:16:32 -0800721 suite = self._createSuiteWithMockedTestsAndControlFiles()
722
Chris Masone6fed6462011-10-20 16:36:43 -0700723 recorder = self.mox.CreateMock(base_job.base_job)
Chris Masone99378582012-04-30 13:10:58 -0700724 recorder.record_entry(
725 StatusContains.CreateFromStrings('INFO', 'Start %s' % self._TAG))
726 recorder.record_entry(
727 StatusContains.CreateFromStrings('FAIL', self._TAG, 'waiting'))
Chris Masone6fed6462011-10-20 16:36:43 -0700728
Chris Masone6fed6462011-10-20 16:36:43 -0700729 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone8b7cd422012-02-22 13:16:11 -0800730 suite.schedule(True)
Chris Masone8d6e6412012-06-28 11:20:56 -0700731 self.mox.StubOutWithMock(job_status, 'wait_for_results')
732 job_status.wait_for_results(mox.IgnoreArg(),
733 mox.IgnoreArg(),
734 mox.IgnoreArg()).AndRaise(
735 Exception('Expected during test.'))
Chris Masoneed356392012-05-08 14:07:13 -0700736 self.expect_control_file_reparsing()
Chris Masone6fed6462011-10-20 16:36:43 -0700737 self.mox.ReplayAll()
738
Chris Masone99378582012-04-30 13:10:58 -0700739 suite.run_and_wait(recorder.record_entry, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700740
741
742 def testRunAndWaitScheduleFailure(self):
Chris Masonefef21382012-01-17 11:16:32 -0800743 """Should record failure to schedule jobs."""
744 suite = self._createSuiteWithMockedTestsAndControlFiles()
745
Chris Masone6fed6462011-10-20 16:36:43 -0700746 recorder = self.mox.CreateMock(base_job.base_job)
Chris Masone99378582012-04-30 13:10:58 -0700747 recorder.record_entry(
748 StatusContains.CreateFromStrings('INFO', 'Start %s' % self._TAG))
749 recorder.record_entry(
750 StatusContains.CreateFromStrings('FAIL', self._TAG, 'scheduling'))
Chris Masone6fed6462011-10-20 16:36:43 -0700751
Chris Masone6fed6462011-10-20 16:36:43 -0700752 self.mox.StubOutWithMock(suite, 'schedule')
Chris Masone99378582012-04-30 13:10:58 -0700753 suite.schedule(True).AndRaise(Exception('Expected during test.'))
Chris Masoneed356392012-05-08 14:07:13 -0700754 self.expect_control_file_reparsing()
755 self.mox.ReplayAll()
756
757 suite.run_and_wait(recorder.record_entry, True)
758
759
760 def testRunAndWaitDevServerRacyFailure(self):
761 """Should record discovery of dev server races in listing files."""
762 suite = self._createSuiteWithMockedTestsAndControlFiles()
763
764 recorder = self.mox.CreateMock(base_job.base_job)
765 recorder.record_entry(
766 StatusContains.CreateFromStrings('INFO', 'Start %s' % self._TAG))
767
768 results = [('GOOD', 'good'), ('FAIL', 'bad', 'reason')]
769 self.schedule_and_expect_these_results(suite, results, recorder)
770
771 self.expect_racy_control_file_reparsing(
Chris Masone8d6e6412012-06-28 11:20:56 -0700772 {'new': FakeControlData(self._TAG, '!')})
Chris Masoneed356392012-05-08 14:07:13 -0700773
774 recorder.record_entry(
775 StatusContains.CreateFromStrings('FAIL', self._TAG, 'Dev Server'))
Chris Masone6fed6462011-10-20 16:36:43 -0700776 self.mox.ReplayAll()
777
Chris Masone99378582012-04-30 13:10:58 -0700778 suite.run_and_wait(recorder.record_entry, True)
Chris Masone6fed6462011-10-20 16:36:43 -0700779
780
781if __name__ == '__main__':
782 unittest.main()