[PATCH 4 of 5] hgmerge tests
Steve Borho
steve at borho.org
Fri Feb 1 04:56:49 UTC 2008
# HG changeset patch
# User Steve Borho <steve at borho.org>
# Date 1201840337 21600
# Node ID 9aca680a4d18a5a8a0f3ea9bff4217e297264f70
# Parent f3231b697c3d8f31f1c5729c43edbc04b1a52b80
hgmerge tests
diff --git a/tests/test-hgmerge.py b/tests/test-hgmerge.py
new file mode 100755
--- /dev/null
+++ b/tests/test-hgmerge.py
@@ -0,0 +1,894 @@
+#!/usr/bin/env python
+import os.path, tempfile, sys
+import re
+import stat
+from glob import glob
+import unittest
+import shutil
+
+from mercurial import hgmerge
+import mercurial.hgmerge._plugins as mergeplugs
+import mercurial.hgmerge._simplemerge as hgsimplemerge
+import mercurial.hgmerge._pluginapi as mergepluginapi
+import mercurial.util as hgutil
+
+class MthdMock:
+ def __init__(self, returnval=None, func=None):
+ self.mock_args = self.mock_kwds = None
+ self.__retval = returnval
+ self.__args2retval = []
+ self.__func = func
+ self.__exc = None
+
+ def __call__(self, *args, **kwds):
+ self.mock_args = args
+ self.mock_kwds = kwds
+ if self.__func is not None:
+ return self.__func(*args, **kwds)
+ if self.__exc is not None:
+ raise self.__exc
+ for a, r in self.__args2retval:
+ if a == args:
+ retval = r
+ break
+ else:
+ retval = self.__retval
+ return retval
+
+ @property
+ def mock_is_called(self):
+ return self.mock_args is not None
+
+ def mock_set_returnvalue(self, retval):
+ self.__retval = retval
+
+ def mock_set_returnvalue_for_args(self, args, retval):
+ # Note that we can't use a dict since either of the arguments may be
+ # unhashable
+ self.__args2retval.append((args, retval))
+
+ def mock_set_raises(self, exc):
+ self.__exc = exc
+
+class FakeUi(object):
+ def __init__(self, conf=None, globalconf=None):
+ if conf is None: conf = {}
+ if globalconf is None: globalconf = {}
+ self.__conf = conf
+ self.__globalconf = globalconf
+ self.warn = MthdMock()
+ self.info = MthdMock()
+ self.write = MthdMock()
+ self.prompt = MthdMock()
+ self.debug = MthdMock()
+
+ def configitems(self, key):
+ if key == 'merge-tools':
+ return self.__conf.items()
+ elif key == 'merge':
+ return self.__globalconf.items()
+ return {}.items()
+
+ def config(self, sect, field, default=None):
+ if sect == 'merge-tools':
+ return self.__conf.get(field, default)
+ elif sect == 'merge':
+ return self.__globalconf.get(field, default)
+ return default
+
+ def configbool(self, sect, field, default=None):
+ val = self.config(sect, field, default=default)
+ if val is default:
+ return val
+ val = val.lower()
+ if val == 'true':
+ return True
+ if val == 'false':
+ return False
+ raise ValueError(val)
+
+ def configlist(self, sect, field, default=None):
+ val = self.config(sect, field, default=default)
+ if val is default:
+ return val
+ return val.replace(',', ' ').split()
+
+class FakeRepo(object):
+ def __init__(self, conf=None, globalconf=None):
+ self.ui = FakeUi(conf, globalconf)
+ self.root = os.getcwd()
+
+class _testcase(unittest.TestCase):
+ def setUp(self):
+ unittest.TestCase.setUp(self)
+ self.__origattrs = {}
+ self.__tempfiles = []
+ self.__tempdirs = []
+ self.__scriptdir = os.path.abspath(os.path.join("Data", "bin"))
+ self.__outputdir = os.path.join(self.__scriptdir, "Output")
+ if not os.path.exists(self.__outputdir):
+ os.makedirs(self.__outputdir)
+
+ def tearDown(self):
+ unittest.TestCase.tearDown(self)
+ for (obj, attr), val in self.__origattrs.items():
+ setattr(obj, attr, val)
+ for ftemp in self.__tempfiles:
+ if isinstance(ftemp, file):
+ ftemp.close()
+ fname = ftemp.name
+ else:
+ fname = ftemp
+ try: os.remove(fname)
+ except EnvironmentError: pass
+ for dtemp in self.__tempdirs:
+ if not os.path.exists(dtemp):
+ continue
+ shutil.rmtree(dtemp, ignore_errors=True)
+
+ def assertNot(self, val, msg=None):
+ self.assert_(not val, msg=msg)
+
+ def assertIs(self, lhs, rhs, msg=None):
+ if not lhs is rhs:
+ raise self.failureException, msg or "%r is not %r" % (lhs, rhs)
+
+ def assertIsNot(self, lhs, rhs, msg=None):
+ if lhs is rhs:
+ raise self.failureException, msg or "%r is %r" % (lhs, rhs)
+
+ def assertIn(self, val, seq, msg=None):
+ if not val in seq:
+ raise self.failureException, msg or "%r not in %r" % (val, seq)
+
+ def assertNotIn(self, val, seq, msg=None):
+ if val in seq:
+ raise self.failureException, msg or "%r in %r" % (val, seq)
+
+ def _create_repo(self, conf={}):
+ return FakeRepo(conf)
+
+ def _set_objattr(self, obj, attr, val):
+ if (obj, attr) not in self.__origattrs:
+ self.__origattrs[(obj, attr)] = getattr(obj, attr)
+ setattr(obj, attr, val)
+
+ def _get_tempfname(self):
+ """ Get a temporary filename.
+
+ The file is automatically removed upon teardown if still there.
+ """
+ fd, fpath = tempfile.mkstemp()
+ os.close(fd)
+ self.__tempfiles.append(fpath)
+ return fpath
+
+ def _get_tempfile(self, *args, **kwds):
+ fd, fpath = tempfile.mkstemp()
+ os.close(fd)
+ # Open file directly instead of using fdopen, since the latter will
+ # return a file with a bogus name
+ ftemp = file(fpath, "wb+")
+ self.__tempfiles.append(ftemp)
+ return ftemp
+
+ def _get_tempdir(self, *args, **kwds):
+ """ Create temporary directory that is removed on tearDown. """
+ dtemp = tempfile.mkdtemp(*args, **kwds)
+ self.__tempdirs.append(dtemp)
+ return dtemp
+
+class FakeFileCtx(object):
+ def __init__(self, fname, islink):
+ self.fname = fname
+ self.name = fname
+ self._islink = islink
+ self._target = os.readlink(fname) if islink else None
+ self._eoltp = 'unknown'
+
+ def islink(self):
+ return self._islink
+ def path(self):
+ return self.fname
+ def data(self):
+ return self._target
+
+
+class _FakePlugin(object):
+ def __init__(self, name='Fake', result=0, detect=True,
+ priority=0):
+ self.name = name
+ self.priority = priority
+ self._symlink = False
+ self._binary = False
+ self.__result, self.__detect = result, detect
+ self.fake_mergeargs = None
+ self.fake_opts = {}
+ self.fake_mergeexc = None
+
+ @property
+ def fake_is_called(self):
+ return self.fake_mergeargs is not None
+
+ def set_options(self, options, ui):
+ for attr, val in options.attrs.items():
+ self.fake_opts[attr] = val
+ try: self.priority = options.attrs['priority']
+ except KeyError: pass
+
+ def detect(self, ui):
+ return self.__detect
+
+ def merge(self, base, local, other, output, ui):
+ self.fake_mergeargs = (base, local, other, output, ui)
+ if self.fake_mergeexc is not None:
+ raise self.fake_mergeexc
+ return self.__result
+
+def _read_file(fname):
+ f = file(fname, 'rb')
+ try: data = f.read()
+ finally: f.close()
+ return data
+
+def _create_file(fname, content):
+ f = file(fname, 'wb')
+ try: f.write(content)
+ finally: f.close()
+
+class merge_test(_testcase):
+ ''' Test merge function.
+ '''
+ def setUp(self):
+ _testcase.setUp(self)
+ self.__repo = FakeRepo()
+ self.__ui = self.__repo.ui
+ self.__simplemerge = MthdMock(returnval=0)
+ self._set_objattr(hgsimplemerge, 'simplemerge', self.__simplemerge)
+
+ def tearDown(self):
+ _testcase.tearDown(self)
+ assert hgsimplemerge.simplemerge is not self.__simplemerge
+
+ def test_merge(self):
+ ''' Test simple merge, with no conflicts. '''
+ def merge(base, local, other, output, ui, **kwds):
+ self.__mergeargs = (base, local, other, output, ui)
+ f = file(output, 'w')
+ f.write('Success')
+ f.close()
+ return 0
+
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ self._set_objattr(hgsimplemerge, 'simplemerge', merge)
+
+ r = hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ self.assertEqual(r, 0)
+ self.__verify_mergeargs(self.__mergeargs, local, base, other)
+ self.assertNot(interactive.fake_is_called)
+ # Now verify merge output
+ f = file(local.name, 'r')
+ txt = f.read()
+ f.close()
+ self.assertEqual(txt, 'Success')
+
+ def test_merge_permissions(self):
+ ''' Verify that the permissions are by default the same as for the local
+ copy.
+ '''
+ base, local, other = self.__get_versions()
+ os.chmod(local.name, 0666)
+ interactive, patmatch = self.__create_plug()
+ hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ st = os.stat(local.name)
+ self.assertEqual(stat.S_IMODE(st.st_mode), 0666)
+
+ def test_merge_failure(self):
+ ''' Verify behaviour upon merge failure.
+
+ The idea here is to verify that a botched merge operation does not
+ result in permanent damage.
+ '''
+ def merge(local, base, other, output, ui):
+ self.__mergelocal = local
+ f = file(output, 'w')
+ f.write('Botched')
+ f.close()
+ raise Exception()
+
+ base, local, other = self.__get_versions()
+ f = file(local.name, 'w')
+ f.write('My text')
+ f.close()
+ interactive, patmatch = self.__create_plug()
+ self.__simplemerge.mock_set_returnvalue(1)
+ self._set_objattr(interactive, 'merge', merge)
+
+ r = hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ self.assertEqual(r, 2)
+ # Verify merge output and backup
+ f = file(local.name)
+ txt = f.read()
+ f.close()
+ self.assertEqual(txt, 'Botched')
+ f = file(self.__mergelocal)
+ txt = f.read()
+ f.close()
+ self.assertEqual(txt, 'My text')
+ # Verify that the user is warned
+ self.assert_(self.__ui.warn.mock_args[0].startswith(
+ 'merging failed'))
+
+ def test_merge_conflicts(self):
+ ''' Test merge in which conflicts must be resolved. '''
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ self.__simplemerge.mock_set_returnvalue(1)
+ self.__ui.prompt.mock_set_returnvalue('y')
+
+ r = hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ self.assertEqual(r, 0)
+ self.__verify_mergeargs(getattr(interactive, 'fake_mergeargs'),
+ local, base, other)
+
+ def test_merge_conflicts_return_none(self):
+ ''' Test having merge plug-in return None. '''
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug(result=None)
+ self.__simplemerge.mock_set_returnvalue(1)
+ self.__ui.prompt.mock_set_returnvalue('y')
+
+ r = hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ self.assertEqual(r, 0)
+
+ def test_merge_conflicts_no_plugin(self):
+ ''' Test merge where there are conflicts, but no fallback plug-in. '''
+ def merge(base, local, other, output, ui, **kwds):
+ f = file(output, 'w')
+ try: f.write('Conflicts')
+ finally: f.close()
+ return 1
+
+ base, local, other = self.__get_versions()
+ self._set_objattr(hgsimplemerge, 'simplemerge', merge)
+
+ r = hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (None, False))
+ self.assertEqual(r, 1)
+ self.assertEqual(_read_file(local.name), 'Conflicts')
+ # Make sure no backup is kept
+ self.assertNot(glob('%s.orig.*' % local.name))
+
+ def test_merge_conflicts_dont_resolve(self):
+ ''' Test when user wants to resolve conflicts himself. '''
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ self.__simplemerge.mock_set_returnvalue(1)
+ self.__ui.prompt.mock_set_returnvalue('y')
+
+ repo = FakeRepo(globalconf={'resolve_conflicts': 'false'})
+ r = hgmerge.merge(repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ self.assertEqual(r, 1)
+ # The interactive tool should not be invoked
+ self.assertEqual(interactive.fake_mergeargs, None)
+
+ def __verify_mergeargs(self, mergeargs, local, base, other):
+ mlocal, mbase, mother, moutput, mui = mergeargs
+ self.assertEqual(mbase, base.name)
+ self.assert_(re.match(r'^%s\.orig\.\d+' % local.name, mlocal))
+ self.assertEqual(mother, other.name)
+ self.assertEqual(moutput, local.name)
+ self.assertIs(mui, self.__ui)
+
+ def test_merge_plugins_unspecified(self):
+ ''' Test merge without specifying plugins. '''
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ get_pluginmock = MthdMock(returnval=(interactive, patmatch))
+ self._set_objattr(hgmerge, 'get_plugin', get_pluginmock)
+ repo = self.__repo
+ # Assert that get_plugin is called when no plug-in is specified
+ hgmerge.merge(repo, local, base, other, pluginspec=None)
+ self.assertEqual(get_pluginmock.mock_args, (repo, local.name))
+
+ def test_merge_plugin_none(self):
+ ''' Test having get_plugin return no plug-in while one was requested
+ for this filetype.
+ '''
+ base, local, other = self.__get_versions()
+ get_pluginmock = MthdMock(returnval=(None, True))
+ self._set_objattr(hgmerge, 'get_plugin', get_pluginmock)
+ self.assertEqual(hgmerge.merge(self.__repo, local, base, other),
+ 2)
+
+ def test_merge_unmergeable_success(self):
+ ''' Test merging where file doesn't appear mergeable, but user chooses
+ one version.
+ '''
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ ask_unmergeable = MthdMock(returnval=True)
+ self._set_objattr(hgmerge, '_ask_unmergeable', ask_unmergeable)
+ self._set_objattr(hgmerge, '_is_mergeable', MthdMock(returnval=False))
+
+ r = hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ self.assertEqual(r, 0)
+ self.assert_(ask_unmergeable.mock_is_called)
+
+ def test_merge_unmergeable_failure(self):
+ ''' Test merging where file doesn't appear mergeable, and user doesn't
+ choose one version.
+ '''
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ ask_unmergeable = MthdMock(returnval=False)
+ self._set_objattr(hgmerge, '_ask_unmergeable', ask_unmergeable)
+ self._set_objattr(hgmerge, '_is_mergeable', MthdMock(returnval=False))
+
+ r = hgmerge.merge(self.__repo, local, base, other, pluginspec=
+ (interactive, patmatch))
+ self.assertEqual(r, 2)
+
+ def test__is_mergeable(self):
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ self._set_objattr(hgmerge, "_eoltype", MthdMock(returnval='unix'))
+ self.assert_(hgmerge._is_mergeable(interactive, patmatch, base,
+ local, other))
+
+ def test__is_mergeable_false(self):
+ base, local, other = self.__get_versions()
+ interactive, patmatch = self.__create_plug()
+ eoltype = MthdMock()
+ self._set_objattr(hgmerge, '_eoltype', eoltype)
+ for mergeable, unmergeable in ((local, other), (other, local)):
+ eoltype.mock_set_returnvalue_for_args((mergeable,), 'unix')
+ eoltype.mock_set_returnvalue_for_args((unmergeable,), 'binary')
+ self.assertNot(hgmerge._is_mergeable(interactive,
+ patmatch, base, local, other))
+
+ def test__ask_unmergeable_local(self):
+ ''' Test _ask_unmergeable_method, where user picks local version. '''
+ base, local, other = self.__get_versions()
+ ui = self.__ui
+ ui.prompt.mock_set_returnvalue('k')
+ r = hgmerge._ask_unmergeable(self.__ui, local, other, local)
+ self.assert_(r)
+
+ def test__ask_unmergeable_other(self):
+ ''' Test _ask_unmergeable_method, where user picks other version. '''
+ base, local, other = self.__get_versions()
+ ui = self.__ui
+ ui.prompt.mock_set_returnvalue('o')
+ r = hgmerge._ask_unmergeable(self.__ui, local, other, local)
+ self.assert_(r)
+
+ def test__ask_unmergeable_fail(self):
+ ''' Test _ask_unmergeable_method, where user fails to pick a version.
+ '''
+ # TODO: Find out how user cancellation is communicated
+# raise NotImplementedError()
+
+ def test__ask_unmergeable_binary(self):
+ ''' Test _ask_unmergeable_method, where one version is binary.
+
+ For coverage.
+ '''
+ base, local, other = self.__get_versions()
+ ui = self.__ui
+ ui.prompt.mock_set_returnvalue('o')
+ self._set_objattr(local, '_eoltp', 'binary')
+ hgmerge._ask_unmergeable(self.__ui, local, other, local)
+
+ def test__ask_unmergeable_other_symlink(self):
+ ''' Test _ask_unmergeable_method, where the other version is a symlink.
+ '''
+ ui = self.__ui
+ ui.prompt.mock_set_returnvalue('o')
+ local, linkdest, other = (self._get_tempfname(), self._get_tempfname(),
+ self._get_tempfname())
+ os.remove(other)
+ os.symlink(linkdest, other)
+
+ local = FakeFileCtx(local, False)
+ other = FakeFileCtx(other, True)
+ r = hgmerge._ask_unmergeable(self.__ui, local, other, local)
+ self.assert_(r)
+ self.assertEqual(os.readlink(local.name), linkdest)
+
+ def test__ask_unmergeable_unknown_eol(self):
+ ''' Test _ask_unmergeable_method, where one version has an unknown EOL
+ type.
+
+ For coverage.
+ '''
+ base, local, other = self.__get_versions()
+ ui = self.__ui
+ ui.prompt.mock_set_returnvalue('o')
+ self._set_objattr(local, '_eoltp', 'unknown')
+ hgmerge._ask_unmergeable(self.__ui, local, other, local)
+
+ def __create_plug(self, result=0):
+ """ Create mock plug.
+ """
+ plug = _FakePlugin(result=result)
+ return plug, False
+
+ def __get_versions(self, basecontent=None, localcontent=None,
+ othercontent=None, base_islink=False, local_islink=False,
+ other_islink=False):
+ """ Create three different file versions for merging. """
+ if basecontent is None:
+ basecontent = "First line\n"
+ if localcontent is None:
+ localcontent = "%sMy change\n" % (basecontent,)
+ if othercontent is None:
+ othercontent = "My change\n%s" % (basecontent,)
+
+ base = self._get_tempfname()
+ f = file(base, "w")
+ try: f.write(basecontent)
+ finally: f.close()
+ local, other = self._get_tempfname(), self._get_tempfname()
+ f = file(local, "w")
+ try: f.write(localcontent)
+ finally: f.close()
+ f = file(other, "w")
+ try: f.write(othercontent)
+ finally: f.close()
+
+ return (FakeFileCtx(base, base_islink), FakeFileCtx(local,
+ local_islink), FakeFileCtx(other, other_islink))
+
+class get_plugin_test(_testcase):
+ ''' Test get_plugin function.
+ '''
+ def setUp(self):
+ _testcase.setUp(self)
+
+ def test_get_plugin(self):
+ ''' Test the get_plugin method. '''
+ self._set_objattr(hgmerge, 'plugins', [_FakePlugin()])
+ repo = FakeRepo()
+ interactive, patmatch = self.__get_plugin(repo=repo)
+ self.assertIs(interactive, hgmerge.plugins[0])
+ self.assertNot(patmatch)
+ # There should be no warnings to the user
+ self.assertNot(repo.ui.warn.mock_is_called)
+
+ def test_get_plugin_none(self):
+ ''' Test get_plugin when no interactive plug-in is available. '''
+ self._set_objattr(hgmerge, 'plugins', [_FakePlugin(detect=
+ False)])
+ self.assertEqual(self.__get_plugin(), (None, False))
+
+ def test_get_plugin_initial(self):
+ ''' Ensure that get_plugin calls _setup_plugs initially. '''
+ def setup_plugs(ui):
+ self._set_objattr(hgmerge, 'plugins', [])
+
+ # Make sure this is not defined
+ self._set_objattr(hgmerge, 'plugins', None)
+ setupmock = MthdMock(func=setup_plugs)
+ self._set_objattr(hgmerge, '_setup_plugs', setupmock)
+ repo = FakeRepo()
+ self.assertIs(self.__get_plugin(repo)[0], None)
+ self.assertEqual(setupmock.mock_args, (repo.ui,))
+
+ def test_get_plugin_hgrc_hgmerge_plugin(self):
+ ''' Make sure hgrc parameter 'default' is respected. '''
+ self._set_objattr(hgmerge, 'plugins', [_FakePlugin(),
+ _FakePlugin(name='test2')])
+ repo = FakeRepo(globalconf={'default': 'test2'})
+ plug, patmatch = self.__get_plugin(repo)
+ self.assertEqual(plug.name, 'test2')
+ self.assertNot(patmatch)
+
+ def test_get_plugin_hgrc_ext(self):
+ ''' Make sure hgrc file pattern matchin is respected. '''
+ self._set_objattr(hgmerge, 'plugins', [_FakePlugin(),
+ _FakePlugin(name='test2')])
+ inter, patmatch = self.__get_plugin(FakeRepo(globalconf=
+ {'*.png': 'test2'}), fname='test.png')
+ self.assertEqual(inter.name, 'test2')
+ self.assert_(patmatch)
+
+ def test_get_plugin_hgrc_ext_missing(self):
+ ''' Test when specified plug-in for a file pattern is missing. '''
+ self._set_objattr(hgmerge, 'plugins', [_FakePlugin(),
+ _FakePlugin(name='test2', detect=False)])
+ ret = self.__get_plugin(FakeRepo(globalconf=
+ {'*.png': 'test2'}), fname='test.png')
+ self.assertEqual(ret, (None, True))
+
+ def test_get_plugin_special(self):
+ ''' Test getting special plug-ins. '''
+ for name in ('takelocal', 'takeother'):
+ repo = FakeRepo(globalconf={'default': name})
+ plug, patmatch = self.__get_plugin(repo)
+ self.assertEqual(plug.name, name)
+
+ def __get_plugin(self, repo=None, fname=None):
+ if repo is None:
+ repo = self._create_repo()
+ return hgmerge.get_plugin(repo, fname=fname)
+
+ def test__setup_plugs_modify(self):
+ ''' Test modifying plug via _setup_plugs. '''
+ args = '$local $base $other'
+ self.__setup_plugs({'test.args': args})
+ self.assertEqual(self.__fakeplug.fake_opts, {'args': args.split(),})
+
+ def test__setup_plugs_define(self):
+ ''' Test defining tool via _setup_plugs. '''
+ self.__setup_plugs({'newplug': ''})
+ # Note that user-defined plug-ins should have priority 1
+ plug = hgmerge.plugins[-1]
+ self.assertEqual(plug.name, 'newplug')
+
+ def test__setup_plugs_define_invalid(self):
+ ''' Test providing invalid definition for tool. '''
+ def check_plugs():
+ ''' Verify that the plug-ins aren't changed. '''
+ self.assertEqual(hgmerge.plugins,
+ mergeplugs.plugins)
+
+ conf = {'newplug.priority': 'invalid'}
+ self.__setup_plugs(conf)
+ check_plugs()
+ conf = {'newplug.stdout': 'invalid'}
+ self.__setup_plugs(conf)
+ check_plugs()
+
+ def test__setup_plugs_priority(self):
+ ''' Test defining plug-in with priority. '''
+ self.__setup_plugs({'newplug.priority': '1'})
+ self.assertEqual(hgmerge.plugins[0].name, 'newplug')
+
+ def test__setup_plugs_priority_modify(self):
+ ''' Test modifying plug-in's priority. '''
+ self.__setup_plugs({'test.priority': '10'})
+ self.assertEqual(hgmerge.plugins[0].priority, 10)
+
+ def test_query_plugins(self):
+ def setup_plugs(ui):
+ self._set_objattr(hgmerge, 'plugins', [
+ _FakePlugin(name='test1', detect=False), _FakePlugin(
+ name='test2')])
+ self._set_objattr(hgmerge, 'plugins', None)
+ self._set_objattr(hgmerge, '_setup_plugs', setup_plugs)
+ self.assertEqual(hgmerge.query_plugins(FakeUi())[0].name, 'test2')
+
+ def test_query_plugins_rescan(self):
+ setupmock = MthdMock()
+ self._set_objattr(hgmerge, 'plugins', [])
+ self._set_objattr(hgmerge, '_setup_plugs', setupmock)
+ hgmerge.query_plugins(FakeUi(), rescan=True)
+ self.assert_(setupmock.mock_is_called)
+
+ def __setup_plugs(self, conf):
+ plug = self.__fakeplug = _FakePlugin('test')
+ self._set_objattr(mergeplugs, 'plugins', [plug])
+ ui = self.__ui = FakeUi(conf)
+ hgmerge._setup_plugs(ui)
+
+class hgmerge_misc_test(_testcase):
+ ''' Test miscellaneous in the hgmerge package.
+ '''
+ def test_stocktools(self):
+ ''' Test the stocktools variable.
+ '''
+ self.assertIs(hgmerge.stocktools, mergeplugs.plugins)
+ self.assert_(isinstance(hgmerge.stocktools, (list, tuple)))
+
+class simplemerge_test(_testcase):
+ ''' Test _simplemerge module. '''
+ def test_simplemerge(self):
+ base = self._create_file('Base\n\n')
+ local = self._create_file('Base\n\nLocal\n')
+ other = self._create_file('Other\n\n')
+ output = self._get_tempfname()
+ r = hgsimplemerge.simplemerge(local, base, other, output, FakeUi())
+ self.assertEqual(r, 0)
+ self.assertEqual(_read_file(output), 'Other\n\nLocal\n')
+
+ def test_simplemerge_conflicts(self):
+ ''' Test simplemerge on conflicting changes. '''
+ base = self._create_file('\n')
+ local = self._create_file('Local\n')
+ other = self._create_file('Other\n')
+ output = self._get_tempfname()
+ r = hgsimplemerge.simplemerge(local, base, other, output, FakeUi())
+ self.assertEqual(r, 1)
+
+ self.assertEqual(_read_file(output), '''<<<<<<< %s
+Local
+=======
+Other
+>>>>>>> %s
+''' % (local, other))
+
+ def _create_file(self, contents='', fname=None):
+ if fname is None:
+ fname = self._get_tempfname()
+ f = file(fname, 'w')
+ try: f.write(contents)
+ finally: f.close()
+ return fname
+
+class toolplugin_test(_testcase):
+ ''' Test toolplugin class.
+ '''
+ def setUp(self):
+ _testcase.setUp(self)
+ self.__ui = FakeUi()
+
+ def test_construct_exe_none(self):
+ ''' Test constructing with None for executable.
+ '''
+ plug = self.__create_plug()
+ self.assertEqual(plug.executable, plug.name.lower())
+
+ def test__detect_in_path(self):
+ plug = self.__create_plug()
+ self._set_objattr(hgutil, 'find_exe', MthdMock(returnval='/test'))
+ self.assertEqual(plug._detect_in_path(), '/test')
+
+ def test__detect_in_reg(self):
+ ''' Test _detect_in_reg. '''
+ try: import mercurial.util_win32 as hgutil_win32
+ except ImportError:
+ # Not available anyway
+ return
+ plug = self.__create_plug(winreg_key='SOFTWARE', winreg_name='test')
+ lookupmock = MthdMock(returnval='/test')
+ self._set_objattr(hgutil_win32, 'lookup_reg', lookupmock)
+ self._set_objattr(os, 'access', MthdMock(returnval=True))
+ self.assertEqual(plug._detect_in_reg(), '/test')
+ self.assertEqual(lookupmock.mock_args, ('SOFTWARE', 'test'))
+
+ def test__detect_in_reg_append(self):
+ ''' Test _detect_in_reg when winreg_append is set. '''
+ try: import mercurial.util_win32 as hgutil_win32
+ except ImportError:
+ # Not available anyway
+ return
+ plug = self.__create_plug(winreg_key='SOFTWARE', winreg_name='test',
+ winreg_append='/test.exe')
+ lookupmock = MthdMock(returnval='/test')
+ self._set_objattr(hgutil_win32, 'lookup_reg', lookupmock)
+ self._set_objattr(os, 'access', MthdMock(returnval=True))
+ self.assertEqual(plug._detect_in_reg(), '/test/test.exe')
+ self.assertEqual(lookupmock.mock_args, ('SOFTWARE', 'test'))
+
+ def test__detect_in_reg_not_available(self):
+ ''' Test _detect_in_reg when the method is not available to us. '''
+ plug = self.__create_plug(winreg_key='SOFTWARE', winreg_name='test',
+ winreg_append='/test.exe')
+ try: import mercurial.util_win32 as hgutil_win32
+ except ImportError:
+ pass
+ else:
+ lookupmock = MthdMock(returnval='/test')
+ lookupmock.mock_set_raises(ImportError)
+ self._set_objattr(hgutil_win32, 'lookup_reg', lookupmock)
+
+ self.assertIs(plug._detect_in_reg(), None)
+
+ def test_detect(self):
+ plug = self.__create_plug()
+ self.__detect(plug, path='/test')
+
+ def test_detect_false(self):
+ plug = self.__create_plug()
+ self.__detect(plug)
+
+ def __detect(self, plug, path=None, reg=None):
+ pathmock = plug._detect_in_path = MthdMock(returnval=path)
+ regmock = plug._detect_in_reg = MthdMock(returnval=reg)
+ r = plug.detect(self.__ui)
+ self.assert_(pathmock.mock_is_called)
+ if path is None:
+ self.assert_(regmock.mock_is_called)
+ if path is not None or reg is not None:
+ self.assert_(r)
+ else:
+ self.assertNot(r)
+
+ def test_merge(self):
+ plug = self.__create_plug()
+ plug.executable = 'test'
+ self.assertEqual(self.__merge(plug), 0)
+ # The standard tool arguments are $output $base $other
+ base, other, output = self.__mergeargs[1:4]
+ self.assertEqual(self.__runmock.mock_args, ([plug.executable] +
+ [output, base, other], self.__ui))
+
+ def test_merge_stdout(self):
+ ''' Test merging to stdout.
+ '''
+ plug = self.__create_plug(stdout=True)
+ self.__merge(plug)
+ ofile = self.__runmock.mock_kwds['stdout']
+ self.assertEqual(ofile.name, self.__mergeargs[-1])
+ try: os.remove(ofile.name)
+ except EnvironmentError: pass
+
+ def test_merge_check_conflicts(self):
+ ''' Test checking for conflicts after merge.
+ '''
+ plug = self.__create_plug(check_conflicts=True)
+ self.__merge(plug)
+ self.assertEqual(self.__check_conflictsmock.mock_args[0],
+ self.__mergeargs[-1])
+
+ def test__lookup_reg(self):
+ try:
+ import mercurial.util_win32 as hgutil_win32
+ lookupmock = MthdMock(returnval="testval")
+ self._set_objattr(hgutil_win32, "lookup_reg", lookupmock)
+ except ImportError:
+ return
+ self.assertEqual(mergepluginapi._lookup_reg("\\Software\\Test"), testval)
+
+ def __merge(self, plug, retval=0, conflict=False):
+ self.__runmock = MthdMock(returnval=retval)
+ self._set_objattr(mergepluginapi, 'runcommand', self.__runmock)
+ self.__check_conflictsmock = MthdMock(returnval=conflict)
+ self._set_objattr(mergepluginapi, 'checkconflicts',
+ self.__check_conflictsmock)
+ args = self.__mergeargs = ['local', 'base', 'other', 'output']
+ return plug.merge(*(args + [self.__ui]))
+
+ def __create_plug(self, executable=None, args=None, **kwds):
+ plug = mergeplugs.toolplugin('Test', executable, args, **kwds)
+ return plug
+
+class _FileMock(list):
+ def close(self):
+ pass
+
+class misc_test(_testcase):
+ ''' Test miscellaneous.
+ '''
+ def test__checkconflicts(self):
+ txt = '''Testing
+<<<<<<<
+=======
+>>>>>>>
+'''
+ self.__check_conflicts(txt, True)
+
+ def test__checkconflicts_false(self):
+ ''' Test _checkconflicts with no conflicts. '''
+ txt = '''No
+conflicts
+here
+'''
+ self.__check_conflicts(txt, False)
+
+ def __check_conflicts(self, txt, conflicts):
+ filemock = _FileMock(txt.splitlines())
+ filemock.close = MthdMock()
+ import __builtin__
+ self._set_objattr(__builtin__, 'open', MthdMock(returnval=filemock))
+ self.assertEqual(mergepluginapi.checkconflicts('test'), conflicts)
+
+
+if __name__ == "__main__":
+ # hide the timer
+ import time
+ orig = time.time
+ try:
+ time.time = lambda: 0
+ unittest.main()
+ finally:
+ time.time = orig
diff --git a/tests/test-hgmerge.py.out b/tests/test-hgmerge.py.out
new file mode 100644
--- /dev/null
+++ b/tests/test-hgmerge.py.out
@@ -0,0 +1,5 @@
+.................................................
+----------------------------------------------------------------------
+Ran 49 tests in 0.000s
+
+OK
diff --git a/tests/test-simplemerge.py b/tests/test-simplemerge.py
--- a/tests/test-simplemerge.py
+++ b/tests/test-simplemerge.py
@@ -20,12 +20,8 @@
import imp
import shutil
from mercurial import util
+from mercurial.hgmerge import _simplemerge as simplemerge
-# copy simplemerge to the cwd to avoid creating a .pyc file in the source tree
-shutil.copyfile(os.path.join(os.environ['TESTDIR'], os.path.pardir,
- 'contrib', 'simplemerge'),
- 'simplemerge.py')
-simplemerge = imp.load_source('simplemerge', 'simplemerge.py')
Merge3 = simplemerge.Merge3
CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
More information about the Mercurial-devel
mailing list