blob: 5b3de3b1ca0bb245c0d5953bd3d497043b5f7676 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2011 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.
"""Module that contains unittests for validation_pool module."""
import logging
import mox
import sys
import unittest
import urllib
import copy
import itertools
import constants
sys.path.insert(0, constants.SOURCE_ROOT)
from chromite.buildbot import gerrit_helper
from chromite.buildbot import patch as cros_patch
from chromite.buildbot import validation_pool
from chromite.buildbot import patch_unittest
from chromite.lib import cros_build_lib
_CountingSource = itertools.count()
# pylint: disable=W0212,R0904
class TestValidationPool(mox.MoxTestBase):
"""Tests methods in validation_pool.ValidationPool."""
def setUp(self):
mox.MoxTestBase.setUp(self)
self.mox.StubOutWithMock(validation_pool, '_RunCommand')
@property
def test_json(self):
return copy.deepcopy(patch_unittest.FAKE_PATCH_JSON)
def MockPatch(self, change_id, patch_number=None):
patch = self.mox.CreateMock(cros_patch.GerritPatch)
patch.id = 'ChangeId%i' % (change_id,)
patch.gerrit_number = change_id
patch.patch_number = (patch_number if patch_number is not None else
_CountingSource.next())
patch.url = 'fake_url/%i' % (change_id,)
patch.apply_error_message = None
patch.project = 'chromiumos/chromite'
return patch
def GetPool(self, *args):
pool = validation_pool.ValidationPool(*args)
self.mox.StubOutWithMock(pool, '_SendNotification')
self.mox.StubOutWithMock(pool.gerrit_helper, '_SqlQuery')
self.mox.StubOutWithMock(pool.gerrit_helper, 'FindContentMergingProjects')
return pool
@staticmethod
def SetPoolsContentMergingProjects(pool, *projects):
pool.gerrit_helper.FindContentMergingProjects().AndReturn(
frozenset(projects))
def _TreeStatusFile(self, message, general_state):
"""Returns a file-like object with the status message writtin in it."""
my_response = self.mox.CreateMockAnything()
my_response.json = '{"message": "%s", "general_state": "%s"}' % (
message, general_state)
return my_response
def _TreeStatusTestHelper(self, tree_status, general_state, expected_return,
retries_500=0, max_timeout=0):
"""Tests whether we return the correct value based on tree_status."""
return_status = self._TreeStatusFile(tree_status, general_state)
self.mox.StubOutWithMock(urllib, 'urlopen')
status_url = 'https://chromiumos-status.appspot.com/current?format=json'
for _ in range(retries_500):
urllib.urlopen(status_url).AndReturn(return_status)
return_status.getcode().AndReturn(500)
urllib.urlopen(status_url).MultipleTimes().AndReturn(return_status)
return_status.getcode().MultipleTimes().AndReturn(200)
return_status.read().MultipleTimes().AndReturn(return_status.json)
self.mox.ReplayAll()
self.assertEqual(validation_pool.ValidationPool._IsTreeOpen(max_timeout),
expected_return)
self.mox.VerifyAll()
def testTreeIsOpen(self):
"""Tests that we return True is the tree is open."""
self._TreeStatusTestHelper('Tree is open (flaky bug on flaky builder)',
'open', True)
def testTreeIsClosed(self):
"""Tests that we return false is the tree is closed."""
self._TreeStatusTestHelper('Tree is closed (working on a patch)', 'closed',
False, max_timeout=5)
def testTreeIsOpenWithTimeout(self):
"""Tests that we return True even if we get some failures."""
self._TreeStatusTestHelper('Tree is open (flaky test)', 'open',
True, retries_500=2, max_timeout=10)
def testTreeIsThrottled(self):
"""Tests that we return false is the tree is throttled."""
self._TreeStatusTestHelper('Tree is throttled (waiting to cycle)',
'throttled', True)
def testTreeStatusWithNetworkFailures(self):
"""Checks for non-500 errors.."""
self._TreeStatusTestHelper('Tree is open (flaky bug on flaky builder)',
'open', True, retries_500=2)
def testSimpleDepApplyPoolIntoRepo(self):
"""Test that we can apply changes correctly and respect deps.
This tests a simple out-of-order change where change1 depends on change2
but tries to get applied before change2. What should happen is that
we should notice change2 is a dep of change1 and apply it first.
"""
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
build_root = 'fakebuildroot'
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch1, patch2]
self.SetPoolsContentMergingProjects(pool)
patch1.GerritDependencies(build_root).AndReturn(['ChangeId2'])
patch1.PaladinDependencies(build_root).AndReturn([])
patch2.Apply(build_root, trivial=True)
pool.HandleApplied(patch2)
patch1.Apply(build_root, trivial=True)
pool.HandleApplied(patch1)
self.mox.ReplayAll()
self.assertTrue(pool.ApplyPoolIntoRepo(build_root))
self.mox.VerifyAll()
def testSimpleNoApplyPoolIntoRepo(self):
"""Test that we don't try to apply a change without met dependencies.
Patch2 is in the validation pool that depends on Patch1 (which is not)
Nothing should get applied.
"""
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
patch2.project = 'fake_project'
build_root = 'fakebuildroot'
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch2]
helper = self.mox.CreateMock(gerrit_helper.GerritHelper)
pool.gerrit_helper = helper
patch2.GerritDependencies(build_root).AndReturn(['ChangeId1'])
patch2.PaladinDependencies(build_root).AndReturn([])
helper.IsChangeCommitted(patch1.id, must_match=False).AndReturn(False)
pool._SendNotification(patch2, mox.StrContains('dependent change'))
helper.RemoveCommitReady(patch2, dryrun=False)
self.mox.ReplayAll()
self.assertFalse(pool.ApplyPoolIntoRepo(build_root))
self.mox.VerifyAll()
def testSimpleDepApplyWhenAlreadySubmitted(self):
"""Test that we apply a change with dependency already committed."""
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
patch2.project = 'fake_project'
build_root = 'fakebuildroot'
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch2]
patch2.project = '3way-project'
self.SetPoolsContentMergingProjects(pool, '3way-project')
self.mox.StubOutWithMock(pool.gerrit_helper, 'IsChangeCommitted')
pool.gerrit_helper.IsChangeCommitted(
patch1.id, must_match=False).AndReturn(True)
patch2.GerritDependencies(build_root).AndReturn(['ChangeId1'])
patch2.PaladinDependencies(build_root).AndReturn([])
patch2.Apply(build_root, trivial=False)
pool.HandleApplied(patch2)
self.mox.ReplayAll()
self.assertTrue(pool.ApplyPoolIntoRepo(build_root))
self.mox.VerifyAll()
def testSimpleDepFailedApplyPoolIntoRepo(self):
"""Test that can apply changes correctly when one change fails to apply.
This tests a simple change order where 1 depends on 2 and 1 fails to apply.
Only 1 should get tried as 2 will abort once it sees that 1 can't be
applied. 3 with no dependencies should go through fine.
Since patch1 fails to apply, we should also get a call to handle the
failure.
"""
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
patch3 = self.MockPatch(3)
patch4 = self.MockPatch(4)
build_root = 'fakebuildroot'
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch1, patch2, patch3, patch4]
self.mox.StubOutWithMock(pool.gerrit_helper, 'RemoveCommitReady')
self.SetPoolsContentMergingProjects(pool)
pool.build_log = 'log'
patch1.GerritDependencies(build_root).AndReturn([])
patch1.PaladinDependencies(build_root).AndReturn([])
patch1.Apply(build_root, trivial=True).AndRaise(
cros_patch.ApplyPatchException(patch1))
patch2.GerritDependencies(build_root).AndReturn(['ChangeId1'])
patch2.PaladinDependencies(build_root).AndReturn([])
patch3.GerritDependencies(build_root).AndReturn([])
patch3.PaladinDependencies(build_root).AndReturn([])
patch3.Apply(build_root, trivial=True)
pool.HandleApplied(patch3)
# This one should be handled later (not where patch1 is handled.
patch4.GerritDependencies(build_root).AndReturn([])
patch4.PaladinDependencies(build_root).AndReturn([])
patch4.Apply(build_root, trivial=True).AndRaise(
cros_patch.ApplyPatchException(
patch1,
patch_type=\
cros_patch.ApplyPatchException.TYPE_REBASE_TO_PATCH_INFLIGHT))
pool.HandleCouldNotApply(patch1)
self.mox.ReplayAll()
self.assertTrue(pool.ApplyPoolIntoRepo(build_root))
self.assertTrue(patch4 in pool.changes_that_failed_to_apply_earlier)
self.mox.VerifyAll()
def testSimpleApplyButMissingChangeIDIntoRepo(self):
"""Test that applies changes correctly with a dep with missing changeid."""
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
build_root = 'fakebuildroot'
pool = self.GetPool(False, 1, 'build_name', True, False)
self.mox.StubOutWithMock(pool, 'HandleCouldNotApply')
pool.changes = [patch1, patch2]
pool.build_log = 'log'
self.SetPoolsContentMergingProjects(pool)
patch1.GerritDependencies(build_root).AndRaise(
cros_patch.MissingChangeIDException('Could not find changeid'))
patch2.GerritDependencies(build_root).AndReturn([])
patch2.PaladinDependencies(build_root).AndReturn([])
patch2.Apply(build_root, trivial=True)
pool.HandleApplied(patch2)
pool.HandleCouldNotApply(patch1)
self.mox.ReplayAll()
self.assertTrue(pool.ApplyPoolIntoRepo(build_root))
self.assertEqual([patch2.id], [x.id for x in pool.changes])
self.mox.VerifyAll()
def testMoreComplexDepApplyPoolIntoRepo(self):
"""More complex deps test.
This tests a total of 2 change chains where the first change we see
only has a partial chain with the 3rd change having the whole chain i.e.
1->2, 3->1->2, 4->nothing. Since we get these in the order 1,2,3,4 the
order we should apply is 2,1,3,4.
This test also checks the patch order to verify that Apply re-orders
correctly based on the chain.
"""
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
patch3 = self.MockPatch(3)
patch4 = self.MockPatch(4)
patch5 = self.MockPatch(5)
build_root = 'fakebuildroot'
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch1, patch2, patch3, patch4, patch5]
self.SetPoolsContentMergingProjects(pool)
patch1.GerritDependencies(build_root).AndReturn(['ChangeId2'])
patch1.PaladinDependencies(build_root).AndReturn([])
patch3.GerritDependencies(build_root).AndReturn(['ChangeId1', 'ChangeId2'])
patch3.PaladinDependencies(build_root).AndReturn([])
patch4.GerritDependencies(build_root).AndReturn([])
patch4.PaladinDependencies(build_root).AndReturn(['ChangeId5'])
patch2.Apply(build_root, trivial=True)
pool.HandleApplied(patch2)
patch1.Apply(build_root, trivial=True)
pool.HandleApplied(patch1)
patch3.Apply(build_root, trivial=True)
pool.HandleApplied(patch3)
patch5.Apply(build_root, trivial=True)
pool.HandleApplied(patch5)
patch4.Apply(build_root, trivial=True)
pool.HandleApplied(patch4)
self.mox.ReplayAll()
self.assertTrue(pool.ApplyPoolIntoRepo(build_root))
# Check order.
self.assertEquals([x.id for x in pool.changes],
[y.id for y in [patch2, patch1, patch3, patch5, patch4]])
self.mox.VerifyAll()
def testNoDepsApplyPoolIntoRepo(self):
"""Simple apply of two changes with no dependent CL's."""
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
build_root = 'fakebuildroot'
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch1, patch2]
self.SetPoolsContentMergingProjects(pool)
patch1.GerritDependencies(build_root).AndReturn([])
patch1.PaladinDependencies(build_root).AndReturn([])
patch2.GerritDependencies(build_root).AndReturn([])
patch2.PaladinDependencies(build_root).AndReturn([])
patch1.Apply(build_root, trivial=True)
pool.HandleApplied(patch1)
patch2.Apply(build_root, trivial=True)
pool.HandleApplied(patch2)
self.mox.ReplayAll()
self.assertTrue(pool.ApplyPoolIntoRepo(build_root))
self.mox.VerifyAll()
def testSubmitPoolWithSomeFailures(self):
"""Tests submitting a pool when some changes fail to be submitted.
Tests what happens when we try to submit 3 patches with 2 patches failing
to submit correctly (one with submit failure and the other not showing up
as submitted in Gerrit.
"""
self.mox.StubOutWithMock(validation_pool.ValidationPool, '_IsTreeOpen')
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
patch3 = self.MockPatch(3)
helper = self.mox.CreateMock(gerrit_helper.GerritHelper)
build_root = 'fakebuildroot'
validation_pool.ValidationPool._IsTreeOpen().AndReturn(True)
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch1, patch2, patch3]
pool.gerrit_helper = helper
pool.dryrun = False
self.mox.StubOutWithMock(pool, 'SubmitChange')
self.mox.StubOutWithMock(helper, 'RemoveCommitReady')
pool.SubmitChange(patch1)
helper.IsChangeCommitted(patch1.id, False).AndReturn(False)
pool.HandleCouldNotSubmit(patch1)
pool.SubmitChange(patch2).AndRaise(
cros_build_lib.RunCommandError('Failed to submit', 'cmd', 1))
pool.HandleCouldNotSubmit(patch2)
pool.SubmitChange(patch3)
helper.IsChangeCommitted(patch3.id, False).AndReturn(True)
self.mox.ReplayAll()
self.assertRaises(validation_pool.FailedToSubmitAllChangesException,
validation_pool.ValidationPool.SubmitPool, (pool))
self.mox.VerifyAll()
def testSimpleSubmitPool(self):
"""Tests the ability to submit a list of changes."""
self.mox.StubOutWithMock(validation_pool.ValidationPool, '_IsTreeOpen')
helper = self.mox.CreateMock(gerrit_helper.GerritHelper)
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
build_root = 'fakebuildroot'
validation_pool.ValidationPool._IsTreeOpen().AndReturn(True)
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.changes = [patch1, patch2]
pool.gerrit_helper = helper
pool.dryrun = False
self.mox.StubOutWithMock(pool, 'SubmitChange')
pool.SubmitChange(patch1)
helper.IsChangeCommitted(patch1.id, False).AndReturn(True)
pool.SubmitChange(patch2)
helper.IsChangeCommitted(patch2.id, False).AndReturn(True)
self.mox.ReplayAll()
pool.SubmitPool()
self.mox.VerifyAll()
def testSubmitNonManifestChanges(self):
"""Simple test to make sure we can submit non-manifest changes."""
self.mox.StubOutWithMock(validation_pool.ValidationPool, '_IsTreeOpen')
patch1 = self.MockPatch(1)
patch2 = self.MockPatch(2)
helper = self.mox.CreateMock(gerrit_helper.GerritHelper)
build_root = 'fakebuildroot'
validation_pool.ValidationPool._IsTreeOpen().AndReturn(True)
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.non_manifest_changes = [patch1, patch2]
pool.gerrit_helper = helper
pool.dryrun = False
self.mox.StubOutWithMock(pool, 'SubmitChange')
pool.SubmitChange(patch1)
helper.IsChangeCommitted(patch1.id, False).AndReturn(True)
pool.SubmitChange(patch2)
helper.IsChangeCommitted(patch2.id, False).AndReturn(True)
self.mox.ReplayAll()
pool.SubmitNonManifestChanges()
self.mox.VerifyAll()
def testGerritSubmit(self):
"""Tests submission review string looks correct."""
pool = self.GetPool(False, 1, 'build_name', True, False)
my_patch = cros_patch.GerritPatch(self.test_json, False)
validation_pool._RunCommand(
'ssh -p 29418 gerrit.chromium.org gerrit review '
'--submit 1112,2'.split(), False).AndReturn(None)
self.mox.ReplayAll()
pool.SubmitChange(my_patch)
self.mox.VerifyAll()
def testGerritHandleApplied(self):
"""Tests review string looks correct."""
pool = self.GetPool(False, 1, 'build_name', True, False)
my_patch = cros_patch.GerritPatch(self.test_json, False)
pool._SendNotification(my_patch, mox.IgnoreArg())
self.mox.ReplayAll()
pool.HandleApplied(my_patch)
self.mox.VerifyAll()
def testGerritHandleApplyError(self):
"""Tests review string looks correct."""
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.gerrit_helper._SqlQuery(mox.IgnoreArg(), dryrun=mox.IgnoreArg(),
is_command=True).AndReturn(None)
my_patch = cros_patch.GerritPatch(self.test_json, False)
pool._SendNotification(my_patch, mox.IgnoreArg())
self.mox.ReplayAll()
pool.HandleCouldNotApply(my_patch)
self.mox.VerifyAll()
def testGerritHandleSubmitError(self):
"""Tests review string looks correct."""
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.gerrit_helper._SqlQuery(mox.IgnoreArg(), dryrun=mox.IgnoreArg(),
is_command=True).AndReturn(None)
my_patch = cros_patch.GerritPatch(self.test_json, False)
pool._SendNotification(my_patch, mox.IgnoreArg())
self.mox.ReplayAll()
pool.HandleCouldNotSubmit(my_patch)
self.mox.VerifyAll()
def testGerritHandleVerifyError(self):
"""Tests review string looks correct."""
pool = self.GetPool(False, 1, 'build_name', True, False)
pool.gerrit_helper._SqlQuery(mox.IgnoreArg(), dryrun=mox.IgnoreArg(),
is_command=True).AndReturn(None)
my_patch = cros_patch.GerritPatch(self.test_json, False)
pool._SendNotification(my_patch, mox.IgnoreArg())
self.mox.ReplayAll()
pool.HandleCouldNotVerify(my_patch)
self.mox.VerifyAll()
if __name__ == '__main__':
logging_format = '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s'
date_format = '%H:%M:%S'
logging.basicConfig(level=logging.DEBUG, format=logging_format,
datefmt=date_format)
unittest.main()