event_notification_test.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. # coding: utf-8
  2. #
  3. # Copyright (C) 2015-2016 YouCompleteMe contributors
  4. #
  5. # This file is part of YouCompleteMe.
  6. #
  7. # YouCompleteMe is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # YouCompleteMe is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  19. from __future__ import unicode_literals
  20. from __future__ import print_function
  21. from __future__ import division
  22. from __future__ import absolute_import
  23. # Not installing aliases from python-future; it's unreliable and slow.
  24. from builtins import * # noqa
  25. from ycm.tests.test_utils import ( CurrentWorkingDirectory, ExtendedMock,
  26. MockVimBuffers, MockVimModule, VimBuffer )
  27. MockVimModule()
  28. import contextlib
  29. import os
  30. from ycm.tests import PathToTestFile, YouCompleteMeInstance, WaitUntilReady
  31. from ycmd.responses import ( BuildDiagnosticData, Diagnostic, Location, Range,
  32. UnknownExtraConf, ServerError )
  33. from hamcrest import ( assert_that, contains, has_entries, has_entry, has_item,
  34. has_items, has_key, is_not )
  35. from mock import call, MagicMock, patch
  36. from nose.tools import eq_, ok_
  37. def PresentDialog_Confirm_Call( message ):
  38. """Return a mock.call object for a call to vimsupport.PresentDialog, as called
  39. why vimsupport.Confirm with the supplied confirmation message"""
  40. return call( message, [ 'Ok', 'Cancel' ] )
  41. def PlaceSign_Call( sign_id, line_num, buffer_num, is_error ):
  42. sign_name = 'YcmError' if is_error else 'YcmWarning'
  43. return call( 'sign place {0} name={1} line={2} buffer={3}'
  44. .format( sign_id, sign_name, line_num, buffer_num ) )
  45. def UnplaceSign_Call( sign_id, buffer_num ):
  46. return call( 'try | exec "sign unplace {0} buffer={1}" |'
  47. ' catch /E158/ | endtry'.format( sign_id, buffer_num ) )
  48. @contextlib.contextmanager
  49. def MockArbitraryBuffer( filetype, ycm ):
  50. """Used via the with statement, set up a single buffer with an arbitrary name
  51. and no contents. Its filetype is set to the supplied filetype."""
  52. # Arbitrary, but valid, single buffer open.
  53. current_buffer = VimBuffer( os.path.realpath( 'TEST_BUFFER' ),
  54. window = 1,
  55. filetype = filetype )
  56. with MockVimBuffers( [ current_buffer ], current_buffer, ycm_state = ycm ):
  57. yield
  58. @contextlib.contextmanager
  59. def MockEventNotification( response_method, native_filetype_completer = True ):
  60. """Mock out the EventNotification client request object, replacing the
  61. Response handler's JsonFromFuture with the supplied |response_method|.
  62. Additionally mock out YouCompleteMe's FiletypeCompleterExistsForFiletype
  63. method to return the supplied |native_filetype_completer| parameter, rather
  64. than querying the server"""
  65. # We don't want the event to actually be sent to the server, just have it
  66. # return success
  67. with patch( 'ycm.client.event_notification.EventNotification.'
  68. 'PostDataToHandlerAsync',
  69. return_value = MagicMock( return_value=True ) ):
  70. # We set up a fake a Response (as called by EventNotification.Response)
  71. # which calls the supplied callback method. Generally this callback just
  72. # raises an apropriate exception, otherwise it would have to return a mock
  73. # future object.
  74. #
  75. # Note: JsonFromFuture is actually part of ycm.client.base_request, but we
  76. # must patch where an object is looked up, not where it is defined.
  77. # See https://docs.python.org/dev/library/unittest.mock.html#where-to-patch
  78. # for details.
  79. with patch( 'ycm.client.event_notification.JsonFromFuture',
  80. side_effect = response_method ):
  81. # Filetype available information comes from the server, so rather than
  82. # relying on that request, we mock out the check. The caller decides if
  83. # filetype completion is available
  84. with patch(
  85. 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
  86. return_value = native_filetype_completer ):
  87. with patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReady',
  88. return_value = True ):
  89. yield
  90. @patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
  91. @YouCompleteMeInstance()
  92. def EventNotification_FileReadyToParse_NonDiagnostic_Error_test(
  93. ycm, post_vim_message ):
  94. # This test validates the behaviour of YouCompleteMe.HandleFileParseRequest
  95. # in combination with YouCompleteMe.OnFileReadyToParse when the completer
  96. # raises an exception handling FileReadyToParse event notification
  97. ERROR_TEXT = 'Some completer response text'
  98. def ErrorResponse( *args ):
  99. raise ServerError( ERROR_TEXT )
  100. with MockArbitraryBuffer( 'javascript', ycm ):
  101. with MockEventNotification( ErrorResponse ):
  102. ycm.OnFileReadyToParse()
  103. ok_( ycm.FileParseRequestReady() )
  104. ycm.HandleFileParseRequest()
  105. # The first call raises a warning
  106. post_vim_message.assert_has_exact_calls( [
  107. call( ERROR_TEXT, truncate = True )
  108. ] )
  109. # Subsequent calls don't re-raise the warning
  110. ycm.HandleFileParseRequest()
  111. post_vim_message.assert_has_exact_calls( [
  112. call( ERROR_TEXT, truncate = True )
  113. ] )
  114. # But it does if a subsequent event raises again
  115. ycm.OnFileReadyToParse()
  116. ok_( ycm.FileParseRequestReady() )
  117. ycm.HandleFileParseRequest()
  118. post_vim_message.assert_has_exact_calls( [
  119. call( ERROR_TEXT, truncate = True ),
  120. call( ERROR_TEXT, truncate = True )
  121. ] )
  122. @patch( 'vim.command' )
  123. @YouCompleteMeInstance()
  124. def EventNotification_FileReadyToParse_NonDiagnostic_Error_NonNative_test(
  125. ycm, vim_command ):
  126. with MockArbitraryBuffer( 'javascript', ycm ):
  127. with MockEventNotification( None, False ):
  128. ycm.OnFileReadyToParse()
  129. ycm.HandleFileParseRequest()
  130. vim_command.assert_not_called()
  131. @patch( 'ycm.client.base_request._LoadExtraConfFile',
  132. new_callable = ExtendedMock )
  133. @patch( 'ycm.client.base_request._IgnoreExtraConfFile',
  134. new_callable = ExtendedMock )
  135. @YouCompleteMeInstance()
  136. def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
  137. ycm, ignore_extra_conf, load_extra_conf ):
  138. # This test validates the behaviour of YouCompleteMe.HandleFileParseRequest
  139. # in combination with YouCompleteMe.OnFileReadyToParse when the completer
  140. # raises the (special) UnknownExtraConf exception
  141. FILE_NAME = 'a_file'
  142. MESSAGE = ( 'Found ' + FILE_NAME + '. Load? \n\n(Question can be '
  143. 'turned off with options, see YCM docs)' )
  144. def UnknownExtraConfResponse( *args ):
  145. raise UnknownExtraConf( FILE_NAME )
  146. with MockArbitraryBuffer( 'javascript', ycm ):
  147. with MockEventNotification( UnknownExtraConfResponse ):
  148. # When the user accepts the extra conf, we load it
  149. with patch( 'ycm.vimsupport.PresentDialog',
  150. return_value = 0,
  151. new_callable = ExtendedMock ) as present_dialog:
  152. ycm.OnFileReadyToParse()
  153. ok_( ycm.FileParseRequestReady() )
  154. ycm.HandleFileParseRequest()
  155. present_dialog.assert_has_exact_calls( [
  156. PresentDialog_Confirm_Call( MESSAGE ),
  157. ] )
  158. load_extra_conf.assert_has_exact_calls( [
  159. call( FILE_NAME ),
  160. ] )
  161. # Subsequent calls don't re-raise the warning
  162. ycm.HandleFileParseRequest()
  163. present_dialog.assert_has_exact_calls( [
  164. PresentDialog_Confirm_Call( MESSAGE )
  165. ] )
  166. load_extra_conf.assert_has_exact_calls( [
  167. call( FILE_NAME ),
  168. ] )
  169. # But it does if a subsequent event raises again
  170. ycm.OnFileReadyToParse()
  171. ok_( ycm.FileParseRequestReady() )
  172. ycm.HandleFileParseRequest()
  173. present_dialog.assert_has_exact_calls( [
  174. PresentDialog_Confirm_Call( MESSAGE ),
  175. PresentDialog_Confirm_Call( MESSAGE ),
  176. ] )
  177. load_extra_conf.assert_has_exact_calls( [
  178. call( FILE_NAME ),
  179. call( FILE_NAME ),
  180. ] )
  181. # When the user rejects the extra conf, we reject it
  182. with patch( 'ycm.vimsupport.PresentDialog',
  183. return_value = 1,
  184. new_callable = ExtendedMock ) as present_dialog:
  185. ycm.OnFileReadyToParse()
  186. ok_( ycm.FileParseRequestReady() )
  187. ycm.HandleFileParseRequest()
  188. present_dialog.assert_has_exact_calls( [
  189. PresentDialog_Confirm_Call( MESSAGE ),
  190. ] )
  191. ignore_extra_conf.assert_has_exact_calls( [
  192. call( FILE_NAME ),
  193. ] )
  194. # Subsequent calls don't re-raise the warning
  195. ycm.HandleFileParseRequest()
  196. present_dialog.assert_has_exact_calls( [
  197. PresentDialog_Confirm_Call( MESSAGE )
  198. ] )
  199. ignore_extra_conf.assert_has_exact_calls( [
  200. call( FILE_NAME ),
  201. ] )
  202. # But it does if a subsequent event raises again
  203. ycm.OnFileReadyToParse()
  204. ok_( ycm.FileParseRequestReady() )
  205. ycm.HandleFileParseRequest()
  206. present_dialog.assert_has_exact_calls( [
  207. PresentDialog_Confirm_Call( MESSAGE ),
  208. PresentDialog_Confirm_Call( MESSAGE ),
  209. ] )
  210. ignore_extra_conf.assert_has_exact_calls( [
  211. call( FILE_NAME ),
  212. call( FILE_NAME ),
  213. ] )
  214. @YouCompleteMeInstance()
  215. def EventNotification_FileReadyToParse_Diagnostic_Error_Native_test( ycm ):
  216. _Check_FileReadyToParse_Diagnostic_Error( ycm )
  217. _Check_FileReadyToParse_Diagnostic_Warning( ycm )
  218. _Check_FileReadyToParse_Diagnostic_Clean( ycm )
  219. @patch( 'vim.command' )
  220. def _Check_FileReadyToParse_Diagnostic_Error( ycm, vim_command ):
  221. # Tests Vim sign placement and error/warning count python API
  222. # when one error is returned.
  223. def DiagnosticResponse( *args ):
  224. start = Location( 1, 2, 'TEST_BUFFER' )
  225. end = Location( 1, 4, 'TEST_BUFFER' )
  226. extent = Range( start, end )
  227. diagnostic = Diagnostic( [], start, extent, 'expected ;', 'ERROR' )
  228. return [ BuildDiagnosticData( diagnostic ) ]
  229. with MockArbitraryBuffer( 'cpp', ycm ):
  230. with MockEventNotification( DiagnosticResponse ):
  231. ycm.OnFileReadyToParse()
  232. ok_( ycm.FileParseRequestReady() )
  233. ycm.HandleFileParseRequest()
  234. vim_command.assert_has_calls( [
  235. PlaceSign_Call( 1, 1, 1, True )
  236. ] )
  237. eq_( ycm.GetErrorCount(), 1 )
  238. eq_( ycm.GetWarningCount(), 0 )
  239. # Consequent calls to HandleFileParseRequest shouldn't mess with
  240. # existing diagnostics, when there is no new parse request.
  241. vim_command.reset_mock()
  242. ycm.HandleFileParseRequest()
  243. vim_command.assert_not_called()
  244. eq_( ycm.GetErrorCount(), 1 )
  245. eq_( ycm.GetWarningCount(), 0 )
  246. @patch( 'vim.command' )
  247. def _Check_FileReadyToParse_Diagnostic_Warning( ycm, vim_command ):
  248. # Tests Vim sign placement/unplacement and error/warning count python API
  249. # when one warning is returned.
  250. # Should be called after _Check_FileReadyToParse_Diagnostic_Error
  251. def DiagnosticResponse( *args ):
  252. start = Location( 2, 2, 'TEST_BUFFER' )
  253. end = Location( 2, 4, 'TEST_BUFFER' )
  254. extent = Range( start, end )
  255. diagnostic = Diagnostic( [], start, extent, 'cast', 'WARNING' )
  256. return [ BuildDiagnosticData( diagnostic ) ]
  257. with MockArbitraryBuffer( 'cpp', ycm ):
  258. with MockEventNotification( DiagnosticResponse ):
  259. ycm.OnFileReadyToParse()
  260. ok_( ycm.FileParseRequestReady() )
  261. ycm.HandleFileParseRequest()
  262. vim_command.assert_has_calls( [
  263. PlaceSign_Call( 2, 2, 1, False ),
  264. UnplaceSign_Call( 1, 1 )
  265. ] )
  266. eq_( ycm.GetErrorCount(), 0 )
  267. eq_( ycm.GetWarningCount(), 1 )
  268. # Consequent calls to HandleFileParseRequest shouldn't mess with
  269. # existing diagnostics, when there is no new parse request.
  270. vim_command.reset_mock()
  271. ycm.HandleFileParseRequest()
  272. vim_command.assert_not_called()
  273. eq_( ycm.GetErrorCount(), 0 )
  274. eq_( ycm.GetWarningCount(), 1 )
  275. @patch( 'vim.command' )
  276. def _Check_FileReadyToParse_Diagnostic_Clean( ycm, vim_command ):
  277. # Tests Vim sign unplacement and error/warning count python API
  278. # when there are no errors/warnings left.
  279. # Should be called after _Check_FileReadyToParse_Diagnostic_Warning
  280. with MockArbitraryBuffer( 'cpp', ycm ):
  281. with MockEventNotification( MagicMock( return_value = [] ) ):
  282. ycm.OnFileReadyToParse()
  283. ycm.HandleFileParseRequest()
  284. vim_command.assert_has_calls( [
  285. UnplaceSign_Call( 2, 1 )
  286. ] )
  287. eq_( ycm.GetErrorCount(), 0 )
  288. eq_( ycm.GetWarningCount(), 0 )
  289. @patch( 'ycm.youcompleteme.YouCompleteMe._AddUltiSnipsDataIfNeeded' )
  290. @patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReady',
  291. return_value = True )
  292. @YouCompleteMeInstance( { 'collect_identifiers_from_tags_files': 1 } )
  293. def EventNotification_FileReadyToParse_TagFiles_UnicodeWorkingDirectory_test(
  294. ycm, *args ):
  295. unicode_dir = PathToTestFile( 'uni¢𐍈d€' )
  296. current_buffer_file = PathToTestFile( 'uni¢𐍈d€', 'current_buffer' )
  297. current_buffer = VimBuffer( name = current_buffer_file,
  298. contents = [ 'current_buffer_contents' ],
  299. filetype = 'some_filetype' )
  300. with patch( 'ycm.client.event_notification.EventNotification.'
  301. 'PostDataToHandlerAsync' ) as post_data_to_handler_async:
  302. with CurrentWorkingDirectory( unicode_dir ):
  303. with MockVimBuffers( [ current_buffer ], current_buffer, ( 6, 5 ), ycm ):
  304. ycm.OnFileReadyToParse()
  305. assert_that(
  306. # Positional arguments passed to PostDataToHandlerAsync.
  307. post_data_to_handler_async.call_args[ 0 ],
  308. contains(
  309. has_entries( {
  310. 'filepath': current_buffer_file,
  311. 'line_num': 6,
  312. 'column_num': 6,
  313. 'file_data': has_entries( {
  314. current_buffer_file: has_entries( {
  315. 'contents': 'current_buffer_contents\n',
  316. 'filetypes': [ 'some_filetype' ]
  317. } )
  318. } ),
  319. 'event_name': 'FileReadyToParse',
  320. 'tag_files': has_item( PathToTestFile( 'uni¢𐍈d€', 'tags' ) )
  321. } ),
  322. 'event_notification'
  323. )
  324. )
  325. @patch( 'ycm.youcompleteme.YouCompleteMe._AddUltiSnipsDataIfNeeded' )
  326. @YouCompleteMeInstance()
  327. def EventNotification_BufferVisit_BuildRequestForCurrentAndUnsavedBuffers_test(
  328. ycm, *args ):
  329. current_buffer_file = os.path.realpath( 'current_buffer' )
  330. current_buffer = VimBuffer( name = current_buffer_file,
  331. number = 1,
  332. contents = [ 'current_buffer_contents' ],
  333. filetype = 'some_filetype',
  334. modified = False )
  335. modified_buffer_file = os.path.realpath( 'modified_buffer' )
  336. modified_buffer = VimBuffer( name = modified_buffer_file,
  337. number = 2,
  338. contents = [ 'modified_buffer_contents' ],
  339. filetype = 'some_filetype',
  340. modified = True )
  341. unmodified_buffer_file = os.path.realpath( 'unmodified_buffer' )
  342. unmodified_buffer = VimBuffer( name = unmodified_buffer_file,
  343. number = 3,
  344. contents = [ 'unmodified_buffer_contents' ],
  345. filetype = 'some_filetype',
  346. modified = False )
  347. with patch( 'ycm.client.event_notification.EventNotification.'
  348. 'PostDataToHandlerAsync' ) as post_data_to_handler_async:
  349. with MockVimBuffers( [ current_buffer, modified_buffer, unmodified_buffer ],
  350. current_buffer,
  351. ( 3, 5 ) ):
  352. ycm.OnBufferVisit()
  353. assert_that(
  354. # Positional arguments passed to PostDataToHandlerAsync.
  355. post_data_to_handler_async.call_args[ 0 ],
  356. contains(
  357. has_entries( {
  358. 'filepath': current_buffer_file,
  359. 'line_num': 3,
  360. 'column_num': 6,
  361. 'file_data': has_entries( {
  362. current_buffer_file: has_entries( {
  363. 'contents': 'current_buffer_contents\n',
  364. 'filetypes': [ 'some_filetype' ]
  365. } ),
  366. modified_buffer_file: has_entries( {
  367. 'contents': 'modified_buffer_contents\n',
  368. 'filetypes': [ 'some_filetype' ]
  369. } )
  370. } ),
  371. 'event_name': 'BufferVisit'
  372. } ),
  373. 'event_notification'
  374. )
  375. )
  376. @YouCompleteMeInstance()
  377. def EventNotification_BufferUnload_BuildRequestForDeletedAndUnsavedBuffers_test(
  378. ycm ):
  379. current_buffer_file = os.path.realpath( 'current_buffer' )
  380. current_buffer = VimBuffer( name = current_buffer_file,
  381. number = 1,
  382. contents = [ 'current_buffer_contents' ],
  383. filetype = 'some_filetype',
  384. modified = True )
  385. deleted_buffer_file = os.path.realpath( 'deleted_buffer' )
  386. deleted_buffer = VimBuffer( name = deleted_buffer_file,
  387. number = 2,
  388. contents = [ 'deleted_buffer_contents' ],
  389. filetype = 'some_filetype',
  390. modified = False )
  391. with patch( 'ycm.client.event_notification.EventNotification.'
  392. 'PostDataToHandlerAsync' ) as post_data_to_handler_async:
  393. with MockVimBuffers( [ current_buffer, deleted_buffer ], current_buffer ):
  394. ycm.OnBufferUnload( deleted_buffer_file )
  395. assert_that(
  396. # Positional arguments passed to PostDataToHandlerAsync.
  397. post_data_to_handler_async.call_args[ 0 ],
  398. contains(
  399. has_entries( {
  400. 'filepath': deleted_buffer_file,
  401. 'line_num': 1,
  402. 'column_num': 1,
  403. 'file_data': has_entries( {
  404. current_buffer_file: has_entries( {
  405. 'contents': 'current_buffer_contents\n',
  406. 'filetypes': [ 'some_filetype' ]
  407. } ),
  408. deleted_buffer_file: has_entries( {
  409. 'contents': 'deleted_buffer_contents\n',
  410. 'filetypes': [ 'some_filetype' ]
  411. } )
  412. } ),
  413. 'event_name': 'BufferUnload'
  414. } ),
  415. 'event_notification'
  416. )
  417. )
  418. @patch( 'ycm.syntax_parse.SyntaxKeywordsForCurrentBuffer',
  419. return_value = [ 'foo', 'bar' ] )
  420. @patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReady',
  421. return_value = True )
  422. @YouCompleteMeInstance( { 'seed_identifiers_with_syntax': 1 } )
  423. def EventNotification_FileReadyToParse_SyntaxKeywords_SeedWithCache_test(
  424. ycm, *args ):
  425. current_buffer = VimBuffer( name = 'current_buffer',
  426. filetype = 'some_filetype' )
  427. with patch( 'ycm.client.event_notification.EventNotification.'
  428. 'PostDataToHandlerAsync' ) as post_data_to_handler_async:
  429. with MockVimBuffers( [ current_buffer ], current_buffer, ycm_state = ycm ):
  430. ycm.OnFileReadyToParse()
  431. assert_that(
  432. # Positional arguments passed to PostDataToHandlerAsync.
  433. post_data_to_handler_async.call_args[ 0 ],
  434. contains(
  435. has_entry( 'syntax_keywords', has_items( 'foo', 'bar' ) ),
  436. 'event_notification'
  437. )
  438. )
  439. # Do not send again syntax keywords in subsequent requests.
  440. ycm.OnFileReadyToParse()
  441. assert_that(
  442. # Positional arguments passed to PostDataToHandlerAsync.
  443. post_data_to_handler_async.call_args[ 0 ],
  444. contains(
  445. is_not( has_key( 'syntax_keywords' ) ),
  446. 'event_notification'
  447. )
  448. )
  449. @patch( 'ycm.syntax_parse.SyntaxKeywordsForCurrentBuffer',
  450. return_value = [ 'foo', 'bar' ] )
  451. @patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReady',
  452. return_value = True )
  453. @YouCompleteMeInstance( { 'seed_identifiers_with_syntax': 1 } )
  454. def EventNotification_FileReadyToParse_SyntaxKeywords_ClearCacheIfRestart_test(
  455. ycm, *args ):
  456. current_buffer = VimBuffer( name = 'current_buffer',
  457. filetype = 'some_filetype' )
  458. with patch( 'ycm.client.event_notification.EventNotification.'
  459. 'PostDataToHandlerAsync' ) as post_data_to_handler_async:
  460. with MockVimBuffers( [ current_buffer ], current_buffer, ycm_state = ycm ):
  461. ycm.OnFileReadyToParse()
  462. assert_that(
  463. # Positional arguments passed to PostDataToHandlerAsync.
  464. post_data_to_handler_async.call_args[ 0 ],
  465. contains(
  466. has_entry( 'syntax_keywords', has_items( 'foo', 'bar' ) ),
  467. 'event_notification'
  468. )
  469. )
  470. # Send again the syntax keywords after restarting the server.
  471. ycm.RestartServer()
  472. WaitUntilReady()
  473. ycm.OnFileReadyToParse()
  474. assert_that(
  475. # Positional arguments passed to PostDataToHandlerAsync.
  476. post_data_to_handler_async.call_args[ 0 ],
  477. contains(
  478. has_entry( 'syntax_keywords', has_items( 'foo', 'bar' ) ),
  479. 'event_notification'
  480. )
  481. )