| # -*- coding: utf-8 -*- |
| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Unittests for completion stages.""" |
| |
| from __future__ import print_function |
| |
| import sys |
| |
| import mock |
| |
| from chromite.cbuildbot import cbuildbot_run |
| from chromite.cbuildbot import commands |
| from chromite.cbuildbot import manifest_version |
| from chromite.cbuildbot import prebuilts |
| from chromite.cbuildbot.stages import completion_stages |
| from chromite.cbuildbot.stages import generic_stages |
| from chromite.cbuildbot.stages import generic_stages_unittest |
| from chromite.cbuildbot.stages import sync_stages_unittest |
| from chromite.cbuildbot.stages import sync_stages |
| from chromite.lib import alerts |
| from chromite.lib import auth |
| from chromite.lib import buildbucket_lib |
| from chromite.lib import builder_status_lib |
| from chromite.lib import build_failure_message |
| from chromite.lib import cidb |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import config_lib |
| from chromite.lib import constants |
| from chromite.lib import portage_util |
| from chromite.lib.buildstore import FakeBuildStore |
| |
| # pylint: disable=protected-access |
| |
| |
| class ManifestVersionedSyncCompletionStageTest( |
| sync_stages_unittest.ManifestVersionedSyncStageTest): |
| """Tests the ManifestVersionedSyncCompletion stage.""" |
| |
| # pylint: disable=abstract-method |
| |
| BOT_ID = 'eve-release' |
| |
| def setUp(self): |
| self.buildstore = FakeBuildStore() |
| |
| def testManifestVersionedSyncCompletedSuccess(self): |
| """Tests basic ManifestVersionedSyncStageCompleted on success""" |
| board_runattrs = self._run.GetBoardRunAttrs('eve') |
| board_runattrs.SetParallel('success', True) |
| update_status_mock = self.PatchObject(manifest_version.BuildSpecsManager, |
| 'UpdateStatus') |
| |
| stage = completion_stages.ManifestVersionedSyncCompletionStage( |
| self._run, self.buildstore, self.sync_stage, success=True) |
| |
| stage.Run() |
| update_status_mock.assert_called_once_with( |
| message=None, success_map={self.BOT_ID: True}) |
| |
| def testManifestVersionedSyncCompletedFailure(self): |
| """Tests basic ManifestVersionedSyncStageCompleted on failure""" |
| stage = completion_stages.ManifestVersionedSyncCompletionStage( |
| self._run, self.buildstore, self.sync_stage, success=False) |
| message = 'foo' |
| get_msg_mock = self.PatchObject( |
| generic_stages.BuilderStage, |
| 'GetBuildFailureMessage', |
| return_value=message) |
| update_status_mock = self.PatchObject(manifest_version.BuildSpecsManager, |
| 'UpdateStatus') |
| |
| stage.Run() |
| update_status_mock.assert_called_once_with( |
| message='foo', success_map={self.BOT_ID: False}) |
| get_msg_mock.assert_called_once_with() |
| |
| def testManifestVersionedSyncCompletedIncomplete(self): |
| """Tests basic ManifestVersionedSyncStageCompleted on incomplete build.""" |
| stage = completion_stages.ManifestVersionedSyncCompletionStage( |
| self._run, self.buildstore, self.sync_stage, success=False) |
| stage.Run() |
| |
| def testGetBuilderSuccessMap(self): |
| """Tests that the builder success map is properly created.""" |
| board_runattrs = self._run.GetBoardRunAttrs('eve') |
| board_runattrs.SetParallel('success', True) |
| builder_success_map = completion_stages.GetBuilderSuccessMap( |
| self._run, True) |
| expected_map = {self.BOT_ID: True} |
| self.assertEqual(expected_map, builder_success_map) |
| |
| |
| class MasterSlaveSyncCompletionStageMockConfigTest( |
| generic_stages_unittest.AbstractStageTestCase): |
| """Tests MasterSlaveSyncCompletionStage with ManifestVersionedSyncStage.""" |
| BOT_ID = 'master' |
| |
| def setUp(self): |
| self.source_repo = 'ssh://source/repo' |
| self.manifest_version_url = 'fake manifest url' |
| self.branch = 'master' |
| self.build_type = constants.PFQ_TYPE |
| |
| # Use our mocked out SiteConfig for all tests. |
| self.test_config = self._GetTestConfig() |
| self._Prepare(site_config=self.test_config) |
| self.buildstore = FakeBuildStore() |
| |
| def ConstructStage(self): |
| sync_stage = sync_stages.ManifestVersionedSyncStage(self._run, |
| self.buildstore) |
| return completion_stages.MasterSlaveSyncCompletionStage( |
| self._run, self.buildstore, sync_stage, success=True) |
| |
| def _GetTestConfig(self): |
| test_config = config_lib.SiteConfig() |
| test_config.Add( |
| 'master', |
| config_lib.BuildConfig(), |
| boards=[], |
| build_type=self.build_type, |
| master=True, |
| slave_configs=['test3', 'test5'], |
| manifest_version=True, |
| ) |
| test_config.Add( |
| 'test1', |
| config_lib.BuildConfig(), |
| boards=['amd64-generic'], |
| manifest_version=True, |
| build_type=constants.PFQ_TYPE, |
| overlays='public', |
| important=False, |
| chrome_rev=None, |
| branch=False, |
| internal=False, |
| master=False, |
| ) |
| test_config.Add( |
| 'test2', |
| config_lib.BuildConfig(), |
| boards=['amd64-generic'], |
| manifest_version=False, |
| build_type=constants.PFQ_TYPE, |
| overlays='public', |
| important=True, |
| chrome_rev=None, |
| branch=False, |
| internal=False, |
| master=False, |
| ) |
| test_config.Add( |
| 'test3', |
| config_lib.BuildConfig(), |
| boards=['amd64-generic'], |
| manifest_version=True, |
| build_type=constants.PFQ_TYPE, |
| overlays='both', |
| important=True, |
| chrome_rev=None, |
| branch=False, |
| internal=True, |
| master=False, |
| ) |
| test_config.Add( |
| 'test4', |
| config_lib.BuildConfig(), |
| boards=['amd64-generic'], |
| manifest_version=True, |
| build_type=constants.PFQ_TYPE, |
| overlays='both', |
| important=True, |
| chrome_rev=None, |
| branch=True, |
| internal=True, |
| master=False, |
| ) |
| test_config.Add( |
| 'test5', |
| config_lib.BuildConfig(), |
| boards=['amd64-generic'], |
| manifest_version=True, |
| build_type=constants.PFQ_TYPE, |
| overlays='public', |
| important=True, |
| chrome_rev=None, |
| branch=False, |
| internal=False, |
| master=False, |
| ) |
| return test_config |
| |
| def testGetSlavesForMaster(self): |
| """Tests that we get the slaves for a fake unified master configuration.""" |
| stage = self.ConstructStage() |
| p = stage._GetSlaveConfigs() |
| self.assertEqual([self.test_config['test3'], self.test_config['test5']], p) |
| |
| |
| class MasterSlaveSyncCompletionStageTest( |
| generic_stages_unittest.AbstractStageTestCase): |
| """Tests MasterSlaveSyncCompletionStage with ManifestVersionedSyncStage.""" |
| BOT_ID = 'amd64-generic-paladin' |
| |
| def setUp(self): |
| self.source_repo = 'ssh://source/repo' |
| self.manifest_version_url = 'fake manifest url' |
| self.branch = 'master' |
| |
| self._Prepare() |
| self.buildstore = FakeBuildStore() |
| |
| # Our API here is not great when it comes to kwargs passing. |
| def _Prepare(self, bot_id=None, **kwargs): # pylint: disable=arguments-differ |
| super(MasterSlaveSyncCompletionStageTest, self)._Prepare(bot_id, **kwargs) |
| self._run.config['manifest_version'] = True |
| |
| def ConstructStage(self): |
| sync_stage = sync_stages.ManifestVersionedSyncStage(self._run, |
| self.buildstore) |
| return completion_stages.MasterSlaveSyncCompletionStage( |
| self._run, self.buildstore, sync_stage, success=True) |
| |
| def testIsFailureFatal(self): |
| """Tests the correctness of the _IsFailureFatal method""" |
| stage = self.ConstructStage() |
| |
| # Test behavior when there are no sanity check builders |
| self.assertFalse(stage._IsFailureFatal(set(), set(), set())) |
| self.assertTrue(stage._IsFailureFatal(set(['test3']), set(), set())) |
| self.assertTrue(stage._IsFailureFatal(set(), set(['test5']), set())) |
| self.assertTrue(stage._IsFailureFatal(set(), set(), set(['test1']))) |
| |
| def testExceptionHandler(self): |
| """Verify _HandleStageException is sane.""" |
| stage = self.ConstructStage() |
| e = ValueError('foo') |
| try: |
| raise e |
| except ValueError: |
| ret = stage._HandleStageException(sys.exc_info()) |
| self.assertTrue(isinstance(ret, tuple)) |
| self.assertEqual(len(ret), 3) |
| self.assertEqual(ret[0], e) |
| |
| def testGetBuilderStatusesFetcher(self): |
| """Test GetBuilderStatusesFetcher.""" |
| mock_fetcher = mock.Mock() |
| self.PatchObject( |
| builder_status_lib, 'BuilderStatusesFetcher', return_value=mock_fetcher) |
| |
| stage = self.ConstructStage() |
| self.assertEqual(stage._GetBuilderStatusesFetcher(), mock_fetcher) |
| |
| |
| class MasterSlaveSyncCompletionStageTestWithMasterPaladin( |
| generic_stages_unittest.AbstractStageTestCase): |
| """Tests MasterSlaveSyncCompletionStage with master-paladin.""" |
| BOT_ID = 'master-paladin' |
| |
| def setUp(self): |
| self.source_repo = 'ssh://source/repo' |
| self.manifest_version_url = 'fake manifest url' |
| self.branch = 'master' |
| |
| self.PatchObject(buildbucket_lib, 'GetServiceAccount', return_value=True) |
| self.PatchObject(auth.AuthorizedHttp, '__init__', return_value=None) |
| self.PatchObject( |
| buildbucket_lib.BuildbucketClient, |
| 'SendBuildbucketRequest', |
| return_value=None) |
| self.PatchObject( |
| buildbucket_lib.BuildbucketClient, |
| '_GetHost', |
| return_value=buildbucket_lib.BUILDBUCKET_TEST_HOST) |
| self.mock_handle_failure = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, 'HandleFailure') |
| self.PatchObject( |
| builder_status_lib.BuilderStatusesFetcher, |
| '__init__', |
| return_value=None) |
| |
| self._Prepare() |
| self.buildstore = FakeBuildStore() |
| |
| def tearDown(self): |
| cidb.CIDBConnectionFactory.ClearMock() |
| |
| def ConstructStage(self): |
| sync_stage = sync_stages.ManifestVersionedSyncStage(self._run, |
| self.buildstore) |
| |
| scheduled_slaves_list = {('build_1', 'buildbucket_id_1', 1), |
| ('build_2', 'buildbucket_id_2', 2)} |
| self._run.attrs.metadata.ExtendKeyListWithList( |
| constants.METADATA_SCHEDULED_IMPORTANT_SLAVES, scheduled_slaves_list) |
| return completion_stages.MasterSlaveSyncCompletionStage( |
| self._run, self.buildstore, sync_stage, success=True) |
| |
| def testGetBuilderStatusesFetcher(self): |
| """Test GetBuilderStatusesFetcher.""" |
| mock_fetcher = mock.Mock() |
| self.PatchObject( |
| builder_status_lib, 'BuilderStatusesFetcher', return_value=mock_fetcher) |
| mock_wait = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, |
| '_WaitForSlavesToComplete') |
| stage = self.ConstructStage() |
| stage._run.attrs.manifest_manager = mock.Mock() |
| |
| self.assertEqual(stage._GetBuilderStatusesFetcher(), mock_fetcher) |
| self.assertEqual(mock_wait.call_count, 1) |
| |
| def testPerformStageWithFatalFailure(self): |
| """Test PerformStage on master-paladin.""" |
| stage = self.ConstructStage() |
| |
| stage._run.attrs.manifest_manager = mock.MagicMock() |
| |
| statuses = { |
| 'build_1': |
| builder_status_lib.BuilderStatus(constants.BUILDER_STATUS_INFLIGHT, |
| None), |
| 'build_2': |
| builder_status_lib.BuilderStatus(constants.BUILDER_STATUS_MISSING, |
| None) |
| } |
| |
| self.PatchObject( |
| builder_status_lib.BuilderStatusesFetcher, |
| 'GetBuilderStatuses', |
| return_value=(statuses, {})) |
| mock_annotate = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, |
| '_AnnotateFailingBuilders') |
| |
| with self.assertRaises(completion_stages.ImportantBuilderFailedException): |
| stage.PerformStage() |
| mock_annotate.assert_called_once_with( |
| set(), {'build_1'}, {'build_2'}, statuses, {}, False) |
| self.mock_handle_failure.assert_called_once_with( |
| set(), {'build_1'}, {'build_2'}, False) |
| |
| mock_annotate.reset_mock() |
| self.mock_handle_failure.reset_mock() |
| stage._run.attrs.metadata.UpdateWithDict( |
| {constants.SELF_DESTRUCTED_BUILD: True}) |
| with self.assertRaises(completion_stages.ImportantBuilderFailedException): |
| stage.PerformStage() |
| mock_annotate.assert_called_once_with( |
| set(), {'build_1'}, {'build_2'}, statuses, {}, True) |
| self.mock_handle_failure.assert_called_once_with( |
| set(), {'build_1'}, {'build_2'}, True) |
| |
| def testStageRunWithImportantBuilderFailedException(self): |
| """Test stage.Run on master-paladin with ImportantBuilderFailedException.""" |
| stage = self.ConstructStage() |
| stage._run.attrs.manifest_manager = mock.MagicMock() |
| statuses = { |
| 'build_1': builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_INFLIGHT, None), |
| 'build_2': builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_MISSING, None) |
| } |
| self.PatchObject(builder_status_lib.BuilderStatusesFetcher, |
| 'GetBuilderStatuses', return_value=(statuses, {})) |
| |
| with self.assertRaises(completion_stages.ImportantBuilderFailedException): |
| stage.Run() |
| |
| def testAnnotateBuildStatusFromBuildbucket(self): |
| """Test AnnotateBuildStatusFromBuildbucket""" |
| stage = self.ConstructStage() |
| |
| scheduled_slaves_list = {('build_1', 'buildbucket_id_1', 1), |
| ('build_2', 'buildbucket_id_2', 2)} |
| stage.buildbucket_info_dict = ( |
| buildbucket_lib.GetScheduledBuildDict(scheduled_slaves_list)) |
| |
| mock_logging_link = self.PatchObject( |
| logging, 'PrintBuildbotLink', side_effect=logging.PrintBuildbotLink) |
| mock_logging_text = self.PatchObject( |
| logging, |
| 'PrintBuildbotStepText', |
| side_effect=logging.PrintBuildbotStepText) |
| |
| no_stat = set(['not_scheduled_build_1']) |
| stage._AnnotateNoStatBuilders(no_stat) |
| mock_logging_text.assert_called_once_with( |
| "%s wasn't scheduled by master." % 'not_scheduled_build_1') |
| |
| build_content = { |
| 'build': { |
| 'status': 'COMPLETED', |
| 'result': 'FAILURE', |
| 'url': 'dashboard_url', |
| 'failure_reason': 'BUILD_FAILURE', |
| } |
| } |
| self.PatchObject( |
| buildbucket_lib.BuildbucketClient, |
| 'GetBuildRequest', |
| return_value=build_content) |
| no_stat = set(['build_1']) |
| stage._AnnotateNoStatBuilders(no_stat) |
| mock_logging_link.assert_called_once_with( |
| '%s: [status] %s [result] %s [failure_reason] %s' % |
| ('build_1', 'COMPLETED', 'FAILURE', 'BUILD_FAILURE'), 'dashboard_url') |
| |
| mock_logging_link.reset_mock() |
| build_content = { |
| 'build': { |
| 'status': 'COMPLETED', |
| 'result': 'CANCELED', |
| 'url': 'dashboard_url', |
| 'cancelation_reason': 'CANCELED_EXPLICITLY', |
| } |
| } |
| self.PatchObject( |
| buildbucket_lib.BuildbucketClient, |
| 'GetBuildRequest', |
| return_value=build_content) |
| no_stat = set(['build_1']) |
| stage._AnnotateNoStatBuilders(no_stat) |
| mock_logging_link.assert_called_once_with( |
| '%s: [status] %s [result] %s [cancelation_reason] %s' % |
| ('build_1', 'COMPLETED', 'CANCELED', 'CANCELED_EXPLICITLY'), |
| 'dashboard_url') |
| |
| mock_logging_text.reset_mock() |
| self.PatchObject( |
| buildbucket_lib.BuildbucketClient, |
| 'GetBuildRequest', |
| side_effect=buildbucket_lib.BuildbucketResponseException) |
| no_stat = set(['build_1']) |
| stage._AnnotateNoStatBuilders(no_stat) |
| mock_logging_text.assert_called_once_with( |
| 'No status found for build %s buildbucket_id %s' % ('build_1', |
| 'buildbucket_id_1')) |
| |
| def test_AnnotateNoStatBuildersWithBuildbucketSchedulder(self): |
| """Tests _AnnotateNoStatBuilders with master using Buildbucket scheduler.""" |
| stage = self.ConstructStage() |
| |
| annotate_mock = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, |
| '_AnnotateNoStatBuilders') |
| |
| no_stat = {'no_stat_1', 'no_stat_2'} |
| stage._AnnotateNoStatBuilders(no_stat) |
| annotate_mock.assert_called_once_with(no_stat) |
| |
| def testAnnotateFailingBuilders(self): |
| """Tests that _AnnotateFailingBuilders is free of syntax errors.""" |
| stage = self.ConstructStage() |
| |
| annotate_mock = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, |
| '_AnnotateNoStatBuilders') |
| |
| failing = {'failing_build'} |
| inflight = {'inflight_build'} |
| no_stat = {'no_stat_build'} |
| failed_msg = build_failure_message.BuildFailureMessage( |
| 'message', [], True, 'reason', 'bot') |
| failed_status = builder_status_lib.BuilderStatus('failed', failed_msg, |
| 'url') |
| inflight_status = builder_status_lib.BuilderStatus('inflight', None, 'url') |
| statuses = {'failing_build' : failed_status, |
| 'inflight_build': inflight_status} |
| |
| stage._AnnotateFailingBuilders(failing, inflight, set(), statuses, {}, |
| False) |
| self.assertEqual(annotate_mock.call_count, 1) |
| |
| stage._AnnotateFailingBuilders(failing, inflight, no_stat, statuses, {}, |
| False) |
| self.assertEqual(annotate_mock.call_count, 2) |
| |
| stage._AnnotateFailingBuilders(failing, inflight, no_stat, statuses, {}, |
| True) |
| self.assertEqual(annotate_mock.call_count, 3) |
| |
| def testAnnotateFailingExperimentalBuilders(self): |
| """Tests _AnnotateFailingBuilders with experimental builders.""" |
| stage = self.ConstructStage() |
| |
| print_build_message_mock = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, '_PrintBuildMessage') |
| |
| failed_msg = build_failure_message.BuildFailureMessage( |
| 'message', [], True, 'reason', 'bot') |
| experimental_statuses = { |
| 'passed_experimental' : builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_PASSED, None, 'url'), |
| 'failing_experimental' : builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_FAILED, failed_msg, 'url'), |
| 'inflight_experimental': builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_INFLIGHT, None, 'url') |
| } |
| |
| stage._AnnotateFailingBuilders(set(), set(), set(), {}, |
| experimental_statuses, False) |
| # Build message should not be printed for the passed builder. |
| self.assertEqual(print_build_message_mock.call_count, 2) |
| |
| def testPerformStageWithFailedExperimentalBuilder(self): |
| """Test PerformStage with a failed experimental builder.""" |
| stage = self.ConstructStage() |
| stage._run.attrs.manifest_manager = mock.MagicMock() |
| status = { |
| 'build_1': builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_PASSED, None) |
| } |
| experimental_status = { |
| 'build_2': builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_FAILED, None) |
| } |
| self.PatchObject(builder_status_lib.BuilderStatusesFetcher, |
| 'GetBuilderStatuses', |
| return_value=(status, experimental_status)) |
| mock_annotate = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, |
| '_AnnotateFailingBuilders') |
| |
| stage._run.attrs.metadata.UpdateWithDict( |
| {constants.METADATA_EXPERIMENTAL_BUILDERS: ['build_2']}) |
| stage.PerformStage() |
| mock_annotate.assert_called_once_with(set(), set(), set(), status, |
| experimental_status, False) |
| |
| |
| class CanaryCompletionStageTest(generic_stages_unittest.AbstractStageTestCase): |
| """Tests how canary master handles failures in CanaryCompletionStage.""" |
| BOT_ID = 'master-release' |
| |
| # We duplicate __init__ to specify a default for bot_id. |
| # pylint: disable=arguments-differ |
| def _Prepare(self, bot_id=BOT_ID, **kwargs): |
| super(CanaryCompletionStageTest, self)._Prepare(bot_id, **kwargs) |
| |
| def setUp(self): |
| self.build_type = constants.CANARY_TYPE |
| self._Prepare() |
| self.buildstore = FakeBuildStore() |
| |
| def ConstructStage(self): |
| """Returns a CanaryCompletionStage object.""" |
| sync_stage = sync_stages.ManifestVersionedSyncStage(self._run, |
| self.buildstore) |
| return completion_stages.CanaryCompletionStage( |
| self._run, self.buildstore, sync_stage, success=True) |
| |
| def testGetBuilderStatusesFetcher(self): |
| """Test GetBuilderStatusesFetcher.""" |
| mock_fetcher = mock.Mock() |
| self.PatchObject( |
| builder_status_lib, 'BuilderStatusesFetcher', return_value=mock_fetcher) |
| mock_wait = self.PatchObject( |
| completion_stages.MasterSlaveSyncCompletionStage, |
| '_WaitForSlavesToComplete') |
| stage = self.ConstructStage() |
| stage._run.attrs.manifest_manager = mock.Mock() |
| |
| self.assertEqual(stage._GetBuilderStatusesFetcher(), mock_fetcher) |
| self.assertEqual(mock_wait.call_count, 1) |
| |
| |
| class BaseCommitQueueCompletionStageTest( |
| generic_stages_unittest.AbstractStageTestCase): |
| """Tests how CQ handles changes in CommitQueueCompletionStage.""" |
| |
| def setUp(self): |
| self.build_type = constants.PFQ_TYPE |
| self._Prepare() |
| self.buildstore = FakeBuildStore() |
| |
| self.partial_submit_changes = ['C', 'D'] |
| self.other_changes = ['A', 'B'] |
| self.changes = self.other_changes + self.partial_submit_changes |
| |
| self.alert_email_mock = self.PatchObject(alerts, 'SendEmail') |
| self.PatchObject(cbuildbot_run._BuilderRunBase, |
| 'InEmailReportingEnvironment', return_value=True) |
| self.PatchObject(completion_stages.MasterSlaveSyncCompletionStage, |
| 'HandleFailure') |
| self.PatchObject(builder_status_lib.BuilderStatusesFetcher, '__init__', |
| return_value=None) |
| self.PatchObject(builder_status_lib, 'GetFailedMessages') |
| |
| def ConstructStage(self): |
| """Returns a CommitQueueCompletionStage object.""" |
| sync_stage = sync_stages.CommitQueueSyncStage(self._run, self.buildstore) |
| sync_stage.pool = mock.MagicMock() |
| sync_stage.pool.applied = self.changes |
| |
| return completion_stages.CommitQueueCompletionStage( |
| self._run, self.buildstore, sync_stage, success=True) |
| |
| def VerifyStage(self, failing=(), inflight=(), no_stat=(), alert=False, |
| stage=None, build_passed=False): |
| """Runs and Verifies PerformStage. |
| |
| Args: |
| failing: The names of the builders that failed. |
| inflight: The names of the buiders that timed out. |
| no_stat: The names of the builders that had status None. |
| alert: If True, sends out an alert email for infra failures. |
| stage: If set, use this constructed stage, otherwise create own. |
| build_passed: Whether the build passed or failed. |
| """ |
| if not stage: |
| stage = self.ConstructStage() |
| |
| stage._run.attrs.manifest_manager = mock.Mock() |
| |
| # Setup the stage to look at the specified configs. |
| all_slaves = set(failing) | set(inflight) | set(no_stat) |
| configs = [config_lib.BuildConfig(name=x) for x in all_slaves] |
| self.PatchObject(stage, '_GetSlaveConfigs', return_value=configs) |
| |
| statuses = {} |
| for x in failing: |
| statuses[x] = builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_FAILED, message=None) |
| for x in inflight: |
| statuses[x] = builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_INFLIGHT, message=None) |
| for x in no_stat: |
| statuses[x] = builder_status_lib.BuilderStatus( |
| constants.BUILDER_STATUS_MISSING, message=None) |
| |
| self.PatchObject(builder_status_lib.BuilderStatusesFetcher, |
| 'GetBuilderStatuses', return_value=(statuses, {})) |
| |
| # Track whether 'HandleSuccess' is called. |
| success_mock = self.PatchObject(stage, 'HandleSuccess') |
| |
| # Actually run the stage. |
| if build_passed: |
| stage.PerformStage() |
| else: |
| with self.assertRaises(completion_stages.ImportantBuilderFailedException): |
| stage.PerformStage() |
| |
| # Verify the calls. |
| self.assertEqual(success_mock.called, build_passed) |
| |
| if not build_passed and self._run.config.master: |
| if alert: |
| self.alert_email_mock.assert_called_once_with( |
| mock.ANY, mock.ANY, server=mock.ANY, message=mock.ANY, |
| extra_fields=mock.ANY) |
| |
| |
| # pylint: disable=too-many-ancestors |
| class SlaveCommitQueueCompletionStageTest(BaseCommitQueueCompletionStageTest): |
| """Tests how CQ a slave handles changes in CommitQueueCompletionStage.""" |
| BOT_ID = 'eve-paladin' |
| |
| def testSuccess(self): |
| """Test the slave succeeding.""" |
| self.VerifyStage(build_passed=True) |
| |
| def testFail(self): |
| """Test the slave failing.""" |
| self.VerifyStage(failing=['foo'], build_passed=False) |
| |
| def testTimeout(self): |
| """Test the slave timing out.""" |
| self.VerifyStage(inflight=['foo'], build_passed=False) |
| |
| |
| class MasterCommitQueueCompletionStageTest(BaseCommitQueueCompletionStageTest): |
| """Tests how CQ master handles changes in CommitQueueCompletionStage.""" |
| BOT_ID = 'master-paladin' |
| |
| # Our API here is not great when it comes to kwargs passing. |
| # pylint: disable=arguments-differ |
| def _Prepare(self, bot_id=BOT_ID, **kwargs): |
| super(MasterCommitQueueCompletionStageTest, self)._Prepare(bot_id, **kwargs) |
| self.assertTrue(self._run.config['master']) |
| # pylint: enable=arguments-differ |
| |
| def tearDown(self): |
| cidb.CIDBConnectionFactory.ClearMock() |
| |
| def testImportantBuildFailing(self): |
| """Test stage when important builds in failing.""" |
| self.VerifyStage(failing=['foo']) |
| |
| def testImportantBuildInflight(self): |
| """Test stage when important builds in inflight.""" |
| self.VerifyStage(inflight=['foo']) |
| |
| def testImportantBuildNostat(self): |
| """Test stage when important builds in no_stat.""" |
| self.VerifyStage(no_stat=['foo']) |
| |
| def testFailingBuildersWithInfraFail(self): |
| """Alert when the failing builders have infra failure messages.""" |
| self.PatchObject(completion_stages.CommitQueueCompletionStage, |
| '_GetInfraFailMessages', return_value=['msg']) |
| self.PatchObject(builder_status_lib, |
| 'GetBuildersWithNoneMessages', return_value=[]) |
| |
| # An alert is sent, since there are infra failures. |
| self.VerifyStage(failing=['foo'], alert=True) |
| |
| def testAlertFailingBuildersWithNoneFailureMessages(self): |
| """Alert when failing builders don't have failure messages.""" |
| self.PatchObject(completion_stages.CommitQueueCompletionStage, |
| '_GetInfraFailMessages', return_value=[]) |
| self.PatchObject(builder_status_lib, |
| 'GetBuildersWithNoneMessages', return_value=['foo']) |
| |
| # An alert is sent, since NonType messages are considered infra failures. |
| self.VerifyStage(failing=['foo'], alert=True) |
| |
| def testAlertInflightBuildersWithNoneInfraFail(self): |
| """Alert when inflight builders don't have failure messages.""" |
| self.PatchObject(completion_stages.CommitQueueCompletionStage, |
| '_GetInfraFailMessages', return_value=[]) |
| self.PatchObject(builder_status_lib, |
| 'GetBuildersWithNoneMessages', return_value=[]) |
| |
| # An alert is sent, since we have an inflight build still. |
| self.VerifyStage(inflight=['foo'], alert=True) |
| |
| def testAlertNostatBuildersWithNoneInfraFail(self): |
| """Alert when no_stat builders don't have failure messages.""" |
| self.PatchObject(completion_stages.CommitQueueCompletionStage, |
| '_GetInfraFailMessages', return_value=[]) |
| self.PatchObject(builder_status_lib, |
| 'GetBuildersWithNoneMessages', return_value=[]) |
| |
| # An alert is sent, since we have an inflight build still. |
| self.VerifyStage(no_stat=['no_stat'], alert=True) |
| |
| def test_IsFailureFatalWithCLs(self): |
| """Test _IsFailureFatal with CLs.""" |
| stage = self.ConstructStage() |
| stage.sync_stage.pool.HasPickedUpCLs.return_value = True |
| |
| self.assertFalse(stage._IsFailureFatal(set(), set(), set())) |
| self.assertTrue(stage._IsFailureFatal(set(['test3']), set(), set())) |
| |
| def test_IsFailureFatalWithoutCLs(self): |
| """Test _IsFailureFatal without CLs.""" |
| stage = self.ConstructStage() |
| stage.sync_stage.pool.HasPickedUpCLs.return_value = False |
| |
| self.assertFalse(stage._IsFailureFatal(set(), set(), set())) |
| self.assertFalse(stage._IsFailureFatal(set(['test3']), set(), set())) |
| |
| def testIsFailureFatalWithSelfDestruction(self): |
| """test _IsFailureFatal with self-destruction.""" |
| mock_cidb = mock.MagicMock() |
| cidb.CIDBConnectionFactory.SetupMockCidb(mock_cidb) |
| self.PatchObject( |
| builder_status_lib, |
| 'GetSlavesAbortedBySelfDestructedMaster', |
| return_value={'slave-1', 'slave-2', 'slave-3'}) |
| stage = self.ConstructStage() |
| |
| self.assertFalse(stage._IsFailureFatal( |
| {'slave-1'}, {'slave-2'}, {'slave-3'}, True)) |
| |
| self.assertTrue(stage._IsFailureFatal( |
| {'slave-1', 'slave-4'}, {'slave-2'}, {'slave-3'}, True)) |
| |
| def testIsFailureFatalWithSelfDestructionWithSuccess(self): |
| """test _IsFailureFatal with self-destruction-with-success.""" |
| mock_cidb = mock.MagicMock() |
| cidb.CIDBConnectionFactory.SetupMockCidb(mock_cidb) |
| self.PatchObject( |
| builder_status_lib, |
| 'GetSlavesAbortedBySelfDestructedMaster', |
| return_value={'slave-1', 'slave-2', 'slave-3'}) |
| stage = self.ConstructStage() |
| stage._run.attrs.metadata.UpdateWithDict( |
| {constants.SELF_DESTRUCTED_WITH_SUCCESS_BUILD: True}) |
| |
| self.assertFalse(stage._IsFailureFatal( |
| {'slave-1', 'slave-4'}, {'slave-2'}, {'slave-3'}, True)) |
| |
| self.assertTrue(stage._IsFailureFatal( |
| {stage._run.config.name}, {'slave-2'}, {'slave-3'}, True)) |
| |
| def testSendInfraAlertIfNeededWithAlerts(self): |
| """Test SendInfraAlertIfNeeded which sends alerts.""" |
| mock_send_alert = self.PatchObject(alerts, 'SendHealthAlert') |
| self.PatchObject(completion_stages.CommitQueueCompletionStage, |
| '_GetInfraFailMessages', return_value=['failure_message']) |
| self.PatchObject(builder_status_lib, |
| 'GetBuildersWithNoneMessages', return_value=[]) |
| stage = self.ConstructStage() |
| failing = {'failing_build'} |
| inflight = {'inflight_build'} |
| no_stat = {'no_stat_build'} |
| |
| stage.SendInfraAlertIfNeeded(failing, inflight, no_stat, False) |
| self.assertEqual(mock_send_alert.call_count, 1) |
| stage.SendInfraAlertIfNeeded(failing, inflight, no_stat, True) |
| self.assertEqual(mock_send_alert.call_count, 2) |
| |
| def testSendInfraAlertIfNeededWithoutAlerts(self): |
| """Test SendInfraAlertIfNeeded which doesn't send alerts.""" |
| mock_send_alert = self.PatchObject(alerts, 'SendHealthAlert') |
| self.PatchObject(completion_stages.CommitQueueCompletionStage, |
| '_GetInfraFailMessages', return_value=[]) |
| self.PatchObject(builder_status_lib, |
| 'GetBuildersWithNoneMessages', return_value=[]) |
| stage = self.ConstructStage() |
| inflight = {'inflight_build'} |
| no_stat = {'no_stat_build'} |
| |
| stage.SendInfraAlertIfNeeded({}, inflight, no_stat, True) |
| self.assertEqual(mock_send_alert.call_count, 0) |
| |
| |
| class PublishUprevChangesStageTest( |
| generic_stages_unittest.AbstractStageTestCase): |
| """Tests for the PublishUprevChanges stage.""" |
| BOT_ID = 'master-chromium-pfq' |
| |
| def setUp(self): |
| self.PatchObject(completion_stages.PublishUprevChangesStage, |
| '_GetPortageEnvVar') |
| |
| overlays_map = { |
| constants.BOTH_OVERLAYS: ['ext', 'int'], |
| constants.PUBLIC_OVERLAYS: ['ext'], |
| constants.PRIVATE_OVERLAYS: ['int'], |
| } |
| |
| self.PatchObject(portage_util, 'FindOverlays', |
| side_effect=lambda o, buildroot: overlays_map[o]) |
| self.PatchObject(prebuilts.BinhostConfWriter, 'Perform') |
| self.push_mock = self.PatchObject(commands, 'UprevPush') |
| self.PatchObject(generic_stages.BuilderStage, 'GetRepoRepository') |
| self.PatchObject(commands, 'UprevPackages') |
| |
| self._Prepare() |
| self.buildstore = FakeBuildStore() |
| |
| def ConstructStage(self): |
| sync_stage = sync_stages.ManifestVersionedSyncStage(self._run, |
| self.buildstore) |
| sync_stage.pool = mock.MagicMock() |
| return completion_stages.PublishUprevChangesStage( |
| self._run, self.buildstore, sync_stage, success=True) |
| |
| def testPush(self): |
| """Test values for PublishUprevChanges.""" |
| self._Prepare(extra_config={'push_overlays': constants.PUBLIC_OVERLAYS}, |
| extra_cmd_args=['--chrome_rev', constants.CHROME_REV_TOT]) |
| self._run.options.prebuilts = True |
| self.RunStage() |
| self.push_mock.assert_called_once_with( |
| self.build_root, overlay_type='public', dryrun=False, |
| staging_branch=None) |
| self.assertTrue(self._run.attrs.metadata.GetValue('UprevvedChrome')) |
| metadata_dict = self._run.attrs.metadata.GetDict() |
| self.assertFalse(metadata_dict.has_key('UprevvedAndroid')) |
| |
| def testCheckSlaveUploadPrebuiltsTest(self): |
| """Tests for CheckSlaveUploadPrebuiltsTest.""" |
| stage = self.ConstructStage() |
| stage._build_stage_id = 'test_build_stage_id' |
| |
| mock_cidb = mock.MagicMock() |
| cidb.CIDBConnectionFactory.SetupMockCidb(mock_cidb) |
| |
| stage_name = 'UploadPrebuilts' |
| |
| slave_a = 'slave_a' |
| slave_b = 'slave_b' |
| slave_c = 'slave_c' |
| |
| slave_configs_a = [{'name': slave_a}, {'name': slave_b}] |
| slave_stages_a = [{'name': stage_name, |
| 'build_config': slave_a, |
| 'status': constants.BUILDER_STATUS_PASSED}, |
| {'name': stage_name, |
| 'build_config': slave_b, |
| 'status': constants.BUILDER_STATUS_PASSED}] |
| |
| self.PatchObject( |
| completion_stages.PublishUprevChangesStage, |
| '_GetSlaveConfigs', |
| return_value=slave_configs_a) |
| self.PatchObject(FakeBuildStore, 'GetBuildStatuses', return_value=[]) |
| self.PatchObject(FakeBuildStore, 'GetBuildsStages', |
| return_value=slave_stages_a) |
| |
| # All important slaves are covered |
| self.assertTrue(stage.CheckSlaveUploadPrebuiltsTest()) |
| |
| slave_stages_b = [{'name': stage_name, |
| 'build_config': slave_a, |
| 'status': constants.BUILDER_STATUS_FAILED}, |
| {'name': stage_name, |
| 'build_config': slave_b, |
| 'status': constants.BUILDER_STATUS_PASSED}] |
| self.PatchObject( |
| completion_stages.PublishUprevChangesStage, |
| '_GetSlaveConfigs', |
| return_value=slave_configs_a) |
| self.PatchObject(FakeBuildStore, 'GetBuildsStages', |
| return_value=slave_stages_b) |
| |
| # Slave_a didn't pass the stage |
| self.assertFalse(stage.CheckSlaveUploadPrebuiltsTest()) |
| |
| slave_configs_b = [{'name': slave_a}, {'name': slave_b}, {'name': slave_c}] |
| self.PatchObject( |
| completion_stages.PublishUprevChangesStage, |
| '_GetSlaveConfigs', |
| return_value=slave_configs_b) |
| self.PatchObject(FakeBuildStore, 'GetBuildsStages', |
| return_value=slave_stages_a) |
| |
| # No stage information for slave_c |
| self.assertFalse(stage.CheckSlaveUploadPrebuiltsTest()) |
| |
| def testAndroidPush(self): |
| """Test values for PublishUprevChanges with Android PFQ.""" |
| self._Prepare( |
| bot_id=constants.NYC_ANDROID_PFQ_MASTER, |
| extra_config={'push_overlays': constants.PUBLIC_OVERLAYS}, |
| extra_cmd_args=['--android_rev', constants.ANDROID_REV_LATEST]) |
| self._run.options.prebuilts = True |
| self.RunStage() |
| self.push_mock.assert_called_once_with( |
| self.build_root, overlay_type='public', dryrun=False, |
| staging_branch=None) |
| self.assertTrue(self._run.attrs.metadata.GetValue('UprevvedAndroid')) |
| metadata_dict = self._run.attrs.metadata.GetDict() |
| self.assertFalse(metadata_dict.has_key('UprevvedChrome')) |
| |
| def testPerformStageOnChromePFQ(self): |
| """Test PerformStage on ChromePFQ.""" |
| stage = self.ConstructStage() |
| stage.sync_stage.pool.HasPickedUpCLs.return_value = True |
| stage.PerformStage() |
| self.push_mock.assert_called_once_with( |
| self.build_root, overlay_type='both', dryrun=False, staging_branch=None) |
| |
| def testPerformStageOnCQMasterWithPickedUpCLs(self): |
| """Test PerformStage on CQ-master with picked up CLs.""" |
| self._Prepare(bot_id=constants.CQ_MASTER) |
| stage = self.ConstructStage() |
| stage.sync_stage.pool.HasPickedUpCLs.return_value = True |
| stage.PerformStage() |
| self.push_mock.assert_called_once_with( |
| self.build_root, overlay_type='both', dryrun=False, staging_branch=None) |
| |
| def testPerformStageOnCQMasterWithoutPickedUpCLs(self): |
| """Test PerformStage on CQ-master without picked up CLs.""" |
| self._Prepare(bot_id=constants.CQ_MASTER) |
| stage = self.ConstructStage() |
| stage.sync_stage.pool.HasPickedUpCLs.return_value = False |
| stage.PerformStage() |
| self.push_mock.assert_not_called() |