OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Unit tests for git_cl.py.""" | 6 """Unit tests for git_cl.py.""" |
7 | 7 |
8 import os | 8 import os |
9 import StringIO | 9 import StringIO |
10 import sys | 10 import sys |
11 import unittest | 11 import unittest |
12 | 12 |
13 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | 13 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
14 | 14 |
15 from testing_support.auto_stub import TestCase | 15 from testing_support.auto_stub import TestCase |
16 | 16 |
17 import git_cl | 17 import git_cl |
18 import subprocess2 | 18 import subprocess2 |
19 | 19 |
20 | 20 |
| 21 class PresubmitMock(object): |
| 22 def __init__(self, *args, **kwargs): |
| 23 self.reviewers = [] |
| 24 @staticmethod |
| 25 def should_continue(): |
| 26 return True |
| 27 |
| 28 |
| 29 class RietveldMock(object): |
| 30 def __init__(self, *args, **kwargs): |
| 31 pass |
| 32 @staticmethod |
| 33 def get_description(issue): |
| 34 return 'Issue: %d' % issue |
| 35 |
| 36 |
| 37 class WatchlistsMock(object): |
| 38 def __init__(self, _): |
| 39 pass |
| 40 @staticmethod |
| 41 def GetWatchersForPaths(_): |
| 42 return ['joe@example.com'] |
| 43 |
| 44 |
21 class TestGitCl(TestCase): | 45 class TestGitCl(TestCase): |
22 def setUp(self): | 46 def setUp(self): |
23 super(TestGitCl, self).setUp() | 47 super(TestGitCl, self).setUp() |
24 self.calls = [] | 48 self.calls = [] |
25 self._calls_done = 0 | 49 self._calls_done = 0 |
26 def mock_call(args, **kwargs): | 50 self.mock(subprocess2, 'call', self._mocked_call) |
27 expected_args, result = self.calls.pop(0) | 51 self.mock(subprocess2, 'check_call', self._mocked_call) |
28 self.assertEquals( | 52 self.mock(subprocess2, 'check_output', self._mocked_call) |
29 expected_args, | 53 self.mock(subprocess2, 'communicate', self._mocked_call) |
30 args, | 54 self.mock(subprocess2, 'Popen', self._mocked_call) |
31 '@%d Expected: %r Actual: %r' % ( | |
32 self._calls_done, expected_args, args)) | |
33 self._calls_done += 1 | |
34 return result | |
35 self.mock(subprocess2, 'call', mock_call) | |
36 self.mock(subprocess2, 'check_call', mock_call) | |
37 self.mock(subprocess2, 'check_output', mock_call) | |
38 self.mock(subprocess2, 'communicate', mock_call) | |
39 self.mock(subprocess2, 'Popen', mock_call) | |
40 | |
41 self.mock(git_cl, 'FindCodereviewSettingsFile', lambda: '') | 55 self.mock(git_cl, 'FindCodereviewSettingsFile', lambda: '') |
42 | 56 self.mock(git_cl, 'ask_for_data', self._mocked_call) |
43 class PresubmitMock(object): | 57 self.mock(git_cl.breakpad, 'post', self._mocked_call) |
44 def __init__(self, *args, **kwargs): | 58 self.mock(git_cl.breakpad, 'SendStack', self._mocked_call) |
45 self.reviewers = [] | |
46 @staticmethod | |
47 def should_continue(): | |
48 return True | |
49 self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock) | 59 self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock) |
50 | |
51 class RietveldMock(object): | |
52 def __init__(self, *args, **kwargs): | |
53 pass | |
54 self.mock(git_cl.rietveld, 'Rietveld', RietveldMock) | 60 self.mock(git_cl.rietveld, 'Rietveld', RietveldMock) |
55 | |
56 self.mock(git_cl.upload, 'RealMain', self.fail) | 61 self.mock(git_cl.upload, 'RealMain', self.fail) |
57 | |
58 class WatchlistsMock(object): | |
59 def __init__(self, _): | |
60 pass | |
61 @staticmethod | |
62 def GetWatchersForPaths(_): | |
63 return ['joe@example.com'] | |
64 self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock) | 62 self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock) |
65 # It's important to reset settings to not have inter-tests interference. | 63 # It's important to reset settings to not have inter-tests interference. |
66 git_cl.settings = None | 64 git_cl.settings = None |
67 | 65 |
68 def tearDown(self): | 66 def tearDown(self): |
69 if not self.has_failed(): | 67 if not self.has_failed(): |
70 self.assertEquals([], self.calls) | 68 self.assertEquals([], self.calls) |
71 super(TestGitCl, self).tearDown() | 69 super(TestGitCl, self).tearDown() |
72 | 70 |
| 71 def _mocked_call(self, *args, **kwargs): |
| 72 self.assertTrue( |
| 73 self.calls, |
| 74 '@%d Expected: <Missing> Actual: %r' % (self._calls_done, args)) |
| 75 expected_args, result = self.calls.pop(0) |
| 76 self.assertEquals( |
| 77 expected_args, |
| 78 args, |
| 79 '@%d Expected: %r Actual: %r' % ( |
| 80 self._calls_done, expected_args, args)) |
| 81 self._calls_done += 1 |
| 82 return result |
| 83 |
73 @classmethod | 84 @classmethod |
74 def _upload_calls(cls): | 85 def _upload_calls(cls): |
75 return cls._git_base_calls() + cls._git_upload_calls() | 86 return cls._git_base_calls() + cls._git_upload_calls() |
76 | 87 |
77 @staticmethod | 88 @staticmethod |
78 def _git_base_calls(): | 89 def _git_base_calls(): |
79 return [ | 90 return [ |
80 (['git', 'update-index', '--refresh', '-q'], ''), | 91 ((['git', 'update-index', '--refresh', '-q'],), ''), |
81 (['git', 'diff-index', 'HEAD'], ''), | 92 ((['git', 'diff-index', 'HEAD'],), ''), |
82 (['git', 'config', 'rietveld.server'], 'codereview.example.com'), | 93 ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'), |
83 (['git', 'symbolic-ref', 'HEAD'], 'master'), | 94 ((['git', 'symbolic-ref', 'HEAD'],), 'master'), |
84 (['git', 'config', 'branch.master.merge'], 'master'), | 95 ((['git', 'config', 'branch.master.merge'],), 'master'), |
85 (['git', 'config', 'branch.master.remote'], 'origin'), | 96 ((['git', 'config', 'branch.master.remote'],), 'origin'), |
86 (['git', 'rev-parse', '--show-cdup'], ''), | 97 ((['git', 'rev-parse', '--show-cdup'],), ''), |
87 (['git', 'rev-parse', 'HEAD'], '12345'), | 98 ((['git', 'rev-parse', 'HEAD'],), '12345'), |
88 (['git', 'diff', '--name-status', '-r', 'master...', '.'], | 99 ((['git', 'diff', '--name-status', '-r', 'master...', '.'],), |
89 'M\t.gitignore\n'), | 100 'M\t.gitignore\n'), |
90 (['git', 'rev-parse', '--git-dir'], '.git'), | 101 ((['git', 'rev-parse', '--git-dir'],), '.git'), |
91 (['git', 'config', 'branch.master.rietveldissue'], ''), | 102 ((['git', 'config', 'branch.master.rietveldissue'],), ''), |
92 (['git', 'config', 'branch.master.rietveldpatchset'], ''), | 103 ((['git', 'config', 'branch.master.rietveldpatchset'],), ''), |
93 (['git', 'log', '--pretty=format:%s%n%n%b', 'master...'], 'foo'), | 104 ((['git', 'log', '--pretty=format:%s%n%n%b', 'master...'],), 'foo'), |
94 (['git', 'config', 'user.email'], 'me@example.com'), | 105 ((['git', 'config', 'user.email'],), 'me@example.com'), |
95 (['git', 'diff', '--no-ext-diff', '--stat', '-M', 'master...'], '+dat'), | 106 ((['git', 'diff', '--no-ext-diff', '--stat', '-M', 'master...'],), |
96 (['git', 'log', '--pretty=format:%s\n\n%b', 'master..'], 'desc\n'), | 107 '+dat'), |
| 108 ((['git', 'log', '--pretty=format:%s\n\n%b', 'master..'],), 'desc\n'), |
97 ] | 109 ] |
98 | 110 |
99 @staticmethod | 111 @staticmethod |
100 def _git_upload_calls(): | 112 def _git_upload_calls(): |
101 return [ | 113 return [ |
102 (['git', 'config', 'rietveld.cc'], ''), | 114 ((['git', 'config', 'rietveld.cc'],), ''), |
103 (['git', 'config', '--get-regexp', '^svn-remote\\.'], (('', None), 0)), | 115 ((['git', 'config', '--get-regexp', '^svn-remote\\.'],), |
104 (['git', 'rev-parse', '--show-cdup'], ''), | 116 (('', None), 0)), |
105 (['git', 'svn', 'info'], ''), | 117 ((['git', 'rev-parse', '--show-cdup'],), ''), |
106 (['git', 'config', 'branch.master.rietveldissue', '1'], ''), | 118 ((['git', 'svn', 'info'],), ''), |
107 (['git', 'config', 'branch.master.rietveldserver', | 119 ((['git', 'config', 'branch.master.rietveldissue', '1'],), ''), |
108 'https://codereview.example.com'], ''), | 120 ((['git', 'config', 'branch.master.rietveldserver', |
109 (['git', 'config', 'branch.master.rietveldpatchset', '2'], ''), | 121 'https://codereview.example.com'],), ''), |
| 122 ((['git', 'config', 'branch.master.rietveldpatchset', '2'],), ''), |
110 ] | 123 ] |
111 | 124 |
| 125 @classmethod |
| 126 def _dcommit_calls_1(cls): |
| 127 return [ |
| 128 ((['git', 'config', '--get-regexp', '^svn-remote\\.'],), |
| 129 ((('svn-remote.svn.url svn://svn.chromium.org/chrome\n' |
| 130 'svn-remote.svn.fetch trunk/src:refs/remotes/origin/master'), |
| 131 None), |
| 132 0)), |
| 133 ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'), |
| 134 ((['git', 'symbolic-ref', 'HEAD'],), 'refs/heads/working'), |
| 135 ((['git', 'config', 'branch.working.merge'],), 'refs/heads/master'), |
| 136 ((['git', 'config', 'branch.working.remote'],), 'origin'), |
| 137 ((['git', 'update-index', '--refresh', '-q'],), ''), |
| 138 ((['git', 'diff-index', 'HEAD'],), ''), |
| 139 ((['git', 'rev-list', '^refs/heads/working', |
| 140 'refs/remotes/origin/master'],), |
| 141 ''), |
| 142 ((['git', 'log', '--grep=^git-svn-id:', '-1', '--pretty=format:%H'],), |
| 143 '3fc18b62c4966193eb435baabe2d18a3810ec82e'), |
| 144 ((['git', 'rev-list', '^3fc18b62c4966193eb435baabe2d18a3810ec82e', |
| 145 'refs/remotes/origin/master'],), ''), |
| 146 ] |
| 147 |
| 148 @classmethod |
| 149 def _dcommit_calls_normal(cls): |
| 150 return [ |
| 151 ((['git', 'rev-parse', '--show-cdup'],), ''), |
| 152 ((['git', 'rev-parse', 'HEAD'],), |
| 153 '00ff397798ea57439712ed7e04ab96e13969ef40'), |
| 154 ((['git', 'diff', '--name-status', '-r', 'refs/remotes/origin/master...', |
| 155 '.'],), |
| 156 'M\tPRESUBMIT.py'), |
| 157 ((['git', 'rev-parse', '--git-dir'],), '.git'), |
| 158 ((['git', 'config', 'branch.working.rietveldissue'],), '12345'), |
| 159 ((['git', 'config', 'branch.working.rietveldserver'],), |
| 160 'codereview.example.com'), |
| 161 ((['git', 'config', 'branch.working.rietveldpatchset'],), '31137'), |
| 162 ((['git', 'config', 'user.email'],), 'author@example.com'), |
| 163 ((['git', 'config', 'rietveld.tree-status-url'],), ''), |
| 164 ] |
| 165 |
| 166 @classmethod |
| 167 def _dcommit_calls_bypassed(cls): |
| 168 return [ |
| 169 ((['git', 'rev-parse', '--git-dir'],), '.git'), |
| 170 ((['git', 'config', 'branch.working.rietveldissue'],), '12345'), |
| 171 ((['git', 'config', 'branch.working.rietveldserver'],), |
| 172 'codereview.example.com'), |
| 173 (('GitClHooksBypassedCommit', |
| 174 'Issue https://codereview.example.com/12345 bypassed hook when ' |
| 175 'committing'), None), |
| 176 ] |
| 177 |
| 178 @classmethod |
| 179 def _dcommit_calls_3(cls): |
| 180 return [ |
| 181 ((['git', 'diff', '--stat', 'refs/remotes/origin/master', |
| 182 'refs/heads/working'],), |
| 183 (' PRESUBMIT.py | 2 +-\n' |
| 184 ' 1 files changed, 1 insertions(+), 1 deletions(-)\n')), |
| 185 (('About to commit; enter to confirm.',), None), |
| 186 ((['git', 'show-ref', '--quiet', '--verify', |
| 187 'refs/heads/git-cl-commit'],), |
| 188 (('', None), 0)), |
| 189 ((['git', 'branch', '-D', 'git-cl-commit'],), ''), |
| 190 ((['git', 'rev-parse', '--show-cdup'],), '\n'), |
| 191 ((['git', 'checkout', '-q', '-b', 'git-cl-commit'],), ''), |
| 192 ((['git', 'reset', '--soft', 'refs/remotes/origin/master'],), ''), |
| 193 ((['git', 'commit', '-m', |
| 194 'Issue: 12345\n\nReview URL: https://codereview.example.com/12345'],), |
| 195 ''), |
| 196 ((['git', 'svn', 'dcommit', '--no-rebase', '--rmdir'],), (('', None), 0)), |
| 197 ((['git', 'checkout', '-q', 'working'],), ''), |
| 198 ((['git', 'branch', '-D', 'git-cl-commit'],), ''), |
| 199 ] |
| 200 |
112 @staticmethod | 201 @staticmethod |
113 def _cmd_line(description, args): | 202 def _cmd_line(description, args): |
114 """Returns the upload command line passed to upload.RealMain().""" | 203 """Returns the upload command line passed to upload.RealMain().""" |
115 msg = description.split('\n', 1)[0] | 204 msg = description.split('\n', 1)[0] |
116 return [ | 205 return [ |
117 'upload', '--assume_yes', '--server', | 206 'upload', '--assume_yes', '--server', |
118 'https://codereview.example.com', | 207 'https://codereview.example.com', |
119 '--message', msg, | 208 '--message', msg, |
120 '--description', description | 209 '--description', description |
121 ] + args + [ | 210 ] + args + [ |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
210 def RunEditor(desc, _): | 299 def RunEditor(desc, _): |
211 return desc | 300 return desc |
212 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor) | 301 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor) |
213 self.mock(sys, 'stderr', mock) | 302 self.mock(sys, 'stderr', mock) |
214 git_cl.main(['upload', '--send-mail']) | 303 git_cl.main(['upload', '--send-mail']) |
215 self.fail() | 304 self.fail() |
216 except SystemExit: | 305 except SystemExit: |
217 self.assertEquals( | 306 self.assertEquals( |
218 'Must specify reviewers to send email.\n', mock.buf.getvalue()) | 307 'Must specify reviewers to send email.\n', mock.buf.getvalue()) |
219 | 308 |
| 309 def test_dcommit(self): |
| 310 self.calls = ( |
| 311 self._dcommit_calls_1() + |
| 312 self._dcommit_calls_normal() + |
| 313 self._dcommit_calls_3()) |
| 314 git_cl.main(['dcommit']) |
| 315 |
| 316 def test_dcommit_bypass_hooks(self): |
| 317 self.calls = ( |
| 318 self._dcommit_calls_1() + |
| 319 self._dcommit_calls_bypassed() + |
| 320 self._dcommit_calls_3()) |
| 321 git_cl.main(['dcommit', '--bypass-hooks']) |
| 322 |
220 | 323 |
221 if __name__ == '__main__': | 324 if __name__ == '__main__': |
222 unittest.main() | 325 unittest.main() |
OLD | NEW |