Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: tests/git_hyper_blame_test.py

Issue 1559943003: Added git hyper-blame, a tool that skips unwanted commits in git blame. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Respond to review. Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tests/git_dates_test.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2016 The Chromium 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.
5 """Tests for git_dates."""
6
7 import datetime
8 import os
9 import shutil
10 import StringIO
11 import sys
12 import tempfile
13 import unittest
14
15 DEPOT_TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16 sys.path.insert(0, DEPOT_TOOLS_ROOT)
17
18 from testing_support import coverage_utils
19 from testing_support import git_test_utils
20
21 import git_common
22
23
24 class GitHyperBlameTestBase(git_test_utils.GitRepoReadOnlyTestBase):
25 @classmethod
26 def setUpClass(cls):
27 super(GitHyperBlameTestBase, cls).setUpClass()
28 import git_hyper_blame
29 cls.git_hyper_blame = git_hyper_blame
30
31 def run_hyperblame(self, ignored, filename, revision):
32 stdout = StringIO.StringIO()
33 stderr = StringIO.StringIO()
34 ignored = [self.repo[c] for c in ignored]
35 retval = self.repo.run(self.git_hyper_blame.hyper_blame, ignored, filename,
36 revision=revision, out=stdout, err=stderr)
37 return retval, stdout.getvalue().rstrip().split('\n')
38
39 def blame_line(self, commit_name, rest, filename=None):
40 """Generate a blame line from a commit.
41
42 Args:
43 commit_name: The commit's schema name.
44 rest: The blame line after the timestamp. e.g., '2) file2 - merged'.
45 """
46 short = self.repo[commit_name][:8]
47 start = '%s %s' % (short, filename) if filename else short
48 author = self.repo.show_commit(commit_name, format_string='%an %ai')
49 return '%s (%s %s' % (start, author, rest)
50
51 class GitHyperBlameMainTest(GitHyperBlameTestBase):
52 """End-to-end tests on a very simple repo."""
53 REPO_SCHEMA = "A B C"
54
55 COMMIT_A = {
56 'some/files/file': {'data': 'line 1\nline 2\n'},
57 }
58
59 COMMIT_B = {
60 'some/files/file': {'data': 'line 1\nline 2.1\n'},
61 }
62
63 COMMIT_C = {
64 'some/files/file': {'data': 'line 1.1\nline 2.1\n'},
65 }
66
67 def testBasicBlame(self):
68 """Tests the main function (simple end-to-end test with no ignores)."""
69 expected_output = [self.blame_line('C', '1) line 1.1'),
70 self.blame_line('B', '2) line 2.1')]
71 stdout = StringIO.StringIO()
72 stderr = StringIO.StringIO()
73 retval = self.repo.run(self.git_hyper_blame.main,
74 args=['tag_C', 'some/files/file'], stdout=stdout,
75 stderr=stderr)
76 self.assertEqual(0, retval)
77 self.assertEqual(expected_output, stdout.getvalue().rstrip().split('\n'))
78 self.assertEqual('', stderr.getvalue())
79
80 def testIgnoreSimple(self):
81 """Tests the main function (simple end-to-end test with ignores)."""
82 expected_output = [self.blame_line('C', ' 1) line 1.1'),
83 self.blame_line('A', '2*) line 2.1')]
84 stdout = StringIO.StringIO()
85 stderr = StringIO.StringIO()
86 retval = self.repo.run(self.git_hyper_blame.main,
87 args=['-i', 'tag_B', 'tag_C', 'some/files/file'],
88 stdout=stdout, stderr=stderr)
89 self.assertEqual(0, retval)
90 self.assertEqual(expected_output, stdout.getvalue().rstrip().split('\n'))
91 self.assertEqual('', stderr.getvalue())
92
93 def testBadRepo(self):
94 """Tests the main function (not in a repo)."""
95 # Make a temp dir that has no .git directory.
96 curdir = os.getcwd()
97 tempdir = tempfile.mkdtemp(suffix='_nogit', prefix='git_repo')
98 try:
99 os.chdir(tempdir)
100 stdout = StringIO.StringIO()
101 stderr = StringIO.StringIO()
102 retval = self.git_hyper_blame.main(
103 args=['-i', 'tag_B', 'tag_C', 'some/files/file'], stdout=stdout,
104 stderr=stderr)
105 finally:
106 shutil.rmtree(tempdir)
107 os.chdir(curdir)
108
109 self.assertNotEqual(0, retval)
110 self.assertEqual('', stdout.getvalue())
111 self.assertRegexpMatches(stderr.getvalue(), '^fatal: Not a git repository')
112
113 def testBadFilename(self):
114 """Tests the main function (bad filename)."""
115 stdout = StringIO.StringIO()
116 stderr = StringIO.StringIO()
117 retval = self.repo.run(self.git_hyper_blame.main,
118 args=['-i', 'tag_B', 'tag_C', 'some/files/xxxx'],
119 stdout=stdout, stderr=stderr)
120 self.assertNotEqual(0, retval)
121 self.assertEqual('', stdout.getvalue())
122 self.assertEqual('fatal: no such path some/files/xxxx in %s\n' %
123 self.repo['C'], stderr.getvalue())
124
125 def testBadRevision(self):
126 """Tests the main function (bad revision to blame from)."""
127 stdout = StringIO.StringIO()
128 stderr = StringIO.StringIO()
129 retval = self.repo.run(self.git_hyper_blame.main,
130 args=['-i', 'tag_B', 'xxxx', 'some/files/file'],
131 stdout=stdout, stderr=stderr)
132 self.assertNotEqual(0, retval)
133 self.assertEqual('', stdout.getvalue())
134 self.assertRegexpMatches(stderr.getvalue(),
135 '^fatal: ambiguous argument \'xxxx\': unknown '
136 'revision or path not in the working tree.')
137
138 def testBadIgnore(self):
139 """Tests the main function (bad revision passed to -i)."""
140 stdout = StringIO.StringIO()
141 stderr = StringIO.StringIO()
142 retval = self.repo.run(self.git_hyper_blame.main,
143 args=['-i', 'xxxx', 'tag_C', 'some/files/file'],
144 stdout=stdout, stderr=stderr)
145 self.assertNotEqual(0, retval)
146 self.assertEqual('', stdout.getvalue())
147 self.assertEqual('fatal: unknown revision \'xxxx\'.\n', stderr.getvalue())
148
149 class GitHyperBlameSimpleTest(GitHyperBlameTestBase):
150 REPO_SCHEMA = """
151 A B D E F G H
152 A C D
153 """
154
155 COMMIT_A = {
156 'some/files/file1': {'data': 'file1'},
157 'some/files/file2': {'data': 'file2'},
158 'some/files/empty': {'data': ''},
159 'some/other/file': {'data': 'otherfile'},
160 }
161
162 COMMIT_B = {
163 'some/files/file2': {
164 'mode': 0755,
165 'data': 'file2 - vanilla\n'},
166 'some/files/empty': {'data': 'not anymore'},
167 'some/files/file3': {'data': 'file3'},
168 }
169
170 COMMIT_C = {
171 'some/files/file2': {'data': 'file2 - merged\n'},
172 }
173
174 COMMIT_D = {
175 'some/files/file2': {'data': 'file2 - vanilla\nfile2 - merged\n'},
176 }
177
178 COMMIT_E = {
179 'some/files/file2': {'data': 'file2 - vanilla\nfile_x - merged\n'},
180 }
181
182 COMMIT_F = {
183 'some/files/file2': {'data': 'file2 - vanilla\nfile_y - merged\n'},
184 }
185
186 # Move file2 from files to other.
187 COMMIT_G = {
188 'some/files/file2': {'data': None},
189 'some/other/file2': {'data': 'file2 - vanilla\nfile_y - merged\n'},
190 }
191
192 COMMIT_H = {
193 'some/other/file2': {'data': 'file2 - vanilla\nfile_z - merged\n'},
194 }
195
196 def testBlameError(self):
197 """Tests a blame on a non-existent file."""
198 expected_output = ['']
199 retval, output = self.run_hyperblame([], 'some/other/file2', 'tag_D')
200 self.assertNotEqual(0, retval)
201 self.assertEqual(expected_output, output)
202
203 def testBlameEmpty(self):
204 """Tests a blame of an empty file with no ignores."""
205 expected_output = ['']
206 retval, output = self.run_hyperblame([], 'some/files/empty', 'tag_A')
207 self.assertEqual(0, retval)
208 self.assertEqual(expected_output, output)
209
210 def testBasicBlame(self):
211 """Tests a basic blame with no ignores."""
212 # Expect to blame line 1 on B, line 2 on C.
213 expected_output = [self.blame_line('B', '1) file2 - vanilla'),
214 self.blame_line('C', '2) file2 - merged')]
215 retval, output = self.run_hyperblame([], 'some/files/file2', 'tag_D')
216 self.assertEqual(0, retval)
217 self.assertEqual(expected_output, output)
218
219 def testBlameRenamed(self):
220 """Tests a blame with no ignores on a renamed file."""
221 # Expect to blame line 1 on B, line 2 on H.
222 # Because the file has a different name than it had when (some of) these
223 # lines were changed, expect the filenames to be displayed.
224 expected_output = [self.blame_line('B', '1) file2 - vanilla',
225 filename='some/files/file2'),
226 self.blame_line('H', '2) file_z - merged',
227 filename='some/other/file2')]
228 retval, output = self.run_hyperblame([], 'some/other/file2', 'tag_H')
229 self.assertEqual(0, retval)
230 self.assertEqual(expected_output, output)
231
232 def testIgnoreSimpleEdits(self):
233 """Tests a blame with simple (line-level changes) commits ignored."""
234 # Expect to blame line 1 on B, line 2 on E.
235 expected_output = [self.blame_line('B', '1) file2 - vanilla'),
236 self.blame_line('E', '2) file_x - merged')]
237 retval, output = self.run_hyperblame([], 'some/files/file2', 'tag_E')
238 self.assertEqual(0, retval)
239 self.assertEqual(expected_output, output)
240
241 # Ignore E; blame line 1 on B, line 2 on C.
242 expected_output = [self.blame_line('B', ' 1) file2 - vanilla'),
243 self.blame_line('C', '2*) file_x - merged')]
244 retval, output = self.run_hyperblame(['E'], 'some/files/file2', 'tag_E')
245 self.assertEqual(0, retval)
246 self.assertEqual(expected_output, output)
247
248 # Ignore E and F; blame line 1 on B, line 2 on C.
249 expected_output = [self.blame_line('B', ' 1) file2 - vanilla'),
250 self.blame_line('C', '2*) file_y - merged')]
251 retval, output = self.run_hyperblame(['E', 'F'], 'some/files/file2',
252 'tag_F')
253 self.assertEqual(0, retval)
254 self.assertEqual(expected_output, output)
255
256 def testIgnoreInitialCommit(self):
257 """Tests a blame with the initial commit ignored."""
258 # Ignore A. Expect A to get blamed anyway.
259 expected_output = [self.blame_line('A', '1) file1')]
260 retval, output = self.run_hyperblame(['A'], 'some/files/file1', 'tag_A')
261 self.assertEqual(0, retval)
262 self.assertEqual(expected_output, output)
263
264 def testIgnoreFileAdd(self):
265 """Tests a blame ignoring the commit that added this file."""
266 # Ignore A. Expect A to get blamed anyway.
267 expected_output = [self.blame_line('B', '1) file3')]
268 retval, output = self.run_hyperblame(['B'], 'some/files/file3', 'tag_B')
269 self.assertEqual(0, retval)
270 self.assertEqual(expected_output, output)
271
272 def testIgnoreFilePopulate(self):
273 """Tests a blame ignoring the commit that added data to an empty file."""
274 # Ignore A. Expect A to get blamed anyway.
275 expected_output = [self.blame_line('B', '1) not anymore')]
276 retval, output = self.run_hyperblame(['B'], 'some/files/empty', 'tag_B')
277 self.assertEqual(0, retval)
278 self.assertEqual(expected_output, output)
279
280 class GitHyperBlameLineMotionTest(GitHyperBlameTestBase):
281 REPO_SCHEMA = """
282 A B C D E
283 """
284
285 COMMIT_A = {
286 'file': {'data': 'A\ngreen\nblue\n'},
287 }
288
289 # Change "green" to "yellow".
290 COMMIT_B = {
291 'file': {'data': 'A\nyellow\nblue\n'},
292 }
293
294 # Insert 2 lines at the top,
295 # Change "yellow" to "red".
296 COMMIT_C = {
297 'file': {'data': 'X\nY\nA\nred\nblue\n'},
298 }
299
300 # Insert 2 more lines at the top.
301 COMMIT_D = {
302 'file': {'data': 'earth\nfire\nX\nY\nA\nred\nblue\n'},
303 }
304
305 # Insert a line before "red", and indent "red" and "blue".
306 COMMIT_E = {
307 'file': {'data': 'earth\nfire\nX\nY\nA\ncolors:\n red\n blue\n'},
308 }
309
310 def testInterHunkLineMotion(self):
311 """Tests a blame with line motion in another hunk in the ignored commit."""
312 # This test was mostly written as a demonstration of the limitations of the
313 # current algorithm (it exhibits non-ideal behaviour).
314
315 # Blame from D, ignoring C.
316 # Lines 1, 2 were added by D.
317 # Lines 3, 4 were added by C (but ignored, so blame A, B, respectively).
318 # TODO(mgiuca): Ideally, this would blame both of these lines on A, because
319 # they add lines nowhere near the changes made by B.
320 # Line 5 was added by A.
321 # Line 6 was modified by C (but ignored, so blame A).
322 # TODO(mgiuca): Ideally, Line 6 would be blamed on B, because that was the
323 # last commit to touch that line (changing "green" to "yellow"), but the
324 # algorithm isn't yet able to figure out that Line 6 in D == Line 4 in C ~=
325 # Line 2 in B.
326 # Line 7 was added by A.
327 expected_output = [self.blame_line('D', ' 1) earth'),
328 self.blame_line('D', ' 2) fire'),
329 self.blame_line('A', '3*) X'),
330 self.blame_line('B', '4*) Y'),
331 self.blame_line('A', ' 5) A'),
332 self.blame_line('A', '6*) red'),
333 self.blame_line('A', ' 7) blue'),
334 ]
335 retval, output = self.run_hyperblame(['C'], 'file', 'tag_D')
336 self.assertEqual(0, retval)
337 self.assertEqual(expected_output, output)
338
339 def testIntraHunkLineMotion(self):
340 """Tests a blame with line motion in the same hunk in the ignored commit."""
341 # This test was mostly written as a demonstration of the limitations of the
342 # current algorithm (it exhibits non-ideal behaviour).
343
344 # Blame from E, ignoring E.
345 # Line 6 was added by E (but ignored, so blame C).
346 # Lines 7, 8 were modified by E (but ignored, so blame A).
347 # TODO(mgiuca): Ideally, this would blame Line 7 on C, because the line
348 # "red" was added by C, and this is just a small change to that line. But
349 # the current algorithm can't deal with line motion within a hunk, so it
350 # just assumes Line 7 in E ~= Line 7 in D == Line 3 in A (which was "blue").
351 expected_output = [self.blame_line('D', ' 1) earth'),
352 self.blame_line('D', ' 2) fire'),
353 self.blame_line('C', ' 3) X'),
354 self.blame_line('C', ' 4) Y'),
355 self.blame_line('A', ' 5) A'),
356 self.blame_line('C', '6*) colors:'),
357 self.blame_line('A', '7*) red'),
358 self.blame_line('A', '8*) blue'),
359 ]
360 retval, output = self.run_hyperblame(['E'], 'file', 'tag_E')
361 self.assertEqual(0, retval)
362 self.assertEqual(expected_output, output)
363
364
365 if __name__ == '__main__':
366 sys.exit(coverage_utils.covered_main(
367 os.path.join(DEPOT_TOOLS_ROOT, 'git_hyper_blame.py')))
OLDNEW
« no previous file with comments | « tests/git_dates_test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698