Skip to content

Commit

Permalink
Auto merge of #2245 - micbou:shutdown, r=puremourning
Browse files Browse the repository at this point in the history
[READY] Implement shutdown handler

This PR depends on ycm-core/ycmd#282.

We don't want to block Vim exit if the server takes too much time to answer the shutdown request so we use a timeout of 100ms for this request. In my experience, it takes ~5ms.

The `YcmStopServer` command only exists for testing purpose. It will be removed in the final version of this PR.

Fixes #876.

<!-- 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/2245)
<!-- Reviewable:end -->
  • Loading branch information
homu committed Jul 23, 2016
2 parents c1047be + 4e94d7c commit 9968a43
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 165 deletions.
54 changes: 54 additions & 0 deletions python/ycm/client/shutdown_request.py
@@ -0,0 +1,54 @@
# 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 requests.exceptions import ReadTimeout

from ycm.client.base_request import BaseRequest

TIMEOUT_SECONDS = 0.1


class ShutdownRequest( BaseRequest ):
def __init__( self ):
super( BaseRequest, self ).__init__()


def Start( self ):
try:
self.PostDataToHandler( {},
'shutdown',
TIMEOUT_SECONDS )
except ReadTimeout:
pass


def Response( self ):
return self._response


def SendShutdownRequest():
request = ShutdownRequest()
# This is a blocking call.
request.Start()
12 changes: 0 additions & 12 deletions python/ycm/test_utils.py
Expand Up @@ -47,18 +47,6 @@
# https://github.com/Valloric/YouCompleteMe/pull/1694
VIM_MOCK = MagicMock()

# The default options which are only relevant to the client, not the server and
# thus are not part of default_options.json, but are required for a working
# YouCompleteMe or OmniCompleter object.
DEFAULT_CLIENT_OPTIONS = {
'server_log_level': 'info',
'extra_conf_vim_data': [],
'show_diagnostics_ui': 1,
'enable_diagnostic_signs': 1,
'enable_diagnostic_highlighting': 0,
'always_populate_location_list': 0,
}


def MockGetBufferNumber( buffer_filename ):
for buffer in VIM_MOCK.buffers:
Expand Down
111 changes: 48 additions & 63 deletions python/ycm/tests/event_notification_test.py
Expand Up @@ -23,14 +23,13 @@
standard_library.install_aliases()
from builtins import * # noqa

from ycm.test_utils import MockVimModule, ExtendedMock, DEFAULT_CLIENT_OPTIONS
from ycm.test_utils import MockVimModule, ExtendedMock
MockVimModule()

import contextlib
import os

from ycm.youcompleteme import YouCompleteMe
from ycmd import user_options_store
from ycm.tests.server_test import Server_test
from ycmd.responses import ( BuildDiagnosticData, Diagnostic, Location, Range,
UnknownExtraConf, ServerError )

Expand Down Expand Up @@ -153,21 +152,7 @@ def MockEventNotification( response_method, native_filetype_completer = True ):
yield


class EventNotification_test( object ):

def setUp( self ):
options = dict( user_options_store.DefaultOptions() )
options.update( DEFAULT_CLIENT_OPTIONS )
user_options_store.SetAll( options )

self.server_state = YouCompleteMe( user_options_store.GetAll() )
pass


def tearDown( self ):
if self.server_state:
self.server_state.OnVimLeave()

class EventNotification_test( Server_test ):

@patch( 'vim.command', new_callable = ExtendedMock )
def FileReadyToParse_NonDiagnostic_Error_test( self, vim_command ):
Expand All @@ -181,25 +166,25 @@ def ErrorResponse( *args ):

with MockArbitraryBuffer( 'javascript' ):
with MockEventNotification( ErrorResponse ):
self.server_state.OnFileReadyToParse()
assert self.server_state.FileParseRequestReady()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
assert self._server_state.FileParseRequestReady()
self._server_state.HandleFileParseRequest()

# The first call raises a warning
vim_command.assert_has_exact_calls( [
PostMultiLineNotice_Call( ERROR_TEXT ),
] )

