Auto merge of #1848 - puremourning:file-ready-to-parse, r=vheon

[READY] Raise warnings on FileReadyToParse for completers not supporting diagnostics

Fixes #1829

## Background

Detailed analysis is available on [the issue](https://github.com/Valloric/YouCompleteMe/issues/1829). But principally: if the filetype being edited is not a filetype suporting YCM native diagnostics, then warnings, errors, etc. (including the "unknown extra conf" warning/prompt) are not displayed after the "FileReadyToParse" `event_notification`

## Resolution

The approach is: for filetypes which do not support diagnostics, rather than ignoring all `FileReadyToParse` event responses, simply call the `Response` method, which raises the appropriate error or handles the "confirm extra-conf" behaviour. Any other data returned by the completer (such as diagnostics!), are simply ignored.

## Manual Testing

Tested with tern completer which raises an error if the `.tern-project` file is not found.
Tested with a modified tern completer with the following `OnFileReadyToParse`:

```python
  def OnFileReadyToParse( self, request_data ):
    self._WarnIfMissingTernProject()

    module = extra_conf_store.ModuleForSourceFile( request_data[ 'filepath' ] )
    if not module:
      if not self._raised_extra_conf_warning:
        self._raised_extra_conf_warning = True
        raise NoExtraConfDetected
      return

    # use module!
```

Results all as expected:

- with no `.tern-config`: tern config warning (once)
- with `.tern-config`, but no `.ycm_extra_conf`: no extra conf warning (once)
- with `.tern-config` and `.ycm_extra_conf`: confirmation prompt (once)

## Automated Testing

This was a marathon. Or a mockathon, I suppose.

Briefly:

- I wrote a `event_notification_test.py` which tests these new `YouCompleteMe` object behaviours, via a complex series of mocking
- I had to refactor the `postcomplete_tests.py` to prevent it from leaving `MagicMock` instances all over the place because this lead to the above test failing (apparently) randomly
- In order to debug the above shenanigans I updated `run_tests.py` to allow passing arguments to nosetests, like we do in `ycmd`

There are more comments in the code and on the various commit messages, explaining what's going on.

With regard to style, there is quite a lot of:

```python

@contextlib.contextmanager
def something():
  with patch(this):
    with patch(that):
      with patch(the_other):
        yield

@patch( foo )
def do_a_test( foo ):
  with something():
    do_a_thing()
    assert foo.called()
```

The reason for this relative ugliness is that there is a lot of boilerplate mocking code. Because of a number of foibles of `mock` and the versions of python we support, I found this to be the least hacky way of ensuring that everything was cleaned up and was relatively sane to read and maintain.

Explanation for the `with`-ception that is going on:

- @patch is my preferred method, but it is more difficult to comment well and doesn't work when used with `@contextlib.contextmanager`, so you have to use `with`
- multiple managers in a single `with` statement is not supported in Python 2.6
- `contextlib.nested` is deprecated and buggy (apparently)

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/1848)
<!-- Reviewable:end -->
This commit is contained in:
Homu 2015-12-25 06:36:55 +09:00
commit f85b8427e0
5 changed files with 509 additions and 172 deletions

View File

@ -601,6 +601,7 @@ function! s:UpdateDiagnosticNotifications()
\ s:DiagnosticUiSupportedForCurrentFiletype()
if !should_display_diagnostics
py ycm_state.ValidateParseRequest()
return
endif

View File

