306f0ace5f
- 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.
263 lines
7.9 KiB
Python
263 lines
7.9 KiB
Python
# encoding: utf-8
|
||
|
||
import os
|
||
import re
|
||
import shutil
|
||
import subprocess
|
||
import tempfile
|
||
import textwrap
|
||
import time
|
||
|
||
from test.constant import (ARR_D, ARR_L, ARR_R, ARR_U, BS, ESC, PYTHON3,
|
||
SEQUENCES)
|
||
|
||
|
||
def wait_until_file_exists(file_path, times=None, interval=0.01):
|
||
while times is None or times:
|
||
if os.path.exists(file_path):
|
||
return True
|
||
time.sleep(interval)
|
||
if times is not None:
|
||
times -= 1
|
||
return False
|
||
|
||
|
||
def read_text_file(filename):
|
||
"""Reads the contens of a text file."""
|
||
if PYTHON3:
|
||
return open(filename, 'r', encoding='utf-8').read()
|
||
else:
|
||
return open(filename, 'r').read()
|
||
|
||
|
||
def is_process_running(pid):
|
||
"""Returns true if a process with pid is running, false otherwise."""
|
||
# from
|
||
# http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
|
||
try:
|
||
os.kill(pid, 0)
|
||
except OSError:
|
||
return False
|
||
else:
|
||
return True
|
||
|
||
|
||
def silent_call(cmd):
|
||
"""Calls 'cmd' and returns the exit value."""
|
||
return subprocess.call(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
||
|
||
|
||
def create_directory(dirname):
|
||
"""Creates 'dirname' and its parents if it does not exist."""
|
||
try:
|
||
os.makedirs(dirname)
|
||
except OSError:
|
||
pass
|
||
|
||
|
||
class TempFileManager(object):
|
||
|
||
def __init__(self, name=''):
|
||
self._temp_dir = tempfile.mkdtemp(prefix='UltiSnipsTest_' + name)
|
||
|
||
def name_temp(self, file_path):
|
||
return os.path.join(self._temp_dir, file_path)
|
||
|
||
def write_temp(self, file_path, content):
|
||
abs_path = self.name_temp(file_path)
|
||
create_directory(os.path.dirname(abs_path))
|
||
if PYTHON3:
|
||
with open(abs_path, 'w', encoding='utf-8') as f:
|
||
f.write(content)
|
||
else:
|
||
with open(abs_path, 'w') as f:
|
||
f.write(content)
|
||
return abs_path
|
||
|
||
def unique_name_temp(self, suffix='', prefix=''):
|
||
file_handler, abspath = tempfile.mkstemp(
|
||
suffix, prefix, self._temp_dir)
|
||
os.close(file_handler)
|
||
os.remove(abspath)
|
||
return abspath
|
||
|
||
def clear_temp(self):
|
||
shutil.rmtree(self._temp_dir)
|
||
create_directory(self._temp_dir)
|
||
|
||
|
||
class VimInterface(TempFileManager):
|
||
|
||
def __init__(self, vim_executable, name):
|
||
TempFileManager.__init__(self, name)
|
||
self._vim_executable = vim_executable
|
||
|
||
def get_buffer_data(self):
|
||
buffer_path = self.unique_name_temp(prefix='buffer_')
|
||
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_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=[]):
|
||
pid_file = self.name_temp('vim.pid')
|
||
done_file = self.name_temp('loading_done')
|
||
if os.path.exists(done_file):
|
||
os.remove(done_file)
|
||
|
||
post_config = []
|
||
post_config.append('%s << EOF' % ('py3' if PYTHON3 else 'py'))
|
||
post_config.append('import vim')
|
||
post_config.append(
|
||
"with open('%s', 'w') as pid_file: pid_file.write(vim.eval('getpid()'))" %
|
||
pid_file)
|
||
post_config.append(
|
||
"with open('%s', 'w') as done_file: pass" %
|
||
done_file)
|
||
post_config.append('EOF')
|
||
|
||
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. 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_to_vim(3 * ESC + ':qa!\n')
|
||
while is_process_running(self._vim_pid):
|
||
time.sleep(.05)
|
||
|
||
|
||
class VimInterfaceTmux(VimInterface):
|
||
|
||
def __init__(self, vim_executable, session):
|
||
VimInterface.__init__(self, vim_executable, 'Tmux')
|
||
self.session = session
|
||
self._check_version()
|
||
|
||
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'\;')
|
||
|
||
if PYTHON3:
|
||
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()
|
||
if PYTHON3:
|
||
stdout = stdout.decode('utf-8')
|
||
m = re.match(r"tmux (\d+).(\d+)", stdout)
|
||
if not m or not (int(m.group(1)), int(m.group(2))) >= (1, 8):
|
||
raise RuntimeError(
|
||
'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 = "<Left>"
|
||
elif s == ARR_R:
|
||
s = "<Right>"
|
||
elif s == ARR_U:
|
||
s = "<Up>"
|
||
elif s == ARR_D:
|
||
s = "<Down>"
|
||
elif s == BS:
|
||
s = "<bs>"
|
||
elif s == ESC:
|
||
s = "<esc>"
|
||
elif s == "<":
|
||
s = "<lt>"
|
||
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('([}{])')
|
||
WIN_ESCAPES = ['+', '^', '%', '~', '[', ']', '<', '>', '(', ')']
|
||
WIN_REPLACES = [
|
||
(BS, '{BS}'),
|
||
(ARR_L, '{LEFT}'),
|
||
(ARR_R, '{RIGHT}'),
|
||
(ARR_U, '{UP}'),
|
||
(ARR_D, '{DOWN}'),
|
||
('\t', '{TAB}'),
|
||
('\n', '~'),
|
||
(ESC, '{ESC}'),
|
||
|
||
# On my system ` waits for a second keystroke, so `+SPACE = "`". On
|
||
# most systems, `+Space = "` ". I work around this, by sending the host
|
||
# ` as `+_+BS. Awkward, but the only way I found to get this working.
|
||
('`', '`_{BS}'),
|
||
('´', '´_{BS}'),
|
||
('{^}', '{^}_{BS}'),
|
||
]
|
||
|
||
def __init__(self):
|
||
# import windows specific modules
|
||
import win32com.client
|
||
import win32gui
|
||
self.win32gui = win32gui
|
||
self.shell = win32com.client.Dispatch('WScript.Shell')
|
||
|
||
def is_focused(self, title=None):
|
||
cur_title = self.win32gui.GetWindowText(
|
||
self.win32gui.GetForegroundWindow())
|
||
if (title or '- GVIM') in cur_title:
|
||
return True
|
||
return False
|
||
|
||
def focus(self, title=None):
|
||
if not self.shell.AppActivate(title or '- GVIM'):
|
||
raise Exception('Failed to switch to GVim window')
|
||
time.sleep(1)
|
||
|
||
def convert_keys(self, keys):
|
||
keys = self.BRACES.sub(r"{\1}", keys)
|
||
for k in self.WIN_ESCAPES:
|
||
keys = keys.replace(k, '{%s}' % k)
|
||
for f, r in self.WIN_REPLACES:
|
||
keys = keys.replace(f, r)
|
||
return keys
|
||
|
||
def send(self, keys):
|
||
keys = self.convert_keys(keys)
|
||
|
||
if not self.is_focused():
|
||
time.sleep(2)
|
||
self.focus()
|
||
if not self.is_focused():
|
||
# This is the only way I can find to stop test execution
|
||
raise KeyboardInterrupt('Failed to focus GVIM')
|
||
|
||
self.shell.SendKeys(keys)
|