OLD | NEW |
(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 StringIO |
| 10 import sys |
| 11 import unittest |
| 12 |
| 13 DEPOT_TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 14 sys.path.insert(0, DEPOT_TOOLS_ROOT) |
| 15 |
| 16 from testing_support import coverage_utils |
| 17 from testing_support import git_test_utils |
| 18 |
| 19 import git_common |
| 20 |
| 21 |
| 22 class GitHyperBlameTestBase(git_test_utils.GitRepoReadOnlyTestBase): |
| 23 @classmethod |
| 24 def setUpClass(cls): |
| 25 super(GitHyperBlameTestBase, cls).setUpClass() |
| 26 import git_hyper_blame |
| 27 cls.git_hyper_blame = git_hyper_blame |
| 28 |
| 29 def run_hyperblame(self, ignored, filename, revision): |
| 30 buf = StringIO.StringIO() |
| 31 ignored = [self.repo[c] for c in ignored] |
| 32 retval = self.repo.run(self.git_hyper_blame.hyperblame, ignored, filename, |
| 33 revision=revision, out=buf) |
| 34 return retval, buf.getvalue().rstrip().split('\n') |
| 35 |
| 36 def blame_line(self, commit_name, rest, filename=None): |
| 37 """Generate a blame line from a commit. |
| 38 |
| 39 Args: |
| 40 commit_name: The commit's schema name. |
| 41 rest: The blame line after the timestamp. e.g., '2) file2 - merged'. |
| 42 """ |
| 43 short = self.repo[commit_name][:8] |
| 44 start = '%s %s' % (short, filename) if filename else short |
| 45 author = self.repo.show_commit(commit_name, format_string='%an %ai') |
| 46 return '%s (%s %s' % (start, author, rest) |
| 47 |
| 48 class GitHyperBlameSimpleTest(GitHyperBlameTestBase): |
| 49 REPO_SCHEMA = """ |
| 50 A B D E F G H |
| 51 A C D |
| 52 """ |
| 53 |
| 54 COMMIT_A = { |
| 55 'some/files/file1': {'data': 'file1'}, |
| 56 'some/files/file2': {'data': 'file2'}, |
| 57 'some/files/empty': {'data': ''}, |
| 58 'some/other/file': {'data': 'otherfile'}, |
| 59 } |
| 60 |
| 61 COMMIT_B = { |
| 62 'some/files/file2': { |
| 63 'mode': 0755, |
| 64 'data': 'file2 - vanilla\n'}, |
| 65 'some/files/empty': {'data': 'not anymore'}, |
| 66 'some/files/file3': {'data': 'file3'}, |
| 67 } |
| 68 |
| 69 COMMIT_C = { |
| 70 'some/files/file2': {'data': 'file2 - merged\n'}, |
| 71 } |
| 72 |
| 73 COMMIT_D = { |
| 74 'some/files/file2': {'data': 'file2 - vanilla\nfile2 - merged\n'}, |
| 75 } |
| 76 |
| 77 COMMIT_E = { |
| 78 'some/files/file2': {'data': 'file2 - vanilla\nfile_x - merged\n'}, |
| 79 } |
| 80 |
| 81 COMMIT_F = { |
| 82 'some/files/file2': {'data': 'file2 - vanilla\nfile_y - merged\n'}, |
| 83 } |
| 84 |
| 85 # Move file2 from files to other. |
| 86 COMMIT_G = { |
| 87 'some/files/file2': {'data': None}, |
| 88 'some/other/file2': {'data': 'file2 - vanilla\nfile_y - merged\n'}, |
| 89 } |
| 90 |
| 91 COMMIT_H = { |
| 92 'some/other/file2': {'data': 'file2 - vanilla\nfile_z - merged\n'}, |
| 93 } |
| 94 |
| 95 def testBlameError(self): |
| 96 """Tests a blame on a non-existent file.""" |
| 97 expected_output = [''] |
| 98 retval, output = self.run_hyperblame([], 'some/other/file2', 'tag_D') |
| 99 self.assertNotEqual(0, retval) |
| 100 self.assertEqual(expected_output, output) |
| 101 |
| 102 def testBlameEmpty(self): |
| 103 """Tests a blame of an empty file with no ignores.""" |
| 104 expected_output = [''] |
| 105 retval, output = self.run_hyperblame([], 'some/files/empty', 'tag_A') |
| 106 self.assertEqual(0, retval) |
| 107 self.assertEqual(expected_output, output) |
| 108 |
| 109 def testBasicBlame(self): |
| 110 """Tests a basic blame with no ignores.""" |
| 111 # Expect to blame line 1 on B, line 2 on C. |
| 112 expected_output = [self.blame_line('B', '1) file2 - vanilla'), |
| 113 self.blame_line('C', '2) file2 - merged')] |
| 114 retval, output = self.run_hyperblame([], 'some/files/file2', 'tag_D') |
| 115 self.assertEqual(0, retval) |
| 116 self.assertEqual(expected_output, output) |
| 117 |
| 118 def testBlameRenamed(self): |
| 119 """Tests a blame with no ignores on a renamed file.""" |
| 120 # Expect to blame line 1 on B, line 2 on H. |
| 121 # Because the file has a different name than it had when (some of) these |
| 122 # lines were changed, expect the filenames to be displayed. |
| 123 expected_output = [self.blame_line('B', '1) file2 - vanilla', |
| 124 filename='some/files/file2'), |
| 125 self.blame_line('H', '2) file_z - merged', |
| 126 filename='some/other/file2')] |
| 127 retval, output = self.run_hyperblame([], 'some/other/file2', 'tag_H') |
| 128 self.assertEqual(0, retval) |
| 129 self.assertEqual(expected_output, output) |
| 130 |
| 131 def testIgnoreSimpleEdits(self): |
| 132 """Tests a blame with simple (line-level changes) commits ignored.""" |
| 133 # Expect to blame line 1 on B, line 2 on E. |
| 134 expected_output = [self.blame_line('B', '1) file2 - vanilla'), |
| 135 self.blame_line('E', '2) file_x - merged')] |
| 136 retval, output = self.run_hyperblame([], 'some/files/file2', 'tag_E') |
| 137 self.assertEqual(0, retval) |
| 138 self.assertEqual(expected_output, output) |
| 139 |
| 140 # Ignore E; blame line 1 on B, line 2 on C. |
| 141 expected_output = [self.blame_line('B', ' 1) file2 - vanilla'), |
| 142 self.blame_line('C', '2*) file_x - merged')] |
| 143 retval, output = self.run_hyperblame(['E'], 'some/files/file2', 'tag_E') |
| 144 self.assertEqual(0, retval) |
| 145 self.assertEqual(expected_output, output) |
| 146 |
| 147 # Ignore E and F; blame line 1 on B, line 2 on C. |
| 148 expected_output = [self.blame_line('B', ' 1) file2 - vanilla'), |
| 149 self.blame_line('C', '2*) file_y - merged')] |
| 150 retval, output = self.run_hyperblame(['E', 'F'], 'some/files/file2', |
| 151 'tag_F') |
| 152 self.assertEqual(0, retval) |
| 153 self.assertEqual(expected_output, output) |
| 154 |
| 155 def testIgnoreInitialCommit(self): |
| 156 """Tests a blame with the initial commit ignored.""" |
| 157 # Ignore A. Expect A to get blamed anyway. |
| 158 expected_output = [self.blame_line('A', '1) file1')] |
| 159 retval, output = self.run_hyperblame(['A'], 'some/files/file1', 'tag_A') |
| 160 self.assertEqual(0, retval) |
| 161 self.assertEqual(expected_output, output) |
| 162 |
| 163 def testIgnoreFileAdd(self): |
| 164 """Tests a blame ignoring the commit that added this file.""" |
| 165 # Ignore A. Expect A to get blamed anyway. |
| 166 expected_output = [self.blame_line('B', '1) file3')] |
| 167 retval, output = self.run_hyperblame(['B'], 'some/files/file3', 'tag_B') |
| 168 self.assertEqual(0, retval) |
| 169 self.assertEqual(expected_output, output) |
| 170 |
| 171 def testIgnoreFilePopulate(self): |
| 172 """Tests a blame ignoring the commit that added data to an empty file.""" |
| 173 # Ignore A. Expect A to get blamed anyway. |
| 174 expected_output = [self.blame_line('B', '1) not anymore')] |
| 175 retval, output = self.run_hyperblame(['B'], 'some/files/empty', 'tag_B') |
| 176 self.assertEqual(0, retval) |
| 177 self.assertEqual(expected_output, output) |
| 178 |
| 179 class GitHyperBlameLineMotionTest(GitHyperBlameTestBase): |
| 180 REPO_SCHEMA = """ |
| 181 A B C D E |
| 182 """ |
| 183 |
| 184 COMMIT_A = { |
| 185 'file': {'data': 'A\ngreen\nblue\n'}, |
| 186 } |
| 187 |
| 188 # Change "green" to "yellow". |
| 189 COMMIT_B = { |
| 190 'file': {'data': 'A\nyellow\nblue\n'}, |
| 191 } |
| 192 |
| 193 # Insert 2 lines at the top, |
| 194 # Change "yellow" to "red". |
| 195 COMMIT_C = { |
| 196 'file': {'data': 'X\nY\nA\nred\nblue\n'}, |
| 197 } |
| 198 |
| 199 # Insert 2 more lines at the top. |
| 200 COMMIT_D = { |
| 201 'file': {'data': 'earth\nfire\nX\nY\nA\nred\nblue\n'}, |
| 202 } |
| 203 |
| 204 # Insert a line before "red", and indent "red" and "blue". |
| 205 COMMIT_E = { |
| 206 'file': {'data': 'earth\nfire\nX\nY\nA\ncolors:\n red\n blue\n'}, |
| 207 } |
| 208 |
| 209 def testInterHunkLineMotion(self): |
| 210 """Tests a blame with line motion in another hunk in the ignored commit.""" |
| 211 # This test was mostly written as a demonstration of the limitations of the |
| 212 # current algorithm (it exhibits non-ideal behaviour). |
| 213 |
| 214 # Blame from D, ignoring C. |
| 215 # Lines 1, 2 were added by D. |
| 216 # Lines 3, 4 were added by C (but ignored, so blame A, B, respectively). |
| 217 # TODO(mgiuca): Ideally, this would blame both of these lines on A, because |
| 218 # they add lines nowhere near the changes made by B. |
| 219 # Line 5 was added by A. |
| 220 # Line 6 was modified by C (but ignored, so blame A). |
| 221 # TODO(mgiuca): Ideally, Line 6 would be blamed on B, because that was the |
| 222 # last commit to touch that line (changing "green" to "yellow"), but the |
| 223 # algorithm isn't yet able to figure out that Line 6 in D == Line 4 in C ~= |
| 224 # Line 2 in B. |
| 225 # Line 7 was added by A. |
| 226 expected_output = [self.blame_line('D', ' 1) earth'), |
| 227 self.blame_line('D', ' 2) fire'), |
| 228 self.blame_line('A', '3*) X'), |
| 229 self.blame_line('B', '4*) Y'), |
| 230 self.blame_line('A', ' 5) A'), |
| 231 self.blame_line('A', '6*) red'), |
| 232 self.blame_line('A', ' 7) blue'), |
| 233 ] |
| 234 retval, output = self.run_hyperblame(['C'], 'file', 'tag_D') |
| 235 self.assertEqual(0, retval) |
| 236 self.assertEqual(expected_output, output) |
| 237 |
| 238 def testIntraHunkLineMotion(self): |
| 239 """Tests a blame with line motion in the same hunk in the ignored commit.""" |
| 240 # This test was mostly written as a demonstration of the limitations of the |
| 241 # current algorithm (it exhibits non-ideal behaviour). |
| 242 |
| 243 # Blame from E, ignoring E. |
| 244 # Line 6 was added by E (but ignored, so blame C). |
| 245 # Lines 7, 8 were modified by E (but ignored, so blame A). |
| 246 # TODO(mgiuca): Ideally, this would blame Line 7 on C, because the line |
| 247 # "red" was added by C, and this is just a small change to that line. But |
| 248 # the current algorithm can't deal with line motion within a hunk, so it |
| 249 # just assumes Line 7 in E ~= Line 7 in D == Line 3 in A (which was "blue"). |
| 250 expected_output = [self.blame_line('D', ' 1) earth'), |
| 251 self.blame_line('D', ' 2) fire'), |
| 252 self.blame_line('C', ' 3) X'), |
| 253 self.blame_line('C', ' 4) Y'), |
| 254 self.blame_line('A', ' 5) A'), |
| 255 self.blame_line('C', '6*) colors:'), |
| 256 self.blame_line('A', '7*) red'), |
| 257 self.blame_line('A', '8*) blue'), |
| 258 ] |
| 259 retval, output = self.run_hyperblame(['E'], 'file', 'tag_E') |
| 260 self.assertEqual(0, retval) |
| 261 self.assertEqual(expected_output, output) |
| 262 |
| 263 |
| 264 if __name__ == '__main__': |
| 265 sys.exit(coverage_utils.covered_main( |
| 266 os.path.join(DEPOT_TOOLS_ROOT, 'git_hyper_blame.py'))) |
OLD | NEW |