UltiSnips/test/vim_interface.py
Holger Rapp 539c557680
Move travis testing into docker for better consistency. (#950)
This now only tests python2 on Vim 7.4, 8.0 and git. No Neovim (that has been broken for a long time anyways) and no longer any python3 testing. But hey, we have green tests again!
2018-04-01 00:56:34 +02:00

289 lines
9.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
self._version = None
@property
def vim_executable(self):
return self._vim_executable
def has_version(self, major, minor, patchlevel):
if self._version is None:
output = subprocess.check_output([
self._vim_executable, "--version"
])
_major = 0
_minor = 0
_patch = 0
for line in output.decode('utf-8').split("\n"):
if line.startswith("VIM - Vi IMproved"):
_major, _minor = map(int, line.split()[4].split('.'))
if line.startswith("Included patches:"):
_patch = int(line.split('-')[1])
self._version = (_major, _minor, _patch)
return self._version >= (major, minor, patchlevel)
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=[]):
"""Returns the python version in Vim as a string, e.g. '2.7'"""
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, sys')
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:" % done_file)
post_config.append(" done_file.write('%i.%i.%i' % sys.version_info[:3])")
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(read_text_file(pid_file))
return read_text_file(done_file).strip()
def leave_with_wait(self):
self.send_to_vim(3 * ESC + ':qa!\n')
while is_process_running(self._vim_pid):
time.sleep(.2)
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 len(s) == 1:
silent_call(['tmux', 'send-keys', '-t', self.session, hex(ord(s))])
else:
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
rv = VimInterfaceTmux.launch(self, config)
self._nvim = neovim.attach('socket', path='/tmp/nvim')
return rv
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)