2014-01-13 14:08:43 -05:00
|
|
|
# Copyright (C) 2011, 2012 Google Inc.
|
2013-05-19 17:20:13 -04:00
|
|
|
#
|
|
|
|
# This file is part of YouCompleteMe.
|
|
|
|
#
|
|
|
|
# YouCompleteMe is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# YouCompleteMe is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2016-02-27 19:12:24 -05:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
from __future__ import print_function
|
|
|
|
from __future__ import division
|
|
|
|
from __future__ import absolute_import
|
|
|
|
from future import standard_library
|
|
|
|
standard_library.install_aliases()
|
|
|
|
from builtins import * # noqa
|
|
|
|
|
2013-05-19 17:20:13 -04:00
|
|
|
from mock import MagicMock
|
2016-01-20 14:09:11 -05:00
|
|
|
from hamcrest import assert_that, equal_to
|
2015-11-07 20:16:13 -05:00
|
|
|
import re
|
2013-05-19 17:20:13 -04:00
|
|
|
import sys
|
2016-04-24 09:22:52 -04:00
|
|
|
import nose
|
|
|
|
import functools
|
|
|
|
|
|
|
|
from ycmd.utils import ToUnicode
|
2013-05-19 17:20:13 -04:00
|
|
|
|
2015-11-07 20:16:13 -05:00
|
|
|
|
2016-10-08 04:53:17 -04:00
|
|
|
BUFNR_REGEX = re.compile( '^bufnr\(\'(?P<buffer_filename>.+)\', ([01])\)$' )
|
|
|
|
BUFWINNR_REGEX = re.compile( '^bufwinnr\((?P<buffer_number>[0-9]+)\)$' )
|
|
|
|
BWIPEOUT_REGEX = re.compile(
|
|
|
|
'^(?:silent! )bwipeout!? (?P<buffer_number>[0-9]+)$' )
|
|
|
|
GETBUFVAR_REGEX = re.compile(
|
|
|
|
'^getbufvar\((?P<buffer_number>[0-9]+), "&(?P<option>.+)"\)$' )
|
2015-11-07 20:16:13 -05:00
|
|
|
|
2015-09-06 15:07:42 -04:00
|
|
|
# One-and only instance of mocked Vim object. The first 'import vim' that is
|
|
|
|
# executed binds the vim module to the instance of MagicMock that is created,
|
2015-11-07 20:16:13 -05:00
|
|
|
# and subsquent assignments to sys.modules[ 'vim' ] don't retrospectively
|
|
|
|
# update them. The result is that while running the tests, we must assign only
|
|
|
|
# one instance of MagicMock to sys.modules[ 'vim' ] and always return it.
|
2015-09-06 15:07:42 -04:00
|
|
|
#
|
|
|
|
# More explanation is available:
|
|
|
|
# https://github.com/Valloric/YouCompleteMe/pull/1694
|
|
|
|
VIM_MOCK = MagicMock()
|
|
|
|
|
2015-11-07 20:16:13 -05:00
|
|
|
|
2015-11-11 08:18:26 -05:00
|
|
|
def MockGetBufferNumber( buffer_filename ):
|
2016-10-08 04:53:17 -04:00
|
|
|
for vim_buffer in VIM_MOCK.buffers:
|
|
|
|
if vim_buffer.name == buffer_filename:
|
|
|
|
return vim_buffer.number
|
2015-11-11 08:18:26 -05:00
|
|
|
return -1
|
|
|
|
|
|
|
|
|
|
|
|
def MockGetBufferWindowNumber( buffer_number ):
|
2016-10-08 04:53:17 -04:00
|
|
|
for vim_buffer in VIM_MOCK.buffers:
|
|
|
|
if vim_buffer.number == buffer_number and vim_buffer.window:
|
|
|
|
return vim_buffer.window
|
2015-11-11 08:18:26 -05:00
|
|
|
return -1
|
|
|
|
|
|
|
|
|
2016-10-08 04:53:17 -04:00
|
|
|
def MockGetBufferVariable( buffer_number, option ):
|
|
|
|
for vim_buffer in VIM_MOCK.buffers:
|
|
|
|
if vim_buffer.number == buffer_number:
|
|
|
|
if option == 'mod':
|
|
|
|
return vim_buffer.modified
|
|
|
|
if option == 'ft':
|
|
|
|
return vim_buffer.filetype
|
|
|
|
return ''
|
|
|
|
return ''
|
|
|
|
|
|
|
|
|
2015-11-11 08:18:26 -05:00
|
|
|
def MockVimEval( value ):
|
2016-10-08 04:53:17 -04:00
|
|
|
if value == 'g:ycm_min_num_of_chars_for_completion':
|
2015-11-11 08:18:26 -05:00
|
|
|
return 0
|
2016-10-08 04:53:17 -04:00
|
|
|
|
|
|
|
if value == 'g:ycm_server_python_interpreter':
|
2015-11-11 08:18:26 -05:00
|
|
|
return ''
|
2016-10-08 04:53:17 -04:00
|
|
|
|
|
|
|
if value == 'tempname()':
|
2015-11-11 08:18:26 -05:00
|
|
|
return '_TEMP_FILE_'
|
2016-10-08 04:53:17 -04:00
|
|
|
|
|
|
|
if value == '&previewheight':
|
2015-11-11 08:18:26 -05:00
|
|
|
# Default value from Vim
|
|
|
|
return 12
|
|
|
|
|
2016-10-08 04:53:17 -04:00
|
|
|
if value == '&omnifunc':
|
|
|
|
return VIM_MOCK.current.buffer.omnifunc
|
|
|
|
|
|
|
|
if value == '&filetype':
|
|
|
|
return VIM_MOCK.current.buffer.filetype
|
|
|
|
|
2015-11-11 08:18:26 -05:00
|
|
|
match = BUFNR_REGEX.search( value )
|
|
|
|
if match:
|
2016-10-08 04:53:17 -04:00
|
|
|
buffer_filename = match.group( 'buffer_filename' )
|
|
|
|
return MockGetBufferNumber( buffer_filename )
|
2015-11-11 08:18:26 -05:00
|
|
|
|
|
|
|
match = BUFWINNR_REGEX.search( value )
|
|
|
|
if match:
|
2016-10-08 04:53:17 -04:00
|
|
|
buffer_number = int( match.group( 'buffer_number' ) )
|
|
|
|
return MockGetBufferWindowNumber( buffer_number )
|
2015-11-11 08:18:26 -05:00
|
|
|
|
2016-10-08 04:53:17 -04:00
|
|
|
match = GETBUFVAR_REGEX.search( value )
|
|
|
|
if match:
|
|
|
|
buffer_number = int( match.group( 'buffer_number' ) )
|
|
|
|
option = match.group( 'option' )
|
|
|
|
return MockGetBufferVariable( buffer_number, option )
|
|
|
|
|
|
|
|
raise ValueError( 'Unexpected evaluation: {0}'.format( value ) )
|
2015-11-11 08:18:26 -05:00
|
|
|
|
|
|
|
|
|
|
|
def MockWipeoutBuffer( buffer_number ):
|
|
|
|
buffers = VIM_MOCK.buffers
|
|
|
|
|
|
|
|
for index, buffer in enumerate( buffers ):
|
2016-10-08 04:53:17 -04:00
|
|
|
if buffer.number == buffer_number:
|
2015-11-11 08:18:26 -05:00
|
|
|
return buffers.pop( index )
|
|
|
|
|
|
|
|
|
|
|
|
def MockVimCommand( command ):
|
|
|
|
match = BWIPEOUT_REGEX.search( command )
|
|
|
|
if match:
|
|
|
|
return MockWipeoutBuffer( int( match.group( 1 ) ) )
|
|
|
|
|
|
|
|
raise RuntimeError( 'Unexpected command: ' + command )
|
|
|
|
|
|
|
|
|
2016-10-08 04:53:17 -04:00
|
|
|
class VimBuffer( object ):
|
|
|
|
"""An object that looks like a vim.buffer object:
|
|
|
|
- |name| : full path of the buffer;
|
|
|
|
- |number| : buffer number;
|
|
|
|
- |contents|: list of lines representing the buffer contents;
|
|
|
|
- |filetype|: buffer filetype. Empty string if no filetype is set;
|
|
|
|
- |modified|: True if the buffer has unsaved changes, False otherwise;
|
|
|
|
- |window| : number of the buffer window. None if the buffer is hidden;
|
|
|
|
- |omnifunc|: omni completion function used by the buffer."""
|
|
|
|
|
|
|
|
def __init__( self, name,
|
|
|
|
number = 1,
|
|
|
|
contents = [],
|
|
|
|
filetype = '',
|
|
|
|
modified = True,
|
|
|
|
window = None,
|
|
|
|
omnifunc = '' ):
|
|
|
|
self.name = name
|
|
|
|
self.number = number
|
|
|
|
self.contents = contents
|
|
|
|
self.filetype = filetype
|
|
|
|
self.modified = modified
|
|
|
|
self.window = window
|
|
|
|
self.omnifunc = omnifunc
|
|
|
|
|
|
|
|
|
|
|
|
def __getitem__( self, index ):
|
|
|
|
"""Return the bytes for a given line at index |index|."""
|
|
|
|
return self.contents[ index ]
|
|
|
|
|
|
|
|
|
|
|
|
def __len__( self ):
|
|
|
|
return len( self.contents )
|
|
|
|
|
|
|
|
|
|
|
|
def __setitem__( self, key, value ):
|
|
|
|
return self.contents.__setitem__( key, value )
|
|
|
|
|
|
|
|
|
|
|
|
def GetLines( self ):
|
|
|
|
"""Return the contents of the buffer as a list of unicode strings."""
|
|
|
|
return [ ToUnicode( x ) for x in self.contents ]
|
|
|
|
|
|
|
|
|
2013-05-19 17:20:13 -04:00
|
|
|
def MockVimModule():
|
|
|
|
"""The 'vim' module is something that is only present when running inside the
|
2015-09-06 15:07:42 -04:00
|
|
|
Vim Python interpreter, so we replace it with a MagicMock for tests. If you
|
|
|
|
need to add additional mocks to vim module functions, then use 'patch' from
|
|
|
|
mock module, to ensure that the state of the vim mock is returned before the
|
|
|
|
next test. That is:
|
|
|
|
|
|
|
|
from ycm.test_utils import MockVimModule
|
|
|
|
from mock import patch
|
|
|
|
|
|
|
|
# Do this once
|
|
|
|
MockVimModule()
|
|
|
|
|
|
|
|
@patch( 'vim.eval', return_value='test' )
|
|
|
|
@patch( 'vim.command', side_effect=ValueError )
|
|
|
|
def test( vim_command, vim_eval ):
|
|
|
|
# use vim.command via vim_command, e.g.:
|
|
|
|
vim_command.assert_has_calls( ... )
|
|
|
|
|
|
|
|
Failure to use this approach may lead to unexpected failures in other
|
|
|
|
tests."""
|
2013-05-19 17:20:13 -04:00
|
|
|
|
2015-11-07 20:16:13 -05:00
|
|
|
VIM_MOCK.buffers = {}
|
2015-11-11 08:18:26 -05:00
|
|
|
VIM_MOCK.eval = MagicMock( side_effect = MockVimEval )
|
2015-09-06 15:07:42 -04:00
|
|
|
sys.modules[ 'vim' ] = VIM_MOCK
|
2013-06-09 22:00:49 -04:00
|
|
|
|
2015-09-06 15:07:42 -04:00
|
|
|
return VIM_MOCK
|
2016-01-20 14:09:11 -05:00
|
|
|
|
|
|
|
|
|
|
|
class ExtendedMock( MagicMock ):
|
2016-04-24 09:22:52 -04:00
|
|
|
"""An extension to the MagicMock class which adds the ability to check that a
|
|
|
|
callable is called with a precise set of calls in a precise order.
|
|
|
|
|
|
|
|
Example Usage:
|
|
|
|
from ycm.test_utils import ExtendedMock
|
|
|
|
@patch( 'test.testing', new_callable = ExtendedMock, ... )
|
|
|
|
def my_test( test_testing ):
|
|
|
|
...
|
|
|
|
"""
|
2016-01-20 14:09:11 -05:00
|
|
|
|
|
|
|
def assert_has_exact_calls( self, calls, any_order = False ):
|
|
|
|
self.assert_has_calls( calls, any_order )
|
|
|
|
assert_that( self.call_count, equal_to( len( calls ) ) )
|
2016-04-24 09:22:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
def ExpectedFailure( reason, *exception_matchers ):
|
|
|
|
"""Defines a decorator to be attached to tests. This decorator
|
|
|
|
marks the test as being known to fail, e.g. where documenting or exercising
|
|
|
|
known incorrect behaviour.
|
|
|
|
|
|
|
|
The parameters are:
|
|
|
|
- |reason| a textual description of the reason for the known issue. This
|
|
|
|
is used for the skip reason
|
|
|
|
- |exception_matchers| additional arguments are hamcrest matchers to apply
|
|
|
|
to the exception thrown. If the matchers don't match, then the
|
|
|
|
test is marked as error, with the original exception.
|
|
|
|
|
|
|
|
If the test fails (for the correct reason), then it is marked as skipped.
|
|
|
|
If it fails for any other reason, it is marked as failed.
|
|
|
|
If the test passes, then it is also marked as failed."""
|
|
|
|
def decorator( test ):
|
|
|
|
@functools.wraps( test )
|
|
|
|
def Wrapper( *args, **kwargs ):
|
|
|
|
try:
|
|
|
|
test( *args, **kwargs )
|
|
|
|
except Exception as test_exception:
|
|
|
|
# Ensure that we failed for the right reason
|
|
|
|
test_exception_message = ToUnicode( test_exception )
|
|
|
|
try:
|
|
|
|
for matcher in exception_matchers:
|
|
|
|
assert_that( test_exception_message, matcher )
|
|
|
|
except AssertionError:
|
|
|
|
# Failed for the wrong reason!
|
|
|
|
import traceback
|
|
|
|
print( 'Test failed for the wrong reason: ' + traceback.format_exc() )
|
|
|
|
# Real failure reason is the *original* exception, we're only trapping
|
|
|
|
# and ignoring the exception that is expected.
|
|
|
|
raise test_exception
|
|
|
|
|
|
|
|
# Failed for the right reason
|
|
|
|
raise nose.SkipTest( reason )
|
|
|
|
else:
|
|
|
|
raise AssertionError( 'Test was expected to fail: {0}'.format(
|
|
|
|
reason ) )
|
|
|
|
return Wrapper
|
|
|
|
|
|
|
|
return decorator
|