292de25c72
This implements an asynchronous message system using a long-poll request to the server. The server provides an endpoint /receive_messages which blocks until either a timeout occurs or we receive a batch of asynchronous messages. We send this request asynchronously and poll it 4 times a second to see if we have received any messages. The messages may either be simply for display (such as startup progress) or diagnostics, which override the diagnostics returned by OnFileReqdyToParse. In the former case, we simply display the message, accepting that this might be overwritten by any other message (indeed, requiring this), and for the latter we fan out diagnostics to any open buffer for the file in question. Unfortunately, Vim has bugs related to timers when there is something displayed (such as a "confirm" prompt or other), so we suspend background timers when doing subcommands to avoid vim bugs. NOTE: This requires a new version of Vim (detected by the presence of the particular functions used).
1018 lines
36 KiB
Python
1018 lines
36 KiB
Python
# Copyright (C) 2016-2017 YouCompleteMe contributors
|
|
#
|
|
# 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/>.
|
|
|
|
from __future__ import unicode_literals
|
|
from __future__ import print_function
|
|
from __future__ import division
|
|
from __future__ import absolute_import
|
|
# Not installing aliases from python-future; it's unreliable and slow.
|
|
from builtins import * # noqa
|
|
|
|
from ycm.tests.test_utils import ( ExtendedMock, MockVimBuffers, MockVimModule,
|
|
VimBuffer, VimMatch )
|
|
MockVimModule()
|
|
|
|
import os
|
|
import sys
|
|
from hamcrest import ( assert_that, contains, empty, equal_to, is_in, is_not,
|
|
matches_regexp )
|
|
from mock import call, MagicMock, patch
|
|
|
|
from ycm.paths import _PathToPythonUsedDuringBuild
|
|
from ycm.youcompleteme import YouCompleteMe
|
|
from ycm.tests import ( MakeUserOptions, StopServer, test_utils,
|
|
WaitUntilReady, YouCompleteMeInstance )
|
|
from ycm.client.base_request import _LoadExtraConfFile
|
|
from ycmd.responses import ServerError
|
|
from ycm.tests.mock_utils import ( MockAsyncServerResponseDone,
|
|
MockAsyncServerResponseInProgress,
|
|
MockAsyncServerResponseException )
|
|
|
|
|
|
from ycm import buffer as ycm_buffer_module
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
def YouCompleteMe_YcmCoreNotImported_test( ycm ):
|
|
assert_that( 'ycm_core', is_not( is_in( sys.modules ) ) )
|
|
|
|
|
|
@patch( 'ycm.vimsupport.PostVimMessage' )
|
|
def YouCompleteMe_InvalidPythonInterpreterPath_test( post_vim_message ):
|
|
try:
|
|
with patch( 'ycm.tests.test_utils.server_python_interpreter',
|
|
'/invalid/path/to/python' ):
|
|
ycm = YouCompleteMe( MakeUserOptions() )
|
|
assert_that( ycm.IsServerAlive(), equal_to( False ) )
|
|
post_vim_message.assert_called_once_with(
|
|
"Unable to start the ycmd server. "
|
|
"Path in 'g:ycm_server_python_interpreter' option does not point "
|
|
"to a valid Python 2.7 or 3.4+. "
|
|
"Correct the error then restart the server with ':YcmRestartServer'." )
|
|
|
|
post_vim_message.reset_mock()
|
|
|
|
with patch( 'ycm.tests.test_utils.server_python_interpreter',
|
|
_PathToPythonUsedDuringBuild() ):
|
|
ycm.RestartServer()
|
|
|
|
assert_that( ycm.IsServerAlive(), equal_to( True ) )
|
|
post_vim_message.assert_called_once_with( 'Restarting ycmd server...' )
|
|
finally:
|
|
WaitUntilReady()
|
|
StopServer( ycm )
|
|
|
|
|
|
@patch( 'ycmd.utils.PathToFirstExistingExecutable', return_value = None )
|
|
@patch( 'ycm.paths._EndsWithPython', return_value = False )
|
|
@patch( 'ycm.vimsupport.PostVimMessage' )
|
|
def YouCompleteMe_NoPythonInterpreterFound_test( post_vim_message, *args ):
|
|
try:
|
|
with patch( 'ycmd.utils.ReadFile', side_effect = IOError ):
|
|
|
|
ycm = YouCompleteMe( MakeUserOptions() )
|
|
assert_that( ycm.IsServerAlive(), equal_to( False ) )
|
|
post_vim_message.assert_called_once_with(
|
|
"Unable to start the ycmd server. Cannot find Python 2.7 or 3.4+. "
|
|
"Set the 'g:ycm_server_python_interpreter' option to a Python "
|
|
"interpreter path. "
|
|
"Correct the error then restart the server with ':YcmRestartServer'." )
|
|
|
|
post_vim_message.reset_mock()
|
|
|
|
with patch( 'ycm.tests.test_utils.server_python_interpreter',
|
|
_PathToPythonUsedDuringBuild() ):
|
|
ycm.RestartServer()
|
|
|
|
assert_that( ycm.IsServerAlive(), equal_to( True ) )
|
|
post_vim_message.assert_called_once_with( 'Restarting ycmd server...' )
|
|
finally:
|
|
WaitUntilReady()
|
|
StopServer( ycm )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
def RunNotifyUserIfServerCrashed( ycm, test, post_vim_message ):
|
|
StopServer( ycm )
|
|
|
|
ycm._logger = MagicMock( autospec = True )
|
|
ycm._server_popen = MagicMock( autospec = True )
|
|
ycm._server_popen.poll.return_value = test[ 'return_code' ]
|
|
|
|
ycm.OnFileReadyToParse()
|
|
|
|
assert_that( ycm._logger.error.call_args[ 0 ][ 0 ],
|
|
test[ 'expected_message' ] )
|
|
assert_that( post_vim_message.call_args[ 0 ][ 0 ],
|
|
test[ 'expected_message' ] )
|
|
|
|
|
|
def YouCompleteMe_NotifyUserIfServerCrashed_UnexpectedCore_test():
|
|
message = ( "The ycmd server SHUT DOWN \(restart with ':YcmRestartServer'\). "
|
|
"Unexpected error while loading the YCM core library. Type "
|
|
"':YcmToggleLogs ycmd_\d+_stderr_.+.log' to check the logs." )
|
|
RunNotifyUserIfServerCrashed( {
|
|
'return_code': 3,
|
|
'expected_message': matches_regexp( message )
|
|
} )
|
|
|
|
|
|
def YouCompleteMe_NotifyUserIfServerCrashed_MissingCore_test():
|
|
message = ( "The ycmd server SHUT DOWN (restart with ':YcmRestartServer'). "
|
|
"YCM core library not detected; you need to compile YCM before "
|
|
"using it. Follow the instructions in the documentation." )
|
|
RunNotifyUserIfServerCrashed( {
|
|
'return_code': 4,
|
|
'expected_message': equal_to( message )
|
|
} )
|
|
|
|
|
|
def YouCompleteMe_NotifyUserIfServerCrashed_Python2Core_test():
|
|
message = ( "The ycmd server SHUT DOWN (restart with ':YcmRestartServer'). "
|
|
"YCM core library compiled for Python 2 but loaded in Python 3. "
|
|
"Set the 'g:ycm_server_python_interpreter' option to a Python 2 "
|
|
"interpreter path." )
|
|
RunNotifyUserIfServerCrashed( {
|
|
'return_code': 5,
|
|
'expected_message': equal_to( message )
|
|
} )
|
|
|
|
|
|
def YouCompleteMe_NotifyUserIfServerCrashed_Python3Core_test():
|
|
message = ( "The ycmd server SHUT DOWN (restart with ':YcmRestartServer'). "
|
|
"YCM core library compiled for Python 3 but loaded in Python 2. "
|
|
"Set the 'g:ycm_server_python_interpreter' option to a Python 3 "
|
|
"interpreter path." )
|
|
RunNotifyUserIfServerCrashed( {
|
|
'return_code': 6,
|
|
'expected_message': equal_to( message )
|
|
} )
|
|
|
|
|
|
def YouCompleteMe_NotifyUserIfServerCrashed_OutdatedCore_test():
|
|
message = ( "The ycmd server SHUT DOWN (restart with ':YcmRestartServer'). "
|
|
"YCM core library too old; PLEASE RECOMPILE by running the "
|
|
"install.py script. See the documentation for more details." )
|
|
RunNotifyUserIfServerCrashed( {
|
|
'return_code': 7,
|
|
'expected_message': equal_to( message )
|
|
} )
|
|
|
|
|
|
def YouCompleteMe_NotifyUserIfServerCrashed_UnexpectedExitCode_test():
|
|
message = ( "The ycmd server SHUT DOWN \(restart with ':YcmRestartServer'\). "
|
|
"Unexpected exit code 1. Type "
|
|
"':YcmToggleLogs ycmd_\d+_stderr_.+.log' to check the logs." )
|
|
RunNotifyUserIfServerCrashed( {
|
|
'return_code': 1,
|
|
'expected_message': matches_regexp( message )
|
|
} )
|
|
|
|
|
|
@YouCompleteMeInstance( { 'extra_conf_vim_data': [ 'tempname()' ] } )
|
|
def YouCompleteMe_DebugInfo_ServerRunning_test( ycm ):
|
|
dir_of_script = os.path.dirname( os.path.abspath( __file__ ) )
|
|
buf_name = os.path.join( dir_of_script, 'testdata', 'test.cpp' )
|
|
extra_conf = os.path.join( dir_of_script, 'testdata', '.ycm_extra_conf.py' )
|
|
_LoadExtraConfFile( extra_conf )
|
|
|
|
current_buffer = VimBuffer( buf_name, filetype='cpp' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
assert_that(
|
|
ycm.DebugInfo(),
|
|
matches_regexp(
|
|
'Client logfile: .+\n'
|
|
'Server Python interpreter: .+\n'
|
|
'Server Python version: .+\n'
|
|
'Server has Clang support compiled in: '
|
|
'(?P<CLANG>True)?(?(CLANG)|False)\n'
|
|
'Clang version: .+\n'
|
|
'Extra configuration file found and loaded\n'
|
|
'Extra configuration path: .*testdata[/\\\\]\\.ycm_extra_conf\\.py\n'
|
|
'(?(CLANG)C-family completer debug information:\n'
|
|
' Compilation database path: None\n'
|
|
' Flags: \\[\'_TEMP_FILE_\'.*\\]\n)'
|
|
'Server running at: .+\n'
|
|
'Server process ID: \d+\n'
|
|
'Server logfiles:\n'
|
|
' .+\n'
|
|
' .+' )
|
|
)
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
def YouCompleteMe_DebugInfo_ServerNotRunning_test( ycm ):
|
|
StopServer( ycm )
|
|
|
|
current_buffer = VimBuffer( 'current_buffer' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
assert_that(
|
|
ycm.DebugInfo(),
|
|
matches_regexp(
|
|
'Client logfile: .+\n'
|
|
'Server errored, no debug info from server\n'
|
|
'Server running at: .+\n'
|
|
'Server process ID: \d+\n'
|
|
'Server logfiles:\n'
|
|
' .+\n'
|
|
' .+' )
|
|
)
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
def YouCompleteMe_OnVimLeave_RemoveClientLogfileByDefault_test( ycm ):
|
|
client_logfile = ycm._client_logfile
|
|
assert_that( os.path.isfile( client_logfile ),
|
|
'Logfile {0} does not exist.'.format( client_logfile ) )
|
|
ycm.OnVimLeave()
|
|
assert_that( not os.path.isfile( client_logfile ),
|
|
'Logfile {0} was not removed.'.format( client_logfile ) )
|
|
|
|
|
|
@YouCompleteMeInstance( { 'keep_logfiles': 1 } )
|
|
def YouCompleteMe_OnVimLeave_KeepClientLogfile_test( ycm ):
|
|
client_logfile = ycm._client_logfile
|
|
assert_that( os.path.isfile( client_logfile ),
|
|
'Logfile {0} does not exist.'.format( client_logfile ) )
|
|
ycm.OnVimLeave()
|
|
assert_that( os.path.isfile( client_logfile ),
|
|
'Logfile {0} was removed.'.format( client_logfile ) )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch( 'ycm.vimsupport.CloseBuffersForFilename', new_callable = ExtendedMock )
|
|
@patch( 'ycm.vimsupport.OpenFilename', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ToggleLogs_WithParameters_test( ycm,
|
|
open_filename,
|
|
close_buffers_for_filename ):
|
|
logfile_buffer = VimBuffer( ycm._client_logfile, window = 1 )
|
|
with MockVimBuffers( [ logfile_buffer ], logfile_buffer ):
|
|
ycm.ToggleLogs( os.path.basename( ycm._client_logfile ),
|
|
'nonexisting_logfile',
|
|
os.path.basename( ycm._server_stdout ) )
|
|
|
|
open_filename.assert_has_exact_calls( [
|
|
call( ycm._server_stdout, { 'size': 12,
|
|
'watch': True,
|
|
'fix': True,
|
|
'focus': False,
|
|
'position': 'end' } )
|
|
] )
|
|
close_buffers_for_filename.assert_has_exact_calls( [
|
|
call( ycm._client_logfile )
|
|
] )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
# Select the second item of the list which is the ycmd stderr logfile.
|
|
@patch( 'ycm.vimsupport.SelectFromList', return_value = 1 )
|
|
@patch( 'ycm.vimsupport.OpenFilename', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ToggleLogs_WithoutParameters_SelectLogfileNotAlreadyOpen_test(
|
|
ycm, open_filename, *args ):
|
|
|
|
current_buffer = VimBuffer( 'current_buffer' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
ycm.ToggleLogs()
|
|
|
|
open_filename.assert_has_exact_calls( [
|
|
call( ycm._server_stderr, { 'size': 12,
|
|
'watch': True,
|
|
'fix': True,
|
|
'focus': False,
|
|
'position': 'end' } )
|
|
] )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
# Select the third item of the list which is the ycmd stdout logfile.
|
|
@patch( 'ycm.vimsupport.SelectFromList', return_value = 2 )
|
|
@patch( 'ycm.vimsupport.CloseBuffersForFilename', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ToggleLogs_WithoutParameters_SelectLogfileAlreadyOpen_test(
|
|
ycm, close_buffers_for_filename, *args ):
|
|
|
|
logfile_buffer = VimBuffer( ycm._server_stdout, window = 1 )
|
|
with MockVimBuffers( [ logfile_buffer ], logfile_buffer ):
|
|
ycm.ToggleLogs()
|
|
|
|
close_buffers_for_filename.assert_has_exact_calls( [
|
|
call( ycm._server_stdout )
|
|
] )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch( 'ycm.vimsupport.SelectFromList',
|
|
side_effect = RuntimeError( 'Error message' ) )
|
|
@patch( 'ycm.vimsupport.PostVimMessage' )
|
|
def YouCompleteMe_ToggleLogs_WithoutParameters_NoSelection_test(
|
|
ycm, post_vim_message, *args ):
|
|
|
|
current_buffer = VimBuffer( 'current_buffer' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
ycm.ToggleLogs()
|
|
|
|
assert_that(
|
|
# Argument passed to PostVimMessage.
|
|
post_vim_message.call_args[ 0 ][ 0 ],
|
|
equal_to( 'Error message' )
|
|
)
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
def YouCompleteMe_GetDefinedSubcommands_ListFromServer_test( ycm ):
|
|
current_buffer = VimBuffer( 'buffer' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
with patch( 'ycm.client.base_request.JsonFromFuture',
|
|
return_value = [ 'SomeCommand', 'AnotherCommand' ] ):
|
|
assert_that(
|
|
ycm.GetDefinedSubcommands(),
|
|
contains(
|
|
'SomeCommand',
|
|
'AnotherCommand'
|
|
)
|
|
)
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch( 'ycm.client.base_request._logger', autospec = True )
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
def YouCompleteMe_GetDefinedSubcommands_ErrorFromServer_test( ycm,
|
|
post_vim_message,
|
|
logger ):
|
|
current_buffer = VimBuffer( 'buffer' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
with patch( 'ycm.client.base_request.JsonFromFuture',
|
|
side_effect = ServerError( 'Server error' ) ):
|
|
result = ycm.GetDefinedSubcommands()
|
|
|
|
logger.exception.assert_called_with( 'Error while handling server response' )
|
|
post_vim_message.assert_has_exact_calls( [
|
|
call( 'Server error', truncate = False )
|
|
] )
|
|
assert_that( result, empty() )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ShowDetailedDiagnostic_MessageFromServer_test(
|
|
ycm, post_vim_message ):
|
|
|
|
current_buffer = VimBuffer( 'buffer' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
with patch( 'ycm.client.base_request.JsonFromFuture',
|
|
return_value = { 'message': 'some_detailed_diagnostic' } ):
|
|
ycm.ShowDetailedDiagnostic(),
|
|
|
|
post_vim_message.assert_has_exact_calls( [
|
|
call( 'some_detailed_diagnostic', warning = False )
|
|
] )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ShowDiagnostics_FiletypeNotSupported_test( ycm,
|
|
post_vim_message ):
|
|
|
|
current_buffer = VimBuffer( 'buffer', filetype = 'not_supported' )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
ycm.ShowDiagnostics()
|
|
|
|
post_vim_message.assert_called_once_with(
|
|
'Native filetype completion not supported for current file, '
|
|
'cannot force recompilation.', warning = False )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
@patch( 'ycm.vimsupport.SetLocationListForWindow', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
|
|
ycm, set_location_list_for_window, post_vim_message, *args ):
|
|
|
|
current_buffer = VimBuffer( 'buffer', filetype = 'cpp', window = 99 )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
with patch( 'ycm.client.event_notification.EventNotification.Response',
|
|
return_value = {} ):
|
|
ycm.ShowDiagnostics()
|
|
|
|
post_vim_message.assert_has_exact_calls( [
|
|
call( 'Forcing compilation, this will block Vim until done.',
|
|
warning = False ),
|
|
call( 'Diagnostics refreshed', warning = False ),
|
|
call( 'No warnings or errors detected.', warning = False )
|
|
] )
|
|
set_location_list_for_window.assert_called_once_with( 0, [] )
|
|
|
|
|
|
@YouCompleteMeInstance( { 'log_level': 'debug',
|
|
'keep_logfiles': 1,
|
|
'open_loclist_on_ycm_diags': 0 } )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
@patch( 'ycm.vimsupport.SetLocationListForWindow', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
|
|
ycm, set_location_list_for_window, post_vim_message, *args ):
|
|
|
|
diagnostic = {
|
|
'kind': 'ERROR',
|
|
'text': 'error text',
|
|
'location': {
|
|
'filepath': 'buffer',
|
|
'line_num': 19,
|
|
'column_num': 2
|
|
}
|
|
}
|
|
|
|
current_buffer = VimBuffer( 'buffer',
|
|
filetype = 'cpp',
|
|
number = 3,
|
|
window = 99 )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
with patch( 'ycm.client.event_notification.EventNotification.Response',
|
|
return_value = [ diagnostic ] ):
|
|
ycm.ShowDiagnostics()
|
|
|
|
post_vim_message.assert_has_exact_calls( [
|
|
call( 'Forcing compilation, this will block Vim until done.',
|
|
warning = False ),
|
|
call( 'Diagnostics refreshed', warning = False )
|
|
] )
|
|
set_location_list_for_window.assert_called_once_with( 0, [ {
|
|
'bufnr': 3,
|
|
'lnum': 19,
|
|
'col': 2,
|
|
'text': 'error text',
|
|
'type': 'E',
|
|
'valid': 1
|
|
} ] )
|
|
|
|
|
|
@YouCompleteMeInstance( { 'open_loclist_on_ycm_diags': 1 } )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
@patch( 'ycm.vimsupport.SetLocationListForWindow', new_callable = ExtendedMock )
|
|
@patch( 'ycm.vimsupport.OpenLocationList', new_callable = ExtendedMock )
|
|
def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
|
|
ycm,
|
|
open_location_list,
|
|
set_location_list_for_window,
|
|
post_vim_message,
|
|
*args ):
|
|
|
|
diagnostic = {
|
|
'kind': 'ERROR',
|
|
'text': 'error text',
|
|
'location': {
|
|
'filepath': 'buffer',
|
|
'line_num': 19,
|
|
'column_num': 2
|
|
}
|
|
}
|
|
|
|
current_buffer = VimBuffer( 'buffer',
|
|
filetype = 'cpp',
|
|
number = 3,
|
|
window = 99 )
|
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
|
with patch( 'ycm.client.event_notification.EventNotification.Response',
|
|
return_value = [ diagnostic ] ):
|
|
ycm.ShowDiagnostics()
|
|
|
|
post_vim_message.assert_has_exact_calls( [
|
|
call( 'Forcing compilation, this will block Vim until done.',
|
|
warning = False ),
|
|
call( 'Diagnostics refreshed', warning = False )
|
|
] )
|
|
set_location_list_for_window.assert_called_once_with( 0, [ {
|
|
'bufnr': 3,
|
|
'lnum': 19,
|
|
'col': 2,
|
|
'text': 'error text',
|
|
'type': 'E',
|
|
'valid': 1
|
|
} ] )
|
|
open_location_list.assert_called_once_with( focus = True )
|
|
|
|
|
|
@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
|
|
'enable_diagnostic_signs': 1,
|
|
'enable_diagnostic_highlighting': 1 } )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
@patch( 'vim.command', new_callable = ExtendedMock )
|
|
def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
|
|
ycm, vim_command, post_vim_message, *args ):
|
|
|
|
contents = """int main() {
|
|
int x, y;
|
|
x == y
|
|
}"""
|
|
|
|
# List of diagnostics returned by ycmd for the above code.
|
|
diagnostics = [ {
|
|
'kind': 'ERROR',
|
|
'text': "expected ';' after expression",
|
|
'location': {
|
|
'filepath': 'buffer',
|
|
'line_num': 3,
|
|
'column_num': 9
|
|
},
|
|
# Looks strange but this is really what ycmd is returning.
|
|
'location_extent': {
|
|
'start': {
|
|
'filepath': '',
|
|
'line_num': 0,
|
|
'column_num': 0,
|
|
},
|
|
'end': {
|
|
'filepath': '',
|
|
'line_num': 0,
|
|
'column_num': 0,
|
|
}
|
|
},
|
|
'ranges': [],
|
|
'fixit_available': True
|
|
}, {
|
|
'kind': 'WARNING',
|
|
'text': 'equality comparison result unused',
|
|
'location': {
|
|
'filepath': 'buffer',
|
|
'line_num': 3,
|
|
'column_num': 7,
|
|
},
|
|
'location_extent': {
|
|
'start': {
|
|
'filepath': 'buffer',
|
|
'line_num': 3,
|
|
'column_num': 5,
|
|
},
|
|
'end': {
|
|
'filepath': 'buffer',
|
|
'line_num': 3,
|
|
'column_num': 7,
|
|
}
|
|
},
|
|
'ranges': [ {
|
|
'start': {
|
|
'filepath': 'buffer',
|
|
'line_num': 3,
|
|
'column_num': 3,
|
|
},
|
|
'end': {
|
|
'filepath': 'buffer',
|
|
'line_num': 3,
|
|
'column_num': 9,
|
|
}
|
|
} ],
|
|
'fixit_available': True
|
|
} ]
|
|
|
|
current_buffer = VimBuffer( 'buffer',
|
|
filetype = 'c',
|
|
contents = contents.splitlines(),
|
|
number = 5,
|
|
window = 2 )
|
|
|
|
test_utils.VIM_MATCHES = []
|
|
|
|
with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 1 ) ):
|
|
with patch( 'ycm.client.event_notification.EventNotification.Response',
|
|
return_value = diagnostics ):
|
|
ycm.OnFileReadyToParse()
|
|
ycm.HandleFileParseRequest( block = True )
|
|
|
|
# The error on the current line is echoed, not the warning.
|
|
post_vim_message.assert_called_once_with(
|
|
"expected ';' after expression (FixIt)",
|
|
truncate = True, warning = False )
|
|
|
|
# Error match is added after warning matches.
|
|
assert_that(
|
|
test_utils.VIM_MATCHES,
|
|
contains(
|
|
VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
|
|
VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' ),
|
|
VimMatch( 'YcmErrorSection', '\%3l\%8c' )
|
|
)
|
|
)
|
|
|
|
# Only the error sign is placed.
|
|
vim_command.assert_has_exact_calls( [
|
|
call( 'sign place 1 name=YcmError line=3 buffer=5' ),
|
|
] )
|
|
|
|
# The error is not echoed again when moving the cursor along the line.
|
|
with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 2 ) ):
|
|
post_vim_message.reset_mock()
|
|
ycm.OnCursorMoved()
|
|
post_vim_message.assert_not_called()
|
|
|
|
# The error is cleared when moving the cursor to another line.
|
|
with MockVimBuffers( [ current_buffer ], current_buffer, ( 2, 2 ) ):
|
|
post_vim_message.reset_mock()
|
|
ycm.OnCursorMoved()
|
|
post_vim_message.assert_called_once_with( "", warning = False )
|
|
|
|
# The error is echoed when moving the cursor back.
|
|
with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 2 ) ):
|
|
post_vim_message.reset_mock()
|
|
ycm.OnCursorMoved()
|
|
post_vim_message.assert_called_once_with(
|
|
"expected ';' after expression (FixIt)",
|
|
truncate = True, warning = False )
|
|
|
|
vim_command.reset_mock()
|
|
with patch( 'ycm.client.event_notification.EventNotification.Response',
|
|
return_value = diagnostics[ 1 : ] ):
|
|
ycm.OnFileReadyToParse()
|
|
ycm.HandleFileParseRequest( block = True )
|
|
|
|
assert_that(
|
|
test_utils.VIM_MATCHES,
|
|
contains(
|
|
VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
|
|
VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' )
|
|
)
|
|
)
|
|
|
|
vim_command.assert_has_exact_calls( [
|
|
call( 'sign place 2 name=YcmWarning line=3 buffer=5' ),
|
|
call( 'try | exec "sign unplace 1 buffer=5" | catch /E158/ | endtry' )
|
|
] )
|
|
|
|
|
|
@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
|
|
'always_populate_location_list': 1 } )
|
|
@patch.object( ycm_buffer_module,
|
|
'DIAGNOSTIC_UI_ASYNC_FILETYPES',
|
|
[ 'ycmtest' ] )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
def YouCompleteMe_AsyncDiagnosticUpdate_SingleFile_test( ycm,
|
|
post_vim_message,
|
|
*args ):
|
|
|
|
# This test simulates asynchronous diagnostic updates associated with a single
|
|
# file (e.g. Translation Unit), but where the actual errors refer to other
|
|
# open files and other non-open files. This is not strictly invalid, nor is it
|
|
# completely normal, but it is supported and does work.
|
|
|
|
# Contrast with the next test which sends the diagnostics filewise, which is
|
|
# what the language server protocol will do.
|
|
|
|
diagnostics = [
|
|
{
|
|
'kind': 'ERROR',
|
|
'text': 'error text in current buffer',
|
|
'location': {
|
|
'filepath': '/current',
|
|
'line_num': 1,
|
|
'column_num': 1
|
|
},
|
|
},
|
|
{
|
|
'kind': 'ERROR',
|
|
'text': 'error text in hidden buffer',
|
|
'location': {
|
|
'filepath': '/has_diags',
|
|
'line_num': 4,
|
|
'column_num': 2
|
|
},
|
|
},
|
|
{
|
|
'kind': 'ERROR',
|
|
'text': 'error text in buffer not open in Vim',
|
|
'location': {
|
|
'filepath': '/not_open',
|
|
'line_num': 8,
|
|
'column_num': 4
|
|
},
|
|
},
|
|
]
|
|
|
|
current_buffer = VimBuffer( '/current',
|
|
filetype = 'ycmtest',
|
|
number = 1,
|
|
window = 10 )
|
|
buffers = [
|
|
current_buffer,
|
|
VimBuffer( '/no_diags',
|
|
filetype = 'ycmtest',
|
|
number = 2,
|
|
window = 9 ),
|
|
VimBuffer( '/has_diags',
|
|
filetype = 'ycmtest',
|
|
number = 3,
|
|
window = 8 ),
|
|
]
|
|
|
|
# Register each buffer internally with YCM
|
|
for current in buffers:
|
|
with MockVimBuffers( buffers, current, ( 1, 1 ) ):
|
|
ycm.OnFileReadyToParse()
|
|
|
|
with patch( 'ycm.vimsupport.SetLocationListForWindow',
|
|
new_callable = ExtendedMock ) as set_location_list_for_window:
|
|
with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
|
|
ycm.UpdateWithNewDiagnosticsForFile( '/current', diagnostics )
|
|
|
|
# We update the diagnostic on the current cursor position
|
|
post_vim_message.assert_has_exact_calls( [
|
|
call( "error text in current buffer", truncate = True, warning = False ),
|
|
] )
|
|
|
|
# Ensure we included all the diags though
|
|
set_location_list_for_window.assert_has_exact_calls( [
|
|
call( 0, [
|
|
{
|
|
'lnum': 1,
|
|
'col': 1,
|
|
'bufnr': 1,
|
|
'valid': 1,
|
|
'type': 'E',
|
|
'text': 'error text in current buffer',
|
|
},
|
|
{
|
|
'lnum': 4,
|
|
'col': 2,
|
|
'bufnr': 3,
|
|
'valid': 1,
|
|
'type': 'E',
|
|
'text': 'error text in hidden buffer',
|
|
},
|
|
{
|
|
'lnum': 8,
|
|
'col': 4,
|
|
'bufnr': -1, # sic: Our mocked bufnr function actually returns -1,
|
|
# even though YCM is passing "create if needed".
|
|
# FIXME? we shouldn't do that, and we should pass
|
|
# filename instead
|
|
'valid': 1,
|
|
'type': 'E',
|
|
'text': 'error text in buffer not open in Vim'
|
|
}
|
|
] )
|
|
] )
|
|
|
|
|
|
@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
|
|
'always_populate_location_list': 1 } )
|
|
@patch.object( ycm_buffer_module,
|
|
'DIAGNOSTIC_UI_ASYNC_FILETYPES',
|
|
[ 'ycmtest' ] )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
|
def YouCompleteMe_AsyncDiagnosticUpdate_PerFile_test( ycm,
|
|
post_vim_message,
|
|
*args ):
|
|
|
|
# This test simulates asynchronous diagnostic updates which are delivered per
|
|
# file, including files which are open and files which are not.
|
|
|
|
# Ordered to ensure that the calls to update are deterministic
|
|
diagnostics_per_file = [
|
|
( '/current', [ {
|
|
'kind': 'ERROR',
|
|
'text': 'error text in current buffer',
|
|
'location': {
|
|
'filepath': '/current',
|
|
'line_num': 1,
|
|
'column_num': 1
|
|
}, }, ] ),
|
|
( '/has_diags', [ {
|
|
'kind': 'ERROR',
|
|
'text': 'error text in hidden buffer',
|
|
'location': {
|
|
'filepath': '/has_diags',
|
|
'line_num': 4,
|
|
'column_num': 2
|
|
}, }, ] ),
|
|
( '/not_open', [ {
|
|
'kind': 'ERROR',
|
|
'text': 'error text in buffer not open in Vim',
|
|
'location': {
|
|
'filepath': '/not_open',
|
|
'line_num': 8,
|
|
'column_num': 4
|
|
}, }, ] )
|
|
]
|
|
|
|
current_buffer = VimBuffer( '/current',
|
|
filetype = 'ycmtest',
|
|
number = 1,
|
|
window = 10 )
|
|
buffers = [
|
|
current_buffer,
|
|
VimBuffer( '/no_diags',
|
|
filetype = 'ycmtest',
|
|
number = 2,
|
|
window = 9 ),
|
|
VimBuffer( '/has_diags',
|
|
filetype = 'ycmtest',
|
|
number = 3,
|
|
window = 8 ),
|
|
]
|
|
|
|
# Register each buffer internally with YCM
|
|
for current in buffers:
|
|
with MockVimBuffers( buffers, current, ( 1, 1 ) ):
|
|
ycm.OnFileReadyToParse()
|
|
|
|
with patch( 'ycm.vimsupport.SetLocationListForWindow',
|
|
new_callable = ExtendedMock ) as set_location_list_for_window:
|
|
with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
|
|
for filename, diagnostics in diagnostics_per_file:
|
|
ycm.UpdateWithNewDiagnosticsForFile( filename, diagnostics )
|
|
|
|
# We update the diagnostic on the current cursor position
|
|
post_vim_message.assert_has_exact_calls( [
|
|
call( "error text in current buffer", truncate = True, warning = False ),
|
|
] )
|
|
|
|
# Ensure we included all the diags though
|
|
set_location_list_for_window.assert_has_exact_calls( [
|
|
call( 0, [
|
|
{
|
|
'lnum': 1,
|
|
'col': 1,
|
|
'bufnr': 1,
|
|
'valid': 1,
|
|
'type': 'E',
|
|
'text': 'error text in current buffer',
|
|
},
|
|
] ),
|
|
|
|
call( 8, [
|
|
{
|
|
'lnum': 4,
|
|
'col': 2,
|
|
'bufnr': 3,
|
|
'valid': 1,
|
|
'type': 'E',
|
|
'text': 'error text in hidden buffer',
|
|
},
|
|
] )
|
|
] )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
def YouCompleteMe_OnPeriodicTick_ServerNotRunning_test( ycm, *args ):
|
|
with patch.object( ycm, 'IsServerAlive', return_value = False ):
|
|
assert_that( ycm.OnPeriodicTick(), equal_to( False ) )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
def YouCompleteMe_OnPeriodicTick_ServerNotReady_test( ycm, *args ):
|
|
with patch.object( ycm, 'IsServerAlive', return_value = True ):
|
|
with patch.object( ycm, 'IsServerReady', return_value = False ):
|
|
assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch.object( ycm_buffer_module,
|
|
'DIAGNOSTIC_UI_ASYNC_FILETYPES',
|
|
[ 'ycmtest' ] )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.client.base_request._ValidateResponseObject', return_value = True )
|
|
@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync' )
|
|
def YouCompelteMe_OnPeriodicTick_DontRetry_test( ycm,
|
|
post_data_to_handler_async,
|
|
*args ):
|
|
|
|
current_buffer = VimBuffer( '/current',
|
|
filetype = 'ycmtest',
|
|
number = 1,
|
|
window = 10 )
|
|
buffers = [ current_buffer ]
|
|
|
|
# Create the request and make the first poll; we expect no response
|
|
with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
|
|
assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
|
|
post_data_to_handler_async.assert_called()
|
|
|
|
assert ycm._message_poll_request is not None
|
|
post_data_to_handler_async.reset_mock()
|
|
|
|
# OK that sent the request, now poll to check if it is complete (say it is
|
|
# not)
|
|
with patch.object( ycm._message_poll_request,
|
|
'_response_future',
|
|
new = MockAsyncServerResponseInProgress() ) as mock_future:
|
|
poll_again = ycm.OnPeriodicTick()
|
|
mock_future.done.assert_called()
|
|
mock_future.result.assert_not_called()
|
|
assert_that( poll_again, equal_to( True ) )
|
|
|
|
# Poll again, but return a response (telling us to stop polling)
|
|
with patch.object( ycm._message_poll_request,
|
|
'_response_future',
|
|
new = MockAsyncServerResponseDone( False ) ) \
|
|
as mock_future:
|
|
poll_again = ycm.OnPeriodicTick()
|
|
mock_future.done.assert_called()
|
|
mock_future.result.assert_called()
|
|
post_data_to_handler_async.assert_not_called()
|
|
# We reset and don't poll anymore
|
|
assert_that( ycm._message_poll_request is None )
|
|
assert_that( poll_again, equal_to( False ) )
|
|
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch.object( ycm_buffer_module,
|
|
'DIAGNOSTIC_UI_ASYNC_FILETYPES',
|
|
[ 'ycmtest' ] )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.client.base_request._ValidateResponseObject', return_value = True )
|
|
@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync' )
|
|
def YouCompelteMe_OnPeriodicTick_Exception_test( ycm,
|
|
post_data_to_handler_async,
|
|
*args ):
|
|
|
|
current_buffer = VimBuffer( '/current',
|
|
filetype = 'ycmtest',
|
|
number = 1,
|
|
window = 10 )
|
|
buffers = [ current_buffer ]
|
|
|
|
# Create the request and make the first poll; we expect no response
|
|
with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
|
|
assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
|
|
post_data_to_handler_async.assert_called()
|
|
|
|
post_data_to_handler_async.reset_mock()
|
|
|
|
# Poll again, but return an exception response
|
|
mock_response = MockAsyncServerResponseException( RuntimeError( 'test' ) )
|
|
with patch.object( ycm._message_poll_request,
|
|
'_response_future',
|
|
new = mock_response ) as mock_future:
|
|
assert_that( ycm.OnPeriodicTick(), equal_to( False ) )
|
|
mock_future.done.assert_called()
|
|
mock_future.result.assert_called()
|
|
post_data_to_handler_async.assert_not_called()
|
|
# We reset and don't poll anymore
|
|
assert_that( ycm._message_poll_request is None )
|
|
|
|
|
|
@YouCompleteMeInstance()
|
|
@patch.object( ycm_buffer_module,
|
|
'DIAGNOSTIC_UI_ASYNC_FILETYPES',
|
|
[ 'ycmtest' ] )
|
|
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
|
|
return_value = True )
|
|
@patch( 'ycm.client.base_request._ValidateResponseObject', return_value = True )
|
|
@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync' )
|
|
@patch( 'ycm.client.messages_request._HandlePollResponse' )
|
|
def YouCompelteMe_OnPeriodicTick_ValidResponse_test( ycm,
|
|
handle_poll_response,
|
|
post_data_to_handler_async,
|
|
*args ):
|
|
|
|
current_buffer = VimBuffer( '/current',
|
|
filetype = 'ycmtest',
|
|
number = 1,
|
|
window = 10 )
|
|
buffers = [ current_buffer ]
|
|
|
|
# Create the request and make the first poll; we expect no response
|
|
with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
|
|
assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
|
|
post_data_to_handler_async.assert_called()
|
|
|
|
post_data_to_handler_async.reset_mock()
|
|
|
|
# Poll again, and return a _proper_ response (finally!).
|
|
# Note, _HandlePollResponse is tested independently (for simplicity)
|
|
with patch.object( ycm._message_poll_request,
|
|
'_response_future',
|
|
new = MockAsyncServerResponseDone( [] ) ) as mock_future:
|
|
assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
|
|
handle_poll_response.assert_called()
|
|
mock_future.done.assert_called()
|
|
mock_future.result.assert_called()
|
|
post_data_to_handler_async.assert_called() # Poll again!
|
|
assert_that( ycm._message_poll_request is not None )
|