blob: a49be5000b8ceec74867a1c36c414ffe9d4e32de [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2015 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_drover."""
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
if sys.version_info.major == 2:
import mock
else:
from unittest import mock
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import gclient_utils
import git_drover
class GitDroverTest(unittest.TestCase):
def setUp(self):
super(GitDroverTest, self).setUp()
self.maxDiff = None
self._temp_directory = tempfile.mkdtemp()
self._parent_repo = os.path.join(self._temp_directory, 'parent_repo')
self._target_repo = os.path.join(self._temp_directory, 'drover_branch_123')
os.makedirs(os.path.join(self._parent_repo, '.git'))
with open(os.path.join(self._parent_repo, '.git', 'config'), 'w') as f:
f.write('config')
with open(os.path.join(self._parent_repo, '.git', 'HEAD'), 'w') as f:
f.write('HEAD')
os.mkdir(os.path.join(self._parent_repo, '.git', 'info'))
with open(
os.path.join(self._parent_repo, '.git', 'info', 'refs'), 'w') as f:
f.write('refs')
mock.patch('tempfile.mkdtemp', self._mkdtemp).start()
mock.patch('gclient_utils.AskForData', self._get_input).start()
mock.patch('subprocess.check_call', self._check_call).start()
mock.patch('subprocess.check_output', self._check_call).start()
self.real_popen = subprocess.Popen
mock.patch('subprocess.Popen', self._Popen).start()
self.addCleanup(mock.patch.stopall)
self._commands = []
self._input = []
self._fail_on_command = None
self._reviewers = ''
self.REPO_CHECK_COMMANDS = [
(['git', '--help'], self._parent_repo),
(['git', 'status'], self._parent_repo),
(['git', 'fetch', 'origin'], self._parent_repo),
(['git', 'rev-parse', 'refs/remotes/branch-heads/branch^{commit}'],
self._parent_repo),
(['git', 'rev-parse', 'cl^{commit}'], self._parent_repo),
(['git', 'show', '-s', 'cl'], self._parent_repo),
]
self.LOCAL_REPO_COMMANDS = [
(['git', 'rev-parse', '--git-dir'], self._parent_repo),
(['git', 'config', 'core.sparsecheckout', 'true'], self._target_repo),
(['git', 'checkout', '-b', 'drover_branch_123',
'refs/remotes/branch-heads/branch'], self._target_repo),
(['git', 'cherry-pick', '-x', 'cl'], self._target_repo),
]
self.UPLOAD_COMMANDS = [
(['git', 'reset', '--hard'], self._target_repo),
(['git', 'log', '-1', '--format=%ae'], self._target_repo),
(['git', 'cl', 'upload', '--send-mail', '--tbrs', 'author@domain.org'],
self._target_repo),
]
self.LAND_COMMAND = [
(['git', 'cl', 'set-commit'], self._target_repo),
]
if os.name == 'nt':
self.BRANCH_CLEANUP_COMMANDS = [
(['rmdir', '/s', '/q', self._target_repo], None),
(['git', 'branch', '-D', 'drover_branch_123'], self._parent_repo),
]
else:
self.BRANCH_CLEANUP_COMMANDS = [
(['git', 'branch', '-D', 'drover_branch_123'], self._parent_repo),
]
self.MANUAL_RESOLVE_PREPARATION_COMMANDS = [
(['git', '-c', 'core.quotePath=false', 'status', '--porcelain'],
self._target_repo),
(['git', 'update-index', '--skip-worktree', '--stdin'],
self._target_repo),
]
self.FINISH_MANUAL_RESOLVE_COMMANDS = [
(['git', 'commit', '--no-edit'], self._target_repo),
]
def tearDown(self):
shutil.rmtree(self._temp_directory)
super(GitDroverTest, self).tearDown()
def _mkdtemp(self, prefix='tmp'):
self.assertEqual('drover_branch_', prefix)
os.mkdir(self._target_repo)
return self._target_repo
def _get_input(self, message):
result = self._input.pop(0)
if result == 'EOF':
raise EOFError
return result
def _check_call(self, args, stderr=None, stdout=None, shell='', cwd=None):
if args[0] == 'rmdir':
subprocess.call(args, shell=shell)
else:
self.assertFalse(shell)
self._commands.append((args, cwd))
if (self._fail_on_command is not None and
self._fail_on_command == len(self._commands)):
self._fail_on_command = None
raise subprocess.CalledProcessError(1, args[0])
rv = ''
if args == ['git', 'rev-parse', '--git-dir']:
rv = os.path.join(self._parent_repo, '.git')
if args == ['git', '-c', 'core.quotePath=false', 'status', '--porcelain']:
rv = ' D foo\nUU baz\n D bar\n'
if args == ['git', 'log', '-1', '--format=%ae']:
rv = 'author@domain.org'
if sys.version_info.major == 3:
return bytes(rv, 'utf-8')
return rv
def _Popen(self, args, shell=False, cwd=None, stdin=None, stdout=None,
stderr=None):
if args == ['git', 'update-index', '--skip-worktree', '--stdin']:
self._commands.append((args, cwd))
self.assertFalse(shell)
self.assertEqual(stdin, subprocess.PIPE)
class MockPopen(object):
def __init__(self, *args, **kwargs):
self.returncode = -999
def communicate(self, stdin):
if stdin == b'foo\nbar\n':
self.returncode = 0
else:
self.returncode = 1
return MockPopen()
else:
return self.real_popen(args, shell=shell, cwd=cwd, stdin=stdin,
stdout=stdout, stderr=stderr)
def testSuccess(self):
self._input = ['y', 'y']
git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
self.assertEqual(
self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
self.UPLOAD_COMMANDS + self.LAND_COMMAND + self.BRANCH_CLEANUP_COMMANDS,
self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testDryRun(self):
self._input = ['y']
git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, True)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
self.BRANCH_CLEANUP_COMMANDS, self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testCancelEarly(self):
self._input = ['n']
git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS, self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testEOFOnConfirm(self):
self._input = ['EOF']
git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS, self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testCancelLate(self):
self._input = ['y', 'n']
git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
self.UPLOAD_COMMANDS + self.BRANCH_CLEANUP_COMMANDS,
self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testFailDuringCheck(self):
self._input = []
self._fail_on_command = 1
self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS[:1], self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testFailDuringBranchCreation(self):
self._input = ['y']
self._fail_on_command = 8
self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS[:2] +
self.BRANCH_CLEANUP_COMMANDS[:-1], self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testFailDuringCherryPickAndAbort(self):
self._input = ['y']
self._fail_on_command = 10
self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS[:4] +
self.MANUAL_RESOLVE_PREPARATION_COMMANDS, self._commands)
self.assertTrue(os.path.exists(self._target_repo))
self.assertFalse(self._input)
self._commands = []
git_drover.abort_cherry_pick(self._target_repo)
self.assertEqual(self.BRANCH_CLEANUP_COMMANDS, self._commands)
self.assertFalse(os.path.exists(self._target_repo))
def testFailDuringCherryPickAndContinue(self):
self._input = ['y']
self._fail_on_command = 10
self.assertRaises(git_drover.PatchError, git_drover.cherry_pick_change,
'branch', 'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS[:4] +
self.MANUAL_RESOLVE_PREPARATION_COMMANDS, self._commands)
self.assertTrue(os.path.exists(self._target_repo))
self.assertFalse(self._input)
self._commands = []
self._input = ['n']
git_drover.continue_cherry_pick(self._target_repo)
self.assertEqual(self.UPLOAD_COMMANDS, self._commands)
self.assertTrue(os.path.exists(self._target_repo))
self.assertFalse(self._input)
self._commands = []
self._input = ['y']
git_drover.continue_cherry_pick(self._target_repo)
self.assertEqual(
self.UPLOAD_COMMANDS + self.LAND_COMMAND + self.BRANCH_CLEANUP_COMMANDS,
self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testFailDuringCherryPickAndContinueWithoutCommitting(self):
self._input = ['y']
self._fail_on_command = 10
self.assertRaises(git_drover.PatchError, git_drover.cherry_pick_change,
'branch', 'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS[:4] +
self.MANUAL_RESOLVE_PREPARATION_COMMANDS, self._commands)
self.assertTrue(os.path.exists(self._target_repo))
self.assertFalse(self._input)
self._commands = []
with open(os.path.join(self._target_repo, '.git', 'CHERRY_PICK_HEAD'), 'w'):
pass
self._commands = []
self._input = ['y']
git_drover.continue_cherry_pick(self._target_repo)
self.assertEqual(self.FINISH_MANUAL_RESOLVE_COMMANDS + self.UPLOAD_COMMANDS
+ self.LAND_COMMAND + self.BRANCH_CLEANUP_COMMANDS,
self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testFailDuringCherryPickAndContinueWithoutResolving(self):
self._input = ['y']
self._fail_on_command = 10
self.assertRaises(git_drover.PatchError, git_drover.cherry_pick_change,
'branch', 'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS[:4] +
self.MANUAL_RESOLVE_PREPARATION_COMMANDS, self._commands)
self.assertTrue(os.path.exists(self._target_repo))
self.assertFalse(self._input)
self._commands = []
self._fail_on_command = 1
with open(os.path.join(self._target_repo, '.git', 'CHERRY_PICK_HEAD'), 'w'):
pass
self.assertRaisesRegexp(git_drover.Error,
'All conflicts must be resolved before continuing',
git_drover.continue_cherry_pick, self._target_repo)
self.assertEqual(self.FINISH_MANUAL_RESOLVE_COMMANDS, self._commands)
self.assertTrue(os.path.exists(self._target_repo))
self._commands = []
git_drover.abort_cherry_pick(self._target_repo)
self.assertEqual(self.BRANCH_CLEANUP_COMMANDS, self._commands)
self.assertFalse(os.path.exists(self._target_repo))
def testFailAfterCherryPick(self):
self._input = ['y']
self._fail_on_command = 11
self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
self.UPLOAD_COMMANDS[:1] + self.BRANCH_CLEANUP_COMMANDS,
self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testFailOnUpload(self):
self._input = ['y']
self._fail_on_command = 13
self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
'cl', self._parent_repo, False)
self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
self.UPLOAD_COMMANDS + self.BRANCH_CLEANUP_COMMANDS,
self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testInvalidParentRepoDirectory(self):
self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
'cl', os.path.join(self._parent_repo, 'fake'), False)
self.assertFalse(self._commands)
self.assertFalse(os.path.exists(self._target_repo))
self.assertFalse(self._input)
def testContinueInvalidWorkdir(self):
self.assertRaises(git_drover.Error, git_drover.continue_cherry_pick,
self._parent_repo)
def testAbortInvalidWorkdir(self):
self.assertRaises(git_drover.Error, git_drover.abort_cherry_pick,
self._parent_repo)
if __name__ == '__main__':
unittest.main()