blob: 2e50be27145db72f9636e425589e5136f6320acf [file] [log] [blame]
maruel@chromium.orgddd59412011-11-30 14:20:38 +00001#!/usr/bin/env python
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgddd59412011-11-30 14:20:38 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Unit tests for git_cl.py."""
7
8import os
maruel@chromium.orga3353652011-11-30 14:26:57 +00009import StringIO
maruel@chromium.orgddd59412011-11-30 14:20:38 +000010import sys
11import unittest
12
13sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14
15from testing_support.auto_stub import TestCase
16
17import git_cl
18import subprocess2
19
20
maruel@chromium.org2e72bb12012-01-17 15:18:35 +000021class PresubmitMock(object):
22 def __init__(self, *args, **kwargs):
23 self.reviewers = []
24 @staticmethod
25 def should_continue():
26 return True
27
28
29class RietveldMock(object):
30 def __init__(self, *args, **kwargs):
31 pass
32 @staticmethod
33 def get_description(issue):
34 return 'Issue: %d' % issue
35
36
37class WatchlistsMock(object):
38 def __init__(self, _):
39 pass
40 @staticmethod
41 def GetWatchersForPaths(_):
42 return ['joe@example.com']
43
44
maruel@chromium.orgddd59412011-11-30 14:20:38 +000045class TestGitCl(TestCase):
46 def setUp(self):
47 super(TestGitCl, self).setUp()
48 self.calls = []
49 self._calls_done = 0
maruel@chromium.org2e72bb12012-01-17 15:18:35 +000050 self.mock(subprocess2, 'call', self._mocked_call)
51 self.mock(subprocess2, 'check_call', self._mocked_call)
52 self.mock(subprocess2, 'check_output', self._mocked_call)
53 self.mock(subprocess2, 'communicate', self._mocked_call)
54 self.mock(subprocess2, 'Popen', self._mocked_call)
maruel@chromium.orgddd59412011-11-30 14:20:38 +000055 self.mock(git_cl, 'FindCodereviewSettingsFile', lambda: '')
maruel@chromium.org2e72bb12012-01-17 15:18:35 +000056 self.mock(git_cl, 'ask_for_data', self._mocked_call)
57 self.mock(git_cl.breakpad, 'post', self._mocked_call)
58 self.mock(git_cl.breakpad, 'SendStack', self._mocked_call)
maruel@chromium.orgddd59412011-11-30 14:20:38 +000059 self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock)
maruel@chromium.orgddd59412011-11-30 14:20:38 +000060 self.mock(git_cl.rietveld, 'Rietveld', RietveldMock)
maruel@chromium.orgddd59412011-11-30 14:20:38 +000061 self.mock(git_cl.upload, 'RealMain', self.fail)
maruel@chromium.orgddd59412011-11-30 14:20:38 +000062 self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock)
63 # It's important to reset settings to not have inter-tests interference.
64 git_cl.settings = None
65
66 def tearDown(self):
67 if not self.has_failed():
68 self.assertEquals([], self.calls)
69 super(TestGitCl, self).tearDown()
70
maruel@chromium.org2e72bb12012-01-17 15:18:35 +000071 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
maruel@chromium.orga3353652011-11-30 14:26:57 +000084 @classmethod
85 def _upload_calls(cls):
86 return cls._git_base_calls() + cls._git_upload_calls()
87
maruel@chromium.orgddd59412011-11-30 14:20:38 +000088 @staticmethod
maruel@chromium.orga3353652011-11-30 14:26:57 +000089 def _git_base_calls():
maruel@chromium.orgddd59412011-11-30 14:20:38 +000090 return [
ukai@chromium.orge8077812012-02-03 03:41:46 +000091 ((['git', 'config', 'gerrit.host'],), ''),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +000092 ((['git', 'update-index', '--refresh', '-q'],), ''),
93 ((['git', 'diff-index', 'HEAD'],), ''),
94 ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
95 ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
96 ((['git', 'config', 'branch.master.merge'],), 'master'),
97 ((['git', 'config', 'branch.master.remote'],), 'origin'),
98 ((['git', 'rev-parse', '--show-cdup'],), ''),
99 ((['git', 'rev-parse', 'HEAD'],), '12345'),
100 ((['git', 'diff', '--name-status', '-r', 'master...', '.'],),
101 'M\t.gitignore\n'),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000102 ((['git', 'config', 'branch.master.rietveldissue'],), ''),
103 ((['git', 'config', 'branch.master.rietveldpatchset'],), ''),
104 ((['git', 'log', '--pretty=format:%s%n%n%b', 'master...'],), 'foo'),
105 ((['git', 'config', 'user.email'],), 'me@example.com'),
106 ((['git', 'diff', '--no-ext-diff', '--stat', '-M', 'master...'],),
107 '+dat'),
108 ((['git', 'log', '--pretty=format:%s\n\n%b', 'master..'],), 'desc\n'),
maruel@chromium.orga3353652011-11-30 14:26:57 +0000109 ]
110
111 @staticmethod
112 def _git_upload_calls():
113 return [
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000114 ((['git', 'config', 'rietveld.cc'],), ''),
115 ((['git', 'config', '--get-regexp', '^svn-remote\\.'],),
116 (('', None), 0)),
117 ((['git', 'rev-parse', '--show-cdup'],), ''),
118 ((['git', 'svn', 'info'],), ''),
119 ((['git', 'config', 'branch.master.rietveldissue', '1'],), ''),
120 ((['git', 'config', 'branch.master.rietveldserver',
121 'https://codereview.example.com'],), ''),
122 ((['git', 'config', 'branch.master.rietveldpatchset', '2'],), ''),
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000123 ]
124
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000125 @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'),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000157 ((['git', 'config', 'branch.working.rietveldissue'],), '12345'),
evan@chromium.org0af9b702012-02-11 00:42:16 +0000158 ((['git', 'config', 'branch.working.rietveldpatchset'],), '31137'),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000159 ((['git', 'config', 'branch.working.rietveldserver'],),
160 'codereview.example.com'),
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000161 ((['git', 'config', 'user.email'],), 'author@example.com'),
162 ((['git', 'config', 'rietveld.tree-status-url'],), ''),
163 ]
164
165 @classmethod
166 def _dcommit_calls_bypassed(cls):
167 return [
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000168 ((['git', 'config', 'branch.working.rietveldissue'],), '12345'),
169 ((['git', 'config', 'branch.working.rietveldserver'],),
170 'codereview.example.com'),
171 (('GitClHooksBypassedCommit',
172 'Issue https://codereview.example.com/12345 bypassed hook when '
173 'committing'), None),
174 ]
175
176 @classmethod
177 def _dcommit_calls_3(cls):
178 return [
179 ((['git', 'diff', '--stat', 'refs/remotes/origin/master',
180 'refs/heads/working'],),
181 (' PRESUBMIT.py | 2 +-\n'
182 ' 1 files changed, 1 insertions(+), 1 deletions(-)\n')),
183 (('About to commit; enter to confirm.',), None),
184 ((['git', 'show-ref', '--quiet', '--verify',
185 'refs/heads/git-cl-commit'],),
186 (('', None), 0)),
187 ((['git', 'branch', '-D', 'git-cl-commit'],), ''),
188 ((['git', 'rev-parse', '--show-cdup'],), '\n'),
189 ((['git', 'checkout', '-q', '-b', 'git-cl-commit'],), ''),
190 ((['git', 'reset', '--soft', 'refs/remotes/origin/master'],), ''),
191 ((['git', 'commit', '-m',
192 'Issue: 12345\n\nReview URL: https://codereview.example.com/12345'],),
193 ''),
194 ((['git', 'svn', 'dcommit', '--no-rebase', '--rmdir'],), (('', None), 0)),
195 ((['git', 'checkout', '-q', 'working'],), ''),
196 ((['git', 'branch', '-D', 'git-cl-commit'],), ''),
197 ]
198
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000199 @staticmethod
200 def _cmd_line(description, args):
201 """Returns the upload command line passed to upload.RealMain()."""
jam@chromium.org31083642012-01-27 03:14:45 +0000202 msg = description.split('\n', 1)[0]
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000203 return [
204 'upload', '--assume_yes', '--server',
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000205 'https://codereview.example.com',
jam@chromium.org31083642012-01-27 03:14:45 +0000206 '--message', msg,
207 '--description', description
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000208 ] + args + [
209 '--cc', 'joe@example.com',
210 'master...'
211 ]
212
213 def _run_reviewer_test(
214 self,
215 upload_args,
216 expected_description,
217 returned_description,
218 final_description,
219 reviewers):
220 """Generic reviewer test framework."""
221 self.calls = self._upload_calls()
222 def RunEditor(desc, _):
223 self.assertEquals(
224 '# Enter a description of the change.\n'
225 '# This will displayed on the codereview site.\n'
226 '# The first line will also be used as the subject of the review.\n' +
227 expected_description,
228 desc)
229 return returned_description
230 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
231 def check_upload(args):
232 self.assertEquals(self._cmd_line(final_description, reviewers), args)
233 return 1, 2
234 self.mock(git_cl.upload, 'RealMain', check_upload)
235 git_cl.main(['upload'] + upload_args)
236
237 def test_no_reviewer(self):
238 self._run_reviewer_test(
239 [],
240 'desc\n\nBUG=\nTEST=\n',
241 '# Blah blah comment.\ndesc\n\nBUG=\nTEST=\n',
242 'desc\n\nBUG=\nTEST=\n',
243 [])
244
245 def test_reviewers_cmd_line(self):
246 # Reviewer is passed as-is
247 description = 'desc\n\nR=foo@example.com\nBUG=\nTEST=\n'
248 self._run_reviewer_test(
249 ['-r' 'foo@example.com'],
250 description,
251 '\n%s\n' % description,
252 description,
253 ['--reviewers', 'foo@example.com'])
254
255 def test_reviewer_tbr_overriden(self):
256 # Reviewer is overriden with TBR
257 # Also verifies the regexp work without a trailing LF
258 description = 'Foo Bar\nTBR=reviewer@example.com\n'
259 self._run_reviewer_test(
260 ['-r' 'foo@example.com'],
261 'desc\n\nR=foo@example.com\nBUG=\nTEST=\n',
262 description.strip('\n'),
263 description,
264 ['--reviewers', 'reviewer@example.com'])
265
266 def test_reviewer_multiple(self):
267 # Handles multiple R= or TBR= lines.
268 description = (
269 'Foo Bar\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n')
270 self._run_reviewer_test(
271 [],
272 'desc\n\nBUG=\nTEST=\n',
273 description,
274 description,
275 ['--reviewers', 'reviewer@example.com,another@example.com'])
276
maruel@chromium.orga3353652011-11-30 14:26:57 +0000277 def test_reviewer_send_mail(self):
278 # --send-mail can be used without -r if R= is used
279 description = 'Foo Bar\nR=reviewer@example.com\n'
280 self._run_reviewer_test(
281 ['--send-mail'],
282 'desc\n\nBUG=\nTEST=\n',
283 description.strip('\n'),
284 description,
285 ['--reviewers', 'reviewer@example.com', '--send_mail'])
286
287 def test_reviewer_send_mail_no_rev(self):
288 # Fails without a reviewer.
289 class FileMock(object):
290 buf = StringIO.StringIO()
291 def write(self, content):
292 self.buf.write(content)
293
294 mock = FileMock()
295 try:
296 self.calls = self._git_base_calls()
297 def RunEditor(desc, _):
298 return desc
299 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
300 self.mock(sys, 'stderr', mock)
301 git_cl.main(['upload', '--send-mail'])
302 self.fail()
303 except SystemExit:
304 self.assertEquals(
305 'Must specify reviewers to send email.\n', mock.buf.getvalue())
306
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000307 def test_dcommit(self):
308 self.calls = (
309 self._dcommit_calls_1() +
310 self._dcommit_calls_normal() +
311 self._dcommit_calls_3())
312 git_cl.main(['dcommit'])
313
314 def test_dcommit_bypass_hooks(self):
315 self.calls = (
316 self._dcommit_calls_1() +
317 self._dcommit_calls_bypassed() +
318 self._dcommit_calls_3())
319 git_cl.main(['dcommit', '--bypass-hooks'])
320
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000321
ukai@chromium.orge8077812012-02-03 03:41:46 +0000322 @staticmethod
323 def _gerrit_base_calls():
324 return [
325 ((['git', 'config', 'gerrit.host'],), 'gerrit.example.com'),
326 ((['git', 'update-index', '--refresh', '-q'],), ''),
327 ((['git', 'diff-index', 'HEAD'],), ''),
328 ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
329 ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
330 ((['git', 'config', 'branch.master.merge'],), 'master'),
331 ((['git', 'config', 'branch.master.remote'],), 'origin'),
332 ((['git', 'rev-parse', '--show-cdup'],), ''),
333 ((['git', 'rev-parse', 'HEAD'],), '12345'),
334 ((['git', 'diff', '--name-status', '-r', 'master...', '.'],),
335 'M\t.gitignore\n'),
ukai@chromium.orge8077812012-02-03 03:41:46 +0000336 ((['git', 'config', 'branch.master.rietveldissue'],), ''),
337 ((['git', 'config', 'branch.master.rietveldpatchset'],), ''),
338 ((['git', 'log', '--pretty=format:%s%n%n%b', 'master...'],), 'foo'),
339 ((['git', 'config', 'user.email'],), 'me@example.com'),
340 ((['git', 'diff', '--no-ext-diff', '--stat', '-M', 'master...'],),
341 '+dat'),
342 ]
343
344 @staticmethod
345 def _gerrit_upload_calls(description, reviewers):
346 calls = [
347 ((['git', 'log', '--pretty=format:%s\n\n%b', 'master..'],),
348 description),
349 ((['git', 'config', 'rietveld.cc'],), '')
350 ]
ukai@chromium.org19bbfa22012-02-03 16:18:11 +0000351 receive_pack = '--receive-pack=git receive-pack '
ukai@chromium.orge8077812012-02-03 03:41:46 +0000352 receive_pack += '--cc=joe@example.com' # from watch list
353 if reviewers:
354 receive_pack += ' '
355 receive_pack += ' '.join(['--reviewer=' + email for email in reviewers])
ukai@chromium.org19bbfa22012-02-03 16:18:11 +0000356 receive_pack += ''
ukai@chromium.orge8077812012-02-03 03:41:46 +0000357 calls += [
358 ((['git', 'push', receive_pack, 'origin', 'HEAD:refs/for/master'],),
359 '')
360 ]
361 return calls
362
363 def _run_gerrit_reviewer_test(
364 self,
365 upload_args,
366 description,
367 reviewers):
368 """Generic gerrit reviewer test framework."""
369 self.calls = self._gerrit_base_calls()
370 self.calls += self._gerrit_upload_calls(description, reviewers)
371 git_cl.main(['upload'] + upload_args)
372
373 def test_gerrit_no_reviewer(self):
374 self._run_gerrit_reviewer_test(
375 [],
376 'desc\n\nBUG=\nTEST=\n',
377 [])
378
379 def test_gerrit_reviewers_cmd_line(self):
380 self._run_gerrit_reviewer_test(
381 ['-r', 'foo@example.com'],
382 'desc\n\nBUG=\nTEST=\n',
383 ['foo@example.com'])
384
385 def test_gerrit_reviewer_multiple(self):
386 self._run_gerrit_reviewer_test(
387 [],
388 'desc\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n',
389 ['reviewer@example.com', 'another@example.com'])
390
391
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000392if __name__ == '__main__':
393 unittest.main()