blob: 2c6b55386f0d040bf053462e826afc48a9e5f127 [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'),
102 ((['git', 'rev-parse', '--git-dir'],), '.git'),
103 ((['git', 'config', 'branch.master.rietveldissue'],), ''),
104 ((['git', 'config', 'branch.master.rietveldpatchset'],), ''),
105 ((['git', 'log', '--pretty=format:%s%n%n%b', 'master...'],), 'foo'),
106 ((['git', 'config', 'user.email'],), 'me@example.com'),
107 ((['git', 'diff', '--no-ext-diff', '--stat', '-M', 'master...'],),
108 '+dat'),
109 ((['git', 'log', '--pretty=format:%s\n\n%b', 'master..'],), 'desc\n'),
maruel@chromium.orga3353652011-11-30 14:26:57 +0000110 ]
111
112 @staticmethod
113 def _git_upload_calls():
114 return [
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000115 ((['git', 'config', 'rietveld.cc'],), ''),
116 ((['git', 'config', '--get-regexp', '^svn-remote\\.'],),
117 (('', None), 0)),
118 ((['git', 'rev-parse', '--show-cdup'],), ''),
119 ((['git', 'svn', 'info'],), ''),
120 ((['git', 'config', 'branch.master.rietveldissue', '1'],), ''),
121 ((['git', 'config', 'branch.master.rietveldserver',
122 'https://codereview.example.com'],), ''),
123 ((['git', 'config', 'branch.master.rietveldpatchset', '2'],), ''),
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000124 ]
125
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000126 @classmethod
127 def _dcommit_calls_1(cls):
128 return [
129 ((['git', 'config', '--get-regexp', '^svn-remote\\.'],),
130 ((('svn-remote.svn.url svn://svn.chromium.org/chrome\n'
131 'svn-remote.svn.fetch trunk/src:refs/remotes/origin/master'),
132 None),
133 0)),
134 ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
135 ((['git', 'symbolic-ref', 'HEAD'],), 'refs/heads/working'),
136 ((['git', 'config', 'branch.working.merge'],), 'refs/heads/master'),
137 ((['git', 'config', 'branch.working.remote'],), 'origin'),
138 ((['git', 'update-index', '--refresh', '-q'],), ''),
139 ((['git', 'diff-index', 'HEAD'],), ''),
140 ((['git', 'rev-list', '^refs/heads/working',
141 'refs/remotes/origin/master'],),
142 ''),
143 ((['git', 'log', '--grep=^git-svn-id:', '-1', '--pretty=format:%H'],),
144 '3fc18b62c4966193eb435baabe2d18a3810ec82e'),
145 ((['git', 'rev-list', '^3fc18b62c4966193eb435baabe2d18a3810ec82e',
146 'refs/remotes/origin/master'],), ''),
147 ]
148
149 @classmethod
150 def _dcommit_calls_normal(cls):
151 return [
152 ((['git', 'rev-parse', '--show-cdup'],), ''),
153 ((['git', 'rev-parse', 'HEAD'],),
154 '00ff397798ea57439712ed7e04ab96e13969ef40'),
155 ((['git', 'diff', '--name-status', '-r', 'refs/remotes/origin/master...',
156 '.'],),
157 'M\tPRESUBMIT.py'),
158 ((['git', 'rev-parse', '--git-dir'],), '.git'),
159 ((['git', 'config', 'branch.working.rietveldissue'],), '12345'),
160 ((['git', 'config', 'branch.working.rietveldserver'],),
161 'codereview.example.com'),
162 ((['git', 'config', 'branch.working.rietveldpatchset'],), '31137'),
163 ((['git', 'config', 'user.email'],), 'author@example.com'),
164 ((['git', 'config', 'rietveld.tree-status-url'],), ''),
165 ]
166
167 @classmethod
168 def _dcommit_calls_bypassed(cls):
169 return [
170 ((['git', 'rev-parse', '--git-dir'],), '.git'),
171 ((['git', 'config', 'branch.working.rietveldissue'],), '12345'),
172 ((['git', 'config', 'branch.working.rietveldserver'],),
173 'codereview.example.com'),
174 (('GitClHooksBypassedCommit',
175 'Issue https://codereview.example.com/12345 bypassed hook when '
176 'committing'), None),
177 ]
178
179 @classmethod
180 def _dcommit_calls_3(cls):
181 return [
182 ((['git', 'diff', '--stat', 'refs/remotes/origin/master',
183 'refs/heads/working'],),
184 (' PRESUBMIT.py | 2 +-\n'
185 ' 1 files changed, 1 insertions(+), 1 deletions(-)\n')),
186 (('About to commit; enter to confirm.',), None),
187 ((['git', 'show-ref', '--quiet', '--verify',
188 'refs/heads/git-cl-commit'],),
189 (('', None), 0)),
190 ((['git', 'branch', '-D', 'git-cl-commit'],), ''),
191 ((['git', 'rev-parse', '--show-cdup'],), '\n'),
192 ((['git', 'checkout', '-q', '-b', 'git-cl-commit'],), ''),
193 ((['git', 'reset', '--soft', 'refs/remotes/origin/master'],), ''),
194 ((['git', 'commit', '-m',
195 'Issue: 12345\n\nReview URL: https://codereview.example.com/12345'],),
196 ''),
197 ((['git', 'svn', 'dcommit', '--no-rebase', '--rmdir'],), (('', None), 0)),
198 ((['git', 'checkout', '-q', 'working'],), ''),
199 ((['git', 'branch', '-D', 'git-cl-commit'],), ''),
200 ]
201
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000202 @staticmethod
203 def _cmd_line(description, args):
204 """Returns the upload command line passed to upload.RealMain()."""
jam@chromium.org31083642012-01-27 03:14:45 +0000205 msg = description.split('\n', 1)[0]
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000206 return [
207 'upload', '--assume_yes', '--server',
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000208 'https://codereview.example.com',
jam@chromium.org31083642012-01-27 03:14:45 +0000209 '--message', msg,
210 '--description', description
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000211 ] + args + [
212 '--cc', 'joe@example.com',
213 'master...'
214 ]
215
216 def _run_reviewer_test(
217 self,
218 upload_args,
219 expected_description,
220 returned_description,
221 final_description,
222 reviewers):
223 """Generic reviewer test framework."""
224 self.calls = self._upload_calls()
225 def RunEditor(desc, _):
226 self.assertEquals(
227 '# Enter a description of the change.\n'
228 '# This will displayed on the codereview site.\n'
229 '# The first line will also be used as the subject of the review.\n' +
230 expected_description,
231 desc)
232 return returned_description
233 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
234 def check_upload(args):
235 self.assertEquals(self._cmd_line(final_description, reviewers), args)
236 return 1, 2
237 self.mock(git_cl.upload, 'RealMain', check_upload)
238 git_cl.main(['upload'] + upload_args)
239
240 def test_no_reviewer(self):
241 self._run_reviewer_test(
242 [],
243 'desc\n\nBUG=\nTEST=\n',
244 '# Blah blah comment.\ndesc\n\nBUG=\nTEST=\n',
245 'desc\n\nBUG=\nTEST=\n',
246 [])
247
248 def test_reviewers_cmd_line(self):
249 # Reviewer is passed as-is
250 description = 'desc\n\nR=foo@example.com\nBUG=\nTEST=\n'
251 self._run_reviewer_test(
252 ['-r' 'foo@example.com'],
253 description,
254 '\n%s\n' % description,
255 description,
256 ['--reviewers', 'foo@example.com'])
257
258 def test_reviewer_tbr_overriden(self):
259 # Reviewer is overriden with TBR
260 # Also verifies the regexp work without a trailing LF
261 description = 'Foo Bar\nTBR=reviewer@example.com\n'
262 self._run_reviewer_test(
263 ['-r' 'foo@example.com'],
264 'desc\n\nR=foo@example.com\nBUG=\nTEST=\n',
265 description.strip('\n'),
266 description,
267 ['--reviewers', 'reviewer@example.com'])
268
269 def test_reviewer_multiple(self):
270 # Handles multiple R= or TBR= lines.
271 description = (
272 'Foo Bar\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n')
273 self._run_reviewer_test(
274 [],
275 'desc\n\nBUG=\nTEST=\n',
276 description,
277 description,
278 ['--reviewers', 'reviewer@example.com,another@example.com'])
279
maruel@chromium.orga3353652011-11-30 14:26:57 +0000280 def test_reviewer_send_mail(self):
281 # --send-mail can be used without -r if R= is used
282 description = 'Foo Bar\nR=reviewer@example.com\n'
283 self._run_reviewer_test(
284 ['--send-mail'],
285 'desc\n\nBUG=\nTEST=\n',
286 description.strip('\n'),
287 description,
288 ['--reviewers', 'reviewer@example.com', '--send_mail'])
289
290 def test_reviewer_send_mail_no_rev(self):
291 # Fails without a reviewer.
292 class FileMock(object):
293 buf = StringIO.StringIO()
294 def write(self, content):
295 self.buf.write(content)
296
297 mock = FileMock()
298 try:
299 self.calls = self._git_base_calls()
300 def RunEditor(desc, _):
301 return desc
302 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
303 self.mock(sys, 'stderr', mock)
304 git_cl.main(['upload', '--send-mail'])
305 self.fail()
306 except SystemExit:
307 self.assertEquals(
308 'Must specify reviewers to send email.\n', mock.buf.getvalue())
309
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000310 def test_dcommit(self):
311 self.calls = (
312 self._dcommit_calls_1() +
313 self._dcommit_calls_normal() +
314 self._dcommit_calls_3())
315 git_cl.main(['dcommit'])
316
317 def test_dcommit_bypass_hooks(self):
318 self.calls = (
319 self._dcommit_calls_1() +
320 self._dcommit_calls_bypassed() +
321 self._dcommit_calls_3())
322 git_cl.main(['dcommit', '--bypass-hooks'])
323
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000324
ukai@chromium.orge8077812012-02-03 03:41:46 +0000325 @staticmethod
326 def _gerrit_base_calls():
327 return [
328 ((['git', 'config', 'gerrit.host'],), 'gerrit.example.com'),
329 ((['git', 'update-index', '--refresh', '-q'],), ''),
330 ((['git', 'diff-index', 'HEAD'],), ''),
331 ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
332 ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
333 ((['git', 'config', 'branch.master.merge'],), 'master'),
334 ((['git', 'config', 'branch.master.remote'],), 'origin'),
335 ((['git', 'rev-parse', '--show-cdup'],), ''),
336 ((['git', 'rev-parse', 'HEAD'],), '12345'),
337 ((['git', 'diff', '--name-status', '-r', 'master...', '.'],),
338 'M\t.gitignore\n'),
339 ((['git', 'rev-parse', '--git-dir'],), '.git'),
340 ((['git', 'config', 'branch.master.rietveldissue'],), ''),
341 ((['git', 'config', 'branch.master.rietveldpatchset'],), ''),
342 ((['git', 'log', '--pretty=format:%s%n%n%b', 'master...'],), 'foo'),
343 ((['git', 'config', 'user.email'],), 'me@example.com'),
344 ((['git', 'diff', '--no-ext-diff', '--stat', '-M', 'master...'],),
345 '+dat'),
346 ]
347
348 @staticmethod
349 def _gerrit_upload_calls(description, reviewers):
350 calls = [
351 ((['git', 'log', '--pretty=format:%s\n\n%b', 'master..'],),
352 description),
353 ((['git', 'config', 'rietveld.cc'],), '')
354 ]
ukai@chromium.org19bbfa22012-02-03 16:18:11 +0000355 receive_pack = '--receive-pack=git receive-pack '
ukai@chromium.orge8077812012-02-03 03:41:46 +0000356 receive_pack += '--cc=joe@example.com' # from watch list
357 if reviewers:
358 receive_pack += ' '
359 receive_pack += ' '.join(['--reviewer=' + email for email in reviewers])
ukai@chromium.org19bbfa22012-02-03 16:18:11 +0000360 receive_pack += ''
ukai@chromium.orge8077812012-02-03 03:41:46 +0000361 calls += [
362 ((['git', 'push', receive_pack, 'origin', 'HEAD:refs/for/master'],),
363 '')
364 ]
365 return calls
366
367 def _run_gerrit_reviewer_test(
368 self,
369 upload_args,
370 description,
371 reviewers):
372 """Generic gerrit reviewer test framework."""
373 self.calls = self._gerrit_base_calls()
374 self.calls += self._gerrit_upload_calls(description, reviewers)
375 git_cl.main(['upload'] + upload_args)
376
377 def test_gerrit_no_reviewer(self):
378 self._run_gerrit_reviewer_test(
379 [],
380 'desc\n\nBUG=\nTEST=\n',
381 [])
382
383 def test_gerrit_reviewers_cmd_line(self):
384 self._run_gerrit_reviewer_test(
385 ['-r', 'foo@example.com'],
386 'desc\n\nBUG=\nTEST=\n',
387 ['foo@example.com'])
388
389 def test_gerrit_reviewer_multiple(self):
390 self._run_gerrit_reviewer_test(
391 [],
392 'desc\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n',
393 ['reviewer@example.com', 'another@example.com'])
394
395
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000396if __name__ == '__main__':
397 unittest.main()