| Index: tests/git_hyper_blame_test.py
|
| diff --git a/tests/git_hyper_blame_test.py b/tests/git_hyper_blame_test.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..6a86404fbb7d8276834e6c5bfb9a6b42cbb066a6
|
| --- /dev/null
|
| +++ b/tests/git_hyper_blame_test.py
|
| @@ -0,0 +1,367 @@
|
| +#!/usr/bin/env python
|
| +# 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."""
|
| +
|
| +import datetime
|
| +import os
|
| +import shutil
|
| +import StringIO
|
| +import sys
|
| +import tempfile
|
| +import unittest
|
| +
|
| +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 git_common
|
| +
|
| +
|
| +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 run_hyperblame(self, ignored, filename, revision):
|
| + stdout = StringIO.StringIO()
|
| + stderr = StringIO.StringIO()
|
| + ignored = [self.repo[c] for c in ignored]
|
| + retval = self.repo.run(self.git_hyper_blame.hyper_blame, ignored, filename,
|
| + revision=revision, out=stdout, err=stderr)
|
| + return retval, stdout.getvalue().rstrip().split('\n')
|
| +
|
| + def blame_line(self, commit_name, rest, 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'.
|
| + """
|
| + short = self.repo[commit_name][:8]
|
| + start = '%s %s' % (short, filename) if filename else short
|
| + author = self.repo.show_commit(commit_name, format_string='%an %ai')
|
| + return '%s (%s %s' % (start, author, rest)
|
| +
|
| +class GitHyperBlameMainTest(GitHyperBlameTestBase):
|
| + """End-to-end tests on a very simple repo."""
|
| + REPO_SCHEMA = "A B C"
|
| +
|
| + COMMIT_A = {
|
| + 'some/files/file': {'data': 'line 1\nline 2\n'},
|
| + }
|
| +
|
| + COMMIT_B = {
|
| + 'some/files/file': {'data': 'line 1\nline 2.1\n'},
|
| + }
|
| +
|
| + COMMIT_C = {
|
| + 'some/files/file': {'data': 'line 1.1\nline 2.1\n'},
|
| + }
|
| +
|
| + 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')]
|
| + stdout = StringIO.StringIO()
|
| + stderr = StringIO.StringIO()
|
| + retval = self.repo.run(self.git_hyper_blame.main,
|
| + args=['tag_C', 'some/files/file'], stdout=stdout,
|
| + stderr=stderr)
|
| + self.assertEqual(0, retval)
|
| + self.assertEqual(expected_output, stdout.getvalue().rstrip().split('\n'))
|
| + self.assertEqual('', 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')]
|
| + stdout = StringIO.StringIO()
|
| + stderr = StringIO.StringIO()
|
| + retval = self.repo.run(self.git_hyper_blame.main,
|
| + args=['-i', 'tag_B', 'tag_C', 'some/files/file'],
|
| + stdout=stdout, stderr=stderr)
|
| + self.assertEqual(0, retval)
|
| + self.assertEqual(expected_output, stdout.getvalue().rstrip().split('\n'))
|
| + self.assertEqual('', 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)
|
| + stdout = StringIO.StringIO()
|
| + stderr = StringIO.StringIO()
|
| + retval = self.git_hyper_blame.main(
|
| + args=['-i', 'tag_B', 'tag_C', 'some/files/file'], stdout=stdout,
|
| + stderr=stderr)
|
| + finally:
|
| + shutil.rmtree(tempdir)
|
| + os.chdir(curdir)
|
| +
|
| + self.assertNotEqual(0, retval)
|
| + self.assertEqual('', stdout.getvalue())
|
| + self.assertRegexpMatches(stderr.getvalue(), '^fatal: Not a git repository')
|
| +
|
| + def testBadFilename(self):
|
| + """Tests the main function (bad filename)."""
|
| + stdout = StringIO.StringIO()
|
| + stderr = StringIO.StringIO()
|
| + retval = self.repo.run(self.git_hyper_blame.main,
|
| + args=['-i', 'tag_B', 'tag_C', 'some/files/xxxx'],
|
| + stdout=stdout, stderr=stderr)
|
| + self.assertNotEqual(0, retval)
|
| + self.assertEqual('', stdout.getvalue())
|
| + self.assertEqual('fatal: no such path some/files/xxxx in %s\n' %
|
| + self.repo['C'], stderr.getvalue())
|
| +
|
| + def testBadRevision(self):
|
| + """Tests the main function (bad revision to blame from)."""
|
| + stdout = StringIO.StringIO()
|
| + stderr = StringIO.StringIO()
|
| + retval = self.repo.run(self.git_hyper_blame.main,
|
| + args=['-i', 'tag_B', 'xxxx', 'some/files/file'],
|
| + stdout=stdout, stderr=stderr)
|
| + self.assertNotEqual(0, retval)
|
| + self.assertEqual('', stdout.getvalue())
|
| + self.assertRegexpMatches(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)."""
|
| + stdout = StringIO.StringIO()
|
| + stderr = StringIO.StringIO()
|
| + retval = self.repo.run(self.git_hyper_blame.main,
|
| + args=['-i', 'xxxx', 'tag_C', 'some/files/file'],
|
| + stdout=stdout, stderr=stderr)
|
| + self.assertNotEqual(0, retval)
|
| + self.assertEqual('', stdout.getvalue())
|
| + self.assertEqual('fatal: unknown revision \'xxxx\'.\n', stderr.getvalue())
|
| +
|
| +class GitHyperBlameSimpleTest(GitHyperBlameTestBase):
|
| + REPO_SCHEMA = """
|
| + A B D E F G H
|
| + A C D
|
| + """
|
| +
|
| + COMMIT_A = {
|
| + 'some/files/file1': {'data': 'file1'},
|
| + 'some/files/file2': {'data': 'file2'},
|
| + 'some/files/empty': {'data': ''},
|
| + 'some/other/file': {'data': 'otherfile'},
|
| + }
|
| +
|
| + COMMIT_B = {
|
| + 'some/files/file2': {
|
| + 'mode': 0755,
|
| + 'data': 'file2 - vanilla\n'},
|
| + 'some/files/empty': {'data': 'not anymore'},
|
| + 'some/files/file3': {'data': 'file3'},
|
| + }
|
| +
|
| + COMMIT_C = {
|
| + 'some/files/file2': {'data': 'file2 - merged\n'},
|
| + }
|
| +
|
| + COMMIT_D = {
|
| + 'some/files/file2': {'data': 'file2 - vanilla\nfile2 - merged\n'},
|
| + }
|
| +
|
| + COMMIT_E = {
|
| + 'some/files/file2': {'data': 'file2 - vanilla\nfile_x - merged\n'},
|
| + }
|
| +
|
| + COMMIT_F = {
|
| + 'some/files/file2': {'data': 'file2 - vanilla\nfile_y - merged\n'},
|
| + }
|
| +
|
| + # Move file2 from files to other.
|
| + COMMIT_G = {
|
| + 'some/files/file2': {'data': None},
|
| + 'some/other/file2': {'data': 'file2 - vanilla\nfile_y - merged\n'},
|
| + }
|
| +
|
| + COMMIT_H = {
|
| + 'some/other/file2': {'data': 'file2 - vanilla\nfile_z - merged\n'},
|
| + }
|
| +
|
| + def testBlameError(self):
|
| + """Tests a blame on a non-existent file."""
|
| + expected_output = ['']
|
| + 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 = ['']
|
| + 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
|
| + """
|
| +
|
| + COMMIT_A = {
|
| + 'file': {'data': 'A\ngreen\nblue\n'},
|
| + }
|
| +
|
| + # Change "green" to "yellow".
|
| + COMMIT_B = {
|
| + 'file': {'data': 'A\nyellow\nblue\n'},
|
| + }
|
| +
|
| + # Insert 2 lines at the top,
|
| + # Change "yellow" to "red".
|
| + COMMIT_C = {
|
| + 'file': {'data': 'X\nY\nA\nred\nblue\n'},
|
| + }
|
| +
|
| + # Insert 2 more lines at the top.
|
| + COMMIT_D = {
|
| + 'file': {'data': 'earth\nfire\nX\nY\nA\nred\nblue\n'},
|
| + }
|
| +
|
| + # Insert a line before "red", and indent "red" and "blue".
|
| + COMMIT_E = {
|
| + 'file': {'data': 'earth\nfire\nX\nY\nA\ncolors:\n red\n blue\n'},
|
| + }
|
| +
|
| + def testInterHunkLineMotion(self):
|
| + """Tests a blame with line motion in another 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 D, ignoring C.
|
| + # Lines 1, 2 were added by D.
|
| + # Lines 3, 4 were added by C (but ignored, so blame A, B, respectively).
|
| + # TODO(mgiuca): Ideally, this would blame both of these lines on A, because
|
| + # they add lines nowhere near the changes made by B.
|
| + # Line 5 was added by A.
|
| + # Line 6 was modified by C (but ignored, so blame A).
|
| + # TODO(mgiuca): Ideally, Line 6 would be blamed on B, because that was the
|
| + # last commit to touch that line (changing "green" to "yellow"), but the
|
| + # algorithm isn't yet able to figure out that Line 6 in D == Line 4 in C ~=
|
| + # Line 2 in B.
|
| + # Line 7 was added by A.
|
| + expected_output = [self.blame_line('D', ' 1) earth'),
|
| + self.blame_line('D', ' 2) fire'),
|
| + self.blame_line('A', '3*) X'),
|
| + self.blame_line('B', '4*) Y'),
|
| + self.blame_line('A', ' 5) A'),
|
| + self.blame_line('A', '6*) red'),
|
| + self.blame_line('A', ' 7) blue'),
|
| + ]
|
| + 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'),
|
| + ]
|
| + retval, output = self.run_hyperblame(['E'], 'file', 'tag_E')
|
| + 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')))
|
|
|