blob: ced4a5d2009b09bb8cd022c05e0b548591ebcdbb [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2013 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.
"""Unit tests for chromite.lib.git and helpers for testing that module."""
import functools
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(
os.path.abspath(__file__)))))
from chromite.lib import cros_build_lib
from chromite.lib import cros_build_lib_unittest
from chromite.lib import cros_test_lib
from chromite.lib import git
from chromite.lib import partial_mock
import mock
class ManifestMock(partial_mock.PartialMock):
"""Partial mock for git.Manifest."""
TARGET = 'chromite.lib.git.Manifest'
ATTRS = ('_RunParser',)
def _RunParser(self, *_args):
pass
class ManifestCheckoutMock(partial_mock.PartialMock):
"""Partial mock for git.ManifestCheckout."""
TARGET = 'chromite.lib.git.ManifestCheckout'
ATTRS = ('_GetManifestsBranch',)
def _GetManifestsBranch(self, _root):
return 'default'
class NormalizeRefTest(cros_test_lib.TestCase):
"""Test the Normalize*Ref functions."""
def _TestNormalize(self, functor, tests):
"""Helper function for testing Normalize*Ref functions.
Args:
functor: Normalize*Ref functor that only needs the input
ref argument.
tests: Dict of test inputs to expected test outputs.
"""
for test_input, test_output in tests.iteritems():
result = functor(test_input)
msg = ('Expected %s to translate %r to %r, but got %r.' %
(functor.__name__, test_input, test_output, result))
self.assertEquals(test_output, result, msg)
def testNormalizeRef(self):
"""Test git.NormalizeRef function."""
tests = {
# These should all get 'refs/heads/' prefix.
'foo': 'refs/heads/foo',
'foo-bar-123': 'refs/heads/foo-bar-123',
# If input starts with 'refs/' it should be left alone.
'refs/foo/bar': 'refs/foo/bar',
'refs/heads/foo': 'refs/heads/foo',
# Plain 'refs' is nothing special.
'refs': 'refs/heads/refs',
None: None,
}
self._TestNormalize(git.NormalizeRef, tests)
def testNormalizeRemoteRef(self):
"""Test git.NormalizeRemoteRef function."""
remote = 'TheRemote'
tests = {
# These should all get 'refs/remotes/TheRemote' prefix.
'foo': 'refs/remotes/%s/foo' % remote,
'foo-bar-123': 'refs/remotes/%s/foo-bar-123' % remote,
# These should be translated from local to remote ref.
'refs/heads/foo': 'refs/remotes/%s/foo' % remote,
'refs/heads/foo-bar-123': 'refs/remotes/%s/foo-bar-123' % remote,
# These should be moved from one remote to another.
'refs/remotes/OtherRemote/foo': 'refs/remotes/%s/foo' % remote,
# These should be left alone.
'refs/remotes/%s/foo' % remote: 'refs/remotes/%s/foo' % remote,
'refs/foo/bar': 'refs/foo/bar',
# Plain 'refs' is nothing special.
'refs': 'refs/remotes/%s/refs' % remote,
None: None,
}
# Add remote arg to git.NormalizeRemoteRef.
functor = functools.partial(git.NormalizeRemoteRef, remote)
functor.__name__ = git.NormalizeRemoteRef.__name__
self._TestNormalize(functor, tests)
class ProjectCheckoutTest(cros_test_lib.TestCase):
"""Tests for git.ProjectCheckout"""
def setUp(self):
self.fake_unversioned_patchable = git.ProjectCheckout(
dict(name='chromite',
path='src/chromite',
revision='remotes/for/master'))
self.fake_unversioned_unpatchable = git.ProjectCheckout(
dict(name='chromite',
path='src/platform/somethingsomething/chromite',
# Pinned to a SHA1.
revision='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf'))
self.fake_versioned_patchable = git.ProjectCheckout(
dict(name='chromite',
path='src/chromite',
revision='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf',
upstream='remotes/for/master'))
self.fake_versioned_unpatchable = git.ProjectCheckout(
dict(name='chromite',
path='src/chromite',
revision='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf',
upstream='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf'))
def testIsPatchable(self):
self.assertTrue(self.fake_unversioned_patchable.IsPatchable())
self.assertFalse(self.fake_unversioned_unpatchable.IsPatchable())
self.assertTrue(self.fake_versioned_patchable.IsPatchable())
self.assertFalse(self.fake_versioned_unpatchable.IsPatchable())
class GitPushTest(cros_test_lib.MockTestCase):
"""Tests for git.GitPush function."""
# Non fast-forward push error message.
NON_FF_PUSH_ERROR = ('To https://localhost/repo.git\n'
'! [remote rejected] master -> master (non-fast-forward)\n'
'error: failed to push some refs to \'https://localhost/repo.git\'\n')
# List of possible GoB transient errors.
TRANSIENT_ERRORS = (
# Hook error when creating a new branch from SHA1 ref.
('remote: Processing changes: (-)To https://localhost/repo.git\n'
'! [remote rejected] 6c78ca083c3a9d64068c945fd9998eb1e0a3e739 -> '
'stabilize-4636.B (error in hook)\n'
'error: failed to push some refs to \'https://localhost/repo.git\'\n'),
# 'failed to lock' error when creating a new branch from SHA1 ref.
('remote: Processing changes: done\nTo https://localhost/repo.git\n'
'! [remote rejected] 4ea09c129b5fedb261bae2431ce2511e35ac3923 -> '
'stabilize-daisy-4319.96.B (failed to lock)\n'
'error: failed to push some refs to \'https://localhost/repo.git\'\n'),
# Hook error when pushing branch.
('remote: Processing changes: (\)To https://localhost/repo.git\n'
'! [remote rejected] temp_auto_checkin_branch -> '
'master (error in hook)\n'
'error: failed to push some refs to \'https://localhost/repo.git\'\n'),
# Another kind of error when pushing a branch.
'fatal: remote error: Internal Server Error',
)
def setUp(self):
self.StartPatcher(mock.patch('time.sleep'))
@staticmethod
def _RunGitPush():
"""Runs git.GitPush with some default arguments."""
git.GitPush('some_repo_path', 'local-ref',
git.RemoteRef('some-remote', 'remote-ref'),
dryrun=True, retry=True)
def testPushSuccess(self):
"""Test handling of successful git push."""
with cros_build_lib_unittest.RunCommandMock() as rc_mock:
rc_mock.AddCmdResult(partial_mock.In('push'), returncode=0)
self._RunGitPush()
def testNonFFPush(self):
"""Non fast-forward push error propagates to the caller."""
with cros_build_lib_unittest.RunCommandMock() as rc_mock:
rc_mock.AddCmdResult(partial_mock.In('push'), returncode=128,
error=self.NON_FF_PUSH_ERROR)
self.assertRaises(cros_build_lib.RunCommandError, self._RunGitPush)
def testPersistentTransientError(self):
"""GitPush fails if transient error occurs multiple times."""
for error in self.TRANSIENT_ERRORS:
with cros_build_lib_unittest.RunCommandMock() as rc_mock:
rc_mock.AddCmdResult(partial_mock.In('push'), returncode=128,
error=error)
self.assertRaises(cros_build_lib.RunCommandError, self._RunGitPush)
def testOneTimeTransientError(self):
"""GitPush retries transient errors."""
for error in self.TRANSIENT_ERRORS:
with cros_build_lib_unittest.RunCommandMock() as rc_mock:
results = [
rc_mock.CmdResult(128, '', error),
rc_mock.CmdResult(0, 'success', ''),
]
side_effect = lambda *_args, **_kwargs: results.pop(0)
rc_mock.AddCmdResult(partial_mock.In('push'), side_effect=side_effect)
self._RunGitPush()
if __name__ == '__main__':
cros_test_lib.main()