@ -0,0 +1,286 @@
#
# Copyright (C) 2015 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 ycm.test_utils import MockVimModule
MockVimModule()
import contextlib, os
from ycm.youcompleteme import YouCompleteMe
from ycmd import user_options_store
from ycmd.responses import UnknownExtraConf
from mock import call, MagicMock, patch
# 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 object.
DEFAULT_CLIENT_OPTIONS = {
'server_log_level': 'info',
'extra_conf_vim_data': [],
}
def PostVimMessage_Call( message ):
"""Return a mock.call object for a call to vimsupport.PostVimMesasge with the
supplied message"""
return call( 'redraw | echohl WarningMsg | echom \''
+ message +
'\' | echohl None' )
def PresentDialog_Confirm_Call( message ):
"""Return a mock.call object for a call to vimsupport.PresentDialog, as called
why vimsupport.Confirm with the supplied confirmation message"""
return call( message, [ 'Ok', 'Cancel' ] )
@contextlib.contextmanager
def MockArbitraryBuffer( filetype, native_available = True ):
"""Used via the with statement, set up mocked versions of the vim module such
that a single buffer is open with an arbitrary name and arbirary contents. Its
filetype is set to the supplied filetype"""
with patch( 'vim.current' ) as vim_current:
def VimEval( value ):
"""Local mock of the vim.eval() function, used to ensure we get the
correct behvaiour"""
if value == '&omnifunc':
# The omnicompleter is not required here
return ''
if value == 'getbufvar(0, "&mod")':
# Ensure that we actually send the even to the server
return 1
if value == 'getbufvar(0, "&ft")' or value == '&filetype':
return filetype
raise ValueError( 'Unexpected evaluation' )
# Arbitrary, but valid, cursor position
vim_current.window.cursor = (1,2)
# Arbitrary, but valid, single buffer open
current_buffer = MagicMock()
current_buffer.number = 0
current_buffer.filename = os.path.realpath( 'TEST_BUFFER' )
current_buffer.name = 'TEST_BUFFER'
# The rest just mock up the Vim module so that our single arbitrary buffer
# makes sense to vimsupport module.
with patch( 'vim.buffers', [ current_buffer ] ):
with patch( 'vim.current.buffer', current_buffer ):
with patch( 'vim.eval', side_effect=VimEval ):
yield
@contextlib.contextmanager
def MockEventNotification( response_method, native_filetype_completer = True ):
"""Mock out the EventNotification client request object, replacing the
Response handler's JsonFromFuture with the supplied |response_method|.
Additionally mock out YouCompleteMe's FiletypeCompleterExistsForFiletype
method to return the supplied |native_filetype_completer| parameter, rather
than querying the server"""
# We don't want the event to actually be sent to the server, just have it
# return success
with patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync',
return_value = MagicMock( return_value=True ) ):
# We set up a fake a Response (as called by EventNotification.Response)
# which calls the supplied callback method. Generally this callback just
# raises an apropriate exception, otherwise it would have to return a mock
# future object.
#
# Note: JsonFromFuture is actually part of ycm.client.base_request, but we
# must patch where an object is looked up, not where it is defined.
# See https://docs.python.org/dev/library/unittest.mock.html#where-to-patch
# for details.
with patch( 'ycm.client.event_notification.JsonFromFuture',
side_effect = response_method ):
# Filetype available information comes from the server, so rather than
# relying on that request, we mock out the check. The caller decides if
# filetype completion is available
with patch(
'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = native_filetype_completer ):
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()
@patch( 'vim.command' )
def FileReadyToParse_NonDiagnostic_Error_test( self, vim_command ):
# This test validates the behaviour of YouCompleteMe.ValidateParseRequest in
# combination with YouCompleteMe.OnFileReadyToParse when the completer
# raises an exception handling FileReadyToParse event notification
ERROR_TEXT = 'Some completer response text'
def ErrorResponse( *args ):
raise RuntimeError( ERROR_TEXT )
with MockArbitraryBuffer( 'javascript' ):
with MockEventNotification( ErrorResponse ):
self.server_state.OnFileReadyToParse()
assert self.server_state.DiagnosticsForCurrentFileReady()
self.server_state.ValidateParseRequest()
# The first call raises a warning
vim_command.assert_has_calls( [
PostVimMessage_Call( ERROR_TEXT ),
] )
# Subsequent calls don't re-raise the warning
self.server_state.ValidateParseRequest()
vim_command.assert_has_calls( [
PostVimMessage_Call( ERROR_TEXT ),
] )
# But it does if a subsequent event raises again
self.server_state.OnFileReadyToParse()
assert self.server_state.DiagnosticsForCurrentFileReady()
self.server_state.ValidateParseRequest()
vim_command.assert_has_calls( [
PostVimMessage_Call( ERROR_TEXT ),
PostVimMessage_Call( ERROR_TEXT ),
] )
@patch( 'vim.command' )
def FileReadyToParse_NonDiagnostic_Error_NonNative_test( self, vim_command ):
with MockArbitraryBuffer( 'javascript' ):
with MockEventNotification( None, False ):
self.server_state.OnFileReadyToParse()
self.server_state.ValidateParseRequest()
vim_command.assert_not_called()
@patch( 'ycm.client.event_notification._LoadExtraConfFile' )
@patch( 'ycm.client.event_notification._IgnoreExtraConfFile' )
def FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
self,
ignore_extra_conf,
load_extra_conf,
*args ):
# This test validates the behaviour of YouCompleteMe.ValidateParseRequest in
# combination with YouCompleteMe.OnFileReadyToParse when the completer
# raises the (special) UnknownExtraConf exception
FILE_NAME = 'a_file'
MESSAGE = ( 'Found ' + FILE_NAME + '. Load? \n\n(Question can be '
'turned off with options, see YCM docs)' )
def UnknownExtraConfResponse( *args ):
raise UnknownExtraConf( FILE_NAME )
with MockArbitraryBuffer( 'javascript' ):
with MockEventNotification( UnknownExtraConfResponse ):
# When the user accepts the extra conf, we load it
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 0 ) as present_dialog:
self.server_state.OnFileReadyToParse()
assert self.server_state.DiagnosticsForCurrentFileReady()
self.server_state.ValidateParseRequest()
present_dialog.assert_has_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
] )
load_extra_conf.assert_has_calls( [
call( FILE_NAME ),
] )
# Subsequent calls don't re-raise the warning
self.server_state.ValidateParseRequest()
present_dialog.assert_has_calls( [
PresentDialog_Confirm_Call( MESSAGE )
] )
load_extra_conf.assert_has_calls( [
call( FILE_NAME ),
] )
# But it does if a subsequent event raises again
self.server_state.OnFileReadyToParse()
assert self.server_state.DiagnosticsForCurrentFileReady()
self.server_state.ValidateParseRequest()
present_dialog.assert_has_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
PresentDialog_Confirm_Call( MESSAGE ),
] )
load_extra_conf.assert_has_calls( [
call( FILE_NAME ),
call( FILE_NAME ),
] )
# When the user rejects the extra conf, we reject it
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 1 ) as present_dialog:
self.server_state.OnFileReadyToParse()
assert self.server_state.DiagnosticsForCurrentFileReady()
self.server_state.ValidateParseRequest()
present_dialog.assert_has_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
] )
ignore_extra_conf.assert_has_calls( [
call( FILE_NAME ),
] )
# Subsequent calls don't re-raise the warning
self.server_state.ValidateParseRequest()
present_dialog.assert_has_calls( [
PresentDialog_Confirm_Call( MESSAGE )
] )
ignore_extra_conf.assert_has_calls( [
call( FILE_NAME ),
] )
# But it does if a subsequent event raises again
self.server_state.OnFileReadyToParse()
assert self.server_state.DiagnosticsForCurrentFileReady()
self.server_state.ValidateParseRequest()
present_dialog.assert_has_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
PresentDialog_Confirm_Call( MESSAGE ),
] )
ignore_extra_conf.assert_has_calls( [
call( FILE_NAME ),
call( FILE_NAME ),
] )

