Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / buildbot / cbuildbot_run_unittest.py
1 #!/usr/bin/python
2
3 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """Test the cbuildbot_run module."""
8
9 import logging
10 import os
11 import cPickle
12 import sys
13 import time
14
15 sys.path.insert(0, os.path.abspath('%s/../..' % os.path.dirname(__file__)))
16 from chromite.buildbot import cbuildbot_config
17 from chromite.buildbot import cbuildbot_run
18 from chromite.lib import cros_test_lib
19 from chromite.lib import parallel
20
21 import mock
22
23 DEFAULT_ARCHIVE_GS_PATH = 'bogus_bucket/TheArchiveBase'
24 DEFAULT_ARCHIVE_BASE = 'gs://%s' % DEFAULT_ARCHIVE_GS_PATH
25 DEFAULT_BUILDROOT = '/tmp/foo/bar/buildroot'
26 DEFAULT_BUILDNUMBER = 12345
27 DEFAULT_BRANCH = 'TheBranch'
28 DEFAULT_CHROME_BRANCH = 'TheChromeBranch'
29 DEFAULT_VERSION_STRING = 'TheVersionString'
30 DEFAULT_BOARD = 'TheBoard'
31 DEFAULT_BOT_NAME = 'TheCoolBot'
32
33 # Access to protected member.
34 # pylint: disable=W0212
35
36 DEFAULT_OPTIONS = cros_test_lib.EasyAttr(
37     archive_base=DEFAULT_ARCHIVE_BASE,
38     buildroot=DEFAULT_BUILDROOT,
39     buildnumber=DEFAULT_BUILDNUMBER,
40     buildbot=True,
41     branch=DEFAULT_BRANCH,
42     remote_trybot=False,
43     debug=False,
44     postsync_patch=True,
45 )
46 DEFAULT_CONFIG = cbuildbot_config._config(
47     name=DEFAULT_BOT_NAME,
48     master=True,
49     boards=[DEFAULT_BOARD],
50     postsync_patch=True,
51     child_configs=[cbuildbot_config._config(name='foo', postsync_patch=False,
52                                             boards=[]),
53                    cbuildbot_config._config(name='bar', postsync_patch=False,
54                                             boards=[]),
55                   ],
56 )
57
58 DEFAULT_VERSION = '6543.2.1'
59
60
61 def _ExtendDefaultOptions(**kwargs):
62   """Extend DEFAULT_OPTIONS with keys/values in kwargs."""
63   options_kwargs = DEFAULT_OPTIONS.copy()
64   options_kwargs.update(kwargs)
65   return cros_test_lib.EasyAttr(**options_kwargs)
66
67
68 def _ExtendDefaultConfig(**kwargs):
69   """Extend DEFAULT_CONFIG with keys/values in kwargs."""
70   config_kwargs = DEFAULT_CONFIG.copy()
71   config_kwargs.update(kwargs)
72   return cbuildbot_config._config(**config_kwargs)
73
74
75 class ExceptionsTest(cros_test_lib.TestCase):
76   """Test that the exceptions in the module are sane."""
77
78   def _TestException(self, err, expected_startswith):
79     """Test that str and pickle behavior of |err| are as expected."""
80     err2 = cPickle.loads(cPickle.dumps(err, cPickle.HIGHEST_PROTOCOL))
81
82     self.assertTrue(str(err).startswith(expected_startswith))
83     self.assertEqual(str(err), str(err2))
84
85   def testParallelAttributeError(self):
86     """Test ParallelAttributeError message and pickle behavior."""
87     err1 = cbuildbot_run.ParallelAttributeError('SomeAttr')
88     self._TestException(err1, 'No such parallel run attribute')
89
90     err2 = cbuildbot_run.ParallelAttributeError('SomeAttr', 'SomeBoard',
91                                                 'SomeTarget')
92     self._TestException(err2, 'No such board-specific parallel run attribute')
93
94   def testAttrSepCountError(self):
95     """Test AttrSepCountError message and pickle behavior."""
96     err1 = cbuildbot_run.AttrSepCountError('SomeAttr')
97     self._TestException(err1, 'Attribute name has an unexpected number')
98
99   def testAttrNotPickleableError(self):
100     """Test AttrNotPickleableError message and pickle behavior."""
101     err1 = cbuildbot_run.AttrNotPickleableError('SomeAttr', 'SomeValue')
102     self._TestException(err1, 'Run attribute "SomeAttr" value cannot')
103
104
105 # TODO(mtennant): Turn this into a PartialMock.
106 class _BuilderRunTestCase(cros_test_lib.MockTestCase):
107   """Provide methods for creating BuilderRun or ChildBuilderRun."""
108
109   def setUp(self):
110     self._manager = parallel.Manager()
111
112     # Mimic entering a 'with' statement.
113     self._manager.__enter__()
114
115   def tearDown(self):
116     # Mimic exiting a 'with' statement.
117     self._manager.__exit__(None, None, None)
118
119   def _NewRunAttributes(self):
120     return cbuildbot_run.RunAttributes(self._manager)
121
122   def _NewBuilderRun(self, options=None, config=None):
123     """Create a BuilderRun objection from options and config values.
124
125     Args:
126       options: Specify options or default to DEFAULT_OPTIONS.
127       config: Specify build config or default to DEFAULT_CONFIG.
128
129     Returns:
130       BuilderRun object.
131     """
132     options = options or DEFAULT_OPTIONS
133     config = config or DEFAULT_CONFIG
134
135     return cbuildbot_run.BuilderRun(options, config, self._manager)
136
137   def _NewChildBuilderRun(self, child_index, options=None, config=None):
138     """Create a ChildBuilderRun objection from options and config values.
139
140     Args:
141       child_index: Index of child config to use within config.
142       options: Specify options or default to DEFAULT_OPTIONS.
143       config: Specify build config or default to DEFAULT_CONFIG.
144
145     Returns:
146       ChildBuilderRun object.
147     """
148     run = self._NewBuilderRun(options, config)
149     return cbuildbot_run.ChildBuilderRun(run, child_index)
150
151
152 class BuilderRunPickleTest(_BuilderRunTestCase):
153   """Make sure BuilderRun objects can be pickled."""
154
155   def setUp(self):
156     self.real_config = cbuildbot_config.config['x86-alex-release-group']
157     self.PatchObject(cbuildbot_run._BuilderRunBase, 'GetVersion',
158                      return_value=DEFAULT_VERSION)
159
160   def _TestPickle(self, run1):
161     self.assertEquals(DEFAULT_VERSION, run1.GetVersion())
162     run1.attrs.release_tag = 'TheReleaseTag'
163
164     # Accessing a method on BuilderRun has special behavior, so access and
165     # use one before pickling.
166     patch_after_sync = run1.ShouldPatchAfterSync()
167
168     # Access the archive object before pickling, too.
169     upload_url = run1.GetArchive().upload_url
170
171     # Pickle and unpickle run1 into run2.
172     run2 = cPickle.loads(cPickle.dumps(run1, cPickle.HIGHEST_PROTOCOL))
173
174     self.assertEquals(run1.buildnumber, run2.buildnumber)
175     self.assertEquals(run1.config.boards, run2.config.boards)
176     self.assertEquals(run1.options.branch, run2.options.branch)
177     self.assertEquals(run1.attrs.release_tag, run2.attrs.release_tag)
178     self.assertRaises(AttributeError, getattr, run1.attrs, 'manifest_manager')
179     self.assertRaises(AttributeError, getattr, run2.attrs, 'manifest_manager')
180     self.assertEquals(patch_after_sync, run2.ShouldPatchAfterSync())
181     self.assertEquals(upload_url, run2.GetArchive().upload_url)
182
183     # The attrs objects should be identical.
184     self.assertTrue(run1.attrs is run2.attrs)
185
186     # And the run objects themselves are different.
187     self.assertFalse(run1 is run2)
188
189   def testPickleBuilderRun(self):
190     self._TestPickle(self._NewBuilderRun(config=self.real_config))
191
192   def testPickleChildBuilderRun(self):
193     self._TestPickle(self._NewChildBuilderRun(0, config=self.real_config))
194
195
196 class BuilderRunTest(_BuilderRunTestCase):
197   """Test the BuilderRun class."""
198
199   def testInit(self):
200     with mock.patch.object(cbuildbot_run._BuilderRunBase, 'GetVersion') as m:
201       m.return_value = DEFAULT_VERSION
202
203       run = self._NewBuilderRun()
204       self.assertEquals(DEFAULT_BUILDROOT, run.buildroot)
205       self.assertEquals(DEFAULT_BUILDNUMBER, run.buildnumber)
206       self.assertEquals(DEFAULT_BRANCH, run.manifest_branch)
207       self.assertEquals(DEFAULT_OPTIONS, run.options)
208       self.assertEquals(DEFAULT_CONFIG, run.config)
209       self.assertTrue(isinstance(run.attrs, cbuildbot_run.RunAttributes))
210       self.assertTrue(isinstance(run.GetArchive(),
211                                  cbuildbot_run.cbuildbot_archive.Archive))
212
213       # Make sure methods behave normally, since BuilderRun messes with them.
214       meth1 = run.GetVersionInfo
215       meth2 = run.GetVersionInfo
216       self.assertEqual(meth1.__name__, meth2.__name__)
217
218       # We actually do not support identity and equality checks right now.
219       self.assertNotEqual(meth1, meth2)
220       self.assertFalse(meth1 is meth2)
221
222   def testOptions(self):
223     options = _ExtendDefaultOptions(foo=True, bar=10)
224     run = self._NewBuilderRun(options=options)
225
226     self.assertEquals(True, run.options.foo)
227     self.assertEquals(10, run.options.__getattr__('bar'))
228     self.assertRaises(AttributeError, run.options.__getattr__, 'baz')
229
230   def testConfig(self):
231     config = _ExtendDefaultConfig(foo=True, bar=10)
232     run = self._NewBuilderRun(config=config)
233
234     self.assertEquals(True, run.config.foo)
235     self.assertEquals(10, run.config.__getattr__('bar'))
236     self.assertRaises(AttributeError, run.config.__getattr__, 'baz')
237
238   def testAttrs(self):
239     run = self._NewBuilderRun()
240
241     # manifest_manager is a valid run attribute.  It gives Attribute error
242     # if accessed before being set, but thereafter works fine.
243     self.assertRaises(AttributeError, run.attrs.__getattribute__,
244                       'manifest_manager')
245     run.attrs.manifest_manager = 'foo'
246     self.assertEquals('foo', run.attrs.manifest_manager)
247     self.assertEquals('foo', run.attrs.__getattribute__('manifest_manager'))
248
249     # foobar is not a valid run attribute.  It gives AttributeError when
250     # accessed or changed.
251     self.assertRaises(AttributeError, run.attrs.__getattribute__, 'foobar')
252     self.assertRaises(AttributeError, run.attrs.__setattr__, 'foobar', 'foo')
253
254   def testArchive(self):
255     run = self._NewBuilderRun()
256
257     with mock.patch.object(cbuildbot_run._BuilderRunBase, 'GetVersion') as m:
258       m.return_value = DEFAULT_VERSION
259
260       archive = run.GetArchive()
261
262       # Check archive.archive_path.
263       expected = ('%s/%s/%s/%s' %
264                   (DEFAULT_BUILDROOT,
265                    cbuildbot_run.cbuildbot_archive.Archive._BUILDBOT_ARCHIVE,
266                    DEFAULT_BOT_NAME, DEFAULT_VERSION))
267       self.assertEqual(expected, archive.archive_path)
268
269       # Check archive.upload_url.
270       expected = '%s/%s/%s' % (DEFAULT_ARCHIVE_BASE, DEFAULT_BOT_NAME,
271                                DEFAULT_VERSION)
272       self.assertEqual(expected, archive.upload_url)
273
274       # Check archive.download_url.
275       expected = ('%s%s/%s/%s' %
276                   (cbuildbot_run.cbuildbot_archive.gs.PRIVATE_BASE_HTTPS_URL,
277                    DEFAULT_ARCHIVE_GS_PATH, DEFAULT_BOT_NAME, DEFAULT_VERSION))
278       self.assertEqual(expected, archive.download_url)
279
280   def _RunAccessor(self, method_name, options_dict, config_dict):
281     """Run the given accessor method of the BuilderRun class.
282
283     Create a BuilderRun object with the options and config provided and
284     then return the result of calling the given method on it.
285
286     Args:
287       method_name: A BuilderRun method to call, specified by name.
288       options_dict: Extend default options with this.
289       config_dict: Extend default config with this.
290
291     Returns:
292       Result of calling the given method.
293     """
294     options = _ExtendDefaultOptions(**options_dict)
295     config = _ExtendDefaultConfig(**config_dict)
296     run = self._NewBuilderRun(options=options, config=config)
297     method = getattr(run, method_name)
298     self.assertEqual(method.__name__, method_name)
299     return method()
300
301   def testDualEnableSetting(self):
302     settings = {
303         'prebuilts': 'ShouldUploadPrebuilts',
304         'postsync_patch': 'ShouldPatchAfterSync',
305     }
306
307     # Both option and config enabled should result in True.
308     # Create truth table with three variables in this order:
309     # <key> option value, <key> config value (e.g. <key> == 'prebuilts').
310     truth_table = cros_test_lib.TruthTable(inputs=[(True, True)])
311
312     for inputs in truth_table:
313       option_val, config_val = inputs
314       for key, accessor in settings.iteritems():
315         self.assertEquals(
316             self._RunAccessor(accessor, {key: option_val}, {key: config_val}),
317             truth_table.GetOutput(inputs))
318
319   def testShouldReexecAfterSync(self):
320     # If option and config have postsync_reexec enabled, and this file is not
321     # in the build root, then we expect ShouldReexecAfterSync to return True.
322
323     # Construct a truth table across three variables in this order:
324     # postsync_reexec option value, postsync_reexec config value, same_root.
325     truth_table = cros_test_lib.TruthTable(inputs=[(True, True, False)])
326
327     for inputs in truth_table:
328       option_val, config_val, same_root = inputs
329
330       if same_root:
331         build_root = os.path.dirname(os.path.dirname(__file__))
332       else:
333         build_root = DEFAULT_BUILDROOT
334
335       result = self._RunAccessor(
336           'ShouldReexecAfterSync',
337           {'postsync_reexec': option_val, 'buildroot': build_root},
338           {'postsync_reexec': config_val})
339
340       self.assertEquals(result, truth_table.GetOutput(inputs))
341
342
343 class GetVersionTest(_BuilderRunTestCase):
344   """Test the GetVersion and GetVersionInfo methods of BuilderRun class."""
345   # Access to protected member.
346   # pylint: disable=W0212
347
348   def testGetVersionInfo(self):
349     verinfo = object()
350
351     with mock.patch('cbuildbot_run.manifest_version.VersionInfo.from_repo',
352                     return_value=verinfo) as m:
353       result = cbuildbot_run._BuilderRunBase.GetVersionInfo(DEFAULT_BUILDROOT)
354       self.assertEquals(result, verinfo)
355
356       m.assert_called_once_with(DEFAULT_BUILDROOT)
357
358   def _TestGetVersionReleaseTag(self, release_tag):
359     with mock.patch.object(cbuildbot_run._BuilderRunBase,
360                            'GetVersionInfo') as m:
361       verinfo_mock = mock.Mock()
362       verinfo_mock.chrome_branch = DEFAULT_CHROME_BRANCH
363       verinfo_mock.VersionString = mock.Mock(return_value='VS')
364       m.return_value = verinfo_mock
365
366       # Prepare a real BuilderRun object with a release tag.
367       run = self._NewBuilderRun()
368       run.attrs.release_tag = release_tag
369
370       # Run the test return the result.
371       result = run.GetVersion()
372       m.assert_called_once_with(DEFAULT_BUILDROOT)
373       if release_tag is None:
374         verinfo_mock.VersionString.assert_called_once()
375
376       return result
377
378   def testGetVersionReleaseTag(self):
379     result = self._TestGetVersionReleaseTag('RT')
380     self.assertEquals('R%s-%s' % (DEFAULT_CHROME_BRANCH, 'RT'), result)
381
382   def testGetVersionNoReleaseTag(self):
383     result = self._TestGetVersionReleaseTag(None)
384     expected_result = ('R%s-%s-b%s' %
385                        (DEFAULT_CHROME_BRANCH, 'VS', DEFAULT_BUILDNUMBER))
386     self.assertEquals(result, expected_result)
387
388
389 class ChildBuilderRunTest(_BuilderRunTestCase):
390   """Test the ChildBuilderRun class"""
391
392   def testInit(self):
393     with mock.patch.object(cbuildbot_run._BuilderRunBase, 'GetVersion') as m:
394       m.return_value = DEFAULT_VERSION
395
396       crun = self._NewChildBuilderRun(0)
397       self.assertEquals(DEFAULT_BUILDROOT, crun.buildroot)
398       self.assertEquals(DEFAULT_BUILDNUMBER, crun.buildnumber)
399       self.assertEquals(DEFAULT_BRANCH, crun.manifest_branch)
400       self.assertEquals(DEFAULT_OPTIONS, crun.options)
401       self.assertEquals(DEFAULT_CONFIG.child_configs[0], crun.config)
402       self.assertEquals('foo', crun.config.name)
403       self.assertTrue(isinstance(crun.attrs, cbuildbot_run.RunAttributes))
404       self.assertTrue(isinstance(crun.GetArchive(),
405                                  cbuildbot_run.cbuildbot_archive.Archive))
406
407       # Make sure methods behave normally, since BuilderRun messes with them.
408       meth1 = crun.GetVersionInfo
409       meth2 = crun.GetVersionInfo
410       self.assertEqual(meth1.__name__, meth2.__name__)
411
412       # We actually do not support identity and equality checks right now.
413       self.assertNotEqual(meth1, meth2)
414       self.assertFalse(meth1 is meth2)
415
416
417 class RunAttributesTest(_BuilderRunTestCase):
418   """Test the RunAttributes class."""
419
420   BOARD = 'SomeBoard'
421   TARGET = 'SomeConfigName'
422   VALUE = 'AnyValueWillDo'
423
424   # Any valid board-specific attribute will work here.
425   BATTR = 'breakpad_symbols_generated'
426   UNIQUIFIED_BATTR = cbuildbot_run.RunAttributes._GetBoardAttrName(
427       BATTR, BOARD, TARGET)
428
429   def testRegisterBoardTarget(self):
430     """Test behavior of attributes before and after registering board target."""
431     ra = self._NewRunAttributes()
432
433     self.assertFalse(ra.HasBoardParallel(self.BATTR, self.BOARD, self.TARGET))
434     self.assertFalse(ra.HasParallel(self.UNIQUIFIED_BATTR))
435
436     ra.RegisterBoardAttrs(self.BOARD, self.TARGET)
437
438     self.assertFalse(ra.HasBoardParallel(self.BATTR, self.BOARD, self.TARGET))
439     self.assertFalse(ra.HasParallel(self.UNIQUIFIED_BATTR))
440
441     ra.SetBoardParallel(self.BATTR, 'TheValue', self.BOARD, self.TARGET)
442
443     self.assertTrue(ra.HasBoardParallel(self.BATTR, self.BOARD, self.TARGET))
444     self.assertTrue(ra.HasParallel(self.UNIQUIFIED_BATTR))
445
446   def testSetGet(self):
447     """Test simple set/get of regular and parallel run attributes."""
448     ra = self._NewRunAttributes()
449     value = 'foobar'
450
451     # Set/Get a regular run attribute using direct access.
452     ra.release_tag = value
453     self.assertEqual(value, ra.release_tag)
454
455     # Set/Get of a parallel run attribute using direct access fails.
456     self.assertRaises(AttributeError, setattr, ra, 'unittest_value', value)
457     self.assertRaises(AttributeError, getattr, ra, 'unittest_value')
458
459     # Set/Get of a parallel run attribute with supported interface.
460     ra.SetParallel('unittest_value', value)
461     self.assertEqual(value, ra.GetParallel('unittest_value'))
462
463     # Set/Get a board parallel run attribute, testing both the encouraged
464     # interface and the underlying interface.
465     ra.RegisterBoardAttrs(self.BOARD, self.TARGET)
466     ra.SetBoardParallel(self.BATTR, value, self.BOARD, self.TARGET)
467     self.assertEqual(value,
468                      ra.GetBoardParallel(self.BATTR, self.BOARD, self.TARGET))
469     self.assertEqual(value,
470                      ra.GetParallel(self.UNIQUIFIED_BATTR))
471
472   def testSetDefault(self):
473     """Test setting default value of parallel run attributes."""
474     ra = self._NewRunAttributes()
475     value = 'foobar'
476
477     # Attribute starts off not set.
478     self.assertFalse(ra.HasParallel('unittest_value'))
479
480     # Use SetParallelDefault to set it.
481     ra.SetParallelDefault('unittest_value', value)
482     self.assertTrue(ra.HasParallel('unittest_value'))
483     self.assertEqual(value, ra.GetParallel('unittest_value'))
484
485     # Calling SetParallelDefault again has no effect.
486     ra.SetParallelDefault('unittest_value', 'junk')
487     self.assertTrue(ra.HasParallel('unittest_value'))
488     self.assertEqual(value, ra.GetParallel('unittest_value'))
489
490     # Run through same sequence for a board-specific attribute.
491
492     # Attribute starts off not set.
493     self.assertFalse(ra.HasBoardParallel(self.BATTR, self.BOARD, self.TARGET))
494     ra.RegisterBoardAttrs(self.BOARD, self.TARGET)
495     self.assertFalse(ra.HasBoardParallel(self.BATTR, self.BOARD, self.TARGET))
496
497     # Use SetBoardParallelDefault to set it.
498     ra.SetBoardParallelDefault(self.BATTR, value, self.BOARD, self.TARGET)
499     self.assertTrue(ra.HasBoardParallel(self.BATTR, self.BOARD, self.TARGET))
500     self.assertEqual(value,
501                      ra.GetBoardParallel(self.BATTR, self.BOARD, self.TARGET))
502
503     # Calling SetBoardParallelDefault again has no effect.
504     ra.SetBoardParallelDefault(self.BATTR, 'junk', self.BOARD, self.TARGET)
505     self.assertTrue(ra.HasBoardParallel(self.BATTR, self.BOARD, self.TARGET))
506     self.assertEqual(value,
507                      ra.GetBoardParallel(self.BATTR, self.BOARD, self.TARGET))
508
509   def testAttributeError(self):
510     """Test accessing run attributes that do not exist."""
511     ra = self._NewRunAttributes()
512     value = 'foobar'
513
514     # Set/Get on made up attribute name.
515     self.assertRaises(AttributeError, setattr, ra, 'foo', value)
516     self.assertRaises(AttributeError, getattr, ra, 'foo')
517
518     # self.UNIQUIFIED_BATTR is valid, but only if board/target registered first.
519     self.assertRaises(AttributeError, ra.GetBoardParallel,
520                       self.BATTR, self.BOARD, self.TARGET)
521     self.assertRaises(AttributeError, ra.SetParallel,
522                       self.UNIQUIFIED_BATTR, value)
523     self.assertRaises(AttributeError, ra.GetParallel, self.UNIQUIFIED_BATTR)
524
525
526 class BoardRunAttributesTest(_BuilderRunTestCase):
527   """Test the BoardRunAttributes class."""
528
529   BOARD = 'SomeBoard'
530   TARGET = 'SomeConfigName'
531   VALUE = 'AnyValueWillDo'
532
533   # Any valid board-specific attribute will work here.
534   BATTR = 'breakpad_symbols_generated'
535
536   class _SetAttr(object):
537     """Stage-like class to set attr on a BoardRunAttributes obj."""
538     def __init__(self, bra, attr, value, delay=1):
539       self.bra = bra
540       self.attr = attr
541       self.value = value
542       self.delay = delay
543
544     def Run(self):
545       if self.delay:
546         time.sleep(self.delay)
547       self.bra.SetParallel(self.attr, self.value)
548
549   class _WaitForAttr(object):
550     """Stage-like class to wait for attr on BoardRunAttributes obj."""
551     def __init__(self, bra, attr, expected_value, timeout=10):
552       self.bra = bra
553       self.attr = attr
554       self.expected_value = expected_value
555       self.timeout = timeout
556
557     def GetParallel(self):
558       return self.bra.GetParallel(self.attr, timeout=self.timeout)
559
560   class _CheckWaitForAttr(_WaitForAttr):
561     """Stage-like class to wait for then check attr on BoardRunAttributes."""
562     def Run(self):
563       value = self.GetParallel()
564       assert value == self.expected_value, \
565           ('For run attribute %s expected value %r but got %r.' %
566            (self.attr, self.expected_value, value))
567
568   class _TimeoutWaitForAttr(_WaitForAttr):
569     """Stage-like class to time-out waiting for attr on BoardRunAttributes."""
570     def Run(self):
571       try:
572         self.GetParallel()
573         assert False, 'Expected AttrTimeoutError'
574       except cbuildbot_run.AttrTimeoutError:
575         pass
576
577   def setUp(self):
578     self.ra = self._NewRunAttributes()
579     self.bra = self.ra.RegisterBoardAttrs(self.BOARD, self.TARGET)
580
581   def _TestParallelSetGet(self, stage_args):
582     """Helper to run "stages" in parallel, according to |stage_args|.
583
584     Args:
585       stage_args: List of tuples of the form (stage_object, extra_args, ...)
586         where stage_object has a Run method which takes a BoardRunAttributes
587         object as the first argument and extra_args for the remaining arguments.
588     """
589     stages = [a[0](self.bra, *a[1:]) for a in stage_args]
590     steps = [stage.Run for stage in stages]
591
592     parallel.RunParallelSteps(steps)
593
594   def testParallelSetGetFast(self):
595     """Pass the parallel run attribute around with no delay."""
596     stage_args = [
597         (self._CheckWaitForAttr, self.BATTR, self.VALUE),
598         (self._SetAttr, self.BATTR, self.VALUE),
599     ]
600     self._TestParallelSetGet(stage_args)
601     self.assertRaises(AttributeError,
602                       getattr, self.bra, self.BATTR)
603     self.assertEqual(self.VALUE, self.bra.GetParallel(self.BATTR))
604
605   def testParallelSetGetSlow(self):
606     """Pass the parallel run attribute around with a delay."""
607     stage_args = [
608         (self._SetAttr, self.BATTR, self.VALUE, 10),
609         (self._TimeoutWaitForAttr, self.BATTR, self.VALUE, 2),
610     ]
611     self._TestParallelSetGet(stage_args)
612     self.assertEqual(self.VALUE, self.bra.GetParallel(self.BATTR))
613
614   def testParallelSetGetManyGets(self):
615     """Set the parallel run attribute in one stage, access in many stages."""
616     stage_args = [
617         (self._SetAttr, self.BATTR, self.VALUE, 8),
618         (self._CheckWaitForAttr, self.BATTR, self.VALUE, 16),
619         (self._CheckWaitForAttr, self.BATTR, self.VALUE, 16),
620         (self._CheckWaitForAttr, self.BATTR, self.VALUE, 16),
621         (self._TimeoutWaitForAttr, self.BATTR, self.VALUE, 1),
622     ]
623     self._TestParallelSetGet(stage_args)
624     self.assertEqual(self.VALUE, self.bra.GetParallel(self.BATTR))
625
626   def testParallelSetGetManySets(self):
627     """Set the parallel run attribute in many stages, access in one stage."""
628     # Three "stages" set the value, with increasing delays.  The stage that
629     # checks the value should get the first value set.
630     stage_args = [
631         (self._SetAttr, self.BATTR, self.VALUE + '1', 1),
632         (self._SetAttr, self.BATTR, self.VALUE + '2', 11),
633         (self._CheckWaitForAttr, self.BATTR, self.VALUE + '1', 12),
634     ]
635     self._TestParallelSetGet(stage_args)
636     self.assertEqual(self.VALUE + '2', self.bra.GetParallel(self.BATTR))
637
638   def testSetGet(self):
639     """Test that board-specific attrs do not work with set/get directly."""
640     self.assertRaises(AttributeError, setattr,
641                       self.bra, 'breakpad_symbols_generated', self.VALUE)
642     self.assertRaises(AttributeError, getattr,
643                       self.bra, 'breakpad_symbols_generated')
644
645   def testAccessRegularRunAttr(self):
646     """Test that regular attributes are not known to BoardRunAttributes."""
647     self.assertRaises(AttributeError, getattr, self.bra, 'release_tag')
648     self.assertRaises(AttributeError, setattr, self.bra, 'release_tag', 'foo')
649
650
651 if __name__ == '__main__':
652   cros_test_lib.main(level=logging.DEBUG)