blob: de0d7e62501101cf7eaa26fd69aae15cb31430cd [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012 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.
"""Unit tests for checkout.py."""
import logging
import os
import shutil
import sys
import unittest
from xml.etree import ElementTree
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(ROOT_DIR))
from testing_support import fake_repos
from testing_support.patches_data import GIT, RAW
import checkout
import patch
import subprocess2
# pass -v to enable it.
DEBUGGING = False
# A patch that will fail to apply.
BAD_PATCH = ''.join(
[l for l in GIT.PATCH.splitlines(True) if l.strip() != 'e'])
class FakeRepos(fake_repos.FakeReposBase):
TEST_GIT_REPO = 'repo_1'
def populateGit(self):
"""Creates a few revisions of changes files."""
self._commit_git(self.TEST_GIT_REPO, self._git_tree())
# Fix for the remote rejected error. For more details see:
# http://stackoverflow.com/questions/2816369/git-push-error-remote
subprocess2.check_output(
['git', '--git-dir',
os.path.join(self.git_root, self.TEST_GIT_REPO, '.git'),
'config', '--bool', 'core.bare', 'true'])
assert os.path.isdir(
os.path.join(self.git_root, self.TEST_GIT_REPO, '.git'))
@staticmethod
def _git_tree():
fs = {}
fs['origin'] = 'git@1'
fs['extra'] = 'dummy\n' # new
fs['codereview.settings'] = (
'# Test data\n'
'bar: pouet\n')
fs['chrome/file.cc'] = (
'a\n'
'bb\n'
'ccc\n'
'dd\n'
'e\n'
'ff\n'
'ggg\n'
'hh\n'
'i\n'
'jj\n'
'kkk\n'
'll\n'
'm\n'
'nn\n'
'ooo\n'
'pp\n'
'q\n')
fs['chromeos/views/DOMui_menu_widget.h'] = (
'// Copyright (c) 2010\n'
'// Use of this source code\n'
'// found in the LICENSE file.\n'
'\n'
'#ifndef DOM\n'
'#define DOM\n'
'#pragma once\n'
'\n'
'#include <string>\n'
'#endif\n')
return fs
# pylint: disable=R0201
class BaseTest(fake_repos.FakeReposTestBase):
name = 'foo'
FAKE_REPOS_CLASS = FakeRepos
is_read_only = False
def setUp(self):
super(BaseTest, self).setUp()
self._old_call = subprocess2.call
def redirect_call(args, **kwargs):
if not DEBUGGING:
kwargs.setdefault('stdout', subprocess2.PIPE)
kwargs.setdefault('stderr', subprocess2.STDOUT)
return self._old_call(args, **kwargs)
subprocess2.call = redirect_call
self.usr, self.pwd = self.FAKE_REPOS.USERS[0]
self.previous_log = None
def tearDown(self):
subprocess2.call = self._old_call
super(BaseTest, self).tearDown()
def get_patches(self):
return patch.PatchSet([
patch.FilePatchDiff('new_dir/subdir/new_file', GIT.NEW_SUBDIR, []),
patch.FilePatchDiff('chrome/file.cc', GIT.PATCH, []),
# TODO(maruel): Test with is_new == False.
patch.FilePatchBinary('bin_file', '\x00', [], is_new=True),
patch.FilePatchDelete('extra', False),
])
def get_trunk(self, modified):
raise NotImplementedError()
def _check_base(self, co, root, expected):
raise NotImplementedError()
def _check_exception(self, co, err_msg):
co.prepare(None)
try:
co.apply_patch([patch.FilePatchDiff('chrome/file.cc', BAD_PATCH, [])])
self.fail()
except checkout.PatchApplicationFailed, e:
self.assertEquals(e.filename, 'chrome/file.cc')
self.assertEquals(e.status, err_msg)
def _log(self):
raise NotImplementedError()
def _test_process(self, co_lambda):
"""Makes sure the process lambda is called correctly."""
post_processors = [lambda *args: results.append(args)]
co = co_lambda(post_processors)
self.assertEquals(post_processors, co.post_processors)
co.prepare(None)
ps = self.get_patches()
results = []
co.apply_patch(ps)
expected_co = getattr(co, 'checkout', co)
# Because of ReadOnlyCheckout.
expected = [(expected_co, p) for p in ps.patches]
self.assertEquals(len(expected), len(results))
self.assertEquals(expected, results)
def _check_move(self, co):
"""Makes sure file moves are handled correctly."""
co.prepare(None)
patchset = patch.PatchSet([
patch.FilePatchDelete('chromeos/views/DOMui_menu_widget.h', False),
patch.FilePatchDiff(
'chromeos/views/webui_menu_widget.h', GIT.RENAME_PARTIAL, []),
])
co.apply_patch(patchset)
# Make sure chromeos/views/DOMui_menu_widget.h is deleted and
# chromeos/views/webui_menu_widget.h is correctly created.
root = os.path.join(self.root_dir, self.name)
tree = self.get_trunk(False)
del tree['chromeos/views/DOMui_menu_widget.h']
tree['chromeos/views/webui_menu_widget.h'] = (
'// Copyright (c) 2011\n'
'// Use of this source code\n'
'// found in the LICENSE file.\n'
'\n'
'#ifndef WEB\n'
'#define WEB\n'
'#pragma once\n'
'\n'
'#include <string>\n'
'#endif\n')
#print patchset[0].get()
#print fake_repos.read_tree(root)
self.assertTree(tree, root)
class GitBaseTest(BaseTest):
def setUp(self):
super(GitBaseTest, self).setUp()
self.enabled = self.FAKE_REPOS.set_up_git()
self.assertTrue(self.enabled)
self.previous_log = self._log()
# pylint: disable=W0221
def _log(self, log_from_local_repo=False):
if log_from_local_repo:
repo_root = os.path.join(self.root_dir, self.name)
else:
repo_root = os.path.join(self.FAKE_REPOS.git_root,
self.FAKE_REPOS.TEST_GIT_REPO)
out = subprocess2.check_output(
['git',
'--git-dir',
os.path.join(repo_root, '.git'),
'log', '--pretty=format:"%H%x09%ae%x09%ad%x09%s"',
'--max-count=1']).strip('"')
if out and len(out.split()) != 0:
revision = out.split()[0]
else:
return {'revision': 0}
return {
'revision': revision,
'author': out.split()[1],
'msg': out.split()[-1],
}
def _check_base(self, co, root, expected):
read_only = isinstance(co, checkout.ReadOnlyCheckout)
self.assertEquals(read_only, self.is_read_only)
if not read_only:
self.FAKE_REPOS.git_dirty = True
self.assertEquals(root, co.project_path)
git_rev = co.prepare(None)
self.assertEquals(unicode, type(git_rev))
self.assertEquals(self.previous_log['revision'], git_rev)
self.assertEquals('pouet', co.get_settings('bar'))
self.assertTree(self.get_trunk(False), root)
patches = self.get_patches()
co.apply_patch(patches)
self.assertEquals(
['bin_file', 'chrome/file.cc', 'new_dir/subdir/new_file', 'extra'],
patches.filenames)
# Hackish to verify _branches() internal function.
# pylint: disable=W0212
self.assertEquals(
(['master', 'working_branch'], 'working_branch'),
co._branches())
# Verify that the patch is applied even for read only checkout.
self.assertTree(self.get_trunk(True), root)
fake_author = self.FAKE_REPOS.USERS[1][0]
revision = co.commit(u'msg', fake_author)
# Nothing changed.
self.assertTree(self.get_trunk(True), root)
if read_only:
self.assertEquals('FAKE', revision)
self.assertEquals(self.previous_log['revision'], co.prepare(None))
# Changes should be reverted now.
self.assertTree(self.get_trunk(False), root)
expected = self.previous_log
else:
self.assertEquals(self._log()['revision'], revision)
self.assertEquals(self._log()['revision'], co.prepare(None))
self.assertTree(self.get_trunk(True), root)
expected = self._log()
actual = self._log(log_from_local_repo=True)
self.assertEquals(expected, actual)
def get_trunk(self, modified):
tree = {}
for k, v in self.FAKE_REPOS.git_hashes[
self.FAKE_REPOS.TEST_GIT_REPO][1][1].iteritems():
assert k not in tree
tree[k] = v
if modified:
content_lines = tree['chrome/file.cc'].splitlines(True)
tree['chrome/file.cc'] = ''.join(
content_lines[0:5] + ['FOO!\n'] + content_lines[5:])
tree['bin_file'] = '\x00'
del tree['extra']
tree['new_dir/subdir/new_file'] = 'A new file\nshould exist.\n'
return tree
def _test_prepare(self, co):
print co.prepare(None)
class GitCheckout(GitBaseTest):
def _get_co(self, post_processors):
self.assertNotEqual(False, post_processors)
return checkout.GitCheckout(
root_dir=self.root_dir,
project_name=self.name,
remote_branch='master',
git_url=os.path.join(self.FAKE_REPOS.git_root,
self.FAKE_REPOS.TEST_GIT_REPO),
commit_user=self.usr,
post_processors=post_processors)
def testAll(self):
root = os.path.join(self.root_dir, self.name)
self._check_base(self._get_co(None), root, None)
@unittest.skip('flaky')
def testException(self):
self._check_exception(
self._get_co(None),
'While running git apply --index -3 -p1;\n fatal: corrupt patch at '
'line 12\n')
def testProcess(self):
self._test_process(self._get_co)
def _testPrepare(self):
self._test_prepare(self._get_co(None))
def testMove(self):
co = self._get_co(None)
self._check_move(co)
out = subprocess2.check_output(
['git', 'diff', '--staged', '--name-status'], cwd=co.project_path)
out = sorted(out.splitlines())
expected = sorted(
[
'A\tchromeos/views/webui_menu_widget.h',
'D\tchromeos/views/DOMui_menu_widget.h',
])
self.assertEquals(expected, out)
if __name__ == '__main__':
if '-v' in sys.argv:
DEBUGGING = True
logging.basicConfig(
level=logging.DEBUG,
format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
else:
logging.basicConfig(
level=logging.ERROR,
format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
unittest.main()