123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- #!/usr/bin/env vpython3
- # Copyright 2016 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """Tests for git_dates."""
- from io import BytesIO, StringIO
- import os
- import re
- import shutil
- import sys
- import tempfile
- from unittest import mock
- DEPOT_TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- sys.path.insert(0, DEPOT_TOOLS_ROOT)
- from testing_support import coverage_utils
- from testing_support import git_test_utils
- import gclient_utils
- GitRepo = git_test_utils.GitRepo
- # TODO: Should fix these warnings.
- # pylint: disable=line-too-long
- class GitHyperBlameTestBase(git_test_utils.GitRepoReadOnlyTestBase):
- @classmethod
- def setUpClass(cls):
- super(GitHyperBlameTestBase, cls).setUpClass()
- import git_hyper_blame
- cls.git_hyper_blame = git_hyper_blame
- def setUp(self):
- mock.patch('sys.stderr', StringIO()).start()
- self.addCleanup(mock.patch.stopall)
- def run_hyperblame(self, ignored, filename, revision):
- outbuf = BytesIO()
- ignored = [self.repo[c] for c in ignored]
- retval = self.repo.run(self.git_hyper_blame.hyper_blame, outbuf,
- ignored, filename, revision)
- return retval, outbuf.getvalue().rstrip().split(b'\n')
- def blame_line(self, commit_name, rest, author=None, filename=None):
- """Generate a blame line from a commit.
- Args:
- commit_name: The commit's schema name.
- rest: The blame line after the timestamp. e.g., '2) file2 - merged'.
- author: The author's name. If omitted, reads the name out of the commit.
- filename: The filename. If omitted, not shown in the blame line.
- """
- short = self.repo[commit_name][:8]
- start = '%s %s' % (short, filename) if filename else short
- if author is None:
- author = self.repo.show_commit(commit_name, format_string='%an %ai')
- else:
- author += self.repo.show_commit(commit_name, format_string=' %ai')
- return ('%s (%s %s' % (start, author, rest)).encode('utf-8')
- class GitHyperBlameMainTest(GitHyperBlameTestBase):
- """End-to-end tests on a very simple repo."""
- REPO_SCHEMA = "A B C D"
- COMMIT_A = {
- 'some/files/file': {
- 'data': b'line 1\nline 2\n'
- },
- }
- COMMIT_B = {
- 'some/files/file': {
- 'data': b'line 1\nline 2.1\n'
- },
- }
- COMMIT_C = {
- 'some/files/file': {
- 'data': b'line 1.1\nline 2.1\n'
- },
- }
- COMMIT_D = {
- # This file should be automatically considered for ignore.
- '.git-blame-ignore-revs': {
- 'data': b'tag_C'
- },
- # This file should not be considered.
- 'some/files/.git-blame-ignore-revs': {
- 'data': b'tag_B'
- },
- }
- def setUp(self):
- super(GitHyperBlameMainTest, self).setUp()
- # Most tests want to check out C (so the .git-blame-ignore-revs is not
- # used).
- self.repo.git('checkout', '-f', 'tag_C')
- def testBasicBlame(self):
- """Tests the main function (simple end-to-end test with no ignores)."""
- expected_output = [
- self.blame_line('C', '1) line 1.1'),
- self.blame_line('B', '2) line 2.1')
- ]
- outbuf = BytesIO()
- retval = self.repo.run(self.git_hyper_blame.main,
- ['tag_C', 'some/files/file'], outbuf)
- self.assertEqual(0, retval)
- self.assertEqual(expected_output,
- outbuf.getvalue().rstrip().split(b'\n'))
- self.assertEqual('', sys.stderr.getvalue())
- def testIgnoreSimple(self):
- """Tests the main function (simple end-to-end test with ignores)."""
- expected_output = [
- self.blame_line('C', ' 1) line 1.1'),
- self.blame_line('A', '2*) line 2.1')
- ]
- outbuf = BytesIO()
- retval = self.repo.run(self.git_hyper_blame.main,
- ['-i', 'tag_B', 'tag_C', 'some/files/file'],
- outbuf)
- self.assertEqual(0, retval)
- self.assertEqual(expected_output,
- outbuf.getvalue().rstrip().split(b'\n'))
- self.assertEqual('', sys.stderr.getvalue())
- def testBadRepo(self):
- """Tests the main function (not in a repo)."""
- # Make a temp dir that has no .git directory.
- curdir = os.getcwd()
- tempdir = tempfile.mkdtemp(suffix='_nogit', prefix='git_repo')
- try:
- os.chdir(tempdir)
- outbuf = BytesIO()
- retval = self.git_hyper_blame.main(
- ['-i', 'tag_B', 'tag_C', 'some/files/file'], outbuf)
- finally:
- os.chdir(curdir)
- shutil.rmtree(tempdir)
- self.assertNotEqual(0, retval)
- self.assertEqual(b'', outbuf.getvalue())
- r = re.compile('^fatal: Not a git repository', re.I)
- self.assertRegexpMatches(sys.stderr.getvalue(), r)
- def testBadFilename(self):
- """Tests the main function (bad filename)."""
- outbuf = BytesIO()
- retval = self.repo.run(self.git_hyper_blame.main,
- ['-i', 'tag_B', 'tag_C', 'some/files/xxxx'],
- outbuf)
- self.assertNotEqual(0, retval)
- self.assertEqual(b'', outbuf.getvalue())
- # TODO(mgiuca): This test used to test the exact string, but it broke
- # due to an upstream bug in git-blame. For now, just check the start of
- # the string. A patch has been sent upstream; when it rolls out we can
- # revert back to the original test logic.
- self.assertTrue(sys.stderr.getvalue().startswith(
- 'fatal: no such path some/files/xxxx in '))
- def testBadRevision(self):
- """Tests the main function (bad revision to blame from)."""
- outbuf = BytesIO()
- retval = self.repo.run(self.git_hyper_blame.main,
- ['-i', 'tag_B', 'xxxx', 'some/files/file'],
- outbuf)
- self.assertNotEqual(0, retval)
- self.assertEqual(b'', outbuf.getvalue())
- self.assertRegexpMatches(
- sys.stderr.getvalue(),
- '^fatal: ambiguous argument \'xxxx\': unknown '
- 'revision or path not in the working tree.')
- def testBadIgnore(self):
- """Tests the main function (bad revision passed to -i)."""
- expected_output = [
- self.blame_line('C', '1) line 1.1'),
- self.blame_line('B', '2) line 2.1')
- ]
- outbuf = BytesIO()
- retval = self.repo.run(self.git_hyper_blame.main,
- ['-i', 'xxxx', 'tag_C', 'some/files/file'],
- outbuf)
- self.assertEqual(0, retval)
- self.assertEqual(expected_output,
- outbuf.getvalue().rstrip().split(b'\n'))
- self.assertEqual('warning: unknown revision \'xxxx\'.\n',
- sys.stderr.getvalue())
- def testIgnoreFile(self):
- """Tests passing the ignore list in a file."""
- expected_output = [
- self.blame_line('C', ' 1) line 1.1'),
- self.blame_line('A', '2*) line 2.1')
- ]
- outbuf = BytesIO()
- with gclient_utils.temporary_file() as ignore_file:
- gclient_utils.FileWrite(
- ignore_file, '# Line comments are allowed.\n'
- '\n'
- '{}\n'
- 'xxxx\n'.format(self.repo['B']))
- retval = self.repo.run(
- self.git_hyper_blame.main,
- ['--ignore-file', ignore_file, 'tag_C', 'some/files/file'],
- outbuf)
- self.assertEqual(0, retval)
- self.assertEqual(expected_output,
- outbuf.getvalue().rstrip().split(b'\n'))
- self.assertEqual('warning: unknown revision \'xxxx\'.\n',
- sys.stderr.getvalue())
- def testDefaultIgnoreFile(self):
- """Tests automatically using a default ignore list."""
- # Check out revision D. We expect the script to use the default ignore
- # list that is checked out, *not* the one committed at the given
- # revision.
- self.repo.git('checkout', '-f', 'tag_D')
- expected_output = [
- self.blame_line('A', '1*) line 1.1'),
- self.blame_line('B', ' 2) line 2.1')
- ]
- outbuf = BytesIO()
- retval = self.repo.run(self.git_hyper_blame.main,
- ['tag_D', 'some/files/file'], outbuf)
- self.assertEqual(0, retval)
- self.assertEqual(expected_output,
- outbuf.getvalue().rstrip().split(b'\n'))
- self.assertEqual('', sys.stderr.getvalue())
- # Test blame from a different revision. Despite the default ignore file
- # *not* being committed at that revision, it should still be picked up
- # because D is currently checked out.
- outbuf = BytesIO()
- retval = self.repo.run(self.git_hyper_blame.main,
- ['tag_C', 'some/files/file'], outbuf)
- self.assertEqual(0, retval)
- self.assertEqual(expected_output,
- outbuf.getvalue().rstrip().split(b'\n'))
- self.assertEqual('', sys.stderr.getvalue())
- def testNoDefaultIgnores(self):
- """Tests the --no-default-ignores switch."""
- # Check out revision D. This has a .git-blame-ignore-revs file, which we
- # expect to be ignored due to --no-default-ignores.
- self.repo.git('checkout', '-f', 'tag_D')
- expected_output = [
- self.blame_line('C', '1) line 1.1'),
- self.blame_line('B', '2) line 2.1')
- ]
- outbuf = BytesIO()
- retval = self.repo.run(
- self.git_hyper_blame.main,
- ['tag_D', 'some/files/file', '--no-default-ignores'], outbuf)
- self.assertEqual(0, retval)
- self.assertEqual(expected_output,
- outbuf.getvalue().rstrip().split(b'\n'))
- self.assertEqual('', sys.stderr.getvalue())
- class GitHyperBlameSimpleTest(GitHyperBlameTestBase):
- REPO_SCHEMA = """
- A B D E F G H
- A C D
- """
- COMMIT_A = {
- 'some/files/file1': {
- 'data': b'file1'
- },
- 'some/files/file2': {
- 'data': b'file2'
- },
- 'some/files/empty': {
- 'data': b''
- },
- 'some/other/file': {
- 'data': b'otherfile'
- },
- }
- COMMIT_B = {
- 'some/files/file2': {
- 'mode': 0o755,
- 'data': b'file2 - vanilla\n'
- },
- 'some/files/empty': {
- 'data': b'not anymore'
- },
- 'some/files/file3': {
- 'data': b'file3'
- },
- }
- COMMIT_C = {
- 'some/files/file2': {
- 'data': b'file2 - merged\n'
- },
- }
- COMMIT_D = {
- 'some/files/file2': {
- 'data': b'file2 - vanilla\nfile2 - merged\n'
- },
- }
- COMMIT_E = {
- 'some/files/file2': {
- 'data': b'file2 - vanilla\nfile_x - merged\n'
- },
- }
- COMMIT_F = {
- 'some/files/file2': {
- 'data': b'file2 - vanilla\nfile_y - merged\n'
- },
- }
- # Move file2 from files to other.
- COMMIT_G = {
- 'some/files/file2': {
- 'data': None
- },
- 'some/other/file2': {
- 'data': b'file2 - vanilla\nfile_y - merged\n'
- },
- }
- COMMIT_H = {
- 'some/other/file2': {
- 'data': b'file2 - vanilla\nfile_z - merged\n'
- },
- }
- def testBlameError(self):
- """Tests a blame on a non-existent file."""
- expected_output = [b'']
- retval, output = self.run_hyperblame([], 'some/other/file2', 'tag_D')
- self.assertNotEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testBlameEmpty(self):
- """Tests a blame of an empty file with no ignores."""
- expected_output = [b'']
- retval, output = self.run_hyperblame([], 'some/files/empty', 'tag_A')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testBasicBlame(self):
- """Tests a basic blame with no ignores."""
- # Expect to blame line 1 on B, line 2 on C.
- expected_output = [
- self.blame_line('B', '1) file2 - vanilla'),
- self.blame_line('C', '2) file2 - merged')
- ]
- retval, output = self.run_hyperblame([], 'some/files/file2', 'tag_D')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testBlameRenamed(self):
- """Tests a blame with no ignores on a renamed file."""
- # Expect to blame line 1 on B, line 2 on H.
- # Because the file has a different name than it had when (some of) these
- # lines were changed, expect the filenames to be displayed.
- expected_output = [
- self.blame_line('B',
- '1) file2 - vanilla',
- filename='some/files/file2'),
- self.blame_line('H',
- '2) file_z - merged',
- filename='some/other/file2')
- ]
- retval, output = self.run_hyperblame([], 'some/other/file2', 'tag_H')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testIgnoreSimpleEdits(self):
- """Tests a blame with simple (line-level changes) commits ignored."""
- # Expect to blame line 1 on B, line 2 on E.
- expected_output = [
- self.blame_line('B', '1) file2 - vanilla'),
- self.blame_line('E', '2) file_x - merged')
- ]
- retval, output = self.run_hyperblame([], 'some/files/file2', 'tag_E')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- # Ignore E; blame line 1 on B, line 2 on C.
- expected_output = [
- self.blame_line('B', ' 1) file2 - vanilla'),
- self.blame_line('C', '2*) file_x - merged')
- ]
- retval, output = self.run_hyperblame(['E'], 'some/files/file2', 'tag_E')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- # Ignore E and F; blame line 1 on B, line 2 on C.
- expected_output = [
- self.blame_line('B', ' 1) file2 - vanilla'),
- self.blame_line('C', '2*) file_y - merged')
- ]
- retval, output = self.run_hyperblame(['E', 'F'], 'some/files/file2',
- 'tag_F')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testIgnoreInitialCommit(self):
- """Tests a blame with the initial commit ignored."""
- # Ignore A. Expect A to get blamed anyway.
- expected_output = [self.blame_line('A', '1) file1')]
- retval, output = self.run_hyperblame(['A'], 'some/files/file1', 'tag_A')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testIgnoreFileAdd(self):
- """Tests a blame ignoring the commit that added this file."""
- # Ignore A. Expect A to get blamed anyway.
- expected_output = [self.blame_line('B', '1) file3')]
- retval, output = self.run_hyperblame(['B'], 'some/files/file3', 'tag_B')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testIgnoreFilePopulate(self):
- """Tests a blame ignoring the commit that added data to an empty file."""
- # Ignore A. Expect A to get blamed anyway.
- expected_output = [self.blame_line('B', '1) not anymore')]
- retval, output = self.run_hyperblame(['B'], 'some/files/empty', 'tag_B')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- class GitHyperBlameLineMotionTest(GitHyperBlameTestBase):
- REPO_SCHEMA = """
- A B C D E F
- """
- COMMIT_A = {
- 'file': {
- 'data': b'A\ngreen\nblue\n'
- },
- }
- # Change "green" to "yellow".
- COMMIT_B = {
- 'file': {
- 'data': b'A\nyellow\nblue\n'
- },
- }
- # Insert 2 lines at the top,
- # Change "yellow" to "red".
- # Insert 1 line at the bottom.
- COMMIT_C = {
- 'file': {
- 'data': b'X\nY\nA\nred\nblue\nZ\n'
- },
- }
- # Insert 2 more lines at the top.
- COMMIT_D = {
- 'file': {
- 'data': b'earth\nfire\nX\nY\nA\nred\nblue\nZ\n'
- },
- }
- # Insert a line before "red", and indent "red" and "blue".
- COMMIT_E = {
- 'file': {
- 'data': b'earth\nfire\nX\nY\nA\ncolors:\n red\n blue\nZ\n'
- },
- }
- # Insert a line between "A" and "colors".
- COMMIT_F = {
- 'file': {
- 'data': b'earth\nfire\nX\nY\nA\nB\ncolors:\n red\n blue\nZ\n'
- },
- }
- def testCacheDiffHunks(self):
- """Tests the cache_diff_hunks internal function."""
- expected_hunks = [
- ((0, 0), (1, 2)),
- ((2, 1), (4, 1)),
- ((3, 0), (6, 1)),
- ]
- hunks = self.repo.run(self.git_hyper_blame.cache_diff_hunks, 'tag_B',
- 'tag_C')
- self.assertEqual(expected_hunks, hunks)
- def testApproxLinenoAcrossRevs(self):
- """Tests the approx_lineno_across_revs internal function."""
- # Note: For all of these tests, the "old revision" and "new revision"
- # are reversed, which matches the usage by hyper_blame.
- # Test an unchanged line before any hunks in the diff. Should be
- # unchanged.
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_B', 'tag_A', 1)
- self.assertEqual(1, lineno)
- # Test an unchanged line after all hunks in the diff. Should be matched
- # to the line's previous position in the file.
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_D', 'tag_C', 6)
- self.assertEqual(4, lineno)
- # Test a line added in a new hunk. Should be matched to the line
- # *before* where the hunk was inserted in the old version of the file.
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_F', 'tag_E', 6)
- self.assertEqual(5, lineno)
- # Test lines added in a new hunk at the very start of the file. This
- # tests an edge case: normally it would be matched to the line *before*
- # where the hunk was inserted (Line 0), but since the hunk is at the
- # start of the file, we match to Line 1.
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_C', 'tag_B', 1)
- self.assertEqual(1, lineno)
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_C', 'tag_B', 2)
- self.assertEqual(1, lineno)
- # Test an unchanged line in between hunks in the diff. Should be matched
- # to the line's previous position in the file.
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_C', 'tag_B', 3)
- self.assertEqual(1, lineno)
- # Test a changed line. Should be matched to the hunk's previous position
- # in the file.
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_C', 'tag_B', 4)
- self.assertEqual(2, lineno)
- # Test a line added in a new hunk at the very end of the file. Should be
- # matched to the line *before* where the hunk was inserted (the last
- # line of the file). Technically same as the case above but good to
- # boundary test.
- lineno = self.repo.run(self.git_hyper_blame.approx_lineno_across_revs,
- 'file', 'file', 'tag_C', 'tag_B', 6)
- self.assertEqual(3, lineno)
- def testInterHunkLineMotion(self):
- """Tests a blame with line motion in another hunk in the ignored commit."""
- # Blame from D, ignoring C.
- # Lines 1, 2 were added by D.
- # Lines 3, 4 were added by C (but ignored, so blame A).
- # Line 5 was added by A.
- # Line 6 was modified by C (but ignored, so blame B). (Note: This
- # requires the algorithm to figure out that Line 6 in D == Line 4 in C
- # ~= Line 2 in B, so it blames B. Otherwise, it would blame A.) Line 7
- # was added by A. Line 8 was added by C (but ignored, so blame A).
- expected_output = [
- self.blame_line('D', ' 1) earth'),
- self.blame_line('D', ' 2) fire'),
- self.blame_line('A', '3*) X'),
- self.blame_line('A', '4*) Y'),
- self.blame_line('A', ' 5) A'),
- self.blame_line('B', '6*) red'),
- self.blame_line('A', ' 7) blue'),
- self.blame_line('A', '8*) Z'),
- ]
- retval, output = self.run_hyperblame(['C'], 'file', 'tag_D')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- def testIntraHunkLineMotion(self):
- """Tests a blame with line motion in the same hunk in the ignored commit."""
- # This test was mostly written as a demonstration of the limitations of
- # the current algorithm (it exhibits non-ideal behaviour).
- # Blame from E, ignoring E.
- # Line 6 was added by E (but ignored, so blame C).
- # Lines 7, 8 were modified by E (but ignored, so blame A).
- # TODO(mgiuca): Ideally, this would blame Line 7 on C, because the line
- # "red" was added by C, and this is just a small change to that line.
- # But the current algorithm can't deal with line motion within a hunk,
- # so it just assumes Line 7 in E ~= Line 7 in D == Line 3 in A (which
- # was "blue").
- expected_output = [
- self.blame_line('D', ' 1) earth'),
- self.blame_line('D', ' 2) fire'),
- self.blame_line('C', ' 3) X'),
- self.blame_line('C', ' 4) Y'),
- self.blame_line('A', ' 5) A'),
- self.blame_line('C', '6*) colors:'),
- self.blame_line('A', '7*) red'),
- self.blame_line('A', '8*) blue'),
- self.blame_line('C', ' 9) Z'),
- ]
- retval, output = self.run_hyperblame(['E'], 'file', 'tag_E')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- class GitHyperBlameLineNumberTest(GitHyperBlameTestBase):
- REPO_SCHEMA = """
- A B C D
- """
- COMMIT_A = {
- 'file': {
- 'data': b'red\nblue\n'
- },
- }
- # Change "blue" to "green".
- COMMIT_B = {
- 'file': {
- 'data': b'red\ngreen\n'
- },
- }
- # Insert 2 lines at the top,
- COMMIT_C = {
- 'file': {
- 'data': b'\n\nred\ngreen\n'
- },
- }
- # Change "green" to "yellow".
- COMMIT_D = {
- 'file': {
- 'data': b'\n\nred\nyellow\n'
- },
- }
- def testTwoChangesWithAddedLines(self):
- """Regression test for https://crbug.com/709831.
- Tests a line with multiple ignored edits, and a line number change in
- between (such that the line number in the current revision is bigger than
- the file's line count at the older ignored revision).
- """
- expected_output = [
- self.blame_line('C', ' 1) '),
- self.blame_line('C', ' 2) '),
- self.blame_line('A', ' 3) red'),
- self.blame_line('A', '4*) yellow'),
- ]
- # Due to https://crbug.com/709831, ignoring both B and D would crash,
- # because of C (in between those revisions) which moves Line 2 to Line
- # 4. The algorithm would incorrectly think that Line 4 was still on Line
- # 4 in Commit B, even though it was Line 2 at that time. Its index is
- # out of range in the number of lines in Commit B.
- retval, output = self.run_hyperblame(['B', 'D'], 'file', 'tag_D')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- class GitHyperBlameUnicodeTest(GitHyperBlameTestBase):
- REPO_SCHEMA = """
- A B C
- """
- COMMIT_A = {
- GitRepo.AUTHOR_NAME: 'ASCII Author',
- 'file': {
- 'data': b'red\nblue\n'
- },
- }
- # Add a line.
- COMMIT_B = {
- GitRepo.AUTHOR_NAME: '\u4e2d\u56fd\u4f5c\u8005',
- 'file': {
- 'data': b'red\ngreen\nblue\n'
- },
- }
- # Modify a line with non-UTF-8 author and file text.
- COMMIT_C = {
- GitRepo.AUTHOR_NAME: 'Lat\xedn-1 Author',
- 'file': {
- 'data': 'red\ngre\u00e9n\nblue\n'.encode('latin-1')
- },
- }
- def testNonUTF8Data(self):
- """Ensures correct behaviour even if author or file data is not UTF-8.
- There is no guarantee that a file will be UTF-8-encoded, so this is
- realistic.
- """
- expected_output = [
- self.blame_line('A', '1) red', author='ASCII Author '),
- # The Author has been re-encoded as UTF-8. The file data is
- # converted to UTF8 and unknown characters replaced.
- self.blame_line('C', '2) gre\ufffdn', author='Lat\xedn-1 Author'),
- self.blame_line('A', '3) blue', author='ASCII Author '),
- ]
- retval, output = self.run_hyperblame([], 'file', 'tag_C')
- self.assertEqual(0, retval)
- self.assertEqual(expected_output, output)
- if __name__ == '__main__':
- sys.exit(
- coverage_utils.covered_main(
- os.path.join(DEPOT_TOOLS_ROOT, 'git_hyper_blame.py')))
|