blob: dd3e0314882864a8fa4cf68b376c3c262db1db77 [file] [log] [blame]
#!/usr/bin/python
# 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 report stages."""
from __future__ import print_function
import mox
import os
import sys
sys.path.insert(0, os.path.abspath('%s/../../..' % os.path.dirname(__file__)))
from chromite.cbuildbot import commands
from chromite.cbuildbot import constants
from chromite.cbuildbot import failures_lib
from chromite.cbuildbot import validation_pool
from chromite.cbuildbot.cbuildbot_unittest import BuilderRunMock
from chromite.cbuildbot.stages import sync_stages
from chromite.cbuildbot.stages import sync_stages_unittest
from chromite.cbuildbot.stages import report_stages
from chromite.cbuildbot.stages import generic_stages_unittest
from chromite.lib import cidb
from chromite.lib import cros_test_lib
from chromite.lib import alerts
from chromite.lib import osutils
# TODO(build): Finish test wrapper (http://crosbug.com/37517).
# Until then, this has to be after the chromite imports.
import mock
# pylint: disable=R0901,W0212
class BuildStartStageTest(generic_stages_unittest.AbstractStageTest):
"""Tests that BuildStartStage behaves as expected."""
def setUp(self):
self.mock_cidb = mox.MockObject(cidb.CIDBConnection)
cidb.CIDBConnectionFactory.SetupMockCidb(self.mock_cidb)
os.environ['BUILDBOT_MASTERNAME'] = 'chromiumos'
self._Prepare(build_id = None)
def tearDown(self):
mox.Verify(self.mock_cidb)
def testUnknownWaterfall(self):
"""Test that an assertion is thrown is master name is not valid."""
os.environ['BUILDBOT_MASTERNAME'] = 'gibberish'
self.assertRaises(failures_lib.StepFailure, self.RunStage)
def testPerformStage(self):
"""Test that a normal run of the stage does a database insert."""
self.mock_cidb.InsertBuild(bot_hostname=mox.IgnoreArg(),
build_config='x86-generic-paladin',
build_number=1234321,
builder_name=mox.IgnoreArg(),
master_build_id=None,
waterfall='chromiumos').AndReturn(31337)
mox.Replay(self.mock_cidb)
self.RunStage()
self.assertEqual(self._run.attrs.metadata.GetValue('build_id'), 31337)
self.assertEqual(self._run.attrs.metadata.GetValue('db_type'),
cidb.CIDBConnectionFactory._CONNECTION_TYPE_MOCK)
def testHandleSkipWithInstanceChange(self):
"""Test that HandleSkip disables cidb and dies when necessary."""
# This test verifies that switching to a 'mock' database type once
# metadata already has an id in 'previous_db_type' will fail.
self._run.attrs.metadata.UpdateWithDict({'build_id': 31337,
'db_type': 'previous_db_type'})
stage = self.ConstructStage()
self.assertRaises(AssertionError, stage.HandleSkip)
self.assertEqual(cidb.CIDBConnectionFactory.GetCIDBConnectionType(),
cidb.CIDBConnectionFactory._CONNECTION_TYPE_INV)
# The above test has the side effect of invalidating CIDBConnectionFactory.
# Undo that side effect so other unit tests can run.
cidb.CIDBConnectionFactory._ClearCIDBSetup()
cidb.CIDBConnectionFactory.SetupMockCidb()
def testHandleSkipWithNoDbType(self):
"""Test that HandleSkip passes when db_type is missing."""
self._run.attrs.metadata.UpdateWithDict({'build_id': 31337})
stage = self.ConstructStage()
stage.HandleSkip()
def testHandleSkipWithDbType(self):
"""Test that HandleSkip passes when db_type is specified."""
self._run.attrs.metadata.UpdateWithDict(
{'build_id': 31337,
'db_type': cidb.CIDBConnectionFactory._CONNECTION_TYPE_MOCK})
stage = self.ConstructStage()
stage.HandleSkip()
def ConstructStage(self):
return report_stages.BuildStartStage(self._run)
# pylint: disable=R0901,W0212
class ReportStageTest(generic_stages_unittest.AbstractStageTest):
"""Test the Report stage."""
RELEASE_TAG = ''
def setUp(self):
for cmd in ((osutils, 'WriteFile'),
(commands, 'UploadArchivedFile'),
(alerts, 'SendEmail')):
self.StartPatcher(mock.patch.object(*cmd, autospec=True))
self.StartPatcher(BuilderRunMock())
self.cq = sync_stages_unittest.CLStatusMock()
self.StartPatcher(self.cq)
self.sync_stage = None
# Set up a general purpose cidb mock. Tests with more specific
# mock requirements can replace this with a separate call to
# SetupMockCidb
mock_cidb = mox.MockObject(cidb.CIDBConnection)
cidb.CIDBConnectionFactory.SetupMockCidb(mock_cidb)
self._Prepare()
def _SetupUpdateStreakCounter(self, counter_value=-1):
self.PatchObject(report_stages.ReportStage, '_UpdateStreakCounter',
autospec=True, return_value=counter_value)
def _SetupCommitQueueSyncPool(self):
self.sync_stage = sync_stages.CommitQueueSyncStage(self._run)
pool = validation_pool.ValidationPool(constants.BOTH_OVERLAYS,
self.build_root, build_number=3, builder_name=self._bot_id,
is_master=True, dryrun=True)
pool.changes = [sync_stages_unittest.MockPatch()]
self.sync_stage.pool = pool
def ConstructStage(self):
return report_stages.ReportStage(self._run, self.sync_stage, None)
def testCheckResults(self):
"""Basic sanity check for results stage functionality"""
self._SetupUpdateStreakCounter()
self.PatchObject(report_stages.ReportStage, '_UploadArchiveIndex',
return_value={'any': 'dict'})
self.RunStage()
filenames = (
'LATEST-%s' % self.TARGET_MANIFEST_BRANCH,
'LATEST-%s' % BuilderRunMock.VERSION,
)
calls = [mock.call(mock.ANY, mock.ANY, 'metadata.json', False,
update_list=True, acl=mock.ANY)]
calls += [mock.call(mock.ANY, mock.ANY, filename, False,
acl=mock.ANY) for filename in filenames]
# pylint: disable=E1101
self.assertEquals(calls, commands.UploadArchivedFile.call_args_list)
def testCommitQueueResults(self):
"""Check that commit queue patches get serialized"""
self._SetupUpdateStreakCounter()
self._SetupCommitQueueSyncPool()
self.RunStage()
def testAlertEmail(self):
"""Send out alerts when streak counter reaches the threshold."""
self._Prepare(extra_config={'health_threshold': 3,
'health_alert_recipients': ['foo@bar.org']})
self._SetupUpdateStreakCounter(counter_value=-3)
self._SetupCommitQueueSyncPool()
self.RunStage()
# pylint: disable=E1101
self.assertGreater(alerts.SendEmail.call_count, 0,
'CQ health alerts emails were not sent.')
def testAlertEmailOnFailingStreak(self):
"""Continue sending out alerts when streak counter exceeds the threshold."""
self._Prepare(extra_config={'health_threshold': 3,
'health_alert_recipients': ['foo@bar.org']})
self._SetupUpdateStreakCounter(counter_value=-5)
self._SetupCommitQueueSyncPool()
self.RunStage()
# pylint: disable=E1101
self.assertGreater(alerts.SendEmail.call_count, 0,
'CQ health alerts emails were not sent.')
def testWriteBasicMetadata(self):
"""Test that WriteBasicMetadata writes expected keys correctly."""
report_stages.WriteBasicMetadata(self._run)
metadata_dict = self._run.attrs.metadata.GetDict()
self.assertEqual(metadata_dict['build-number'],
generic_stages_unittest.DEFAULT_BUILD_NUMBER)
self.assertTrue(metadata_dict.has_key('builder-name'))
self.assertTrue(metadata_dict.has_key('bot-hostname'))
if __name__ == '__main__':
cros_test_lib.main()