blob: 8b46eba0a52717412185ac32cb28929bbd9eb89a [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 [
maruel@chromium.org2e72bb12012-01-17 15:18:35 +000091 ((['git', 'update-index', '--refresh', '-q'],), ''),
92 ((['git', 'diff-index', 'HEAD'],), ''),
93 ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
94 ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
95 ((['git', 'config', 'branch.master.merge'],), 'master'),
96 ((['git', 'config', 'branch.master.remote'],), 'origin'),
97 ((['git', 'rev-parse', '--show-cdup'],), ''),
98 ((['git', 'rev-parse', 'HEAD'],), '12345'),
99 ((['git', 'diff', '--name-status', '-r', 'master...', '.'],),
100 'M\t.gitignore\n'),
101 ((['git', 'rev-parse', '--git-dir'],), '.git'),
102 ((['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'),
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
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000201 @staticmethod
202 def _cmd_line(description, args):
203 """Returns the upload command line passed to upload.RealMain()."""
jam@chromium.org31083642012-01-27 03:14:45 +0000204 msg = description.split('\n', 1)[0]
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000205 return [
206 'upload', '--assume_yes', '--server',
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000207 'https://codereview.example.com',
jam@chromium.org31083642012-01-27 03:14:45 +0000208 '--message', msg,
209 '--description', description
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000210 ] + args + [
211 '--cc', 'joe@example.com',
212 'master...'
213 ]
214
215 def _run_reviewer_test(
216 self,
217 upload_args,
218 expected_description,
219 returned_description,
220 final_description,
221 reviewers):
222 """Generic reviewer test framework."""
223 self.calls = self._upload_calls()
224 def RunEditor(desc, _):
225 self.assertEquals(
226 '# Enter a description of the change.\n'
227 '# This will displayed on the codereview site.\n'
228 '# The first line will also be used as the subject of the review.\n' +
229 expected_description,
230 desc)
231 return returned_description
232 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
233 def check_upload(args):
234 self.assertEquals(self._cmd_line(final_description, reviewers), args)
235 return 1, 2
236 self.mock(git_cl.upload, 'RealMain', check_upload)
237 git_cl.main(['upload'] + upload_args)
238
239 def test_no_reviewer(self):
240 self._run_reviewer_test(
241 [],
242 'desc\n\nBUG=\nTEST=\n',
243 '# Blah blah comment.\ndesc\n\nBUG=\nTEST=\n',
244 'desc\n\nBUG=\nTEST=\n',
245 [])
246
247 def test_reviewers_cmd_line(self):
248 # Reviewer is passed as-is
249 description = 'desc\n\nR=foo@example.com\nBUG=\nTEST=\n'
250 self._run_reviewer_test(
251 ['-r' 'foo@example.com'],
252 description,
253 '\n%s\n' % description,
254 description,
255 ['--reviewers', 'foo@example.com'])
256
257 def test_reviewer_tbr_overriden(self):
258 # Reviewer is overriden with TBR
259 # Also verifies the regexp work without a trailing LF
260 description = 'Foo Bar\nTBR=reviewer@example.com\n'
261 self._run_reviewer_test(
262 ['-r' 'foo@example.com'],
263 'desc\n\nR=foo@example.com\nBUG=\nTEST=\n',
264 description.strip('\n'),
265 description,
266 ['--reviewers', 'reviewer@example.com'])
267
268 def test_reviewer_multiple(self):
269 # Handles multiple R= or TBR= lines.
270 description = (
271 'Foo Bar\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n')
272 self._run_reviewer_test(
273 [],
274 'desc\n\nBUG=\nTEST=\n',
275 description,
276 description,
277 ['--reviewers', 'reviewer@example.com,another@example.com'])
278
maruel@chromium.orga3353652011-11-30 14:26:57 +0000279 def test_reviewer_send_mail(self):
280 # --send-mail can be used without -r if R= is used
281 description = 'Foo Bar\nR=reviewer@example.com\n'
282 self._run_reviewer_test(
283 ['--send-mail'],
284 'desc\n\nBUG=\nTEST=\n',
285 description.strip('\n'),
286 description,
287 ['--reviewers', 'reviewer@example.com', '--send_mail'])
288
289 def test_reviewer_send_mail_no_rev(self):
290 # Fails without a reviewer.
291 class FileMock(object):
292 buf = StringIO.StringIO()
293 def write(self, content):
294 self.buf.write(content)
295
296 mock = FileMock()
297 try:
298 self.calls = self._git_base_calls()
299 def RunEditor(desc, _):
300 return desc
301 self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
302 self.mock(sys, 'stderr', mock)
303 git_cl.main(['upload', '--send-mail'])
304 self.fail()
305 except SystemExit:
306 self.assertEquals(
307 'Must specify reviewers to send email.\n', mock.buf.getvalue())
308
maruel@chromium.org2e72bb12012-01-17 15:18:35 +0000309 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
maruel@chromium.orgddd59412011-11-30 14:20:38 +0000323
324if __name__ == '__main__':
325 unittest.main()