From 306f0ace5f1a19aeaf4d3e5fa7eac43e6a41e036 Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Tue, 14 Jul 2015 21:58:30 +0200 Subject: [PATCH] Add a testing interface that works for Neovim. - Remove support for python 3.2 to reduce number of test cases and because it actually fails with Neovim. It is not a supported version anyways. - Due to Neovim not handling fast typing through the console properly (https://github.com/neovim/neovim/issues/2454), the typing is actually simulated through the Python client. We need to differentiate now if a keystroke is meant for the terminal or for the Vim session. Using neovim.input() introduces additional chances for races since inputs are not buffered but processed right away. This results in more retries for some tests. - Neovim needs more parameters and configuration passed in through the test script. Added command line arguments for these. - Skip an extra test under Neovim due to https://github.com/neovim/python-client/issues/128. --- .travis.yml | 8 +----- test/test_ContextSnippets.py | 18 ++++++------ test/test_Fixes.py | 2 +- test/test_Plugin.py | 2 +- test/test_UltiSnipFunc.py | 6 ++-- test/vim_interface.py | 53 ++++++++++++++++++++++++++++++++---- test/vim_test_case.py | 26 +++++++++++++++--- test_all.py | 29 +++++++++++++++++--- travis_install.sh | 3 +- travis_test.sh | 29 ++++++++++++++------ 10 files changed, 133 insertions(+), 43 deletions(-) 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