View File

@ -17,28 +17,74 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from mock import ( MagicMock, DEFAULT )
from ycm.test_utils import MockVimModule
MockVimModule()
from mock import ( MagicMock, DEFAULT, patch )
from nose.tools import eq_
from hamcrest import assert_that, empty
from ycm import vimsupport
from ycm.youcompleteme import YouCompleteMe
def GetCompleteDoneHooks_ResultOnCsharp_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
import contextlib
def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None,
info = None, kind = None ):
def Result( variable ):
if variable == 'v:completed_item':
return {
'word': word,
'abbr': abbr,
'menu': menu,
'info': info,
'kind': kind,
}
else:
return DEFAULT
return MagicMock( side_effect = Result )
@contextlib.contextmanager
def _SetupForCsharpCompletionDone( completions ):
with patch( 'ycm.vimsupport.InsertNamespace' ):
with patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Test' ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
request = MagicMock();
request.Done = MagicMock( return_value = True )
request.RawResponse = MagicMock( return_value = completions )
ycm_state._latest_completion_request = request
yield ycm_state
def _BuildCompletion( namespace = None, insertion_text = 'Test',
menu_text = None, extra_menu_info = None,
detailed_info = None, kind = None ):
return {
'extra_data': { 'required_namespace_import' : namespace },
'insertion_text': insertion_text,
'menu_text': menu_text,
'extra_menu_info': extra_menu_info,
'kind': kind,
'detailed_info': detailed_info,
}
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "cs" ] )
def GetCompleteDoneHooks_ResultOnCsharp_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
result = ycm_state.GetCompleteDoneHooks()
eq_( 1, len( list( result ) ) )
def GetCompleteDoneHooks_EmptyOnOtherFiletype_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
def GetCompleteDoneHooks_EmptyOnOtherFiletype_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
result = ycm_state.GetCompleteDoneHooks()
eq_( 0, len( list( result ) ) )
def OnCompleteDone_WithActionCallsIt_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
def OnCompleteDone_WithActionCallsIt_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
action = MagicMock()
ycm_state._complete_done_hooks[ "txt" ] = action
@ -47,17 +93,18 @@ def OnCompleteDone_WithActionCallsIt_test():
assert action.called
def OnCompleteDone_NoActionNoError_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
def OnCompleteDone_NoActionNoError_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
ycm_state.OnCompleteDone()
def FilterToCompletedCompletions_NewVim_MatchIsReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
def FilterToCompletedCompletions_NewVim_MatchIsReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
@ -65,19 +112,21 @@ def FilterToCompletedCompletions_NewVim_MatchIsReturned_test():
eq_( list( result ), completions )
def FilterToCompletedCompletions_NewVim_ShortTextDoesntRaise_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'A' ) )
def FilterToCompletedCompletions_NewVim_ShortTextDoesntRaise_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "A" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._FilterToMatchingCompletions( completions, False )
def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Test' ) )
def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
@ -85,10 +134,11 @@ def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test():
eq_( list( result ), completions )
def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( ' Quote' ) )
def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
@ -96,10 +146,10 @@ def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test():
assert_that( list( result ), empty() )
def FilterToCompletedCompletions_OldVim_MatchIsReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = " Test" )
def FilterToCompletedCompletions_OldVim_MatchIsReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
@ -107,19 +157,19 @@ def FilterToCompletedCompletions_OldVim_MatchIsReturned_test():
eq_( list( result ), completions )
def FilterToCompletedCompletions_OldVim_ShortTextDoesntRaise_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "X" )
def FilterToCompletedCompletions_OldVim_ShortTextDoesntRaise_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._FilterToMatchingCompletions( completions, False )
def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "Test" )
def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
@ -127,10 +177,10 @@ def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test():
eq_( list( result ), completions )
def FilterToCompletedCompletions_OldVim_NonMatchIsntReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = " Quote" )
def FilterToCompletedCompletions_OldVim_NonMatchIsntReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
@ -138,10 +188,11 @@ def FilterToCompletedCompletions_OldVim_NonMatchIsntReturned_test():
assert_that( list( result ), empty() )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_MatchIsReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = " Te" )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_MatchIsReturned_test(
*args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Te" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
@ -149,19 +200,18 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_MatchIsReturned_test()
eq_( result, True )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ShortTextDoesntRaise_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "X" )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ShortTextDoesntRaise_test( *args ) :
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "Test" )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
@ -169,10 +219,10 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned
eq_( result, False )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_NonMatchIsntReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = " Quote" )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_NonMatchIsntReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
@ -180,10 +230,13 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_NonMatchIsntReturned_t
eq_( result, False )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_MatchIsReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( "Te") )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = " Quote" )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_MatchIsReturned_test(
*args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Te" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
@ -191,19 +244,24 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_MatchIsReturned_test()
eq_( result, True )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ShortTextDoesntRaise_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( "X") )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = " Quote" )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ShortTextDoesntRaise_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( "Test" ) )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned_test(
*args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
@ -211,10 +269,12 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned
eq_( result, False )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_NonMatchIsntReturned_test():
@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
@patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( " Quote" ) )
@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = ' Quote' )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_NonMatchIsntReturned_test( *args ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
@ -238,156 +298,119 @@ def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test():
def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfNotDone_test():
ycm_state = _SetupForCsharpCompletionDone( [] )
ycm_state._latest_completion_request.Done = MagicMock( return_value = False )
with _SetupForCsharpCompletionDone( [] ) as ycm_state:
ycm_state._latest_completion_request.Done = MagicMock( return_value = False )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_NewVim_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Te" )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
with patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Te' ) ):
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_OldVim_test():
def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_OldVim_test(
*args ):
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Te" )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
with patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( 'Te' ) ):
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_NewVim_test():
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_NewVim_test(
*args ):
info = [ "NS","Test", "Abbr", "Menu", "Info", "Kind" ]
completions = [ _BuildCompletion( *info ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] )
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
with patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test():
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test( *args ):
info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
completions = [ _BuildCompletion( *info ),
_BuildCompletion( insertion_text = "TestTest" ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] )
eq_( [ completions[ 0 ] ], ycm_state.GetCompletionsUserMayHaveCompleted() )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
with patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
eq_( [ completions[ 0 ] ],
ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_DontReturnMatchIfNontExactMatchesAndPartial_NewVim_test():
info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
completions = [ _BuildCompletion( insertion_text = info[ 0 ] ),
_BuildCompletion( insertion_text = "TestTest" ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
with patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_NewVim_test():
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_NewVim_test( *args ):
completions = [ _BuildCompletion( None ) ]
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
with patch( 'ycm.vimsupport.GetVariableValue',
GetVariableValue_CompleteItemIs( "Test" ) ):
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_OldVim_test( *args ):
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_OldVim_test():
def PostCompleteCsharp_EmptyDoesntInsertNamespace_test( *args ):
with _SetupForCsharpCompletionDone( [] ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
ycm_state._OnCompleteDone_Csharp()
assert not vimsupport.InsertNamespace.called
def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test( *args
):
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
ycm_state._OnCompleteDone_Csharp()
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
assert not vimsupport.InsertNamespace.called
def PostCompleteCsharp_EmptyDoesntInsertNamespace_test():
ycm_state = _SetupForCsharpCompletionDone( [] )
ycm_state._OnCompleteDone_Csharp()
assert not vimsupport.InsertNamespace.called
def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
ycm_state._OnCompleteDone_Csharp()
assert not vimsupport.InsertNamespace.called
def PostCompleteCsharp_ValueDoesInsertNamespace_test():
def PostCompleteCsharp_ValueDoesInsertNamespace_test( *args ):
namespace = "A_NAMESPACE"
completions = [ _BuildCompletion( namespace ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
ycm_state._OnCompleteDone_Csharp()
ycm_state._OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace )
vimsupport.InsertNamespace.assert_called_once_with( namespace )
def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test():
def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test( *args ):
namespace = "A_NAMESPACE"
namespace2 = "ANOTHER_NAMESPACE"
completions = [
_BuildCompletion( namespace ),
_BuildCompletion( namespace2 ),
]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.PresentDialog = MagicMock( return_value = 1 )
with _SetupForCsharpCompletionDone( completions ) as ycm_state:
with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
with patch( 'ycm.vimsupport.PresentDialog', return_value = 1 ):
ycm_state._OnCompleteDone_Csharp()
ycm_state._OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace2 )
def _SetupForCsharpCompletionDone( completions ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
request = MagicMock();
request.Done = MagicMock( return_value = True )
request.RawResponse = MagicMock( return_value = completions )
ycm_state._latest_completion_request = request
vimsupport.InsertNamespace = MagicMock()
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
return ycm_state
def _BuildCompletion( namespace = None, insertion_text = 'Test',
menu_text = None, extra_menu_info = None,
detailed_info = None, kind = None ):
return {
'extra_data': { 'required_namespace_import' : namespace },
'insertion_text': insertion_text,
'menu_text': menu_text,
'extra_menu_info': extra_menu_info,
'kind': kind,
'detailed_info': detailed_info,
}
def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None,
info = None, kind = None ):
def Result( variable ):
if variable == 'v:completed_item':
return {
'word': word,
'abbr': abbr,
'menu': menu,
'info': info,
'kind': kind,
}
else:
return DEFAULT
return MagicMock( side_effect = Result )
vimsupport.InsertNamespace.assert_called_once_with( namespace2 )

View File

@ -484,6 +484,32 @@ class YouCompleteMe( object ):
self.GetDiagnosticsFromStoredRequest() )
def ValidateParseRequest( self ):
if ( self.DiagnosticsForCurrentFileReady() and
self.NativeFiletypeCompletionUsable() ):
# YCM client has a hard-coded list of filetypes which are known to support
# diagnostics. These are found in autoload/youcompleteme.vim in
# s:diagnostic_ui_filetypes.
#
# For filetypes which don't support diagnostics, we just want to check the
# _latest_file_parse_request for any exception or UnknownExtraConf
# response, to allow the server to raise configuration warnings, etc.
# to the user. We ignore any other supplied data.
self._latest_file_parse_request.Response()
# We set the diagnostics request to None because we want to prevent
# repeated issuing of the same warnings/errors/prompts. Setting this to
# None makes DiagnosticsForCurrentFileReady return False until the next
# request is created.
#
# Note: it is the server's responsibility to determine the frequency of
# error/warning/prompts when receiving a FileReadyToParse event, but
# it our responsibility to ensure that we only apply the
# warning/error/prompt received once (for each event).
self._latest_file_parse_request = None
def ShowDetailedDiagnostic( self ):
if not self.IsServerAlive():
return

View File

@ -38,7 +38,7 @@ def ParseArguments():
parser.add_argument( '--skip-build', action = 'store_true',
help = 'Do not build ycmd before testing.' )
return parser.parse_args()
return parser.parse_known_args()
def BuildYcmdLibs( args ):
@ -49,19 +49,20 @@ def BuildYcmdLibs( args ):
] )
def NoseTests():
def NoseTests( extra_args ):
subprocess.check_call( [
'nosetests',
'-v',
'-w',
p.join( DIR_OF_THIS_SCRIPT, 'python' )
] )
] + extra_args )
def Main():
parsed_args = ParseArguments()
( parsed_args, extra_args ) = ParseArguments()
RunFlake8()
BuildYcmdLibs( parsed_args )
NoseTests()
NoseTests( extra_args )
if __name__ == "__main__":
Main()