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.
6 """Unittests for the artifact stages."""
8 from __future__ import print_function
15 sys.path.insert(0, os.path.abspath('%s/../../..' % os.path.dirname(__file__)))
16 from chromite.cbuildbot import commands
17 from chromite.cbuildbot import constants
18 from chromite.cbuildbot import failures_lib
19 from chromite.cbuildbot import prebuilts
20 from chromite.cbuildbot.cbuildbot_unittest import BuilderRunMock
21 from chromite.cbuildbot.stages import artifact_stages
22 from chromite.cbuildbot.stages import build_stages_unittest
23 from chromite.cbuildbot.stages import generic_stages_unittest
24 from chromite.lib import cros_build_lib
25 from chromite.lib import cros_build_lib_unittest
26 from chromite.lib import cros_test_lib
27 from chromite.lib import git
28 from chromite.lib import osutils
29 from chromite.lib import parallel
30 from chromite.lib import parallel_unittest
31 from chromite.lib import partial_mock
33 from chromite.cbuildbot.stages.generic_stages_unittest import patch
34 from chromite.cbuildbot.stages.generic_stages_unittest import patches
37 DEFAULT_CHROME_BRANCH = '27'
40 # pylint: disable=R0901,W0212
41 class ArchiveStageTest(generic_stages_unittest.AbstractStageTest):
42 """Exercise ArchiveStage functionality."""
46 def _PatchDependencies(self):
47 """Patch dependencies of ArchiveStage.PerformStage()."""
49 (parallel, 'RunParallelSteps'), (commands, 'PushImages'),
50 (commands, 'UploadArchivedFile')]
51 self.AutoPatch(to_patch)
54 self.StartPatcher(BuilderRunMock())
55 self._PatchDependencies()
59 def _Prepare(self, bot_id=None, **kwargs):
60 extra_config = {'upload_symbols': True, 'push_image': True}
61 super(ArchiveStageTest, self)._Prepare(bot_id, extra_config=extra_config,
64 def ConstructStage(self):
65 self._run.GetArchive().SetupArchivePath()
66 return artifact_stages.ArchiveStage(self._run, self._current_board)
68 def testArchive(self):
69 """Simple did-it-run test."""
70 # TODO(davidjames): Test the individual archive steps as well.
73 # TODO(build): This test is not actually testing anything real. It confirms
74 # that PushImages is not called, but the mock for RunParallelSteps already
75 # prevents PushImages from being called, regardless of whether this is a
77 def testNoPushImagesForRemoteTrybot(self):
78 """Test that remote trybot overrides work to disable push images."""
79 self._Prepare('x86-mario-release',
80 cmd_args=['--remote-trybot', '-r', self.build_root,
81 '--buildnumber=1234'])
83 # pylint: disable=E1101
84 self.assertEquals(commands.PushImages.call_count, 0)
86 def ConstructStageForArchiveStep(self):
87 """Stage construction for archive steps."""
88 stage = self.ConstructStage()
89 self.PatchObject(stage._upload_queue, 'put', autospec=True)
90 self.PatchObject(git, 'ReinterpretPathForChroot', return_value='',
94 def testBuildAndArchiveDeltaSysroot(self):
95 """Test tarball is added to upload queue."""
96 stage = self.ConstructStageForArchiveStep()
97 with cros_build_lib_unittest.RunCommandMock() as rc:
98 rc.SetDefaultCmdResult()
99 stage.BuildAndArchiveDeltaSysroot()
100 stage._upload_queue.put.assert_called_with([constants.DELTA_SYSROOT_TAR])
102 def testBuildAndArchiveDeltaSysrootFailure(self):
103 """Test tarball not added to upload queue on command exception."""
104 stage = self.ConstructStageForArchiveStep()
105 with cros_build_lib_unittest.RunCommandMock() as rc:
106 rc.AddCmdResult(partial_mock.In('generate_delta_sysroot'), returncode=1,
107 error='generate_delta_sysroot: error')
108 self.assertRaises2(cros_build_lib.RunCommandError,
109 stage.BuildAndArchiveDeltaSysroot)
110 self.assertFalse(stage._upload_queue.put.called)
113 class UploadPrebuiltsStageTest(
114 generic_stages_unittest.RunCommandAbstractStageTest):
115 """Tests for the UploadPrebuilts stage."""
117 CMD = './upload_prebuilts'
121 self.StartPatcher(BuilderRunMock())
123 def _Prepare(self, bot_id=None, **kwargs):
124 super(UploadPrebuiltsStageTest, self)._Prepare(bot_id, **kwargs)
126 self._run.options.prebuilts = True
128 def ConstructStage(self):
129 return artifact_stages.UploadPrebuiltsStage(self._run,
130 self._run.config.boards[-1])
132 def _VerifyBoardMap(self, bot_id, count, board_map, public_args=None,
134 """Verify that the prebuilts are uploaded for the specified bot.
137 bot_id: Bot to upload prebuilts for.
138 count: Number of assert checks that should be performed.
139 board_map: Map from slave boards to whether the bot is public.
140 public_args: List of extra arguments for public boards.
141 private_args: List of extra arguments for private boards.
143 self._Prepare(bot_id)
145 public_prefix = [self.CMD] + (public_args or [])
146 private_prefix = [self.CMD] + (private_args or [])
147 for board, public in board_map.iteritems():
148 if public or public_args:
149 public_cmd = public_prefix + ['--slave-board', board]
150 self.assertCommandContains(public_cmd, expected=public)
152 private_cmd = private_prefix + ['--slave-board', board, '--private']
153 self.assertCommandContains(private_cmd, expected=not public)
156 self.assertCommandContains([self.CMD, '--set-version',
157 self._run.GetVersion()])
159 self.assertEqual(count, 0,
160 'Number of asserts performed does not match (%d remaining)' % count)
162 def testFullPrebuiltsUpload(self):
163 """Test uploading of full builder prebuilts."""
164 self._VerifyBoardMap('x86-generic-full', 0, {})
165 self.assertCommandContains([self.CMD, '--git-sync'])
167 def testIncorrectCount(self):
168 """Test that _VerifyBoardMap asserts when the count is wrong."""
169 self.assertRaises(AssertionError, self._VerifyBoardMap, 'x86-generic-full',
173 class MasterUploadPrebuiltsStageTest(
174 generic_stages_unittest.RunCommandAbstractStageTest):
175 """Tests for the MasterUploadPrebuilts stage."""
177 CMD = './upload_prebuilts'
178 RELEASE_TAG = '1234.5.6'
179 VERSION = 'R%s-%s' % (DEFAULT_CHROME_BRANCH, RELEASE_TAG)
182 self.StartPatcher(BuilderRunMock())
184 def _Prepare(self, bot_id=None, **kwargs):
185 super(MasterUploadPrebuiltsStageTest, self)._Prepare(bot_id, **kwargs)
187 self._run.options.prebuilts = True
189 def ConstructStage(self):
190 return artifact_stages.MasterUploadPrebuiltsStage(self._run)
192 def _RunStage(self, bot_id):
193 """Run the stage under test with the given |bot_id| config.
196 bot_id: Builder config target name.
198 self._Prepare(bot_id)
201 def _VerifyResults(self, public_slave_boards=(), private_slave_boards=()):
202 """Verify that the expected prebuilt commands were run.
204 Do various assertions on the two RunCommands that were run by stage.
205 There should be one private (--private) and one public (default) run.
208 public_slave_boards: List of public slave boards.
209 private_slave_boards: List of private slave boards.
211 # TODO(mtennant): Add functionality in partial_mock to support more flexible
212 # asserting. For example here, asserting that '--sync-host' appears in
213 # the command that did not include '--public'.
215 # Some args are expected for any public run.
216 if public_slave_boards:
217 # It would be nice to confirm that --private is not in command, but note
218 # that --sync-host should not appear in the --private command.
219 cmd = [self.CMD, '--sync-binhost-conf', '--sync-host']
220 self.assertCommandContains(cmd, expected=True)
222 # Some args are expected for any private run.
223 if private_slave_boards:
224 cmd = [self.CMD, '--sync-binhost-conf', '--private']
225 self.assertCommandContains(cmd, expected=True)
227 # Assert public slave boards are mentioned in public run.
228 for board in public_slave_boards:
229 # This check does not actually confirm that this board was in the public
230 # run rather than the private run, unfortunately.
231 cmd = [self.CMD, '--slave-board', board]
232 self.assertCommandContains(cmd, expected=True)
234 # Assert private slave boards are mentioned in private run.
235 for board in private_slave_boards:
236 cmd = [self.CMD, '--slave-board', board, '--private']
237 self.assertCommandContains(cmd, expected=True)
239 # We expect --set-version so long as build config has manifest_version=True.
240 self.assertCommandContains([self.CMD, '--set-version', self.VERSION],
241 expected=self._run.config.manifest_version)
243 def testMasterPaladinUpload(self):
244 self._RunStage('master-paladin')
246 # Provide a sample of private/public slave boards that are expected.
247 public_slave_boards = ('amd64-generic', 'x86-generic')
248 private_slave_boards = ('x86-mario', 'x86-alex', 'lumpy', 'daisy_spring')
250 self._VerifyResults(public_slave_boards=public_slave_boards,
251 private_slave_boards=private_slave_boards)
253 def testMasterChromiumPFQUpload(self):
254 self._RunStage('master-chromium-pfq')
256 # Provide a sample of private/public slave boards that are expected.
257 public_slave_boards = ('amd64-generic', 'x86-generic', 'daisy')
258 private_slave_boards = ('x86-alex', 'lumpy', 'daisy_spring', 'falco')
260 self._VerifyResults(public_slave_boards=public_slave_boards,
261 private_slave_boards=private_slave_boards)
264 class UploadDevInstallerPrebuiltsStageTest(
265 generic_stages_unittest.AbstractStageTest):
266 """Tests for the UploadDevInstallerPrebuilts stage."""
271 self.mox.StubOutWithMock(prebuilts, 'UploadDevInstallerPrebuilts')
273 self.StartPatcher(BuilderRunMock())
277 def _Prepare(self, bot_id=None, **kwargs):
278 super(UploadDevInstallerPrebuiltsStageTest, self)._Prepare(bot_id, **kwargs)
280 self._run.options.chrome_rev = None
281 self._run.options.prebuilts = True
282 self._run.config['dev_installer_prebuilts'] = True
283 self._run.config['binhost_bucket'] = 'gs://testbucket'
284 self._run.config['binhost_key'] = 'dontcare'
285 self._run.config['binhost_base_url'] = 'https://dontcare/here'
287 def ConstructStage(self):
288 return artifact_stages.DevInstallerPrebuiltsStage(self._run,
291 def testDevInstallerUpload(self):
292 """Basic sanity test testing uploads of dev installer prebuilts."""
293 version = 'R%s-%s' % (DEFAULT_CHROME_BRANCH, self.RELEASE_TAG)
295 prebuilts.UploadDevInstallerPrebuilts(
296 binhost_bucket=self._run.config.binhost_bucket,
297 binhost_key=self._run.config.binhost_key,
298 binhost_base_url=self._run.config.binhost_base_url,
299 buildroot=self.build_root,
300 board=self._current_board,
301 extra_args=mox.And(mox.IsA(list),
309 class CPEExportStageTest(generic_stages_unittest.AbstractStageTest):
310 """Test CPEExportStage"""
313 self.StartPatcher(BuilderRunMock())
314 self.StartPatcher(generic_stages_unittest.ArchivingStageMixinMock())
315 self.StartPatcher(parallel_unittest.ParallelMock())
317 self.rc_mock = self.StartPatcher(cros_build_lib_unittest.RunCommandMock())
318 self.rc_mock.SetDefaultCmdResult(output='')
322 def ConstructStage(self):
323 """Create a CPEExportStage instance for testing"""
324 self._run.GetArchive().SetupArchivePath()
325 return artifact_stages.CPEExportStage(self._run, self._current_board)
327 def assertBoardAttrEqual(self, attr, expected_value):
328 """Assert the value of a board run |attr| against |expected_value|."""
329 value = self.stage.board_runattrs.GetParallel(attr)
330 self.assertEqual(expected_value, value)
332 def _TestPerformStage(self):
333 """Run PerformStage for the stage."""
335 self._run.attrs.release_tag = BuilderRunMock.VERSION
337 self.stage = self.ConstructStage()
338 self.stage.PerformStage()
340 def testCPEExport(self):
341 """Test that CPEExport stage runs without syntax errors."""
342 self._TestPerformStage()
345 class DebugSymbolsStageTest(generic_stages_unittest.AbstractStageTest):
346 """Test DebugSymbolsStage"""
349 self.StartPatcher(BuilderRunMock())
350 self.StartPatcher(generic_stages_unittest.ArchivingStageMixinMock())
351 self.StartPatcher(parallel_unittest.ParallelMock())
353 self.gen_mock = self.PatchObject(commands, 'GenerateBreakpadSymbols')
354 self.upload_mock = self.PatchObject(commands, 'UploadSymbols')
355 self.tar_mock = self.PatchObject(commands, 'GenerateDebugTarball')
357 self.rc_mock = self.StartPatcher(cros_build_lib_unittest.RunCommandMock())
358 self.rc_mock.SetDefaultCmdResult(output='')
362 def ConstructStage(self):
363 """Create a DebugSymbolsStage instance for testing"""
364 self._run.GetArchive().SetupArchivePath()
365 return artifact_stages.DebugSymbolsStage(self._run, self._current_board)
367 def assertBoardAttrEqual(self, attr, expected_value):
368 """Assert the value of a board run |attr| against |expected_value|."""
369 value = self.stage.board_runattrs.GetParallel(attr)
370 self.assertEqual(expected_value, value)
372 def _TestPerformStage(self, extra_config=None):
373 """Run PerformStage for the stage with the given extra config."""
376 'archive_build_debug': True,
378 'upload_symbols': True,
381 self._Prepare(extra_config=extra_config)
382 self._run.attrs.release_tag = BuilderRunMock.VERSION
384 self.tar_mock.side_effect = '/my/tar/ball'
385 self.stage = self.ConstructStage()
387 self.stage.PerformStage()
389 self.stage._HandleStageException(sys.exc_info())
392 def testPerformStageWithSymbols(self):
393 """Smoke test for an PerformStage when debugging is enabled"""
394 self._TestPerformStage()
396 self.assertEqual(self.gen_mock.call_count, 1)
397 self.assertEqual(self.upload_mock.call_count, 1)
398 self.assertEqual(self.tar_mock.call_count, 1)
400 self.assertBoardAttrEqual('breakpad_symbols_generated', True)
401 self.assertBoardAttrEqual('debug_tarball_generated', True)
403 def testPerformStageNoSymbols(self):
404 """Smoke test for an PerformStage when debugging is disabled"""
406 'archive_build_debug': False,
408 'upload_symbols': False,
410 self._TestPerformStage(extra_config)
412 self.assertEqual(self.gen_mock.call_count, 1)
413 self.assertEqual(self.upload_mock.call_count, 0)
414 self.assertEqual(self.tar_mock.call_count, 1)
416 self.assertBoardAttrEqual('breakpad_symbols_generated', True)
417 self.assertBoardAttrEqual('debug_tarball_generated', True)
419 def testGenerateCrashStillNotifies(self):
420 """Crashes in symbol generation should still notify external events."""
421 self.skipTest('Test skipped due to crbug.com/363339')
422 class TestError(Exception):
423 """Unique test exception"""
425 self.gen_mock.side_effect = TestError('mew')
426 self.assertRaises(TestError, self._TestPerformStage)
428 self.assertEqual(self.gen_mock.call_count, 1)
429 self.assertEqual(self.upload_mock.call_count, 0)
430 self.assertEqual(self.tar_mock.call_count, 0)
432 self.assertBoardAttrEqual('breakpad_symbols_generated', False)
433 self.assertBoardAttrEqual('debug_tarball_generated', False)
435 def testUploadCrashStillNotifies(self):
436 """Crashes in symbol upload should still notify external events."""
437 class TestError(failures_lib.CrashCollectionFailure):
438 """Unique test exception"""
440 self.upload_mock.side_effect = TestError('mew')
441 self.assertRaises(TestError, self._TestPerformStage)
443 self.assertEqual(self.gen_mock.call_count, 1)
444 self.assertEqual(self.upload_mock.call_count, 1)
445 self.assertEqual(self.tar_mock.call_count, 1)
447 self.assertBoardAttrEqual('breakpad_symbols_generated', True)
448 self.assertBoardAttrEqual('debug_tarball_generated', True)
451 class UploadTestArtifactsStageMock(
452 generic_stages_unittest.ArchivingStageMixinMock):
453 """Partial mock for BuildImageStage."""
455 TARGET = 'chromite.cbuildbot.stages.artifact_stages.UploadTestArtifactsStage'
456 ATTRS = (generic_stages_unittest.ArchivingStageMixinMock.ATTRS +
457 ('BuildAutotestTarballs',))
459 def BuildAutotestTarballs(self, *args, **kwargs):
461 patch(commands, 'BuildTarball'),
462 patch(commands, 'FindFilesWithPattern', return_value=['foo.txt'])):
463 self.backup['BuildAutotestTarballs'](*args, **kwargs)
466 class UploadTestArtifactsStageTest(build_stages_unittest.AllConfigsTestCase):
467 """Tests UploadTestArtifactsStage."""
470 self._release_tag = None
472 self.StartPatcher(BuilderRunMock())
473 osutils.SafeMakedirs(os.path.join(self.build_root, 'chroot', 'tmp'))
474 self.StartPatcher(UploadTestArtifactsStageMock())
476 def ConstructStage(self):
477 return artifact_stages.UploadTestArtifactsStage(self._run,
480 def RunTestsWithBotId(self, bot_id, options_tests=True):
481 """Test with the config for the specified bot_id."""
482 self._Prepare(bot_id)
483 self._run.options.tests = options_tests
484 self._run.attrs.release_tag = '0.0.1'
486 # Simulate images being ready.
487 board_runattrs = self._run.GetBoardRunAttrs(self._current_board)
488 board_runattrs.SetParallel('images_generated', True)
490 chroot_base = os.path.join(self.build_root, 'chroot')
492 def _ExtractOutputParam(cmd):
493 """Extract the --output option from a list of arguments."""
494 argparser = argparse.ArgumentParser()
495 argparser.add_argument('--output', action='store')
496 options, _ = argparser.parse_known_args(cmd)
497 return options.output
499 def _SimUpdatePayload(cmd, *_args, **kwargs):
500 """Simulate cros_generate_update_payload by creating its output file."""
501 self.assertTrue(kwargs.get('enter_chroot'))
503 output = _ExtractOutputParam(cmd)
504 self.assertTrue(output)
505 self.assertTrue(os.path.dirname(output))
507 # Join these paths manually since output is absolute and os.path.join
508 # will throw away chroot_base.
509 output = os.sep.join([chroot_base, output])
511 if not os.path.isdir(os.path.dirname(output)):
512 os.makedirs(os.path.dirname(output))
513 self.assertFalse(os.path.exists(output))
515 osutils.Touch(output)
517 def _SimUpdateStatefulPayload(cmd, *_args, **kwargs):
518 """Simulate cros_generate_stateful_update_payload like above."""
519 self.assertTrue(kwargs.get('enter_chroot'))
521 output = _ExtractOutputParam(cmd)
522 self.assertTrue(output)
524 # Join these paths manually since output is absolute and os.path.join
525 # will throw away chroot_base.
526 output = os.sep.join([chroot_base, output])
528 if not os.path.isdir(output):
531 output = os.path.join(output, commands.STATEFUL_FILE)
533 self.assertFalse(os.path.exists(output))
535 osutils.Touch(output)
537 def _HookRunCommand(rc):
539 partial_mock.ListRegex('cros_generate_update_payload'),
540 side_effect=_SimUpdatePayload)
542 partial_mock.ListRegex('cros_generate_stateful_update_payload'),
543 side_effect=_SimUpdateStatefulPayload)
545 with parallel_unittest.ParallelMock():
546 with self.RunStageWithConfig(mock_configurator=_HookRunCommand) as rc:
547 if self._run.config.upload_hw_test_artifacts:
548 self.assertNotEqual(rc.call_count, 0)
550 self.assertEqual(rc.call_count, 0)
552 def testAllConfigs(self):
553 """Test all major configurations"""
554 self.RunAllConfigs(self.RunTestsWithBotId, skip_missing=True)
557 # TODO: Delete ArchivingMock once ArchivingStage is deprecated.
558 class ArchivingMock(partial_mock.PartialMock):
559 """Partial mock for ArchivingStage."""
561 TARGET = 'chromite.cbuildbot.stages.artifact_stages.ArchivingStage'
562 ATTRS = ('UploadArtifact',)
564 def UploadArtifact(self, *args, **kwargs):
565 with patch(commands, 'ArchiveFile', return_value='foo.txt'):
566 with patch(commands, 'UploadArchivedFile'):
567 self.backup['UploadArtifact'](*args, **kwargs)
570 # TODO: Delete ArchivingStageTest once ArchivingStage is deprecated.
571 class ArchivingStageTest(generic_stages_unittest.AbstractStageTest):
572 """Excerise ArchivingStage functionality."""
576 self.StartPatcher(BuilderRunMock())
577 self.StartPatcher(ArchivingMock())
581 def ConstructStage(self):
582 self._run.GetArchive().SetupArchivePath()
583 archive_stage = artifact_stages.ArchiveStage(
584 self._run, self._current_board)
585 return artifact_stages.ArchivingStage(
586 self._run, self._current_board, archive_stage)
589 if __name__ == '__main__':