Auto merge of #2453 - micbou:handle-server-exception, r=puremourning
[READY] Catch and log all server exceptions A lot of different errors may occur when sending a request to ycmd and receiving its response: - [any exception from the requests module](http://docs.python-requests.org/en/master/_modules/requests/exceptions/); - exceptions from ycmd: `ServerError` and `UnknownExtraConf`; - `RuntimeError` exception from invalid HMAC; - and possibly others. Thanks to PR #2430, we can now catch and log these exceptions. I am marking this PR as WIP because I'd like to add tests but I am not sure on how to implement them. Should we use actual code for these tests or mock the response from the ycmd server? Fixes #2216. Fixes #2272. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/2453) <!-- Reviewable:end -->
This commit is contained in:
commit
e1e7e648d4
@ -23,6 +23,8 @@ from future import standard_library
|
|||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
from builtins import * # noqa
|
from builtins import * # noqa
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import logging
|
||||||
import requests
|
import requests
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import json
|
import json
|
||||||
@ -41,6 +43,7 @@ _EXECUTOR = UnsafeThreadPoolExecutor( max_workers = 30 )
|
|||||||
# Setting this to None seems to screw up the Requests/urllib3 libs.
|
# Setting this to None seems to screw up the Requests/urllib3 libs.
|
||||||
_DEFAULT_TIMEOUT_SEC = 30
|
_DEFAULT_TIMEOUT_SEC = 30
|
||||||
_HMAC_HEADER = 'x-ycm-hmac'
|
_HMAC_HEADER = 'x-ycm-hmac'
|
||||||
|
_logger = logging.getLogger( __name__ )
|
||||||
|
|
||||||
|
|
||||||
class BaseRequest( object ):
|
class BaseRequest( object ):
|
||||||
@ -193,7 +196,48 @@ def JsonFromFuture( future ):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def HandleServerException( exception, truncate = False ):
|
@contextlib.contextmanager
|
||||||
|
def HandleServerException( display = True, truncate = False ):
|
||||||
|
"""Catch any exception raised through server communication. If it is raised
|
||||||
|
because of a unknown .ycm_extra_conf.py file, load the file or ignore it after
|
||||||
|
asking the user. Otherwise, log the exception and display its message to the
|
||||||
|
user on the Vim status line. Unset the |display| parameter to hide the message
|
||||||
|
from the user. Set the |truncate| parameter to avoid hit-enter prompts from
|
||||||
|
this message.
|
||||||
|
|
||||||
|
The GetDataFromHandler, PostDataToHandler, and JsonFromFuture functions should
|
||||||
|
always be wrapped by this function to avoid Python exceptions bubbling up to
|
||||||
|
the user.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
with HandleServerException():
|
||||||
|
response = BaseRequest.PostDataToHandler( ... )
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except UnknownExtraConf as e:
|
||||||
|
if vimsupport.Confirm( str( e ) ):
|
||||||
|
_LoadExtraConfFile( e.extra_conf_file )
|
||||||
|
else:
|
||||||
|
_IgnoreExtraConfFile( e.extra_conf_file )
|
||||||
|
except Exception as e:
|
||||||
|
_logger.exception( 'Error while handling server response' )
|
||||||
|
if display:
|
||||||
|
DisplayServerException( e, truncate )
|
||||||
|
|
||||||
|
|
||||||
|
def _LoadExtraConfFile( filepath ):
|
||||||
|
BaseRequest.PostDataToHandler( { 'filepath': filepath },
|
||||||
|
'load_extra_conf_file' )
|
||||||
|
|
||||||
|
|
||||||
|
def _IgnoreExtraConfFile( filepath ):
|
||||||
|
BaseRequest.PostDataToHandler( { 'filepath': filepath },
|
||||||
|
'ignore_extra_conf_file' )
|
||||||
|
|
||||||
|
|
||||||
|
def DisplayServerException( exception, truncate = False ):
|
||||||
serialized_exception = str( exception )
|
serialized_exception = str( exception )
|
||||||
|
|
||||||
# We ignore the exception about the file already being parsed since it comes
|
# We ignore the exception about the file already being parsed since it comes
|
||||||
|
@ -23,9 +23,6 @@ from future import standard_library
|
|||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
from builtins import * # noqa
|
from builtins import * # noqa
|
||||||
|
|
||||||
from requests.exceptions import ReadTimeout
|
|
||||||
|
|
||||||
from ycmd.responses import ServerError
|
|
||||||
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
||||||
HandleServerException )
|
HandleServerException )
|
||||||
from ycm import vimsupport
|
from ycm import vimsupport
|
||||||
@ -53,11 +50,9 @@ class CommandRequest( BaseRequest ):
|
|||||||
'completer_target': self._completer_target,
|
'completer_target': self._completer_target,
|
||||||
'command_arguments': self._arguments
|
'command_arguments': self._arguments
|
||||||
} )
|
} )
|
||||||
try:
|
with HandleServerException():
|
||||||
self._response = self.PostDataToHandler( request_data,
|
self._response = self.PostDataToHandler( request_data,
|
||||||
'run_completer_command' )
|
'run_completer_command' )
|
||||||
except ( ServerError, ReadTimeout ) as e:
|
|
||||||
HandleServerException( e )
|
|
||||||
|
|
||||||
|
|
||||||
def Response( self ):
|
def Response( self ):
|
||||||
|
@ -23,11 +23,8 @@ from future import standard_library
|
|||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
from builtins import * # noqa
|
from builtins import * # noqa
|
||||||
|
|
||||||
from requests.exceptions import ReadTimeout
|
|
||||||
|
|
||||||
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
||||||
HandleServerException )
|
HandleServerException )
|
||||||
from ycmd.responses import ServerError
|
|
||||||
|
|
||||||
|
|
||||||
class CompleterAvailableRequest( BaseRequest ):
|
class CompleterAvailableRequest( BaseRequest ):
|
||||||
@ -40,11 +37,9 @@ class CompleterAvailableRequest( BaseRequest ):
|
|||||||
def Start( self ):
|
def Start( self ):
|
||||||
request_data = BuildRequestData()
|
request_data = BuildRequestData()
|
||||||
request_data.update( { 'filetypes': self.filetypes } )
|
request_data.update( { 'filetypes': self.filetypes } )
|
||||||
try:
|
with HandleServerException():
|
||||||
self._response = self.PostDataToHandler( request_data,
|
self._response = self.PostDataToHandler( request_data,
|
||||||
'semantic_completion_available' )
|
'semantic_completion_available' )
|
||||||
except ( ServerError, ReadTimeout ) as e:
|
|
||||||
HandleServerException( e )
|
|
||||||
|
|
||||||
|
|
||||||
def Response( self ):
|
def Response( self ):
|
||||||
|
@ -23,13 +23,10 @@ from future import standard_library
|
|||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
from builtins import * # noqa
|
from builtins import * # noqa
|
||||||
|
|
||||||
from requests.exceptions import ReadTimeout
|
|
||||||
|
|
||||||
from ycmd.utils import ToUnicode
|
from ycmd.utils import ToUnicode
|
||||||
from ycm.client.base_request import ( BaseRequest, JsonFromFuture,
|
from ycm.client.base_request import ( BaseRequest, JsonFromFuture,
|
||||||
HandleServerException,
|
HandleServerException,
|
||||||
MakeServerException )
|
MakeServerException )
|
||||||
from ycmd.responses import ServerError
|
|
||||||
|
|
||||||
TIMEOUT_SECONDS = 0.5
|
TIMEOUT_SECONDS = 0.5
|
||||||
|
|
||||||
@ -53,16 +50,15 @@ class CompletionRequest( BaseRequest ):
|
|||||||
def RawResponse( self ):
|
def RawResponse( self ):
|
||||||
if not self._response_future:
|
if not self._response_future:
|
||||||
return []
|
return []
|
||||||
try:
|
with HandleServerException( truncate = True ):
|
||||||
response = JsonFromFuture( self._response_future )
|
response = JsonFromFuture( self._response_future )
|
||||||
|
|
||||||
errors = response[ 'errors' ] if 'errors' in response else []
|
errors = response[ 'errors' ] if 'errors' in response else []
|
||||||
for e in errors:
|
for e in errors:
|
||||||
HandleServerException( MakeServerException( e ) )
|
with HandleServerException( truncate = True ):
|
||||||
|
raise MakeServerException( e )
|
||||||
|
|
||||||
return JsonFromFuture( self._response_future )[ 'completions' ]
|
return response[ 'completions' ]
|
||||||
except ( ServerError, ReadTimeout ) as e:
|
|
||||||
HandleServerException( e, truncate = True )
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
52
python/ycm/client/debug_info_request.py
Normal file
52
python/ycm/client/debug_info_request.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (C) 2016 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
|
||||||
|
from future import standard_library
|
||||||
|
standard_library.install_aliases()
|
||||||
|
from builtins import * # noqa
|
||||||
|
|
||||||
|
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
||||||
|
HandleServerException )
|
||||||
|
|
||||||
|
|
||||||
|
class DebugInfoRequest( BaseRequest ):
|
||||||
|
def __init__( self ):
|
||||||
|
super( DebugInfoRequest, self ).__init__()
|
||||||
|
self._response = None
|
||||||
|
|
||||||
|
|
||||||
|
def Start( self ):
|
||||||
|
request_data = BuildRequestData()
|
||||||
|
with HandleServerException( display = False ):
|
||||||
|
self._response = self.PostDataToHandler( request_data, 'debug_info' )
|
||||||
|
|
||||||
|
|
||||||
|
def Response( self ):
|
||||||
|
if not self._response:
|
||||||
|
return 'Server errored, no debug info from server'
|
||||||
|
return self._response
|
||||||
|
|
||||||
|
|
||||||
|
def SendDebugInfoRequest():
|
||||||
|
request = DebugInfoRequest()
|
||||||
|
# This is a blocking call.
|
||||||
|
request.Start()
|
||||||
|
return request.Response()
|
@ -23,10 +23,6 @@ from future import standard_library
|
|||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
from builtins import * # noqa
|
from builtins import * # noqa
|
||||||
|
|
||||||
from requests.exceptions import ReadTimeout
|
|
||||||
|
|
||||||
from ycm import vimsupport
|
|
||||||
from ycmd.responses import UnknownExtraConf, ServerError
|
|
||||||
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
||||||
JsonFromFuture, HandleServerException )
|
JsonFromFuture, HandleServerException )
|
||||||
|
|
||||||
@ -61,16 +57,8 @@ class EventNotification( BaseRequest ):
|
|||||||
if not self._response_future or self._event_name != 'FileReadyToParse':
|
if not self._response_future or self._event_name != 'FileReadyToParse':
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
with HandleServerException( truncate = True ):
|
||||||
try:
|
self._cached_response = JsonFromFuture( self._response_future )
|
||||||
self._cached_response = JsonFromFuture( self._response_future )
|
|
||||||
except UnknownExtraConf as e:
|
|
||||||
if vimsupport.Confirm( str( e ) ):
|
|
||||||
_LoadExtraConfFile( e.extra_conf_file )
|
|
||||||
else:
|
|
||||||
_IgnoreExtraConfFile( e.extra_conf_file )
|
|
||||||
except ( ServerError, ReadTimeout ) as e:
|
|
||||||
HandleServerException( e )
|
|
||||||
|
|
||||||
return self._cached_response if self._cached_response else []
|
return self._cached_response if self._cached_response else []
|
||||||
|
|
||||||
@ -80,13 +68,3 @@ def SendEventNotificationAsync( event_name,
|
|||||||
extra_data = None ):
|
extra_data = None ):
|
||||||
event = EventNotification( event_name, filepath, extra_data )
|
event = EventNotification( event_name, filepath, extra_data )
|
||||||
event.Start()
|
event.Start()
|
||||||
|
|
||||||
|
|
||||||
def _LoadExtraConfFile( filepath ):
|
|
||||||
BaseRequest.PostDataToHandler( { 'filepath': filepath },
|
|
||||||
'load_extra_conf_file' )
|
|
||||||
|
|
||||||
|
|
||||||
def _IgnoreExtraConfFile( filepath ):
|
|
||||||
BaseRequest.PostDataToHandler( { 'filepath': filepath },
|
|
||||||
'ignore_extra_conf_file' )
|
|
||||||
|
@ -23,9 +23,7 @@ from future import standard_library
|
|||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
from builtins import * # noqa
|
from builtins import * # noqa
|
||||||
|
|
||||||
from requests.exceptions import ReadTimeout
|
from ycm.client.base_request import BaseRequest, HandleServerException
|
||||||
|
|
||||||
from ycm.client.base_request import BaseRequest
|
|
||||||
|
|
||||||
TIMEOUT_SECONDS = 0.1
|
TIMEOUT_SECONDS = 0.1
|
||||||
|
|
||||||
@ -36,12 +34,8 @@ class ShutdownRequest( BaseRequest ):
|
|||||||
|
|
||||||
|
|
||||||
def Start( self ):
|
def Start( self ):
|
||||||
try:
|
with HandleServerException( display = False ):
|
||||||
self.PostDataToHandler( {},
|
self.PostDataToHandler( {}, 'shutdown', TIMEOUT_SECONDS )
|
||||||
'shutdown',
|
|
||||||
TIMEOUT_SECONDS )
|
|
||||||
except ReadTimeout:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def SendShutdownRequest():
|
def SendShutdownRequest():
|
||||||
|
@ -25,7 +25,7 @@ from builtins import * # noqa
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from ycm.client.base_request import BaseRequest
|
from ycm.client.base_request import BaseRequest, HandleServerException
|
||||||
|
|
||||||
|
|
||||||
# This class can be used to keep the ycmd server alive for the duration of the
|
# This class can be used to keep the ycmd server alive for the duration of the
|
||||||
@ -46,9 +46,5 @@ class YcmdKeepalive( object ):
|
|||||||
while True:
|
while True:
|
||||||
time.sleep( self._ping_interval_seconds )
|
time.sleep( self._ping_interval_seconds )
|
||||||
|
|
||||||
# We don't care if there's an intermittent problem in contacting the
|
with HandleServerException( display = False ):
|
||||||
# server; it's fine to just skip this ping.
|
|
||||||
try:
|
|
||||||
BaseRequest.GetDataFromHandler( 'healthy' )
|
BaseRequest.GetDataFromHandler( 'healthy' )
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
@ -26,7 +26,6 @@ from builtins import * # noqa
|
|||||||
import vim
|
import vim
|
||||||
from ycm import vimsupport
|
from ycm import vimsupport
|
||||||
from ycmd import utils
|
from ycmd import utils
|
||||||
from ycmd.responses import ServerError
|
|
||||||
from ycmd.completers.completer import Completer
|
from ycmd.completers.completer import Completer
|
||||||
from ycm.client.base_request import BaseRequest, HandleServerException
|
from ycm.client.base_request import BaseRequest, HandleServerException
|
||||||
|
|
||||||
@ -115,9 +114,7 @@ class OmniCompleter( Completer ):
|
|||||||
'query': query
|
'query': query
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
with HandleServerException():
|
||||||
return BaseRequest.PostDataToHandler( request_data,
|
return BaseRequest.PostDataToHandler( request_data,
|
||||||
'filter_and_sort_candidates' )
|
'filter_and_sort_candidates' )
|
||||||
except ServerError as e:
|
return candidates
|
||||||
HandleServerException( e )
|
|
||||||
return candidates
|
|
||||||
|
44
python/ycm/tests/command_test.py
Normal file
44
python/ycm/tests/command_test.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Copyright (C) 2016 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
|
||||||
|
from future import standard_library
|
||||||
|
standard_library.install_aliases()
|
||||||
|
from builtins import * # noqa
|
||||||
|
|
||||||
|
from ycm.tests.test_utils import ( MockVimModule, MockVimBuffers, VimBuffer )
|
||||||
|
MockVimModule()
|
||||||
|
|
||||||
|
from hamcrest import assert_that, equal_to
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
from ycm.tests import YouCompleteMeInstance
|
||||||
|
|
||||||
|
|
||||||
|
@YouCompleteMeInstance()
|
||||||
|
def SendCommandRequest_test( ycm ):
|
||||||
|
current_buffer = VimBuffer( 'buffer' )
|
||||||
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
||||||
|
with patch( 'ycm.client.base_request.JsonFromFuture',
|
||||||
|
return_value = 'Some response' ):
|
||||||
|
assert_that(
|
||||||
|
ycm.SendCommandRequest( 'GoTo', 'python' ),
|
||||||
|
equal_to( 'Some response' )
|
||||||
|
)
|
@ -25,13 +25,15 @@ from future import standard_library
|
|||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
from builtins import * # noqa
|
from builtins import * # noqa
|
||||||
|
|
||||||
from ycm.tests.test_utils import ( CurrentWorkingDirectory, MockVimModule,
|
from ycm.tests.test_utils import ( CurrentWorkingDirectory, ExtendedMock,
|
||||||
MockVimBuffers, VimBuffer )
|
MockVimModule, MockVimBuffers, VimBuffer )
|
||||||
MockVimModule()
|
MockVimModule()
|
||||||
|
|
||||||
from hamcrest import assert_that, empty, has_entries
|
from hamcrest import assert_that, contains, empty, has_entries
|
||||||
|
from mock import call, patch
|
||||||
|
|
||||||
from ycm.tests import PathToTestFile, YouCompleteMeInstance
|
from ycm.tests import PathToTestFile, YouCompleteMeInstance
|
||||||
|
from ycmd.responses import ServerError
|
||||||
|
|
||||||
|
|
||||||
@YouCompleteMeInstance()
|
@YouCompleteMeInstance()
|
||||||
@ -40,7 +42,7 @@ def CreateCompletionRequest_UnicodeWorkingDirectory_test( ycm ):
|
|||||||
current_buffer = VimBuffer( PathToTestFile( 'uni¢𐍈d€', 'current_buffer' ) )
|
current_buffer = VimBuffer( PathToTestFile( 'uni¢𐍈d€', 'current_buffer' ) )
|
||||||
|
|
||||||
with CurrentWorkingDirectory( unicode_dir ):
|
with CurrentWorkingDirectory( unicode_dir ):
|
||||||
with MockVimBuffers( [ current_buffer ], current_buffer, ( 5, 2 ) ):
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
||||||
ycm.CreateCompletionRequest(),
|
ycm.CreateCompletionRequest(),
|
||||||
|
|
||||||
results = ycm.GetCompletions()
|
results = ycm.GetCompletions()
|
||||||
@ -52,3 +54,86 @@ def CreateCompletionRequest_UnicodeWorkingDirectory_test( ycm ):
|
|||||||
'refresh': 'always'
|
'refresh': 'always'
|
||||||
} )
|
} )
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@YouCompleteMeInstance()
|
||||||
|
@patch( 'ycm.client.base_request._logger', autospec = True )
|
||||||
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
||||||
|
def CreateCompletionRequest_ResponseContainingError_test( ycm,
|
||||||
|
post_vim_message,
|
||||||
|
logger ):
|
||||||
|
current_buffer = VimBuffer( 'buffer' )
|
||||||
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
||||||
|
ycm.CreateCompletionRequest(),
|
||||||
|
|
||||||
|
response = {
|
||||||
|
'completions': [ {
|
||||||
|
'insertion_text': 'insertion_text',
|
||||||
|
'menu_text': 'menu_text',
|
||||||
|
'extra_menu_info': 'extra_menu_info',
|
||||||
|
'detailed_info': 'detailed_info',
|
||||||
|
'kind': 'kind',
|
||||||
|
'extra_data': {
|
||||||
|
'doc_string': 'doc_string'
|
||||||
|
}
|
||||||
|
} ],
|
||||||
|
'completion_start_column': 3,
|
||||||
|
'errors': [ {
|
||||||
|
'exception': {
|
||||||
|
'TYPE': 'Exception'
|
||||||
|
},
|
||||||
|
'message': 'message',
|
||||||
|
'traceback': 'traceback'
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch( 'ycm.client.completion_request.JsonFromFuture',
|
||||||
|
return_value = response ):
|
||||||
|
results = ycm.GetCompletions()
|
||||||
|
|
||||||
|
logger.exception.assert_called_with( 'Error while handling server response' )
|
||||||
|
post_vim_message.assert_has_exact_calls( [
|
||||||
|
call( 'Exception: message', truncate = True )
|
||||||
|
] )
|
||||||
|
assert_that(
|
||||||
|
results,
|
||||||
|
has_entries( {
|
||||||
|
'words': contains( has_entries( {
|
||||||
|
'word': 'insertion_text',
|
||||||
|
'abbr': 'menu_text',
|
||||||
|
'menu': 'extra_menu_info',
|
||||||
|
'info': 'detailed_info\ndoc_string',
|
||||||
|
'kind': 'k',
|
||||||
|
'dup': 1,
|
||||||
|
'empty': 1
|
||||||
|
} ) ),
|
||||||
|
'refresh': 'always'
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@YouCompleteMeInstance()
|
||||||
|
@patch( 'ycm.client.base_request._logger', autospec = True )
|
||||||
|
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
|
||||||
|
def CreateCompletionRequest_ErrorFromServer_test( ycm,
|
||||||
|
post_vim_message,
|
||||||
|
logger ):
|
||||||
|
current_buffer = VimBuffer( 'buffer' )
|
||||||
|
with MockVimBuffers( [ current_buffer ], current_buffer ):
|
||||||
|
ycm.CreateCompletionRequest(),
|
||||||
|
|
||||||
|
with patch( 'ycm.client.completion_request.JsonFromFuture',
|
||||||
|
side_effect = ServerError( 'Server error' ) ):
|
||||||
|
results = ycm.GetCompletions()
|
||||||
|
|
||||||
|
logger.exception.assert_called_with( 'Error while handling server response' )
|
||||||
|
post_vim_message.assert_has_exact_calls( [
|
||||||
|
call( 'Server error', truncate = True )
|
||||||
|
] )
|
||||||
|
assert_that(
|
||||||
|
results,
|
||||||
|
has_entries( {
|
||||||
|
'words': empty(),
|
||||||
|
'refresh': 'always'
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
@ -128,13 +128,13 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_test(
|
|||||||
|
|
||||||
# The first call raises a warning
|
# The first call raises a warning
|
||||||
post_vim_message.assert_has_exact_calls( [
|
post_vim_message.assert_has_exact_calls( [
|
||||||
call( ERROR_TEXT, truncate = False )
|
call( ERROR_TEXT, truncate = True )
|
||||||
] )
|
] )
|
||||||
|
|
||||||
# Subsequent calls don't re-raise the warning
|
# Subsequent calls don't re-raise the warning
|
||||||
ycm.HandleFileParseRequest()
|
ycm.HandleFileParseRequest()
|
||||||
post_vim_message.assert_has_exact_calls( [
|
post_vim_message.assert_has_exact_calls( [
|
||||||
call( ERROR_TEXT, truncate = False )
|
call( ERROR_TEXT, truncate = True )
|
||||||
] )
|
] )
|
||||||
|
|
||||||
# But it does if a subsequent event raises again
|
# But it does if a subsequent event raises again
|
||||||
@ -142,8 +142,8 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_test(
|
|||||||
ok_( ycm.FileParseRequestReady() )
|
ok_( ycm.FileParseRequestReady() )
|
||||||
ycm.HandleFileParseRequest()
|
ycm.HandleFileParseRequest()
|
||||||
post_vim_message.assert_has_exact_calls( [
|
post_vim_message.assert_has_exact_calls( [
|
||||||
call( ERROR_TEXT, truncate = False ),
|
call( ERROR_TEXT, truncate = True ),
|
||||||
call( ERROR_TEXT, truncate = False )
|
call( ERROR_TEXT, truncate = True )
|
||||||
] )
|
] )
|
||||||
|
|
||||||
|
|
||||||
@ -159,9 +159,9 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_NonNative_test(
|
|||||||
vim_command.assert_not_called()
|
vim_command.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@patch( 'ycm.client.event_notification._LoadExtraConfFile',
|
@patch( 'ycm.client.base_request._LoadExtraConfFile',
|
||||||
new_callable = ExtendedMock )
|
new_callable = ExtendedMock )
|
||||||
@patch( 'ycm.client.event_notification._IgnoreExtraConfFile',
|
@patch( 'ycm.client.base_request._IgnoreExtraConfFile',
|
||||||
new_callable = ExtendedMock )
|
new_callable = ExtendedMock )
|
||||||
@YouCompleteMeInstance()
|
@YouCompleteMeInstance()
|
||||||
def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
|
def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
|
||||||
|
@ -30,10 +30,12 @@ MockVimModule()
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from hamcrest import assert_that, is_in, is_not, has_length, matches_regexp
|
from hamcrest import ( assert_that, contains, empty, is_in, is_not, has_length,
|
||||||
|
matches_regexp )
|
||||||
from mock import call, MagicMock, patch
|
from mock import call, MagicMock, patch
|
||||||
|
|
||||||
from ycm.tests import YouCompleteMeInstance
|
from ycm.tests import YouCompleteMeInstance
|
||||||
|
from ycmd.responses import ServerError
|
||||||
|
|
||||||
|
|
||||||
@YouCompleteMeInstance()
|
@YouCompleteMeInstance()
|
||||||
@ -234,3 +236,54 @@ def YouCompleteMe_ToggleLogs_WithoutParameters_test( ycm, post_vim_message ):
|
|||||||
'ycmd_\d+_stderr_.+.log\n'
|
'ycmd_\d+_stderr_.+.log\n'
|
||||||
'ycmd_\d+_stdout_.+.log' )
|
'ycmd_\d+_stdout_.+.log' )
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@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 )
|
||||||
|
] )
|
||||||
|
@ -38,16 +38,17 @@ from ycm import base, paths, vimsupport
|
|||||||
from ycmd import utils
|
from ycmd import utils
|
||||||
from ycmd import server_utils
|
from ycmd import server_utils
|
||||||
from ycmd.request_wrap import RequestWrap
|
from ycmd.request_wrap import RequestWrap
|
||||||
from ycmd.responses import ServerError
|
|
||||||
from ycm.diagnostic_interface import DiagnosticInterface
|
from ycm.diagnostic_interface import DiagnosticInterface
|
||||||
from ycm.omni_completer import OmniCompleter
|
from ycm.omni_completer import OmniCompleter
|
||||||
from ycm import syntax_parse
|
from ycm import syntax_parse
|
||||||
from ycm.client.ycmd_keepalive import YcmdKeepalive
|
from ycm.client.ycmd_keepalive import YcmdKeepalive
|
||||||
from ycm.client.base_request import BaseRequest, BuildRequestData
|
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
||||||
|
HandleServerException )
|
||||||
from ycm.client.completer_available_request import SendCompleterAvailableRequest
|
from ycm.client.completer_available_request import SendCompleterAvailableRequest
|
||||||
from ycm.client.command_request import SendCommandRequest
|
from ycm.client.command_request import SendCommandRequest
|
||||||
from ycm.client.completion_request import ( CompletionRequest,
|
from ycm.client.completion_request import ( CompletionRequest,
|
||||||
ConvertCompletionDataToVimData )
|
ConvertCompletionDataToVimData )
|
||||||
|
from ycm.client.debug_info_request import SendDebugInfoRequest
|
||||||
from ycm.client.omni_completion_request import OmniCompletionRequest
|
from ycm.client.omni_completion_request import OmniCompletionRequest
|
||||||
from ycm.client.event_notification import ( SendEventNotificationAsync,
|
from ycm.client.event_notification import ( SendEventNotificationAsync,
|
||||||
EventNotification )
|
EventNotification )
|
||||||
@ -302,13 +303,10 @@ class YouCompleteMe( object ):
|
|||||||
|
|
||||||
def GetDefinedSubcommands( self ):
|
def GetDefinedSubcommands( self ):
|
||||||
if self.IsServerAlive():
|
if self.IsServerAlive():
|
||||||
try:
|
with HandleServerException():
|
||||||
return BaseRequest.PostDataToHandler( BuildRequestData(),
|
return BaseRequest.PostDataToHandler( BuildRequestData(),
|
||||||
'defined_subcommands' )
|
'defined_subcommands' )
|
||||||
except ServerError:
|
return []
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def GetCurrentCompletionRequest( self ):
|
def GetCurrentCompletionRequest( self ):
|
||||||
@ -636,14 +634,13 @@ class YouCompleteMe( object ):
|
|||||||
def ShowDetailedDiagnostic( self ):
|
def ShowDetailedDiagnostic( self ):
|
||||||
if not self.IsServerAlive():
|
if not self.IsServerAlive():
|
||||||
return
|
return
|
||||||
try:
|
with HandleServerException():
|
||||||
debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
|
detailed_diagnostic = BaseRequest.PostDataToHandler(
|
||||||
'detailed_diagnostic' )
|
BuildRequestData(), 'detailed_diagnostic' )
|
||||||
if 'message' in debug_info:
|
|
||||||
vimsupport.PostVimMessage( debug_info[ 'message' ],
|
if 'message' in detailed_diagnostic:
|
||||||
|
vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ],
|
||||||
warning = False )
|
warning = False )
|
||||||
except ServerError as e:
|
|
||||||
vimsupport.PostVimMessage( str( e ) )
|
|
||||||
|
|
||||||
|
|
||||||
def DebugInfo( self ):
|
def DebugInfo( self ):
|
||||||
@ -651,8 +648,7 @@ class YouCompleteMe( object ):
|
|||||||
if self._client_logfile:
|
if self._client_logfile:
|
||||||
debug_info += 'Client logfile: {0}\n'.format( self._client_logfile )
|
debug_info += 'Client logfile: {0}\n'.format( self._client_logfile )
|
||||||
if self.IsServerAlive():
|
if self.IsServerAlive():
|
||||||
debug_info += BaseRequest.PostDataToHandler( BuildRequestData(),
|
debug_info += SendDebugInfoRequest()
|
||||||
'debug_info' )
|
|
||||||
else:
|
else:
|
||||||
debug_info += 'Server crashed, no debug info from server'
|
debug_info += 'Server crashed, no debug info from server'
|
||||||
debug_info += '\nServer running at: {0}\n'.format(
|
debug_info += '\nServer running at: {0}\n'.format(
|
||||||
|
Loading…
Reference in New Issue
Block a user