2018-01-03 22:51:20 +03:00
|
|
|
# no-check-code -- see T24862348
|
2018-02-10 04:31:46 +03:00
|
|
|
# flake8: noqa
|
2018-01-03 22:51:20 +03:00
|
|
|
|
2010-02-06 19:57:06 +03:00
|
|
|
import difflib
|
2008-11-10 03:08:35 +03:00
|
|
|
import errno
|
2009-09-16 06:34:53 +04:00
|
|
|
import gettext
|
2018-07-06 03:45:27 +03:00
|
|
|
import imp
|
2008-10-08 03:42:43 +04:00
|
|
|
import os
|
2008-11-10 03:08:35 +03:00
|
|
|
import shutil
|
|
|
|
import stat
|
2009-09-16 06:34:53 +04:00
|
|
|
import subprocess
|
|
|
|
import sys
|
2012-05-18 01:15:14 +04:00
|
|
|
import tarfile
|
2008-11-15 01:18:24 +03:00
|
|
|
import tempfile
|
|
|
|
import unittest
|
2008-10-08 03:42:43 +04:00
|
|
|
|
2019-01-30 03:25:33 +03:00
|
|
|
from edenscm.hgext.hgsubversion import compathacks, svnrepo, svnwrap, util
|
|
|
|
from edenscm.mercurial import (
|
2018-07-06 03:45:27 +03:00
|
|
|
cmdutil,
|
|
|
|
commands,
|
|
|
|
context,
|
|
|
|
dispatch as dispatchmod,
|
|
|
|
encoding,
|
|
|
|
extensions,
|
|
|
|
hg,
|
|
|
|
i18n,
|
|
|
|
node,
|
|
|
|
scmutil,
|
|
|
|
ui,
|
|
|
|
util as hgutil,
|
|
|
|
)
|
|
|
|
|
2014-06-03 04:54:02 +04:00
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
try:
|
2019-01-30 03:25:33 +03:00
|
|
|
from edenscm.mercurial import obsolete
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2014-04-22 17:53:11 +04:00
|
|
|
obsolete._enabled
|
2013-08-08 11:25:24 +04:00
|
|
|
except ImportError:
|
|
|
|
obsolete = None
|
|
|
|
|
2010-10-03 01:44:37 +04:00
|
|
|
try:
|
2010-10-10 01:20:52 +04:00
|
|
|
SkipTest = unittest.SkipTest
|
|
|
|
except AttributeError:
|
2018-07-06 03:45:27 +03:00
|
|
|
if "nose" in sys.modules:
|
|
|
|
SkipTest = sys.modules["nose"].SkipTest
|
2016-06-16 06:54:51 +03:00
|
|
|
else:
|
|
|
|
SkipTest = None
|
2010-10-03 01:44:37 +04:00
|
|
|
|
2009-07-29 18:26:29 +04:00
|
|
|
# Documentation for Subprocess.Popen() says:
|
|
|
|
# "Note that on Windows, you cannot set close_fds to true and
|
|
|
|
# also redirect the standard handles by setting stdin, stdout or
|
|
|
|
# stderr."
|
2018-07-06 03:45:27 +03:00
|
|
|
canCloseFds = "win32" not in sys.platform
|
|
|
|
|
|
|
|
if not "win32" in sys.platform:
|
2009-07-29 18:26:29 +04:00
|
|
|
|
2009-07-29 20:01:13 +04:00
|
|
|
def kill_process(popen_obj):
|
|
|
|
os.kill(popen_obj.pid, 9)
|
2018-07-06 03:45:27 +03:00
|
|
|
|
|
|
|
|
2009-07-29 20:01:13 +04:00
|
|
|
else:
|
|
|
|
import ctypes
|
2009-08-13 00:54:21 +04:00
|
|
|
from ctypes.wintypes import BOOL, DWORD, HANDLE, UINT
|
|
|
|
|
|
|
|
def win_status_check(result, func, args):
|
|
|
|
if result == 0:
|
|
|
|
raise ctypes.WinError()
|
|
|
|
return args
|
|
|
|
|
|
|
|
def WINAPI(returns, func, *params):
|
|
|
|
assert len(params) % 2 == 0
|
|
|
|
|
|
|
|
func.argtypes = tuple(params[0::2])
|
|
|
|
func.resvalue = returns
|
|
|
|
func.errcheck = win_status_check
|
|
|
|
|
|
|
|
return func
|
2009-07-29 20:01:13 +04:00
|
|
|
|
|
|
|
# dwDesiredAccess
|
|
|
|
PROCESS_TERMINATE = 0x0001
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
OpenProcess = WINAPI(
|
|
|
|
HANDLE,
|
|
|
|
ctypes.windll.kernel32.OpenProcess,
|
|
|
|
DWORD,
|
|
|
|
"dwDesiredAccess",
|
|
|
|
BOOL,
|
|
|
|
"bInheritHandle",
|
|
|
|
DWORD,
|
|
|
|
"dwProcessId",
|
2009-08-13 00:54:21 +04:00
|
|
|
)
|
2009-07-29 20:01:13 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
CloseHandle = WINAPI(BOOL, ctypes.windll.kernel32.CloseHandle, HANDLE, "hObject")
|
2009-07-29 20:01:13 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
TerminateProcess = WINAPI(
|
|
|
|
BOOL,
|
|
|
|
ctypes.windll.kernel32.TerminateProcess,
|
|
|
|
HANDLE,
|
|
|
|
"hProcess",
|
|
|
|
UINT,
|
|
|
|
"uExitCode",
|
2009-08-13 00:54:21 +04:00
|
|
|
)
|
2009-07-29 20:01:13 +04:00
|
|
|
|
|
|
|
def kill_process(popen_obj):
|
|
|
|
phnd = OpenProcess(PROCESS_TERMINATE, False, popen_obj.pid)
|
|
|
|
TerminateProcess(phnd, 1)
|
|
|
|
CloseHandle(phnd)
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2009-01-22 05:27:51 +03:00
|
|
|
# Fixtures that need to be pulled at a subdirectory of the repo path
|
2018-07-06 03:45:27 +03:00
|
|
|
subdir = {
|
|
|
|
"truncatedhistory.svndump": "/project2",
|
|
|
|
"fetch_missing_files_subdir.svndump": "/foo",
|
|
|
|
"empty_dir_in_trunk_not_repo_root.svndump": "/project",
|
|
|
|
"project_root_not_repo_root.svndump": "/dummyproj",
|
|
|
|
"project_name_with_space.svndump": "/project name",
|
|
|
|
"non_ascii_path_1.svndump": "/b\xC3\xB8b",
|
|
|
|
"non_ascii_path_2.svndump": "/b%C3%B8b",
|
|
|
|
"subdir_is_file_prefix.svndump": "/flaf",
|
|
|
|
"renames_with_prefix.svndump": "/prefix",
|
|
|
|
}
|
2013-08-27 03:40:31 +04:00
|
|
|
# map defining the layouts of the fixtures we can use with custom layout
|
|
|
|
# these are really popular layouts, so I gave them names
|
2018-07-06 03:45:27 +03:00
|
|
|
trunk_only = {"default": "trunk"}
|
|
|
|
trunk_dev_branch = {"default": "trunk", "dev_branch": "branches/dev_branch"}
|
2013-08-27 03:40:31 +04:00
|
|
|
custom = {
|
2018-07-06 03:45:27 +03:00
|
|
|
"addspecial.svndump": {"default": "trunk", "foo": "branches/foo"},
|
|
|
|
"binaryfiles.svndump": trunk_only,
|
|
|
|
"branch_create_with_dir_delete.svndump": trunk_dev_branch,
|
|
|
|
"branch_delete_parent_dir.svndump": trunk_dev_branch,
|
|
|
|
"branchmap.svndump": {
|
|
|
|
"default": "trunk",
|
|
|
|
"badname": "branches/badname",
|
|
|
|
"feature": "branches/feature",
|
|
|
|
},
|
|
|
|
"branch_prop_edit.svndump": trunk_dev_branch,
|
|
|
|
"branch_rename_to_trunk.svndump": {
|
|
|
|
"default": "trunk",
|
|
|
|
"dev_branch": "branches/dev_branch",
|
|
|
|
"old_trunk": "branches/old_trunk",
|
|
|
|
},
|
|
|
|
"copies.svndump": trunk_only,
|
|
|
|
"copyafterclose.svndump": {"default": "trunk", "test": "branches/test"},
|
|
|
|
"copybeforeclose.svndump": {"default": "trunk", "test": "branches/test"},
|
|
|
|
"delentries.svndump": trunk_only,
|
|
|
|
"delete_restore_trunk.svndump": trunk_only,
|
|
|
|
"empty_dir_in_trunk_not_repo_root.svndump": trunk_only,
|
|
|
|
"executebit.svndump": trunk_only,
|
|
|
|
"filecase.svndump": trunk_only,
|
|
|
|
"file_not_in_trunk_root.svndump": trunk_only,
|
|
|
|
"project_name_with_space.svndump": trunk_dev_branch,
|
|
|
|
"pushrenames.svndump": trunk_only,
|
|
|
|
"rename_branch_parent_dir.svndump": trunk_dev_branch,
|
|
|
|
"renamedproject.svndump": {"default": "trunk", "branch": "branches/branch"},
|
|
|
|
"renames.svndump": {"default": "trunk", "branch1": "branches/branch1"},
|
|
|
|
"renames_with_prefix.svndump": {"default": "trunk", "branch1": "branches/branch1"},
|
|
|
|
"replace_branch_with_branch.svndump": {
|
|
|
|
"default": "trunk",
|
|
|
|
"branch1": "branches/branch1",
|
|
|
|
"branch2": "branches/branch2",
|
|
|
|
},
|
|
|
|
"replace_trunk_with_branch.svndump": {"default": "trunk", "test": "branches/test"},
|
|
|
|
"revert.svndump": trunk_only,
|
|
|
|
"siblingbranchfix.svndump": {
|
|
|
|
"default": "trunk",
|
|
|
|
"wrongbranch": "branches/wrongbranch",
|
|
|
|
},
|
|
|
|
"simple_branch.svndump": {"default": "trunk", "the_branch": "branches/the_branch"},
|
|
|
|
"spaces-in-path.svndump": trunk_dev_branch,
|
|
|
|
"symlinks.svndump": trunk_only,
|
|
|
|
"truncatedhistory.svndump": trunk_only,
|
|
|
|
"unorderedbranch.svndump": {"default": "trunk", "branch": "branches/branch"},
|
|
|
|
"unrelatedbranch.svndump": {
|
|
|
|
"default": "trunk",
|
|
|
|
"branch1": "branches/branch1",
|
|
|
|
"branch2": "branches/branch2",
|
|
|
|
},
|
2013-08-27 03:40:31 +04:00
|
|
|
}
|
2009-01-22 05:27:51 +03:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
FIXTURES = os.path.join(os.path.abspath(os.path.dirname(__file__)), "fixtures")
|
|
|
|
|
2008-10-08 03:42:43 +04:00
|
|
|
|
2012-09-11 00:42:49 +04:00
|
|
|
def getlocalpeer(repo):
|
2018-07-06 03:45:27 +03:00
|
|
|
localrepo = getattr(repo, "local", lambda: repo)()
|
2012-09-11 00:42:49 +04:00
|
|
|
if isinstance(localrepo, bool):
|
|
|
|
localrepo = repo
|
|
|
|
return localrepo
|
2010-10-05 08:02:15 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2013-08-10 09:46:21 +04:00
|
|
|
def repolen(repo, svnonly=False):
|
2013-08-09 19:22:50 +04:00
|
|
|
"""Naively calculate the amount of available revisions in a repository.
|
|
|
|
|
|
|
|
this is usually equal to len(repo) -- except in the face of
|
|
|
|
obsolete revisions.
|
2013-08-10 09:46:21 +04:00
|
|
|
|
|
|
|
if svnonly is true, only count revisions converted from Subversion.
|
2013-08-09 19:22:50 +04:00
|
|
|
"""
|
|
|
|
# kind of nasty way of calculating the length, but fortunately,
|
|
|
|
# our test repositories tend to be rather small
|
2013-08-10 09:46:21 +04:00
|
|
|
revs = set(repo)
|
|
|
|
|
|
|
|
if obsolete:
|
2018-07-06 03:45:27 +03:00
|
|
|
revs -= obsolete.getrevs(repo, "obsolete")
|
2013-08-10 09:46:21 +04:00
|
|
|
|
|
|
|
if svnonly:
|
|
|
|
revs = set(r for r in revs if util.getsvnrev(repo[r]))
|
|
|
|
|
|
|
|
return len(revs)
|
2013-08-09 19:22:50 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2010-10-05 08:02:15 +04:00
|
|
|
def _makeskip(name, message):
|
2010-10-08 22:58:26 +04:00
|
|
|
if SkipTest:
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2010-10-08 22:58:26 +04:00
|
|
|
def skip(*args, **kwargs):
|
|
|
|
raise SkipTest(message)
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2010-10-08 22:58:26 +04:00
|
|
|
skip.__name__ = name
|
|
|
|
return skip
|
2010-10-05 08:02:15 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2010-10-05 08:03:12 +04:00
|
|
|
def requiresmodule(mod):
|
|
|
|
"""Skip a test if the specified module is not None."""
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2010-10-05 08:03:12 +04:00
|
|
|
def decorator(fn):
|
2010-10-08 22:58:26 +04:00
|
|
|
if fn is None:
|
|
|
|
return
|
2010-10-05 08:03:12 +04:00
|
|
|
if mod is not None:
|
|
|
|
return fn
|
2018-07-06 03:45:27 +03:00
|
|
|
return _makeskip(fn.__name__, "missing required feature")
|
|
|
|
|
2010-10-05 08:03:12 +04:00
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
2010-09-29 20:04:26 +04:00
|
|
|
def requiresoption(option):
|
2018-07-06 03:45:27 +03:00
|
|
|
"""Skip a test if commands.clone does not take the specified option."""
|
|
|
|
|
2010-09-29 20:04:26 +04:00
|
|
|
def decorator(fn):
|
2018-07-06 03:45:27 +03:00
|
|
|
for entry in cmdutil.findcmd("clone", commands.table)[1][1]:
|
2010-09-29 20:04:26 +04:00
|
|
|
if entry[1] == option:
|
|
|
|
return fn
|
2010-10-03 01:44:37 +04:00
|
|
|
# no match found, so skip
|
|
|
|
if SkipTest:
|
2018-07-06 03:45:27 +03:00
|
|
|
return _makeskip(fn.__name__, "test requires clone to accept %s" % option)
|
2010-10-03 01:44:37 +04:00
|
|
|
# no skipping support, so erase decorated method
|
|
|
|
return
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2010-09-29 20:04:26 +04:00
|
|
|
if not isinstance(option, str):
|
2018-07-06 03:45:27 +03:00
|
|
|
raise TypeError("requiresoption takes a string argument")
|
2010-09-29 20:04:26 +04:00
|
|
|
return decorator
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2013-08-10 01:34:16 +04:00
|
|
|
def requiresreplay(method):
|
2018-07-06 03:45:27 +03:00
|
|
|
"""Skip a test in stupid mode."""
|
|
|
|
|
2013-08-10 01:34:16 +04:00
|
|
|
def test(self, *args, **kwargs):
|
|
|
|
if self.stupid:
|
|
|
|
if SkipTest:
|
2013-08-10 04:41:25 +04:00
|
|
|
raise SkipTest("test requires replay mode")
|
2013-08-10 01:34:16 +04:00
|
|
|
else:
|
|
|
|
return method(self, *args, **kwargs)
|
|
|
|
|
|
|
|
test.__name__ = method.__name__
|
|
|
|
return test
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2009-10-17 07:33:41 +04:00
|
|
|
def filtermanifest(manifest):
|
2010-11-30 04:54:11 +03:00
|
|
|
return [f for f in manifest if f not in util.ignoredfiles]
|
2009-10-17 07:33:41 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2008-11-15 01:52:30 +03:00
|
|
|
def fileurl(path):
|
2018-07-06 03:45:27 +03:00
|
|
|
path = os.path.abspath(path).replace(os.sep, "/")
|
2008-11-10 03:08:35 +03:00
|
|
|
drive, path = os.path.splitdrive(path)
|
|
|
|
if drive:
|
2012-08-26 19:49:58 +04:00
|
|
|
# In svn 1.7, the swig svn wrapper returns local svn URLs
|
|
|
|
# with an uppercase drive letter, try to match that to
|
|
|
|
# simplify svn info tests.
|
2018-07-06 03:45:27 +03:00
|
|
|
drive = "/" + drive.upper()
|
|
|
|
url = "file://%s%s" % (drive, path)
|
2008-11-10 03:08:35 +03:00
|
|
|
return url
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2018-07-24 04:51:37 +03:00
|
|
|
def _execute_and_get_env(script_path, env_vars):
|
|
|
|
"""Executes the shell script located at the path `script_path` and returns a
|
|
|
|
dictionary of the environment variables specified by `env_vars` after the
|
|
|
|
execution."""
|
|
|
|
|
|
|
|
cmd = ["source", script_path]
|
|
|
|
for var in env_vars:
|
|
|
|
cmd.extend(["&&", "export", var])
|
|
|
|
cmd.extend([";", "env"])
|
|
|
|
|
|
|
|
p = subprocess.Popen(
|
|
|
|
" ".join(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
|
|
|
|
)
|
|
|
|
out, err = p.communicate()
|
|
|
|
ret = p.returncode
|
|
|
|
|
|
|
|
if ret == 0:
|
|
|
|
return dict(
|
|
|
|
line.split("=", 1)
|
|
|
|
for line in out.splitlines()
|
|
|
|
if line.startswith(tuple(env_vars))
|
|
|
|
)
|
|
|
|
elif ret == 80:
|
|
|
|
# 80 is the return code for skipped tests from the shell scripts.
|
|
|
|
raise SkipTest(out)
|
|
|
|
else:
|
|
|
|
stderr = sys.stderr
|
|
|
|
stderr.write(err)
|
|
|
|
stderr.flush()
|
|
|
|
sys.exit(ret)
|
|
|
|
|
|
|
|
|
|
|
|
class TestDb(object):
|
|
|
|
_TEST_DIR = os.environ["TESTDIR"]
|
|
|
|
_LIBRARY_SH = os.path.join(_TEST_DIR, "hgsql", "library.sh")
|
|
|
|
|
|
|
|
_DBENGINE = "DBENGINE"
|
|
|
|
_DBHOST = "DBHOST"
|
|
|
|
_DBNAME = "DBNAME"
|
|
|
|
_DBPASS = "DBPASS"
|
|
|
|
_DBPORT = "DBPORT"
|
|
|
|
_DBUSER = "DBUSER"
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self._init_config()
|
|
|
|
|
|
|
|
def _init_config(self):
|
|
|
|
env_vars = [
|
|
|
|
self._DBENGINE,
|
|
|
|
self._DBHOST,
|
|
|
|
self._DBNAME,
|
|
|
|
self._DBPASS,
|
|
|
|
self._DBPORT,
|
|
|
|
self._DBUSER,
|
|
|
|
]
|
|
|
|
self._env = _execute_and_get_env(self._LIBRARY_SH, env_vars)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def engine(self):
|
|
|
|
return self._env[self._DBENGINE]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def host(self):
|
|
|
|
return self._env[self._DBHOST]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return self._env[self._DBNAME]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def password(self):
|
|
|
|
return self._env[self._DBPASS]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def port(self):
|
|
|
|
return self._env[self._DBPORT]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def user(self):
|
|
|
|
return self._env[self._DBUSER]
|
|
|
|
|
|
|
|
|
2017-10-29 16:07:12 +03:00
|
|
|
class _testui(ui.ui):
|
|
|
|
def develwarn(self, msg, stacklevel=1, *args, **kwargs):
|
2019-01-30 03:25:33 +03:00
|
|
|
from edenscm.hgext.hgsubversion import util
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2017-10-29 16:07:12 +03:00
|
|
|
if util.smartset is not None:
|
2018-09-28 17:09:09 +03:00
|
|
|
config = args[0] if args else kwargs.get("config")
|
2018-07-06 03:45:27 +03:00
|
|
|
raise Exception("flunked develwarn: %r (%r)" % (msg, config))
|
|
|
|
return ui.ui.develwarn(self, msg, stacklevel=stacklevel, *args, **kwargs)
|
2017-10-29 16:07:12 +03:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
|
|
|
def testui(stupid=False, layout="auto", startrev=0):
|
|
|
|
encoding.environ["HGPLAIN"] = "True"
|
2017-10-29 16:07:12 +03:00
|
|
|
u = _testui()
|
2018-07-06 03:45:27 +03:00
|
|
|
bools = {True: "true", False: "false"}
|
|
|
|
u.setconfig("ui", "quiet", bools[True])
|
|
|
|
u.setconfig("ui", "username", "automated tests")
|
|
|
|
u.setconfig("extensions", "hgsubversion", "")
|
|
|
|
u.setconfig("hgsubversion", "layout", layout)
|
2018-11-07 03:39:33 +03:00
|
|
|
u.setconfig("hgsubversion", "nativerevs", True)
|
2018-07-06 03:45:27 +03:00
|
|
|
u.setconfig("hgsubversion", "startrev", startrev)
|
2018-11-07 03:39:33 +03:00
|
|
|
u.setconfig("hgsubversion", "stupid", bools[stupid])
|
2018-07-06 03:45:27 +03:00
|
|
|
u.setconfig("devel", "all-warnings", True)
|
|
|
|
u.setconfig("subrepos", "hgsubversion:allowed", True)
|
2010-02-26 16:50:22 +03:00
|
|
|
return u
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2018-07-24 04:51:41 +03:00
|
|
|
def dispatch(cmd, ui=None, repo=None):
|
2018-07-06 03:45:27 +03:00
|
|
|
assert "--quiet" in cmd
|
2018-07-24 04:51:41 +03:00
|
|
|
cmd = getattr(dispatchmod, "request", lambda x: x)(cmd, ui=ui, repo=repo)
|
2013-08-02 18:39:11 +04:00
|
|
|
return dispatchmod.dispatch(cmd)
|
2011-06-15 16:44:14 +04:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2008-11-10 03:08:35 +03:00
|
|
|
def rmtree(path):
|
|
|
|
# Read-only files cannot be removed under Windows
|
|
|
|
for root, dirs, files in os.walk(path):
|
|
|
|
for f in files:
|
|
|
|
f = os.path.join(root, f)
|
|
|
|
try:
|
|
|
|
s = os.stat(f)
|
2018-07-06 03:45:27 +03:00
|
|
|
except OSError as e:
|
2008-11-10 03:08:35 +03:00
|
|
|
if e.errno == errno.ENOENT:
|
|
|
|
continue
|
|
|
|
raise
|
|
|
|
if (s.st_mode & stat.S_IWRITE) == 0:
|
|
|
|
os.chmod(f, s.st_mode | stat.S_IWRITE)
|
|
|
|
shutil.rmtree(path)
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2012-05-12 18:28:23 +04:00
|
|
|
def hgclone(ui, source, dest, update=True, rev=None):
|
2018-07-06 03:45:27 +03:00
|
|
|
if getattr(hg, "peer", None):
|
2011-06-15 16:44:14 +04:00
|
|
|
# Since 1.9 (d976542986d2)
|
2012-05-12 18:28:23 +04:00
|
|
|
src, dest = hg.clone(ui, {}, source, dest, update=update, rev=rev)
|
2011-06-15 16:44:14 +04:00
|
|
|
else:
|
2012-05-12 18:28:23 +04:00
|
|
|
src, dest = hg.clone(ui, source, dest, update=update, rev=rev)
|
2011-06-15 16:44:14 +04:00
|
|
|
return src, dest
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
|
|
|
def svnls(repo_path, path, rev="HEAD"):
|
|
|
|
path = repo_path + "/" + path
|
2012-04-19 20:29:30 +04:00
|
|
|
path = util.normalize_url(fileurl(path))
|
2018-07-06 03:45:27 +03:00
|
|
|
args = ["svn", "ls", "-r", rev, "-R", path]
|
|
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
2012-04-19 20:29:30 +04:00
|
|
|
stdout, stderr = p.communicate()
|
|
|
|
if p.returncode:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise Exception("svn ls failed on %s: %r" % (path, stderr))
|
|
|
|
entries = [e.strip("/") for e in stdout.splitlines()]
|
2012-04-19 20:29:30 +04:00
|
|
|
entries.sort()
|
|
|
|
return entries
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
|
|
|
def svnpropget(repo_path, path, prop, rev="HEAD"):
|
|
|
|
path = repo_path + "/" + path
|
2012-04-19 20:29:31 +04:00
|
|
|
path = util.normalize_url(fileurl(path))
|
2018-07-06 03:45:27 +03:00
|
|
|
args = ["svn", "propget", "-r", str(rev), prop, path]
|
|
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
2012-04-19 20:29:31 +04:00
|
|
|
stdout, stderr = p.communicate()
|
2015-10-30 03:18:41 +03:00
|
|
|
if p.returncode and stderr:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise Exception("svn ls failed on %s: %r" % (path, stderr))
|
|
|
|
if "W200017" in stdout:
|
2015-10-30 03:18:41 +03:00
|
|
|
# subversion >= 1.9 changed 'no properties' to be an error, so let's
|
|
|
|
# avoid that
|
2018-07-06 03:45:27 +03:00
|
|
|
return ""
|
2012-04-19 20:29:31 +04:00
|
|
|
return stdout.strip()
|
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
|
|
|
|
def _obsolete_wrap(cls, name):
|
|
|
|
origfunc = getattr(cls, name)
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
if not name.startswith("test_") or not origfunc:
|
2013-08-08 11:25:24 +04:00
|
|
|
return
|
|
|
|
|
|
|
|
if not obsolete:
|
2018-07-06 03:45:27 +03:00
|
|
|
wrapper = _makeskip(name, "obsolete not available")
|
2013-08-08 11:25:24 +04:00
|
|
|
else:
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
def wrapper(self, *args, **opts):
|
2018-07-06 03:45:27 +03:00
|
|
|
self.assertFalse(obsolete._enabled, "obsolete was already active")
|
2013-08-08 11:25:24 +04:00
|
|
|
|
|
|
|
obsolete._enabled = True
|
|
|
|
|
|
|
|
try:
|
2018-07-06 03:45:27 +03:00
|
|
|
origfunc(self, *args, **opts)
|
|
|
|
self.assertTrue(obsolete._enabled, "obsolete remains active")
|
2013-08-08 11:25:24 +04:00
|
|
|
finally:
|
|
|
|
obsolete._enabled = False
|
|
|
|
|
|
|
|
if not wrapper:
|
|
|
|
return
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
wrapper.__name__ = name + " obsolete"
|
2013-08-08 11:25:24 +04:00
|
|
|
wrapper.__module__ = origfunc.__module__
|
|
|
|
|
|
|
|
if origfunc.__doc__:
|
|
|
|
firstline = origfunc.__doc__.strip().splitlines()[0]
|
2018-07-06 03:45:27 +03:00
|
|
|
wrapper.__doc__ = firstline + " (obsolete)"
|
2013-08-08 11:25:24 +04:00
|
|
|
|
|
|
|
assert getattr(cls, wrapper.__name__, None) is None
|
|
|
|
|
|
|
|
setattr(cls, wrapper.__name__, wrapper)
|
|
|
|
|
2013-08-10 01:34:16 +04:00
|
|
|
|
|
|
|
def _stupid_wrap(cls, name):
|
|
|
|
origfunc = getattr(cls, name)
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
if not name.startswith("test_") or not origfunc:
|
2013-08-10 01:34:16 +04:00
|
|
|
return
|
|
|
|
|
|
|
|
def wrapper(self, *args, **opts):
|
2018-07-06 03:45:27 +03:00
|
|
|
self.assertFalse(self.stupid, "stupid mode was already active")
|
2013-08-10 01:34:16 +04:00
|
|
|
|
|
|
|
self.stupid = True
|
|
|
|
|
|
|
|
try:
|
|
|
|
origfunc(self, *args, **opts)
|
|
|
|
finally:
|
|
|
|
self.stupid = False
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
wrapper.__name__ = name + " stupid"
|
2013-08-10 01:34:16 +04:00
|
|
|
wrapper.__module__ = origfunc.__module__
|
|
|
|
|
|
|
|
if origfunc.__doc__:
|
|
|
|
firstline = origfunc.__doc__.strip().splitlines()[0]
|
2018-07-06 03:45:27 +03:00
|
|
|
wrapper.__doc__ = firstline + " (stupid)"
|
2013-08-10 01:34:16 +04:00
|
|
|
|
|
|
|
assert getattr(cls, wrapper.__name__, None) is None
|
|
|
|
|
|
|
|
setattr(cls, wrapper.__name__, wrapper)
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
class TestMeta(type):
|
|
|
|
def __init__(cls, *args, **opts):
|
|
|
|
if cls.obsolete_mode_tests:
|
|
|
|
for origname in dir(cls):
|
|
|
|
_obsolete_wrap(cls, origname)
|
|
|
|
|
2015-12-31 20:06:58 +03:00
|
|
|
if cls.stupid_mode_tests and svnwrap.subversion_version < (1, 9, 0):
|
2013-08-10 01:34:16 +04:00
|
|
|
for origname in dir(cls):
|
|
|
|
_stupid_wrap(cls, origname)
|
|
|
|
|
2013-08-08 11:25:24 +04:00
|
|
|
return super(TestMeta, cls).__init__(*args, **opts)
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
class TestBase(unittest.TestCase):
|
2013-08-08 11:25:24 +04:00
|
|
|
__metaclass__ = TestMeta
|
|
|
|
|
|
|
|
obsolete_mode_tests = False
|
2013-08-10 01:34:16 +04:00
|
|
|
stupid_mode_tests = False
|
|
|
|
|
|
|
|
stupid = False
|
2013-08-08 11:25:24 +04:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
def setUp(self):
|
2018-07-06 03:45:27 +03:00
|
|
|
if "hgsubversion" in sys.modules:
|
|
|
|
sys.modules["hgext_hgsubversion"] = sys.modules["hgsubversion"]
|
2010-07-14 17:39:24 +04:00
|
|
|
|
2012-05-12 18:19:59 +04:00
|
|
|
# the Python 2.7 default of 640 is obnoxiously low
|
|
|
|
self.maxDiff = 4096
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
self.oldenv = dict(
|
|
|
|
[(k, os.environ.get(k, None)) for k in ("LANG", "LC_ALL", "HGRCPATH")]
|
|
|
|
)
|
2014-10-15 02:12:24 +04:00
|
|
|
try:
|
|
|
|
self.oldugettext = i18n._ugettext # Mercurial >= 3.2
|
|
|
|
except AttributeError:
|
|
|
|
self.oldt = i18n.t
|
2018-07-06 03:45:27 +03:00
|
|
|
os.environ["LANG"] = os.environ["LC_ALL"] = "C"
|
|
|
|
i18n.t = gettext.translation("hg", i18n.localedir, fallback=True)
|
2014-10-15 02:12:24 +04:00
|
|
|
else:
|
2018-07-06 03:45:27 +03:00
|
|
|
os.environ["LANG"] = os.environ["LC_ALL"] = "C"
|
2014-10-15 02:12:24 +04:00
|
|
|
i18n.setdatapath(hgutil.datapath)
|
2009-09-16 06:33:41 +04:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
self.oldwd = os.getcwd()
|
2008-12-21 04:13:46 +03:00
|
|
|
self.tmpdir = tempfile.mkdtemp(
|
2018-07-06 03:45:27 +03:00
|
|
|
"svnwrap_test", dir=os.environ.get("HGSUBVERSION_TEST_TEMP", None)
|
|
|
|
)
|
2013-08-15 04:33:40 +04:00
|
|
|
os.chdir(self.tmpdir)
|
2018-07-06 03:45:27 +03:00
|
|
|
self.hgrc = os.path.join(self.tmpdir, ".hgrc")
|
|
|
|
os.environ["HGRCPATH"] = self.hgrc
|
2013-04-13 02:41:51 +04:00
|
|
|
scmutil._rcpath = None
|
2018-07-06 03:45:27 +03:00
|
|
|
rc = open(self.hgrc, "w")
|
|
|
|
rc.write("[ui]\nusername=test-user\n")
|
|
|
|
for l in "[extensions]", "hgsubversion=":
|
2009-05-12 22:14:15 +04:00
|
|
|
print >> rc, l
|
2008-12-21 04:13:46 +03:00
|
|
|
|
2012-04-19 20:29:32 +04:00
|
|
|
self.repocount = 0
|
2018-07-06 03:45:27 +03:00
|
|
|
self.wc_path = "%s/testrepo_wc" % self.tmpdir
|
2010-09-08 11:57:06 +04:00
|
|
|
self.svn_wc = None
|
2009-05-15 21:18:43 +04:00
|
|
|
|
2012-01-01 18:59:15 +04:00
|
|
|
self.config_dir = self.tmpdir
|
|
|
|
svnwrap.common._svn_config_dir = self.config_dir
|
2018-07-06 03:45:27 +03:00
|
|
|
self.setup_svn_config("")
|
2012-01-01 18:59:15 +04:00
|
|
|
|
2009-05-15 21:18:43 +04:00
|
|
|
# Previously, we had a MockUI class that wrapped ui, and giving access
|
|
|
|
# to the stream. The ui.pushbuffer() and ui.popbuffer() can be used
|
|
|
|
# instead. Using the regular UI class, with all stderr redirected to
|
|
|
|
# stdout ensures that the test setup is much more similar to usage
|
|
|
|
# setups.
|
2009-05-12 22:14:15 +04:00
|
|
|
self.patch = (ui.ui.write_err, ui.ui.write)
|
|
|
|
setattr(ui.ui, self.patch[0].func_name, self.patch[1])
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2012-01-01 18:59:15 +04:00
|
|
|
def setup_svn_config(self, config):
|
2018-07-06 03:45:27 +03:00
|
|
|
c = open(self.config_dir + "/config", "w")
|
2013-08-02 19:34:05 +04:00
|
|
|
try:
|
2012-01-01 18:59:15 +04:00
|
|
|
c.write(config)
|
2013-08-02 19:34:05 +04:00
|
|
|
finally:
|
|
|
|
c.close()
|
2012-01-01 18:59:15 +04:00
|
|
|
|
2012-04-19 20:29:32 +04:00
|
|
|
def _makerepopath(self):
|
|
|
|
self.repocount += 1
|
2018-07-06 03:45:27 +03:00
|
|
|
return "%s/testrepo-%d" % (self.tmpdir, self.repocount)
|
2012-04-19 20:29:32 +04:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
def tearDown(self):
|
2009-09-16 06:33:41 +04:00
|
|
|
for var, val in self.oldenv.iteritems():
|
|
|
|
if val is None:
|
|
|
|
del os.environ[var]
|
|
|
|
else:
|
|
|
|
os.environ[var] = val
|
2014-10-15 02:12:24 +04:00
|
|
|
try:
|
|
|
|
i18n._ugettext = self.oldugettext # Mercurial >= 3.2
|
|
|
|
except AttributeError:
|
|
|
|
i18n.t = self.oldt
|
2008-11-15 01:18:24 +03:00
|
|
|
os.chdir(self.oldwd)
|
2015-05-14 07:49:52 +03:00
|
|
|
rmtree(self.tmpdir)
|
2009-05-12 22:14:15 +04:00
|
|
|
setattr(ui.ui, self.patch[0].func_name, self.patch[0])
|
2008-11-15 01:52:30 +03:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
def ui(self, layout="auto"):
|
2013-08-08 01:16:39 +04:00
|
|
|
return testui(self.stupid, layout)
|
2010-02-26 16:50:22 +03:00
|
|
|
|
2012-04-19 20:29:28 +04:00
|
|
|
def load_svndump(self, fixture_name):
|
2018-07-06 03:45:27 +03:00
|
|
|
"""Loads an svnadmin dump into a fresh repo. Return the svn repo
|
2012-04-19 20:29:28 +04:00
|
|
|
path.
|
2018-07-06 03:45:27 +03:00
|
|
|
"""
|
2012-04-19 20:29:32 +04:00
|
|
|
path = self._makerepopath()
|
|
|
|
assert not os.path.exists(path)
|
2016-06-07 10:15:53 +03:00
|
|
|
with open(os.path.join(FIXTURES, fixture_name)) as inp:
|
|
|
|
svnwrap.create_and_load(path, inp)
|
2012-04-19 20:29:28 +04:00
|
|
|
return path
|
|
|
|
|
2012-05-18 01:15:14 +04:00
|
|
|
def load_repo_tarball(self, fixture_name):
|
2018-07-06 03:45:27 +03:00
|
|
|
"""Extracts a tarball of an svn repo and returns the svn repo path."""
|
2012-05-18 01:15:14 +04:00
|
|
|
path = self._makerepopath()
|
|
|
|
assert not os.path.exists(path)
|
|
|
|
os.mkdir(path)
|
|
|
|
tarball = tarfile.open(os.path.join(FIXTURES, fixture_name))
|
|
|
|
# This is probably somewhat fragile, but I'm not sure how to
|
|
|
|
# do better in particular, I think it assumes that the tar
|
|
|
|
# entries are in the right order and that directories appear
|
|
|
|
# before their contents. This is a valid assummption for sane
|
|
|
|
# tarballs, from what I can tell. In particular, for a simple
|
|
|
|
# tarball of a svn repo with paths relative to the repo root,
|
|
|
|
# it seems to work
|
|
|
|
for entry in tarball:
|
|
|
|
tarball.extract(entry, path)
|
|
|
|
return path
|
|
|
|
|
2018-07-24 04:51:39 +03:00
|
|
|
def clone(
|
2018-07-06 03:45:27 +03:00
|
|
|
self,
|
|
|
|
repo_path,
|
|
|
|
subdir=None,
|
|
|
|
layout="auto",
|
|
|
|
startrev=0,
|
|
|
|
externals=None,
|
|
|
|
noupdate=True,
|
|
|
|
dest=None,
|
|
|
|
rev=None,
|
|
|
|
config=None,
|
|
|
|
):
|
|
|
|
if layout == "single":
|
2009-10-17 07:33:41 +04:00
|
|
|
if subdir is None:
|
2018-07-06 03:45:27 +03:00
|
|
|
subdir = "trunk"
|
2009-10-17 07:33:41 +04:00
|
|
|
elif subdir is None:
|
2018-07-06 03:45:27 +03:00
|
|
|
subdir = ""
|
2012-04-19 20:29:28 +04:00
|
|
|
projectpath = repo_path
|
2012-04-19 20:29:25 +04:00
|
|
|
if subdir:
|
2018-07-06 03:45:27 +03:00
|
|
|
projectpath += "/" + subdir
|
2012-04-19 20:29:25 +04:00
|
|
|
|
|
|
|
cmd = [
|
2018-07-06 03:45:27 +03:00
|
|
|
"clone",
|
|
|
|
"--quiet",
|
|
|
|
"--layout=%s" % layout,
|
|
|
|
"--startrev=%s" % startrev,
|
2012-04-19 20:29:25 +04:00
|
|
|
fileurl(projectpath),
|
|
|
|
self.wc_path,
|
2018-07-06 03:45:27 +03:00
|
|
|
]
|
2013-08-08 01:16:39 +04:00
|
|
|
if self.stupid:
|
2018-07-06 03:45:27 +03:00
|
|
|
cmd.append("--stupid")
|
2012-04-19 20:29:25 +04:00
|
|
|
if noupdate:
|
2018-07-06 03:45:27 +03:00
|
|
|
cmd.append("--noupdate")
|
2012-05-17 03:52:25 +04:00
|
|
|
if rev is not None:
|
2018-07-06 03:45:27 +03:00
|
|
|
cmd.append("--rev=%s" % rev)
|
2012-09-28 23:43:50 +04:00
|
|
|
config = dict(config or {})
|
2012-04-19 20:29:25 +04:00
|
|
|
if externals:
|
2018-07-06 03:45:27 +03:00
|
|
|
config["hgsubversion.externals"] = str(externals)
|
|
|
|
for k, v in reversed(sorted(config.iteritems())):
|
|
|
|
cmd[:0] = ["--config", "%s=%s" % (k, v)]
|
2012-04-19 20:29:25 +04:00
|
|
|
|
2013-08-02 18:39:11 +04:00
|
|
|
r = dispatch(cmd)
|
2018-07-24 04:51:39 +03:00
|
|
|
assert not r, "clone of %s failed" % projectpath
|
2012-04-19 20:29:25 +04:00
|
|
|
|
2018-07-24 04:51:39 +03:00
|
|
|
return self.wc_path
|
|
|
|
|
|
|
|
def fetch(self, svn_repo_path, *args, **opts):
|
|
|
|
hg_repo_path = self.clone(svn_repo_path, *args, **opts)
|
|
|
|
return hg.repository(testui(), hg_repo_path)
|
2012-05-12 13:12:57 +04:00
|
|
|
|
2016-06-07 10:15:53 +03:00
|
|
|
def load(self, fixture_name):
|
2018-07-06 03:45:27 +03:00
|
|
|
if fixture_name.endswith(".svndump"):
|
2012-05-18 01:15:14 +04:00
|
|
|
repo_path = self.load_svndump(fixture_name)
|
2018-07-06 03:45:27 +03:00
|
|
|
elif fixture_name.endswith("tar.gz"):
|
2012-05-18 01:15:14 +04:00
|
|
|
repo_path = self.load_repo_tarball(fixture_name)
|
|
|
|
else:
|
2018-07-06 03:45:27 +03:00
|
|
|
assert False, "Unknown fixture type"
|
2012-05-12 13:12:57 +04:00
|
|
|
|
2016-06-07 10:15:53 +03:00
|
|
|
return repo_path
|
|
|
|
|
2018-07-24 04:51:39 +03:00
|
|
|
def load_and_clone(self, fixture_name, *args, **opts):
|
|
|
|
svn_repo_path = self.load(fixture_name)
|
|
|
|
return self.clone(svn_repo_path, *args, **opts), svn_repo_path
|
|
|
|
|
2016-06-07 10:15:53 +03:00
|
|
|
def load_and_fetch(self, fixture_name, *args, **opts):
|
|
|
|
repo_path = self.load(fixture_name)
|
2012-05-12 13:12:57 +04:00
|
|
|
return self.fetch(repo_path, *args, **opts), repo_path
|
2012-04-19 20:29:28 +04:00
|
|
|
|
|
|
|
def _load_fixture_and_fetch(self, *args, **kwargs):
|
|
|
|
repo, repo_path = self.load_and_fetch(*args, **kwargs)
|
|
|
|
return repo
|
2008-12-12 04:49:36 +03:00
|
|
|
|
2012-04-19 20:29:29 +04:00
|
|
|
def add_svn_rev(self, repo_path, changes):
|
2018-07-06 03:45:27 +03:00
|
|
|
"""changes is a dict of filename -> contents"""
|
2010-09-08 11:57:06 +04:00
|
|
|
if self.svn_wc is None:
|
2018-07-06 03:45:27 +03:00
|
|
|
self.svn_wc = os.path.join(self.tmpdir, "testsvn_wc")
|
|
|
|
subprocess.call(
|
|
|
|
["svn", "co", "-q", fileurl(repo_path), self.svn_wc],
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT,
|
|
|
|
)
|
2010-09-08 11:57:06 +04:00
|
|
|
|
|
|
|
for filename, contents in changes.iteritems():
|
|
|
|
# filenames are / separated
|
2018-07-06 03:45:27 +03:00
|
|
|
filename = filename.replace("/", os.path.sep)
|
2010-09-08 11:57:06 +04:00
|
|
|
filename = os.path.join(self.svn_wc, filename)
|
2018-07-06 03:45:27 +03:00
|
|
|
open(filename, "w").write(contents)
|
2010-09-08 11:57:06 +04:00
|
|
|
# may be redundant
|
2018-07-06 03:45:27 +03:00
|
|
|
subprocess.call(
|
|
|
|
["svn", "add", "-q", filename],
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT,
|
|
|
|
)
|
|
|
|
subprocess.call(
|
|
|
|
["svn", "commit", "-q", self.svn_wc, "-m", "test changes"],
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT,
|
|
|
|
)
|
2010-09-08 11:57:06 +04:00
|
|
|
|
2008-11-15 01:18:24 +03:00
|
|
|
# define this as a property so that it reloads anytime we need it
|
|
|
|
@property
|
|
|
|
def repo(self):
|
2010-02-26 16:50:22 +03:00
|
|
|
return hg.repository(testui(), self.wc_path)
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2013-08-08 01:16:39 +04:00
|
|
|
def pushrevisions(self, expected_extra_back=0):
|
2013-08-09 19:22:50 +04:00
|
|
|
before = repolen(self.repo)
|
2018-07-06 03:45:27 +03:00
|
|
|
self.repo.ui.setconfig("hgsubversion", "stupid", str(self.stupid))
|
2010-06-28 06:18:47 +04:00
|
|
|
res = commands.push(self.repo.ui, self.repo)
|
2013-08-09 19:22:50 +04:00
|
|
|
after = repolen(self.repo)
|
2009-05-28 00:51:04 +04:00
|
|
|
self.assertEqual(expected_extra_back, after - before)
|
2010-06-28 06:18:47 +04:00
|
|
|
return res
|
2008-11-15 01:18:24 +03:00
|
|
|
|
2012-04-19 20:29:30 +04:00
|
|
|
def svnco(self, repo_path, svnpath, rev, path):
|
2010-11-25 23:55:21 +03:00
|
|
|
path = os.path.join(self.wc_path, path)
|
|
|
|
subpath = os.path.dirname(path)
|
|
|
|
if not os.path.isdir(subpath):
|
|
|
|
os.makedirs(subpath)
|
2018-07-06 03:45:27 +03:00
|
|
|
svnpath = fileurl(repo_path + "/" + svnpath)
|
|
|
|
args = ["svn", "co", "-r", rev, svnpath, path]
|
|
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
2010-11-25 23:55:21 +03:00
|
|
|
stdout, stderr = p.communicate()
|
|
|
|
if p.returncode:
|
2018-07-06 03:45:27 +03:00
|
|
|
raise Exception("svn co failed on %s: %r" % (svnpath, stderr))
|
2010-11-25 23:55:21 +03:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
def commitchanges(self, changes, parent="tip", message="automated test"):
|
2008-11-15 01:18:24 +03:00
|
|
|
"""Commit changes to mercurial directory
|
|
|
|
|
|
|
|
'changes' is a sequence of tuples (source, dest, data). It can look
|
|
|
|
like:
|
|
|
|
- (source, source, data) to set source content to data
|
2008-11-15 01:52:30 +03:00
|
|
|
- (source, dest, None) to set dest content to source one, and mark it as
|
2008-11-15 01:18:24 +03:00
|
|
|
copied from source.
|
|
|
|
- (source, dest, data) to set dest content to data, and mark it as copied
|
|
|
|
from source.
|
|
|
|
- (source, None, None) to remove source.
|
|
|
|
"""
|
|
|
|
repo = self.repo
|
2009-04-25 05:31:17 +04:00
|
|
|
parentctx = repo[parent]
|
2008-11-15 01:18:24 +03:00
|
|
|
|
|
|
|
changed, removed = [], []
|
|
|
|
for source, dest, newdata in changes:
|
|
|
|
if dest is None:
|
|
|
|
removed.append(source)
|
|
|
|
else:
|
|
|
|
changed.append(dest)
|
|
|
|
|
|
|
|
def filectxfn(repo, memctx, path):
|
|
|
|
if path in removed:
|
2014-09-17 03:02:44 +04:00
|
|
|
return compathacks.filectxfn_deleted(memctx, path)
|
2008-11-15 01:18:24 +03:00
|
|
|
entry = [e for e in changes if path == e[1]][0]
|
|
|
|
source, dest, newdata = entry
|
|
|
|
if newdata is None:
|
|
|
|
newdata = parentctx[source].data()
|
|
|
|
copied = None
|
|
|
|
if source != dest:
|
|
|
|
copied = source
|
2018-07-06 03:45:27 +03:00
|
|
|
return compathacks.makememfilectx(
|
|
|
|
repo,
|
|
|
|
memctx=memctx,
|
|
|
|
path=dest,
|
|
|
|
data=newdata,
|
|
|
|
islink=False,
|
|
|
|
isexec=False,
|
|
|
|
copied=copied,
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx = context.memctx(
|
|
|
|
repo,
|
|
|
|
(parentctx.node(), node.nullid),
|
|
|
|
message,
|
|
|
|
changed + removed,
|
|
|
|
filectxfn,
|
|
|
|
"an_author",
|
|
|
|
"2008-10-07 20:59:48 -0500",
|
|
|
|
{"branch": parentctx.branch()},
|
|
|
|
)
|
2008-11-15 01:18:24 +03:00
|
|
|
nodeid = repo.commitctx(ctx)
|
|
|
|
repo = self.repo
|
2009-04-25 05:31:17 +04:00
|
|
|
hg.clean(repo, nodeid)
|
2008-11-15 01:18:24 +03:00
|
|
|
return nodeid
|
2008-11-21 07:41:16 +03:00
|
|
|
|
|
|
|
def assertchanges(self, changes, ctx):
|
|
|
|
"""Assert that all 'changes' (as in defined in commitchanged())
|
|
|
|
went into ctx.
|
|
|
|
"""
|
|
|
|
for source, dest, data in changes:
|
|
|
|
if dest is None:
|
|
|
|
self.assertTrue(source not in ctx)
|
|
|
|
continue
|
|
|
|
self.assertTrue(dest in ctx)
|
|
|
|
if data is None:
|
|
|
|
data = ctx.parents()[0][source].data()
|
|
|
|
self.assertEqual(ctx[dest].data(), data)
|
|
|
|
if dest != source:
|
|
|
|
copy = ctx[dest].renamed()
|
|
|
|
self.assertEqual(copy[0], source)
|
2010-01-30 01:36:14 +03:00
|
|
|
|
2010-06-22 23:58:24 +04:00
|
|
|
def assertMultiLineEqual(self, first, second, msg=None):
|
|
|
|
"""Assert that two multi-line strings are equal. (Based on Py3k code.)
|
|
|
|
"""
|
2010-11-11 23:30:31 +03:00
|
|
|
try:
|
2018-07-06 03:45:27 +03:00
|
|
|
return super(TestBase, self).assertMultiLineEqual(first, second, msg)
|
2010-11-11 23:30:31 +03:00
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
self.assert_(isinstance(first, str), ("First argument is not a string"))
|
|
|
|
self.assert_(isinstance(second, str), ("Second argument is not a string"))
|
2010-06-22 23:58:24 +04:00
|
|
|
|
|
|
|
if first != second:
|
2018-07-06 03:45:27 +03:00
|
|
|
diff = "".join(
|
|
|
|
difflib.unified_diff(
|
|
|
|
first.splitlines(True),
|
|
|
|
second.splitlines(True),
|
|
|
|
fromfile="a",
|
|
|
|
tofile="b",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
msg = "%s\n%s" % (msg or "", diff)
|
|
|
|
raise self.failureException(msg)
|
2010-06-22 23:58:24 +04:00
|
|
|
|
2012-10-16 23:17:55 +04:00
|
|
|
def getgraph(self, repo):
|
2010-01-30 01:36:14 +03:00
|
|
|
"""Helper function displaying a repository graph, especially
|
|
|
|
useful when debugging comprehensive tests.
|
|
|
|
"""
|
|
|
|
# Could be more elegant, but it works with stock hg
|
2015-07-09 02:45:04 +03:00
|
|
|
_ui = testui()
|
2010-01-30 01:36:14 +03:00
|
|
|
templ = """\
|
2013-08-12 00:20:59 +04:00
|
|
|
changeset: {rev}:{node|short} (r{svnrev})
|
2010-01-30 01:36:14 +03:00
|
|
|
branch: {branches}
|
2010-02-06 19:36:12 +03:00
|
|
|
tags: {tags}
|
2010-01-30 01:36:14 +03:00
|
|
|
summary: {desc|firstline}
|
|
|
|
files: {files}
|
|
|
|
|
|
|
|
"""
|
2012-10-16 23:17:55 +04:00
|
|
|
_ui.pushbuffer()
|
2018-10-23 21:27:34 +03:00
|
|
|
commands.log(_ui, repo, rev=None, template=templ, graph=True)
|
2012-10-16 23:17:55 +04:00
|
|
|
return _ui.popbuffer()
|
|
|
|
|
2016-06-14 22:31:37 +03:00
|
|
|
def svnlog(self, repo=None):
|
2018-07-06 03:45:27 +03:00
|
|
|
"""log of the remote Subversion repository corresponding to repo
|
2016-06-14 22:31:37 +03:00
|
|
|
|
|
|
|
In order to make the format suitable for direct comparison in
|
|
|
|
tests, we exclude dates and convert the path operations into
|
|
|
|
a tuple.
|
2018-07-06 03:45:27 +03:00
|
|
|
"""
|
2016-06-14 22:31:37 +03:00
|
|
|
|
|
|
|
if repo is None:
|
|
|
|
repo = self.repo
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
return [
|
|
|
|
(
|
|
|
|
r.revnum,
|
|
|
|
r.message,
|
|
|
|
dict(
|
|
|
|
(p, (op.action, op.copyfrom_path, int(op.copyfrom_rev)))
|
|
|
|
for (p, op) in r.paths.items()
|
|
|
|
),
|
|
|
|
)
|
|
|
|
for r in svnrepo.svnremoterepo(repo.ui).svn.revisions()
|
|
|
|
]
|
2016-06-14 22:31:37 +03:00
|
|
|
|
2012-10-16 23:17:55 +04:00
|
|
|
def draw(self, repo):
|
|
|
|
sys.stdout.write(self.getgraph(repo))
|
2018-01-17 14:23:44 +03:00
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
|
2018-01-17 14:23:44 +03:00
|
|
|
def import_test(name):
|
2018-07-06 03:45:27 +03:00
|
|
|
components = name.split("_")
|
|
|
|
components.insert(1, "hgsubversion")
|
|
|
|
testname = "-".join(components) + ".py"
|
2018-01-17 14:23:44 +03:00
|
|
|
dot = os.path.dirname(os.path.abspath(__file__))
|
2018-07-06 03:45:27 +03:00
|
|
|
candidates = [
|
|
|
|
os.path.join(dot, testname),
|
|
|
|
os.path.join(dot, "comprehensive", testname),
|
|
|
|
]
|
2018-01-17 14:23:44 +03:00
|
|
|
for candidate in candidates:
|
|
|
|
if os.path.exists(candidate):
|
|
|
|
return imp.load_source(name, candidate)
|
|
|
|
|
2018-07-06 03:45:27 +03:00
|
|
|
raise ImportError(
|
|
|
|
"Could not import module %s from files like %s" % (name, testname)
|
|
|
|
)
|