From 861546fac03c6fd16646cc09fc11013145d6041f Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sun, 13 Dec 2015 19:28:24 +0000 Subject: [PATCH 1/4] Display errors raised by OnFileReadyToParse in non-diagnostics filetypes --- autoload/youcompleteme.vim | 1 + python/ycm/youcompleteme.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 9158331f..8836b275 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -601,6 +601,7 @@ function! s:UpdateDiagnosticNotifications() \ s:DiagnosticUiSupportedForCurrentFiletype() if !should_display_diagnostics + py ycm_state.ValidateParseRequest() return endif diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index b5b84011..b5a7f6a9 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -484,6 +484,29 @@ class YouCompleteMe( object ): self.GetDiagnosticsFromStoredRequest() ) + def ValidateParseRequest( self ): + if ( self.DiagnosticsForCurrentFileReady() and + self.NativeFiletypeCompletionUsable() ): + + # For filetypes which don't support diagnostics, we just want to check the + # _latest_file_parse_request for any exception or UnknownExtraConf + # response. We don't actually use the results (which are diagnositcs) + # otherwise. FIXME: should the client even _know_ which filetypes support + # diagnostics? Even if it should, it should be told by the sever. + 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 From 6029a672eee4000fb937156444dc8235889efc84 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 21 Dec 2015 19:38:56 +0000 Subject: [PATCH 2/4] Prevent postcomplete_tests.py from leaving mock object lying around Previously, running postcomplete_tests.py could lead to MagicMock objects being left around within the ycm modules. This lead to random test failures in other modules. Further, by using mock.patch appropriately, tests withing postcomplete_tests.py no longer rely on mocking performed by previous tests (and can be successfully run individually) --- python/ycm/tests/postcomplete_tests.py | 357 +++++++++++++------------ 1 file changed, 190 insertions(+), 167 deletions(-) diff --git a/python/ycm/tests/postcomplete_tests.py b/python/ycm/tests/postcomplete_tests.py index caa6de1f..004dd482 100644 --- a/python/ycm/tests/postcomplete_tests.py +++ b/python/ycm/tests/postcomplete_tests.py @@ -17,28 +17,74 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -from mock import ( MagicMock, DEFAULT ) +from ..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 ) From 636279cc585823bfb72a709d35a4be93ea8a5ab5 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 21 Dec 2015 19:40:51 +0000 Subject: [PATCH 3/4] Allow running a subset of tests Like we do in ycmd, allow additional arguments of run_tests to be passed directly to nosetests. In order to maintain the existing behaviour, and not require fiddly command lines, always run nosetests in the python directory (using the -w - working directory) switch. --- run_tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/run_tests.py b/run_tests.py index 9dc18542..f2427921 100755 --- a/run_tests.py +++ b/run_tests.py @@ -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() From 0b07b7467d65d798f5ee3c19a3170432b874701d Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 22 Dec 2015 23:28:36 +0000 Subject: [PATCH 4/4] Add tests for YouCompleteMe.ValidateParseRequest Most of the code is mocking boilerplate but it shows that the behaviour is as defined in the comments. --- python/ycm/tests/event_notification_test.py | 286 ++++++++++++++++++++ python/ycm/tests/postcomplete_tests.py | 2 +- python/ycm/youcompleteme.py | 9 +- 3 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 python/ycm/tests/event_notification_test.py diff --git a/python/ycm/tests/event_notification_test.py b/python/ycm/tests/event_notification_test.py new file mode 100644 index 00000000..f7ee868f --- /dev/null +++ b/python/ycm/tests/event_notification_test.py @@ -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 . + +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 ), + ] ) diff --git a/python/ycm/tests/postcomplete_tests.py b/python/ycm/tests/postcomplete_tests.py index 004dd482..0efda8fb 100644 --- a/python/ycm/tests/postcomplete_tests.py +++ b/python/ycm/tests/postcomplete_tests.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -from ..test_utils import MockVimModule +from ycm.test_utils import MockVimModule MockVimModule() from mock import ( MagicMock, DEFAULT, patch ) diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index b5a7f6a9..6e86646f 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -488,11 +488,14 @@ class YouCompleteMe( object ): 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. We don't actually use the results (which are diagnositcs) - # otherwise. FIXME: should the client even _know_ which filetypes support - # diagnostics? Even if it should, it should be told by the sever. + # 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