Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cbuildbot / stages / generic_stages_unittest.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Unittests for generic stages."""
7
8 import contextlib
9 import copy
10 import mox
11 import os
12 import sys
13 import unittest
14
15 sys.path.insert(0, os.path.abspath('%s/../../..' % os.path.dirname(__file__)))
16 from chromite.cbuildbot import commands
17 from chromite.cbuildbot import cbuildbot_config as config
18 from chromite.cbuildbot import failures_lib
19 from chromite.cbuildbot import results_lib
20 from chromite.cbuildbot import cbuildbot_run
21 from chromite.cbuildbot import portage_utilities
22 from chromite.cbuildbot.stages import generic_stages
23 from chromite.lib import cros_build_lib
24 from chromite.lib import cros_build_lib_unittest
25 from chromite.lib import cros_test_lib
26 from chromite.lib import osutils
27 from chromite.lib import parallel
28 from chromite.lib import partial_mock
29 from chromite.scripts import cbuildbot
30
31 # TODO(build): Finish test wrapper (http://crosbug.com/37517).
32 # Until then, this has to be after the chromite imports.
33 import mock
34
35
36 DEFAULT_BUILD_NUMBER = 1234321
37
38
39 # The inheritence order ensures the patchers are stopped before
40 # cleaning up the temporary directories.
41 # pylint: disable=E1111,E1120,W0212,R0901,R0904
42 class StageTest(cros_test_lib.MockOutputTestCase,
43                 cros_test_lib.MoxTempDirTestCase):
44   """Test running a single stage in isolation."""
45
46   TARGET_MANIFEST_BRANCH = 'ooga_booga'
47   BUILDROOT = 'buildroot'
48
49   # Subclass should override this to default to a different build config
50   # for its tests.
51   BOT_ID = 'x86-generic-paladin'
52
53   # Subclasses can override this.  If non-None, value is inserted into
54   # self._run.attrs.release_tag.
55   RELEASE_TAG = None
56
57   def setUp(self):
58     # Prepare a fake build root in self.tempdir, save at self.build_root.
59     self.build_root = os.path.join(self.tempdir, self.BUILDROOT)
60     osutils.SafeMakedirs(os.path.join(self.build_root, '.repo'))
61
62     self._manager = parallel.Manager()
63     self._manager.__enter__()
64
65     # These are here to make pylint happy.  Values filled in by _Prepare.
66     self._bot_id = None
67     self._current_board = None
68     self._boards = None
69     self._run = None
70
71   def _Prepare(self, bot_id=None, extra_config=None, cmd_args=None,
72                extra_cmd_args=None):
73     """Prepare a BuilderRun at self._run for this test.
74
75     This method must allow being called more than once.  Subclasses can
76     override this method, but those subclass methods should also call this one.
77
78     The idea is that all test preparation that falls out from the choice of
79     build config and cbuildbot options should go in _Prepare.
80
81     This will populate the following attributes on self:
82       run: A BuilderRun object.
83       bot_id: The bot id (name) that was used from config.config.
84       self._boards: Same as self._run.config.boards.  TODO(mtennant): remove.
85       self._current_board: First board in list, if there is one.
86
87     Args:
88       bot_id: Name of build config to use, defaults to self.BOT_ID.
89       extra_config: Dict used to add to the build config for the given
90         bot_id.  Example: {'push_image': True}.
91       cmd_args: List to override the default cbuildbot command args.
92       extra_cmd_args: List to add to default cbuildbot command args.  This
93         is a good way to adjust an options value for your test.
94         Example: ['branch-name', 'some-branch-name'] will effectively cause
95         self._run.options.branch_name to be set to 'some-branch-name'.
96     """
97     # Use cbuildbot parser to create options object and populate default values.
98     parser = cbuildbot._CreateParser()
99     if not cmd_args:
100       # Fill in default command args.
101       cmd_args = [
102           '-r', self.build_root, '--buildbot', '--noprebuilts',
103           '--buildnumber', str(DEFAULT_BUILD_NUMBER),
104           '--branch', self.TARGET_MANIFEST_BRANCH,
105       ]
106     if extra_cmd_args:
107       cmd_args += extra_cmd_args
108     (options, args) = parser.parse_args(cmd_args)
109
110     # The bot_id can either be specified as arg to _Prepare method or in the
111     # cmd_args (as cbuildbot normally accepts it from command line).
112     if args:
113       self._bot_id = args[0]
114       if bot_id:
115         # This means bot_id was specified as _Prepare arg and in cmd_args.
116         # Make sure they are the same.
117         self.assertEquals(self._bot_id, bot_id)
118     else:
119       self._bot_id = bot_id or self.BOT_ID
120       args = [self._bot_id]
121     cbuildbot._FinishParsing(options, args)
122
123     # Populate build_config corresponding to self._bot_id.
124     build_config = copy.deepcopy(config.config[self._bot_id])
125     build_config['manifest_repo_url'] = 'fake_url'
126     if extra_config:
127       build_config.update(extra_config)
128     if options.remote_trybot:
129       build_config = config.OverrideConfigForTrybot(build_config, options)
130
131     self._boards = build_config['boards']
132     self._current_board = self._boards[0] if self._boards else None
133
134     # Some preliminary sanity checks.
135     self.assertEquals(options.buildroot, self.build_root)
136
137     # Construct a real BuilderRun using options and build_config.
138     self._run = cbuildbot_run.BuilderRun(options, build_config, self._manager)
139
140     if self.RELEASE_TAG is not None:
141       self._run.attrs.release_tag = self.RELEASE_TAG
142
143     portage_utilities._OVERLAY_LIST_CMD = '/bin/true'
144
145   def tearDown(self):
146     # Mimic exiting with statement for self._manager.
147     self._manager.__exit__(None, None, None)
148
149   def AutoPatch(self, to_patch):
150     """Patch a list of objects with autospec=True.
151
152     Args:
153       to_patch: A list of tuples in the form (target, attr) to patch.  Will be
154       directly passed to mock.patch.object.
155     """
156     for item in to_patch:
157       self.PatchObject(*item, autospec=True)
158
159   def GetHWTestSuite(self):
160     """Get the HW test suite for the current bot."""
161     hw_tests = self._run.config['hw_tests']
162     if not hw_tests:
163       # TODO(milleral): Add HWTests back to lumpy-chrome-perf.
164       raise unittest.SkipTest('Missing HWTest for %s' % (self._bot_id,))
165
166     return hw_tests[0]
167
168
169 class AbstractStageTest(StageTest):
170   """Base class for tests that test a particular build stage.
171
172   Abstract base class that sets up the build config and options with some
173   default values for testing BuilderStage and its derivatives.
174   """
175
176   def ConstructStage(self):
177     """Returns an instance of the stage to be tested.
178     Implement in subclasses.
179     """
180     raise NotImplementedError(self, "ConstructStage: Implement in your test")
181
182   def RunStage(self):
183     """Creates and runs an instance of the stage to be tested.
184     Requires ConstructStage() to be implemented.
185
186     Raises:
187       NotImplementedError: ConstructStage() was not implemented.
188     """
189
190     # Stage construction is usually done as late as possible because the tests
191     # set up the build configuration and options used in constructing the stage.
192     results_lib.Results.Clear()
193     stage = self.ConstructStage()
194     stage.Run()
195     self.assertTrue(results_lib.Results.BuildSucceededSoFar())
196
197
198 def patch(*args, **kwargs):
199   """Convenience wrapper for mock.patch.object.
200
201   Sets autospec=True by default.
202   """
203   kwargs.setdefault('autospec', True)
204   return mock.patch.object(*args, **kwargs)
205
206
207 @contextlib.contextmanager
208 def patches(*args):
209   """Context manager for a list of patch objects."""
210   with cros_build_lib.ContextManagerStack() as stack:
211     for arg in args:
212       stack.Add(lambda: arg)
213     yield
214
215
216 class BuilderStageTest(AbstractStageTest):
217   """Tests for BuilderStage class."""
218
219   def setUp(self):
220     self._Prepare()
221
222   def ConstructStage(self):
223     return generic_stages.BuilderStage(self._run)
224
225   def testGetPortageEnvVar(self):
226     """Basic test case for _GetPortageEnvVar function."""
227     self.mox.StubOutWithMock(cros_build_lib, 'RunCommand')
228     envvar = 'EXAMPLE'
229     obj = cros_test_lib.EasyAttr(output='RESULT\n')
230     cros_build_lib.RunCommand(mox.And(mox.IsA(list), mox.In(envvar)),
231                               cwd='%s/src/scripts' % self.build_root,
232                               redirect_stdout=True, enter_chroot=True,
233                               error_code_ok=True).AndReturn(obj)
234     self.mox.ReplayAll()
235
236     stage = self.ConstructStage()
237     board = self._current_board
238     result = stage._GetPortageEnvVar(envvar, board)
239     self.mox.VerifyAll()
240
241     self.assertEqual(result, 'RESULT')
242
243   def testStageNamePrefixSmoke(self):
244     """Basic test for the StageNamePrefix() function."""
245     stage = self.ConstructStage()
246     self.assertEqual(stage.StageNamePrefix(), 'Builder')
247
248   def testGetStageNamesSmoke(self):
249     """Basic test for the GetStageNames() function."""
250     stage = self.ConstructStage()
251     self.assertEqual(stage.GetStageNames(), ['Builder'])
252
253   def testConstructDashboardURLSmoke(self):
254     """Basic test for the ConstructDashboardURL() function."""
255     stage = self.ConstructStage()
256
257     exp_url = ('http://build.chromium.org/p/chromiumos/builders/'
258                'x86-generic-paladin/builds/%s' % DEFAULT_BUILD_NUMBER)
259     self.assertEqual(stage.ConstructDashboardURL(), exp_url)
260
261     stage_name = 'Archive'
262     exp_url = '%s/steps/%s/logs/stdio' % (exp_url, stage_name)
263     self.assertEqual(stage.ConstructDashboardURL(stage=stage_name), exp_url)
264
265   def test_ExtractOverlaysSmoke(self):
266     """Basic test for the _ExtractOverlays() function."""
267     stage = self.ConstructStage()
268     self.assertEqual(stage._ExtractOverlays(), ([], []))
269
270   def test_PrintSmoke(self):
271     """Basic test for the _Print() function."""
272     stage = self.ConstructStage()
273     with self.OutputCapturer():
274       stage._Print('hi there')
275     self.AssertOutputContainsLine('hi there', check_stderr=True)
276
277   def test_PrintLoudlySmoke(self):
278     """Basic test for the _PrintLoudly() function."""
279     stage = self.ConstructStage()
280     with self.OutputCapturer():
281       stage._PrintLoudly('hi there')
282     self.AssertOutputContainsLine(r'\*{10}', check_stderr=True)
283     self.AssertOutputContainsLine('hi there', check_stderr=True)
284
285   def testRunSmoke(self):
286     """Basic passing test for the Run() function."""
287     stage = self.ConstructStage()
288     with self.OutputCapturer():
289       stage.Run()
290
291   def _RunCapture(self, stage):
292     """Helper method to run Run() with captured output."""
293     output = self.OutputCapturer()
294     output.StartCapturing()
295     try:
296       stage.Run()
297     finally:
298       output.StopCapturing()
299
300   def testRunException(self):
301     """Verify stage exceptions are handled."""
302     class TestError(Exception):
303       """Unique test exception"""
304
305     perform_mock = self.PatchObject(generic_stages.BuilderStage, 'PerformStage')
306     perform_mock.side_effect = TestError('fail!')
307
308     stage = self.ConstructStage()
309     results_lib.Results.Clear()
310     self.assertRaises(failures_lib.StepFailure, self._RunCapture, stage)
311
312     results = results_lib.Results.Get()[0]
313     self.assertTrue(isinstance(results.result, TestError))
314     self.assertEqual(str(results.result), 'fail!')
315
316   def testHandleExceptionException(self):
317     """Verify exceptions in HandleException handlers are themselves handled."""
318     class TestError(Exception):
319       """Unique test exception"""
320
321     class BadStage(generic_stages.BuilderStage):
322       """Stage that throws an exception when PerformStage is called."""
323
324       handled_exceptions = []
325
326       def PerformStage(self):
327         raise TestError('first fail')
328
329       def _HandleStageException(self, exc_info):
330         self.handled_exceptions.append(str(exc_info[1]))
331         raise TestError('nested')
332
333     stage = BadStage(self._run)
334     results_lib.Results.Clear()
335     self.assertRaises(failures_lib.StepFailure, self._RunCapture, stage)
336
337     # Verify the results tracked the original exception.
338     results = results_lib.Results.Get()[0]
339     self.assertTrue(isinstance(results.result, TestError))
340     self.assertEqual(str(results.result), 'first fail')
341
342     self.assertEqual(stage.handled_exceptions, ['first fail'])
343
344
345 class BoardSpecificBuilderStageTest(cros_test_lib.TestCase):
346   """Tests option/config settings on board-specific stages."""
347
348   # TODO (yjhong): Fix this test.
349   # def testCheckOptions(self):
350   #   """Makes sure options/config settings are setup correctly."""
351   #   parser = cbuildbot._CreateParser()
352   #   (options, _) = parser.parse_args([])
353
354   #   for attr in dir(stages):
355   #     obj = eval('stages.' + attr)
356   #     if not hasattr(obj, '__base__'):
357   #       continue
358   #     if not obj.__base__ is stages.BoardSpecificBuilderStage:
359   #       continue
360   #     if obj.option_name:
361   #       self.assertTrue(getattr(options, obj.option_name))
362   #     if obj.config_name:
363   #       if not obj.config_name in config._settings:
364   #         self.fail(('cbuildbot_stages.%s.config_name "%s" is missing from '
365   #                    'cbuildbot_config._settings') % (attr, obj.config_name))
366
367 # pylint: disable=W0223
368 class RunCommandAbstractStageTest(AbstractStageTest,
369                                   cros_build_lib_unittest.RunCommandTestCase):
370   """Base test class for testing a stage and mocking RunCommand."""
371
372   FULL_BOT_ID = 'x86-generic-full'
373   BIN_BOT_ID = 'x86-generic-paladin'
374
375   def _Prepare(self, bot_id, **kwargs):
376     super(RunCommandAbstractStageTest, self)._Prepare(bot_id, **kwargs)
377
378   def _PrepareFull(self, **kwargs):
379     self._Prepare(self.FULL_BOT_ID, **kwargs)
380
381   def _PrepareBin(self, **kwargs):
382     self._Prepare(self.BIN_BOT_ID, **kwargs)
383
384   def _Run(self, dir_exists):
385     """Helper for running the build."""
386     with patch(os.path, 'isdir', return_value=dir_exists):
387       self.RunStage()
388
389
390 class ArchivingStageMixinMock(partial_mock.PartialMock):
391   """Partial mock for ArchivingStageMixin."""
392
393   TARGET = 'chromite.cbuildbot.stages.generic_stages.ArchivingStageMixin'
394   ATTRS = ('UploadArtifact',)
395
396   def UploadArtifact(self, *args, **kwargs):
397     with patch(commands, 'ArchiveFile', return_value='foo.txt'):
398       with patch(commands, 'UploadArchivedFile'):
399         self.backup['UploadArtifact'](*args, **kwargs)
400
401
402
403 if __name__ == '__main__':
404   cros_test_lib.main()