# Subsequent calls don't re-raise the warning
self.server_state.HandleFileParseRequest()
self._server_state.HandleFileParseRequest()
vim_command.assert_has_exact_calls( [
PostMultiLineNotice_Call( ERROR_TEXT ),
] )

# But it does if a subsequent event raises again
self.server_state.OnFileReadyToParse()
assert self.server_state.FileParseRequestReady()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
assert self._server_state.FileParseRequestReady()
self._server_state.HandleFileParseRequest()
vim_command.assert_has_exact_calls( [
PostMultiLineNotice_Call( ERROR_TEXT ),
PostMultiLineNotice_Call( ERROR_TEXT ),
Expand All @@ -210,8 +195,8 @@ def ErrorResponse( *args ):
def FileReadyToParse_NonDiagnostic_Error_NonNative_test( self, vim_command ):
with MockArbitraryBuffer( 'javascript' ):
with MockEventNotification( None, False ):
self.server_state.OnFileReadyToParse()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
self._server_state.HandleFileParseRequest()
vim_command.assert_not_called()


Expand Down Expand Up @@ -243,9 +228,9 @@ def UnknownExtraConfResponse( *args ):
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 0,
new_callable = ExtendedMock ) as present_dialog:
self.server_state.OnFileReadyToParse()
assert self.server_state.FileParseRequestReady()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
assert self._server_state.FileParseRequestReady()
self._server_state.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
Expand All @@ -255,7 +240,7 @@ def UnknownExtraConfResponse( *args ):
] )

# Subsequent calls don't re-raise the warning
self.server_state.HandleFileParseRequest()
self._server_state.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE )
Expand All @@ -265,9 +250,9 @@ def UnknownExtraConfResponse( *args ):
] )

# But it does if a subsequent event raises again
self.server_state.OnFileReadyToParse()
assert self.server_state.FileParseRequestReady()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
assert self._server_state.FileParseRequestReady()
self._server_state.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
Expand All @@ -282,9 +267,9 @@ def UnknownExtraConfResponse( *args ):
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 1,
new_callable = ExtendedMock ) as present_dialog:
self.server_state.OnFileReadyToParse()
assert self.server_state.FileParseRequestReady()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
assert self._server_state.FileParseRequestReady()
self._server_state.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
Expand All @@ -294,7 +279,7 @@ def UnknownExtraConfResponse( *args ):
] )

# Subsequent calls don't re-raise the warning
self.server_state.HandleFileParseRequest()
self._server_state.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE )
Expand All @@ -304,9 +289,9 @@ def UnknownExtraConfResponse( *args ):
] )

# But it does if a subsequent event raises again
self.server_state.OnFileReadyToParse()
assert self.server_state.FileParseRequestReady()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
assert self._server_state.FileParseRequestReady()
self._server_state.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
Expand Down Expand Up @@ -337,23 +322,23 @@ def DiagnosticResponse( *args ):

with MockArbitraryBuffer( 'cpp' ):
with MockEventNotification( DiagnosticResponse ):
self.server_state.OnFileReadyToParse()
ok_( self.server_state.FileParseRequestReady() )
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
ok_( self._server_state.FileParseRequestReady() )
self._server_state.HandleFileParseRequest()
vim_command.assert_has_calls( [
PlaceSign_Call( 1, 1, 0, True )
] )
eq_( self.server_state.GetErrorCount(), 1 )
eq_( self.server_state.GetWarningCount(), 0 )
eq_( self._server_state.GetErrorCount(), 1 )
eq_( self._server_state.GetWarningCount(), 0 )

# Consequent calls to HandleFileParseRequest shouldn't mess with
# existing diagnostics, when there is no new parse request.
vim_command.reset_mock()
ok_( not self.server_state.FileParseRequestReady() )
self.server_state.HandleFileParseRequest()
ok_( not self._server_state.FileParseRequestReady() )
self._server_state.HandleFileParseRequest()
vim_command.assert_not_called()
eq_( self.server_state.GetErrorCount(), 1 )
eq_( self.server_state.GetWarningCount(), 0 )
eq_( self._server_state.GetErrorCount(), 1 )
eq_( self._server_state.GetWarningCount(), 0 )


