Merge pull request #340 from cwahbong/test-fix

Never rely on waiting for Vim to finish something, instead make it write a file.
This commit is contained in:
Holger Rapp 2014-07-23 06:55:17 +02:00
commit 9521b942b0
2 changed files with 116 additions and 75 deletions

View File

@ -1393,6 +1393,7 @@ individuals have contributed to UltiSnips (in chronological order):
Andrew Ruder - aeruder Andrew Ruder - aeruder
Mathias Fußenegger - mfussenegger Mathias Fußenegger - mfussenegger
Kevin Ballard - kballard Kevin Ballard - kballard
Ahbong Chang - cwahbong
Thank you for your support. Thank you for your support.

190
test.py
View File

@ -9,6 +9,9 @@
# In one terminal, launch a GNU ``screen`` session named ``vim``: # In one terminal, launch a GNU ``screen`` session named ``vim``:
# $ screen -S vim # $ screen -S vim
# #
# Or the following if you use ``tmux``:
# $ tmux new -s vim
#
# Now, from another terminal, launch the testsuite: # Now, from another terminal, launch the testsuite:
# $ ./test.py # $ ./test.py
# #
@ -29,10 +32,8 @@
from textwrap import dedent from textwrap import dedent
import os import os
import platform import platform
import random
import re import re
import shutil import shutil
import string
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
@ -133,27 +134,104 @@ def read_text_file(filename):
else: else:
return open(filename,"r").read() return open(filename,"r").read()
def random_string(n): def wait_until_file_exists(file_path, times=None, interval=0.01):
return ''.join(random.choice(string.ascii_lowercase) for x in range(n)) 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
class TempFileManager(object):
"""A TempFileManager keeps a unique prefix path for temp
files. A temp file, or a name for a temp file generate by a
TempFileManager always belongs to the same directory.
"""
def __init__(self, name=""):
"""The unique prefix path is UltiSnipsTest_{name}XXXXXX.
"""
self._temp_dir = tempfile.mkdtemp(prefix="UltiSnipsTest_" + name)
def name_temp(self, file_path):
"""Get the absolute path of a temp file by given file path.
"""
return os.path.join(self._temp_dir, file_path)
def write_temp(self, file_path, content):
"""Write the content to a temp file by given file path inside
the _temp_dir, and return the absolute path of that file.
"""
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=""):
"""Generate a unique name for a temp file with given suffix and
prefix, and return full absolute path.
"""
file_handler, abspath = tempfile.mkstemp(suffix, prefix, self._temp_dir)
os.close(file_handler)
os.remove(abspath)
return abspath
def clear_temp(self):
"""Clear the temp file directory, but the directory itself is
not removed.
"""
shutil.rmtree(self._temp_dir)
create_directory(self._temp_dir)
class VimInterface(TempFileManager):
def __init__(self, name=""):
TempFileManager.__init__(self, name)
class VimInterface(object):
def get_buffer_data(self): def get_buffer_data(self):
handle, fn = tempfile.mkstemp(prefix="UltiSnips_Test",suffix=".txt") buffer_path = self.unique_name_temp(prefix="buffer_")
os.close(handle) self.send(ESC + ":w! %s\n" % buffer_path)
os.unlink(fn) if wait_until_file_exists(buffer_path, 50):
return read_text_file(buffer_path)[:-1]
self.send(ESC + ":w! %s\n" % fn) def send(self, s):
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",
dedent(os.linesep.join(config + post_config) + "\n"))
# Note the space to exclude it from shell history.
self.send(""" vim -u %s\r\n""" % 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")
while is_process_running(self._vim_pid):
time.sleep(.05)
# Read the output, chop the trailing newline
tries = 50
while tries:
if os.path.exists(fn):
return read_text_file(fn)[:-1]
time.sleep(.01)
tries -= 1
class VimInterfaceScreen(VimInterface): class VimInterfaceScreen(VimInterface):
def __init__(self, session): def __init__(self, session):
VimInterface.__init__(self, "Screen")
self.session = session self.session = session
self.need_screen_escapes = 0 self.need_screen_escapes = 0
self.detect_parsing() self.detect_parsing()
@ -178,9 +256,7 @@ class VimInterfaceScreen(VimInterface):
time.sleep(.2) time.sleep(.2)
def detect_parsing(self): def detect_parsing(self):
self.send(""" vim -u NONE\r\n""") # Space to exclude from shell history self.launch()
time.sleep(1)
# Send a string where the interpretation will depend on version of screen # Send a string where the interpretation will depend on version of screen
string = "$TERM" string = "$TERM"
self.send("i" + string + ESC) self.send("i" + string + ESC)
@ -188,10 +264,11 @@ class VimInterfaceScreen(VimInterface):
# If the output doesn't match the input, need to do additional escaping # If the output doesn't match the input, need to do additional escaping
if output != string: if output != string:
self.need_screen_escapes = 1 self.need_screen_escapes = 1
self.send(ESC + ":q!\n") self.leave_with_wait()
class VimInterfaceTmux(VimInterface): class VimInterfaceTmux(VimInterface):
def __init__(self, session): def __init__(self, session):
VimInterface.__init__(self, "Tmux")
self.session = session self.session = session
self._check_version() self._check_version()
@ -210,8 +287,8 @@ class VimInterfaceTmux(VimInterface):
if PYTHON3: if PYTHON3:
stdout = stdout.decode("utf-8") stdout = stdout.decode("utf-8")
m = re.match(r"tmux (\d+).(\d+)", stdout) m = re.match(r"tmux (\d+).(\d+)", stdout)
if not m or not (int(m.group(1)), int(m.group(2))) >= (1, 9): if not m or not (int(m.group(1)), int(m.group(2))) >= (1, 8):
raise RuntimeError("Need at least tmux 1.9, you have %s." % stdout.strip()) raise RuntimeError("Need at least tmux 1.8, you have %s." % stdout.strip())
class VimInterfaceWindows(VimInterface): class VimInterfaceWindows(VimInterface):
BRACES = re.compile("([}{])") BRACES = re.compile("([}{])")
@ -280,20 +357,8 @@ class VimInterfaceWindows(VimInterface):
self.shell.SendKeys(seq) self.shell.SendKeys(seq)
def create_temp_file(prefix, suffix, content):
"""Create a file in a temporary place with the given 'prefix'
and the given 'suffix' containing 'content'. The file is never
deleted. Returns the name of the temporary file."""
with tempfile.NamedTemporaryFile(
prefix=prefix, suffix=suffix, delete=False
) as temporary_file:
if PYTHON3:
s = s.encode("utf-8")
temporary_file.write(content)
temporary_file.close()
return temporary_file.name
class _VimTest(unittest.TestCase): class _VimTest(unittest.TestCase, TempFileManager):
snippets = () snippets = ()
files = {} files = {}
text_before = " --- some text before --- \n\n" text_before = " --- some text before --- \n\n"
@ -308,6 +373,10 @@ class _VimTest(unittest.TestCase):
skip_if = lambda self: None skip_if = lambda self: None
version = None # Will be set to vim --version output version = None # Will be set to vim --version output
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
TempFileManager.__init__(self, "Case")
def runTest(self): def runTest(self):
# Only checks the output. All work is done in setUp(). # Only checks the output. All work is done in setUp().
wanted = self.text_before + self.wanted + self.text_after wanted = self.text_before + self.wanted + self.text_after
@ -324,33 +393,25 @@ class _VimTest(unittest.TestCase):
def _extra_options_pre_init(self, vim_config): def _extra_options_pre_init(self, vim_config):
"""Adds extra lines to the vim_config list.""" """Adds extra lines to the vim_config list."""
pass
def _extra_options_post_init(self, vim_config): def _extra_options_post_init(self, vim_config):
"""Adds extra lines to the vim_config list.""" """Adds extra lines to the vim_config list."""
pass
def _before_test(self): def _before_test(self):
"""Send these keys before the test runs. Used for buffer local """Send these keys before the test runs. Used for buffer local
variables and other options.""" variables and other options."""
return "" pass
def _create_file(self, file_path, content): def _create_file(self, file_path, content):
"""Creates a file in the runtimepath that is created for this test. """Creates a file in the runtimepath that is created for this test.
Returns the absolute path to the file.""" Returns the absolute path to the file."""
abs_path = os.path.join(self._temporary_directory, *file_path.split("/")) return self.write_temp(file_path, dedent(content + "\n"))
create_directory(os.path.dirname(abs_path))
content = dedent(content + "\n")
if PYTHON3:
with open(abs_path, "w", encoding="utf-8") as file_handle:
file_handle.write(content)
else:
with open(abs_path, "w") as file_handle:
file_handle.write(content)
return abs_path
def _link_file(self, source, relative_destination): def _link_file(self, source, relative_destination):
"""Creates a link from 'source' to the 'relative_destination' in our temp dir.""" """Creates a link from 'source' to the 'relative_destination' in our temp dir."""
absdir = os.path.join(self._temporary_directory, relative_destination) absdir = self.name_temp(relative_destination)
create_directory(absdir) create_directory(absdir)
os.symlink(source, os.path.join(absdir, os.path.basename(source))) os.symlink(source, os.path.join(absdir, os.path.basename(source)))
@ -367,11 +428,9 @@ class _VimTest(unittest.TestCase):
if reason_for_skipping is not None: if reason_for_skipping is not None:
return self.skipTest(reason_for_skipping) return self.skipTest(reason_for_skipping)
self._temporary_directory = tempfile.mkdtemp(prefix="UltiSnips_Test")
vim_config = [] vim_config = []
vim_config.append('set nocompatible') vim_config.append('set nocompatible')
vim_config.append('set runtimepath=$VIMRUNTIME,.,%s' % self._temporary_directory) vim_config.append('set runtimepath=$VIMRUNTIME,.,%s' % self._temp_dir)
if self.plugins: if self.plugins:
self._link_file(os.path.join(plugin_cache_dir(), "vim-pathogen", "autoload"), ".") self._link_file(os.path.join(plugin_cache_dir(), "vim-pathogen", "autoload"), ".")
@ -425,29 +484,13 @@ class _VimTest(unittest.TestCase):
vim_config.append("vim.current.buffer[:] = %r\n" % prefilled_text) vim_config.append("vim.current.buffer[:] = %r\n" % prefilled_text)
vim_config.append("vim.current.window.cursor = (max(len(vim.current.buffer)//2, 1), 0)") vim_config.append("vim.current.window.cursor = (max(len(vim.current.buffer)//2, 1), 0)")
# Create a file to signalize to the test runner that we are done with starting Vim.
vim_pid_file = os.path.join(self._temporary_directory, "vim.pid")
done_file = os.path.join(self._temporary_directory, "loading_done")
vim_config.append("with open('%s', 'w') as pid_file: pid_file.write(vim.eval('getpid()'))" %
vim_pid_file)
vim_config.append("with open('%s', 'w') as done_file: pass" % done_file)
# End of python stuff. # End of python stuff.
vim_config.append("EOF") vim_config.append("EOF")
for name, content in self.files.items(): for name, content in self.files.items():
self._create_file(name, content) self._create_file(name, content)
# Now launch Vim. self.vim.launch(vim_config)
self._create_file("vim_config.vim", os.linesep.join(vim_config))
# Note the shell to exclude it from shell history.
self.vim.send(""" vim -u %s\r\n""" % os.path.join(
self._temporary_directory, "vim_config.vim"))
while True:
if os.path.exists(done_file):
self._vim_pid = int(open(vim_pid_file, "r").read())
break
time.sleep(.01)
self._before_test() self._before_test()
@ -461,12 +504,10 @@ class _VimTest(unittest.TestCase):
def tearDown(self): def tearDown(self):
if self.interrupt: if self.interrupt:
print("Working directory: %s" % (self._temporary_directory)) print("Working directory: %s" % (self._temp_dir))
return return
shutil.rmtree(self._temporary_directory) self.vim.leave_with_wait()
self.vim.send(3*ESC + ":qa!\n") self.clear_temp()
while is_process_running(self._vim_pid):
time.sleep(.05)
########################################################################### ###########################################################################
# BEGINNING OF TEST # # BEGINNING OF TEST #
@ -3381,8 +3422,7 @@ class MySnippetSource(SnippetSource):
return [] return []
""") """)
pyfile = 'py3file' if PYTHON3 else 'pyfile' pyfile = 'py3file' if PYTHON3 else 'pyfile'
vim_config.append("%s %s" % (pyfile, os.path.join( vim_config.append("%s %s" % (pyfile, self.name_temp("snippet_source.py")))
self._temporary_directory, "snippet_source.py")))
# End: Snippet Source #}}} # End: Snippet Source #}}}
# Plugin: YouCompleteMe {{{# # Plugin: YouCompleteMe {{{#