2 # Copyright (c) 2013 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 """Unit tests for chromite.lib.git and helpers for testing that module."""
8 from __future__ import print_function
14 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(
15 os.path.abspath(__file__)))))
17 from chromite.lib import cros_build_lib
18 from chromite.lib import cros_build_lib_unittest
19 from chromite.lib import cros_test_lib
20 from chromite.lib import git
21 from chromite.lib import partial_mock
22 from chromite.lib import patch_unittest
27 class ManifestMock(partial_mock.PartialMock):
28 """Partial mock for git.Manifest."""
29 TARGET = 'chromite.lib.git.Manifest'
30 ATTRS = ('_RunParser',)
32 def _RunParser(self, *_args):
36 class ManifestCheckoutMock(partial_mock.PartialMock):
37 """Partial mock for git.ManifestCheckout."""
38 TARGET = 'chromite.lib.git.ManifestCheckout'
39 ATTRS = ('_GetManifestsBranch',)
41 def _GetManifestsBranch(self, _root):
45 class NormalizeRefTest(cros_test_lib.TestCase):
46 """Test the Normalize*Ref functions."""
48 def _TestNormalize(self, functor, tests):
49 """Helper function for testing Normalize*Ref functions.
52 functor: Normalize*Ref functor that only needs the input
54 tests: Dict of test inputs to expected test outputs.
56 for test_input, test_output in tests.iteritems():
57 result = functor(test_input)
58 msg = ('Expected %s to translate %r to %r, but got %r.' %
59 (functor.__name__, test_input, test_output, result))
60 self.assertEquals(test_output, result, msg)
62 def testNormalizeRef(self):
63 """Test git.NormalizeRef function."""
65 # These should all get 'refs/heads/' prefix.
66 'foo': 'refs/heads/foo',
67 'foo-bar-123': 'refs/heads/foo-bar-123',
69 # If input starts with 'refs/' it should be left alone.
70 'refs/foo/bar': 'refs/foo/bar',
71 'refs/heads/foo': 'refs/heads/foo',
73 # Plain 'refs' is nothing special.
74 'refs': 'refs/heads/refs',
78 self._TestNormalize(git.NormalizeRef, tests)
80 def testNormalizeRemoteRef(self):
81 """Test git.NormalizeRemoteRef function."""
84 # These should all get 'refs/remotes/TheRemote' prefix.
85 'foo': 'refs/remotes/%s/foo' % remote,
86 'foo-bar-123': 'refs/remotes/%s/foo-bar-123' % remote,
88 # These should be translated from local to remote ref.
89 'refs/heads/foo': 'refs/remotes/%s/foo' % remote,
90 'refs/heads/foo-bar-123': 'refs/remotes/%s/foo-bar-123' % remote,
92 # These should be moved from one remote to another.
93 'refs/remotes/OtherRemote/foo': 'refs/remotes/%s/foo' % remote,
95 # These should be left alone.
96 'refs/remotes/%s/foo' % remote: 'refs/remotes/%s/foo' % remote,
97 'refs/foo/bar': 'refs/foo/bar',
99 # Plain 'refs' is nothing special.
100 'refs': 'refs/remotes/%s/refs' % remote,
105 # Add remote arg to git.NormalizeRemoteRef.
106 functor = functools.partial(git.NormalizeRemoteRef, remote)
107 functor.__name__ = git.NormalizeRemoteRef.__name__
109 self._TestNormalize(functor, tests)
112 class ProjectCheckoutTest(cros_test_lib.TestCase):
113 """Tests for git.ProjectCheckout"""
116 self.fake_unversioned_patchable = git.ProjectCheckout(
117 dict(name='chromite',
119 revision='remotes/for/master'))
120 self.fake_unversioned_unpatchable = git.ProjectCheckout(
121 dict(name='chromite',
122 path='src/platform/somethingsomething/chromite',
124 revision='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf'))
125 self.fake_versioned_patchable = git.ProjectCheckout(
126 dict(name='chromite',
128 revision='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf',
129 upstream='remotes/for/master'))
130 self.fake_versioned_unpatchable = git.ProjectCheckout(
131 dict(name='chromite',
133 revision='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf',
134 upstream='1deadbeeaf1deadbeeaf1deadbeeaf1deadbeeaf'))
136 def testIsPatchable(self):
137 self.assertTrue(self.fake_unversioned_patchable.IsPatchable())
138 self.assertFalse(self.fake_unversioned_unpatchable.IsPatchable())
139 self.assertTrue(self.fake_versioned_patchable.IsPatchable())
140 self.assertFalse(self.fake_versioned_unpatchable.IsPatchable())
143 class GitPushTest(cros_test_lib.MockTestCase):
144 """Tests for git.GitPush function."""
146 # Non fast-forward push error message.
147 NON_FF_PUSH_ERROR = ('To https://localhost/repo.git\n'
148 '! [remote rejected] master -> master (non-fast-forward)\n'
149 'error: failed to push some refs to \'https://localhost/repo.git\'\n')
151 # List of possible GoB transient errors.
153 # Hook error when creating a new branch from SHA1 ref.
154 ('remote: Processing changes: (-)To https://localhost/repo.git\n'
155 '! [remote rejected] 6c78ca083c3a9d64068c945fd9998eb1e0a3e739 -> '
156 'stabilize-4636.B (error in hook)\n'
157 'error: failed to push some refs to \'https://localhost/repo.git\'\n'),
159 # 'failed to lock' error when creating a new branch from SHA1 ref.
160 ('remote: Processing changes: done\nTo https://localhost/repo.git\n'
161 '! [remote rejected] 4ea09c129b5fedb261bae2431ce2511e35ac3923 -> '
162 'stabilize-daisy-4319.96.B (failed to lock)\n'
163 'error: failed to push some refs to \'https://localhost/repo.git\'\n'),
165 # Hook error when pushing branch.
166 ('remote: Processing changes: (\)To https://localhost/repo.git\n'
167 '! [remote rejected] temp_auto_checkin_branch -> '
168 'master (error in hook)\n'
169 'error: failed to push some refs to \'https://localhost/repo.git\'\n'),
171 # Another kind of error when pushing a branch.
172 'fatal: remote error: Internal Server Error',
175 ('error: gnutls_handshake() failed: A TLS packet with unexpected length '
176 'was received. while accessing '
177 'http://localhost/repo.git/info/refs?service=git-upload-pack\n'
178 'fatal: HTTP request failed'),
181 ('fatal: unable to access \'https://localhost/repo.git\': GnuTLS recv '
182 'error (-9): A TLS packet with unexpected length was received.'),
186 self.StartPatcher(mock.patch('time.sleep'))
190 """Runs git.GitPush with some default arguments."""
191 git.GitPush('some_repo_path', 'local-ref',
192 git.RemoteRef('some-remote', 'remote-ref'),
193 dryrun=True, retry=True)
195 def testPushSuccess(self):
196 """Test handling of successful git push."""
197 with cros_build_lib_unittest.RunCommandMock() as rc_mock:
198 rc_mock.AddCmdResult(partial_mock.In('push'), returncode=0)
201 def testNonFFPush(self):
202 """Non fast-forward push error propagates to the caller."""
203 with cros_build_lib_unittest.RunCommandMock() as rc_mock:
204 rc_mock.AddCmdResult(partial_mock.In('push'), returncode=128,
205 error=self.NON_FF_PUSH_ERROR)
206 self.assertRaises(cros_build_lib.RunCommandError, self._RunGitPush)
208 def testPersistentTransientError(self):
209 """GitPush fails if transient error occurs multiple times."""
210 for error in self.TRANSIENT_ERRORS:
211 with cros_build_lib_unittest.RunCommandMock() as rc_mock:
212 rc_mock.AddCmdResult(partial_mock.In('push'), returncode=128,
214 self.assertRaises(cros_build_lib.RunCommandError, self._RunGitPush)
216 def testOneTimeTransientError(self):
217 """GitPush retries transient errors."""
218 for error in self.TRANSIENT_ERRORS:
219 with cros_build_lib_unittest.RunCommandMock() as rc_mock:
221 rc_mock.CmdResult(128, '', error),
222 rc_mock.CmdResult(0, 'success', ''),
224 side_effect = lambda *_args, **_kwargs: results.pop(0)
225 rc_mock.AddCmdResult(partial_mock.In('push'), side_effect=side_effect)
229 class GitBranchDetectionTest(patch_unittest.GitRepoPatchTestCase):
230 """Tests that git library functions related to branch detection work."""
231 def testDoesCommitExistInRepoWithAmbiguousBranchName(self):
232 git1 = self._MakeRepo('git1', self.source)
233 git.CreateBranch(git1, 'peach', track=True)
234 self.CommitFile(git1, 'peach', 'Keep me.')
235 self.assertTrue(git.DoesCommitExistInRepo(git1, 'peach'))
238 if __name__ == '__main__':