diff --git a/.travis.yml b/.travis.yml index 85051a1..458da59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,18 +2,12 @@ language: python python: - 2.7 - - 3.2 - 3.3 - 3.4 env: - VIM_VERSION="74" - VIM_VERSION="mercurial" - # - VIM_VERSION="NEOVIM" - -matrix: - allow_failures: - # TODO(sirver): Neovim does not play well with our testing right now. - - env: VIM_VERSION="NEOVIM" + - VIM_VERSION="NEOVIM" install: # Some of these commands fail transiently. We keep retrying them until they succeed. diff --git a/test/test_ContextSnippets.py b/test/test_ContextSnippets.py index eea4086..fc4fe9d 100644 --- a/test/test_ContextSnippets.py +++ b/test/test_ContextSnippets.py @@ -1,5 +1,5 @@ -from test.vim_test_case import VimTestCase as _VimTest from test.constant import * +from test.vim_test_case import VimTestCase as _VimTest class ContextSnippets_SimpleSnippet(_VimTest): @@ -42,7 +42,6 @@ class ContextSnippets_DoNotExpandOnFalse(_VimTest): wanted = keys - class ContextSnippets_UseContext(_VimTest): files = { 'us/all.snippets': r""" global !p @@ -99,8 +98,8 @@ class ContextSnippets_PriorityKeyword(_VimTest): endsnippet """} - keys = "i" + EX - wanted = "b" + keys = 'i' + EX + wanted = 'b' class ContextSnippets_ReportError(_VimTest): @@ -110,18 +109,21 @@ class ContextSnippets_ReportError(_VimTest): endsnippet """} - keys = "e" + EX - wanted = "e" + EX + keys = 'e' + EX + wanted = 'e' + EX expected_error = r"NameError: name 'Tru' is not defined" class ContextSnippets_ReportErrorOnIndexOutOfRange(_VimTest): + # Working around: https://github.com/neovim/python-client/issues/128. + skip_if = lambda self: 'Bug in Neovim.' \ + if self.vim_flavor == 'neovim' else None files = { 'us/all.snippets': r""" snippet e "desc" "buffer[123]" e error endsnippet """} - keys = "e" + EX - wanted = "e" + EX + keys = 'e' + EX + wanted = 'e' + EX expected_error = r"IndexError: line number out of range" diff --git a/test/test_Fixes.py b/test/test_Fixes.py index febb0e9..5ceef5d 100644 --- a/test/test_Fixes.py +++ b/test/test_Fixes.py @@ -78,7 +78,7 @@ class NonUnicodeDataInUnnamedRegister(_VimTest): # The string below was the one a user had on their clipboard when # encountering the UnicodeDecodeError and could not be coerced into # unicode. - self.vim.send( + self.vim.send_to_vim( ':let @" = "\\x80kdI{\\x80@7 1},' + '\\x80kh\\x80kh\\x80kd\\x80kdq\\x80kb\\x1b"\n') # End: #171 #}}} diff --git a/test/test_Plugin.py b/test/test_Plugin.py index a2be20c..a93478f 100644 --- a/test/test_Plugin.py +++ b/test/test_Plugin.py @@ -85,7 +85,7 @@ class Plugin_SuperTab_SimpleTest(_VimTest): def _before_test(self): # Make sure that UltiSnips has the keymap - self.vim.send(':call UltiSnips#map_keys#MapKeys()\n') + self.vim.send_to_vim(':call UltiSnips#map_keys#MapKeys()\n') def _extra_vim_config(self, vim_config): assert EX == '\t' # Otherwise this test needs changing. diff --git a/test/test_UltiSnipFunc.py b/test/test_UltiSnipFunc.py index d2aa4d9..6568c54 100644 --- a/test/test_UltiSnipFunc.py +++ b/test/test_UltiSnipFunc.py @@ -10,7 +10,7 @@ class _AddFuncBase(_VimTest): args = '' def _before_test(self): - self.vim.send(':call UltiSnips#AddSnippetWithPriority(%s)\n' % self.args) + self.vim.send_to_vim(':call UltiSnips#AddSnippetWithPriority(%s)\n' % self.args) class AddFunc_Simple(_AddFuncBase): @@ -61,7 +61,7 @@ hi2...hi3 hi4Hello""" def _before_test(self): - self.vim.send(':set langmap=\\\\;;A\n') + self.vim.send_to_vim(':set langmap=\\\\;;A\n') # Test for bug 871357 # @@ -81,7 +81,7 @@ hi2...hi3 hi4""" def _before_test(self): - self.vim.send( + self.vim.send_to_vim( ":set langmap=йq,цw,уe,кr,еt,нy,гu,шi,щo,зp,х[,ъ],фa,ыs,вd,аf,пg,рh,оj,лk,дl,ж\\;,э',яz,чx,сc,мv,иb,тn,ьm,ю.,ё',ЙQ,ЦW,УE,КR,ЕT,НY,ГU,ШI,ЩO,ЗP,Х\{,Ъ\},ФA,ЫS,ВD,АF,ПG,РH,ОJ,ЛK,ДL,Ж\:,Э\",ЯZ,ЧX,СC,МV,ИB,ТN,ЬM,Б\<,Ю\>\n") # End: Langmap Handling #}}} diff --git a/test/vim_interface.py b/test/vim_interface.py index f9046fe..a1334ff 100644 --- a/test/vim_interface.py +++ b/test/vim_interface.py @@ -94,11 +94,16 @@ class VimInterface(TempFileManager): def get_buffer_data(self): buffer_path = self.unique_name_temp(prefix='buffer_') - self.send(ESC + ':w! %s\n' % buffer_path) + self.send_to_vim(ESC + ':w! %s\n' % buffer_path) if wait_until_file_exists(buffer_path, 50): return read_text_file(buffer_path)[:-1] - def send(self, s): + def send_to_terminal(self, s): + """Types 's' into the terminal.""" + raise NotImplementedError() + + def send_to_vim(self, s): + """Types 's' into the vim instance under test.""" raise NotImplementedError() def launch(self, config=[]): @@ -121,13 +126,16 @@ class VimInterface(TempFileManager): config_path = self.write_temp('vim_config.vim', textwrap.dedent(os.linesep.join(config + post_config) + '\n')) - # Note the space to exclude it from shell history. - self.send(""" %s -u %s\r\n""" % (self._vim_executable, config_path)) + # Note the space to exclude it from shell history. Also we always set + # NVIM_LISTEN_ADDRESS, even when running vanilla vim, because it will + # just not care. + self.send_to_terminal(""" NVIM_LISTEN_ADDRESS=/tmp/nvim %s -u %s\r\n""" % ( + self._vim_executable, config_path)) wait_until_file_exists(done_file) self._vim_pid = int(open(pid_file, 'r').read()) def leave_with_wait(self): - self.send(3 * ESC + ':qa!\n') + self.send_to_vim(3 * ESC + ':qa!\n') while is_process_running(self._vim_pid): time.sleep(.05) @@ -139,7 +147,7 @@ class VimInterfaceTmux(VimInterface): self.session = session self._check_version() - def send(self, s): + def _send(self, s): # I did not find any documentation on what needs escaping when sending # to tmux, but it seems like this is all that is needed for now. s = s.replace(';', r'\;') @@ -148,6 +156,12 @@ class VimInterfaceTmux(VimInterface): s = s.encode('utf-8') silent_call(['tmux', 'send-keys', '-t', self.session, '-l', s]) + def send_to_terminal(self, s): + return self._send(s) + + def send_to_vim(self, s): + return self._send(s) + def _check_version(self): stdout, _ = subprocess.Popen(['tmux', '-V'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() @@ -159,6 +173,33 @@ class VimInterfaceTmux(VimInterface): 'Need at least tmux 1.8, you have %s.' % stdout.strip()) +class VimInterfaceTmuxNeovim(VimInterfaceTmux): + + def __init__(self, vim_executable, session): + VimInterfaceTmux.__init__(self, vim_executable, session) + self._nvim = None + + def send_to_vim(self, s): + if s == ARR_L: + s = "" + elif s == ARR_R: + s = "" + elif s == ARR_U: + s = "" + elif s == ARR_D: + s = "" + elif s == BS: + s = "" + elif s == ESC: + s = "" + elif s == "<": + s = "" + self._nvim.input(s) + + def launch(self, config=[]): + import neovim + VimInterfaceTmux.launch(self, config) + self._nvim = neovim.attach('socket', path='/tmp/nvim') class VimInterfaceWindows(VimInterface): BRACES = re.compile('([}{])') diff --git a/test/vim_test_case.py b/test/vim_test_case.py index dea4846..d335c37 100644 --- a/test/vim_test_case.py +++ b/test/vim_test_case.py @@ -9,7 +9,7 @@ import textwrap import time import unittest -from test.constant import PYTHON3 +from test.constant import PYTHON3, SEQUENCES from test.vim_interface import create_directory, TempFileManager @@ -33,6 +33,7 @@ class VimTestCase(unittest.TestCase, TempFileManager): skip_if = lambda self: None version = None # Will be set to vim --version output maxDiff = None # Show all diff output, always. + vim_flavor = None # will be 'vim' or 'neovim'. def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) @@ -78,6 +79,7 @@ class VimTestCase(unittest.TestCase, TempFileManager): os.symlink(source, os.path.join(absdir, os.path.basename(source))) def setUp(self): + # TODO(sirver): this uses 'vim', but must use --vim from the commandline. if not VimTestCase.version: VimTestCase.version, _ = subprocess.Popen(['vim', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() @@ -110,9 +112,13 @@ class VimTestCase(unittest.TestCase, TempFileManager): 'bundle') vim_config.append('execute pathogen#infect()') - # Vim parameters. + # Some configurations are unnecessary for vanilla Vim, but Neovim + # defines some defaults differently. vim_config.append('syntax on') vim_config.append('filetype plugin indent on') + vim_config.append('set nosmarttab') + vim_config.append('set noautoindent') + vim_config.append('set backspace=""') vim_config.append('set clipboard=""') vim_config.append('set encoding=utf-8') vim_config.append('set fileencoding=utf-8') @@ -127,6 +133,10 @@ class VimTestCase(unittest.TestCase, TempFileManager): 'let g:UltiSnipsUsePythonVersion="%i"' % (3 if PYTHON3 else 2)) vim_config.append('let g:UltiSnipsSnippetDirectories=["us"]') + if self.python_host_prog: + vim_config.append('let g:python_host_prog="%s"' % self.python_host_prog) + if self.python3_host_prog: + vim_config.append('let g:python3_host_prog="%s"' % self.python3_host_prog) self._extra_vim_config(vim_config) @@ -170,9 +180,17 @@ class VimTestCase(unittest.TestCase, TempFileManager): if not self.interrupt: # Go into insert mode and type the keys but leave Vim some time to # react. - for c in 'i' + self.keys: - self.vim.send(c) + text = 'i' + self.keys + while text: + to_send = None + for seq in SEQUENCES: + if text.startswith(seq): + to_send = seq + break + to_send = to_send or text[0] + self.vim.send_to_vim(to_send) time.sleep(self.sleeptime) + text = text[len(to_send):] self.output = self.vim.get_buffer_data() def tearDown(self): diff --git a/test_all.py b/test_all.py index 9ace0f4..8842358 100755 --- a/test_all.py +++ b/test_all.py @@ -39,7 +39,8 @@ import os import platform import subprocess import unittest -from test.vim_interface import (create_directory, tempfile, VimInterfaceTmux) +from test.vim_interface import ( + create_directory, tempfile, VimInterfaceTmux, VimInterfaceTmuxNeovim) def plugin_cache_dir(): @@ -100,8 +101,17 @@ if __name__ == '__main__': 'multiplexer and race conditions in writing to the file system.') p.add_option('-x', '--exitfirst', dest='exitfirst', action='store_true', help='exit instantly on first error or failed test.') - p.add_option('--vim', dest='vim', type=str, default="vim", + p.add_option('--vim', dest='vim', type=str, default='vim', help='executable to run when launching vim.') + p.add_option('--interface', dest='interface', type=str, default='tmux', + help="Interface to use. Use 'tmux' with vanilla Vim and 'tmux_nvim' " + 'with Neovim.') + p.add_option('--python-host-prog', dest='python_host_prog', type=str, default='', + # NOCOM(#sirver): what + help="") + p.add_option('--python3-host-prog', dest='python3_host_prog', type=str, default='', + # NOCOM(#sirver): what + help="") o, args = p.parse_args() return o, args @@ -120,7 +130,15 @@ if __name__ == '__main__': all_test_suites = unittest.defaultTestLoader.discover(start_dir='test') - vim = VimInterfaceTmux(options.vim, options.session) + vim = None + vim_flavor = 'vim' + if options.interface == 'tmux': + vim = VimInterfaceTmux(options.vim, options.session) + vim_flavor = 'vim' + else: + vim = VimInterfaceTmuxNeovim(options.vim, options.session) + vim_flavor = 'neovim' + if not options.clone_plugins and platform.system() == 'Windows': raise RuntimeError( 'TODO: TestSuite is broken under windows. Volunteers wanted!.') @@ -136,7 +154,10 @@ if __name__ == '__main__': test.interrupt = options.interrupt test.retries = options.retries test.test_plugins = options.plugins + test.python_host_prog = options.python_host_prog + test.python3_host_prog = options.python3_host_prog test.vim = vim + test.vim_flavor = vim_flavor all_other_plugins.update(test.plugins) if len(selected_tests): @@ -153,7 +174,7 @@ if __name__ == '__main__': v = 2 if options.verbose else 1 successfull = unittest.TextTestRunner(verbosity=v, - failfast=options.exitfirst).run(suite).wasSuccessful() + failfast=options.exitfirst).run(suite).wasSuccessful() return 0 if successfull else 1 sys.exit(main()) diff --git a/travis_install.sh b/travis_install.sh index 2287075..d0db077 100755 --- a/travis_install.sh +++ b/travis_install.sh @@ -56,7 +56,8 @@ build_vanilla_vim () { if [[ $VIM_VERSION = "74" || $VIM_VERSION = "mercurial" ]]; then build_vanilla_vim elif [[ $VIM_VERSION == "NEOVIM" ]]; then - pip install neovim + PIP=$(which pip) + $PIP install neovim else echo "Unknown VIM_VERSION: $VIM_VERSION" exit 1 diff --git a/travis_test.sh b/travis_test.sh index 47fee9f..75b70f3 100755 --- a/travis_test.sh +++ b/travis_test.sh @@ -2,13 +2,29 @@ set -ex -VIM="${HOME}/bin/vim" PYTHON="python${TRAVIS_PYTHON_VERSION}" PYTHON_CMD="$(which ${PYTHON})" -# This is needed so that vim finds the shared libraries it was build against - -# they are not on the regular path. -export LD_LIBRARY_PATH="$($PYTHON-config --prefix)/lib" +if [[ $VIM_VERSION = "74" || $VIM_VERSION = "mercurial" ]]; then + INTERFACE="--interface tmux" + VIM="${HOME}/bin/vim" + # This is needed so that vim finds the shared libraries it was build against - + # they are not on the regular path. + export LD_LIBRARY_PATH="$($PYTHON-config --prefix)/lib" + +elif [[ $VIM_VERSION == "NEOVIM" ]]; then + VIM="$(which nvim)" + if [[ $TRAVIS_PYTHON_VERSION =~ ^2\. ]]; then + INTERFACE="--interface tmux_nvim --python-host-prog=$PYTHON_CMD" + PY_IN_VIM="py" + else + INTERFACE="--interface tmux_nvim --python3-host-prog=$PYTHON_CMD" + PY_IN_VIM="py3" + fi +else + echo "Unknown VIM_VERSION: $VIM_VERSION" + exit 1 +fi if [[ $TRAVIS_PYTHON_VERSION =~ ^2\. ]]; then PY_IN_VIM="py" @@ -19,9 +35,6 @@ fi echo "Using python from: $PYTHON_CMD Version: $($PYTHON_CMD --version 2>&1)" echo "Using vim from: $VIM. Version: $($VIMn)" -printf "${PY_IN_VIM} import sys;print(sys.version);\nquit" | $VIM -e -V9myVimLog -cat myVimLog - tmux new -d -s vim -$PYTHON_CMD ./test_all.py -v --plugins --session vim --vim $VIM +$PYTHON_CMD ./test_all.py -v --plugins $INTERFACE --session vim --vim $VIM