@patch( 'vim.command' )
Expand All @@ -370,24 +355,24 @@ def DiagnosticResponse( *args ):

with MockArbitraryBuffer( 'cpp' ):
with MockEventNotification( DiagnosticResponse ):
self.server_state.OnFileReadyToParse()
ok_( self.server_state.FileParseRequestReady() )
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
ok_( self._server_state.FileParseRequestReady() )
self._server_state.HandleFileParseRequest()
vim_command.assert_has_calls( [
PlaceSign_Call( 2, 2, 0, False ),
UnplaceSign_Call( 1, 0 )
] )
eq_( self.server_state.GetErrorCount(), 0 )
eq_( self.server_state.GetWarningCount(), 1 )
eq_( self._server_state.GetErrorCount(), 0 )
eq_( self._server_state.GetWarningCount(), 1 )

# Consequent calls to HandleFileParseRequest shouldn't mess with
# existing diagnostics, when there is no new parse request.
vim_command.reset_mock()
ok_( not self.server_state.FileParseRequestReady() )
self.server_state.HandleFileParseRequest()
ok_( not self._server_state.FileParseRequestReady() )
self._server_state.HandleFileParseRequest()
vim_command.assert_not_called()
eq_( self.server_state.GetErrorCount(), 0 )
eq_( self.server_state.GetWarningCount(), 1 )
eq_( self._server_state.GetErrorCount(), 0 )
eq_( self._server_state.GetWarningCount(), 1 )


@patch( 'vim.command' )
Expand All @@ -397,10 +382,10 @@ def _Check_FileReadyToParse_Diagnostic_Clean( self, vim_command ):
# Should be called after _Check_FileReadyToParse_Diagnostic_Warning
with MockArbitraryBuffer( 'cpp' ):
with MockEventNotification( MagicMock( return_value = [] ) ):
self.server_state.OnFileReadyToParse()
self.server_state.HandleFileParseRequest()
self._server_state.OnFileReadyToParse()
self._server_state.HandleFileParseRequest()
vim_command.assert_has_calls( [
UnplaceSign_Call( 2, 0 )
] )
eq_( self.server_state.GetErrorCount(), 0 )
eq_( self.server_state.GetWarningCount(), 0 )
eq_( self._server_state.GetErrorCount(), 0 )
eq_( self._server_state.GetWarningCount(), 0 )
23 changes: 3 additions & 20 deletions python/ycm/tests/omni_completer_test.py
Expand Up @@ -33,11 +33,10 @@
from ycm.test_utils import MockVimModule, ExtendedMock
MockVimModule()

from ycm.test_utils import DEFAULT_CLIENT_OPTIONS, ExpectedFailure
from ycm.omni_completer import OmniCompleter
from ycm.youcompleteme import YouCompleteMe
from ycm.test_utils import ExpectedFailure
from ycm.tests.server_test import MakeUserOptions, Server_test

from ycmd import user_options_store
from ycmd.utils import ToBytes
from ycmd.request_wrap import RequestWrap

Expand Down Expand Up @@ -75,23 +74,7 @@ def BuildRequestWrap( line_num, column_num, contents ):
return RequestWrap( BuildRequest( line_num, column_num, contents ) )


def MakeUserOptions( custom_options = {} ):
options = dict( user_options_store.DefaultOptions() )
options.update( DEFAULT_CLIENT_OPTIONS )
options.update( custom_options )
return options


class OmniCompleter_test( object ):

def setUp( self ):
# We need a server instance for FilterAndSortCandidates
self._server_state = YouCompleteMe( MakeUserOptions() )


def tearDown( self ):
self._server_state.OnVimLeave()

class OmniCompleter_test( Server_test ):

def OmniCompleter_GetCompletions_Cache_List_test( self ):
omni_completer = OmniCompleter( MakeUserOptions( {
Expand Down

0 comments on commit 9968a43

Please sign in to comment.