1# Copyright (C) 2017-2020 ycmd contributors
2#
3# This file is part of ycmd.
4#
5# ycmd is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# ycmd is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with ycmd.  If not, see <http://www.gnu.org/licenses/>.
17
18import time
19from hamcrest import ( assert_that,
20                       contains_exactly,
21                       contains_inanyorder,
22                       empty,
23                       equal_to,
24                       has_entries,
25                       has_entry,
26                       instance_of,
27                       is_not,
28                       matches_regexp )
29from pprint import pformat
30import requests
31import pytest
32import json
33
34from ycmd.utils import ReadFile
35from ycmd.completers.java.java_completer import NO_DOCUMENTATION_MESSAGE
36from ycmd.tests.java import ( PathToTestFile,
37                              SharedYcmd,
38                              StartJavaCompleterServerWithFile,
39                              IsolatedYcmd )
40from ycmd.tests.test_utils import ( BuildRequest,
41                                    ChunkMatcher,
42                                    CombineRequest,
43                                    ErrorMatcher,
44                                    ExpectedFailure,
45                                    LocationMatcher,
46                                    WithRetry )
47from unittest.mock import patch
48from ycmd import handlers
49from ycmd.completers.language_server import language_server_protocol as lsp
50from ycmd.completers.language_server.language_server_completer import (
51  ResponseTimeoutException,
52  ResponseFailedException
53)
54from ycmd.responses import UnknownExtraConf
55
56TESTLAUNCHER_JAVA = PathToTestFile( 'simple_eclipse_project',
57                                    'src',
58                                    'com',
59                                    'test',
60                                    'TestLauncher.java' )
61
62TEST_JAVA = PathToTestFile( 'simple_eclipse_project',
63                            'src',
64                            'com',
65                            'youcompleteme',
66                            'Test.java' )
67
68TSET_JAVA = PathToTestFile( 'simple_eclipse_project',
69                            'src',
70                            'com',
71                            'youcompleteme',
72                            'testing',
73                            'Tset.java' )
74
75
76@WithRetry
77@SharedYcmd
78def Subcommands_DefinedSubcommands_test( app ):
79  subcommands_data = BuildRequest( completer_target = 'java' )
80
81  assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json,
82               contains_inanyorder(
83                 'FixIt',
84                 'ExecuteCommand',
85                 'Format',
86                 'GoToDeclaration',
87                 'GoToDefinition',
88                 'GoToDocumentOutline',
89                 'GoTo',
90                 'GetDoc',
91                 'GetType',
92                 'GoToImplementation',
93                 'GoToReferences',
94                 'GoToType',
95                 'GoToSymbol',
96                 'OpenProject',
97                 'OrganizeImports',
98                 'RefactorRename',
99                 'RestartServer',
100                 'WipeWorkspace' ) )
101
102
103@pytest.mark.parametrize( 'cmd,arguments', [
104  ( 'GoTo', [] ),
105  ( 'GoToDeclaration', [] ),
106  ( 'GoToDefinition', [] ),
107  ( 'GoToReferences', [] ),
108  ( 'GoToDocumentOutline', [] ),
109  ( 'GoToSymbol', [ 'test' ] ),
110  ( 'GetType', [] ),
111  ( 'GetDoc', [] ),
112  ( 'FixIt', [] ),
113  ( 'Format', [] ),
114  ( 'OrganizeImports', [] ),
115  ( 'RefactorRename', [ 'test' ] ),
116] )
117@SharedYcmd
118def Subcommands_ServerNotInitialized_test( app, cmd, arguments ):
119  filepath = PathToTestFile( 'simple_eclipse_project',
120                             'src',
121                             'com',
122                             'test',
123                             'AbstractTestWidget.java' )
124
125  completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] )
126
127  @patch.object( completer, '_ServerIsInitialized', return_value = False )
128  def Test( app, cmd, arguments, *args ):
129    RunTest( app, {
130      'description': 'Subcommand ' + cmd + ' handles server not ready',
131      'request': {
132        'command': cmd,
133        'line_num': 1,
134        'column_num': 1,
135        'filepath': filepath,
136        'arguments': arguments,
137      },
138      'expect': {
139        'response': requests.codes.internal_server_error,
140        'data': ErrorMatcher( RuntimeError,
141                              'Server is initializing. Please wait.' ),
142      }
143    } )
144
145  Test( app, cmd, arguments )
146
147
148def RunTest( app, test, contents = None ):
149  if not contents:
150    contents = ReadFile( test[ 'request' ][ 'filepath' ] )
151
152  # Because we aren't testing this command, we *always* ignore errors. This
153  # is mainly because we (may) want to test scenarios where the completer
154  # throws an exception.
155  app.post_json( '/event_notification',
156                 CombineRequest( test[ 'request' ], {
157                                 'event_name': 'FileReadyToParse',
158                                 'contents': contents,
159                                 'filetype': 'java',
160                                 } ),
161                 expect_errors = True )
162
163  # We also ignore errors here, but then we check the response code
164  # ourself. This is to allow testing of requests returning errors.
165  expiry = time.time() + 10
166  while True:
167    try:
168      response = app.post_json(
169        '/run_completer_command',
170        CombineRequest( test[ 'request' ], {
171          'completer_target': 'filetype_default',
172          'contents': contents,
173          'filetype': 'java',
174          'command_arguments': ( [ test[ 'request' ][ 'command' ] ]
175                                 + test[ 'request' ].get( 'arguments', [] ) )
176        } ),
177        expect_errors = True
178      )
179
180      print( f'completer response: { pformat( response.json ) }' )
181
182      assert_that( response.status_code,
183                   equal_to( test[ 'expect' ][ 'response' ] ) )
184
185      assert_that( response.json, test[ 'expect' ][ 'data' ] )
186      break
187    except AssertionError:
188      if time.time() > expiry:
189        raise
190
191      time.sleep( 0.25 )
192
193
194@WithRetry
195@SharedYcmd
196def Subcommands_GetDoc_NoDoc_test( app ):
197  filepath = PathToTestFile( 'simple_eclipse_project',
198                             'src',
199                             'com',
200                             'test',
201                             'AbstractTestWidget.java' )
202  contents = ReadFile( filepath )
203
204  event_data = BuildRequest( filepath = filepath,
205                             filetype = 'java',
206                             line_num = 18,
207                             column_num = 1,
208                             contents = contents,
209                             command_arguments = [ 'GetDoc' ],
210                             completer_target = 'filetype_default' )
211
212  response = app.post_json( '/run_completer_command',
213                            event_data,
214                            expect_errors = True )
215
216  assert_that( response.status_code,
217               equal_to( requests.codes.internal_server_error ) )
218
219  assert_that( response.json,
220               ErrorMatcher( RuntimeError, NO_DOCUMENTATION_MESSAGE ) )
221
222
223@WithRetry
224@SharedYcmd
225def Subcommands_GetDoc_Method_test( app ):
226  filepath = PathToTestFile( 'simple_eclipse_project',
227                             'src',
228                             'com',
229                             'test',
230                             'AbstractTestWidget.java' )
231  contents = ReadFile( filepath )
232
233  event_data = BuildRequest( filepath = filepath,
234                             filetype = 'java',
235                             line_num = 17,
236                             column_num = 17,
237                             contents = contents,
238                             command_arguments = [ 'GetDoc' ],
239                             completer_target = 'filetype_default' )
240
241  response = app.post_json( '/run_completer_command', event_data ).json
242
243  assert_that( response, has_entry( 'detailed_info',
244    'Return runtime debugging info. Useful for finding the '
245    'actual code which is useful.' ) )
246
247
248@WithRetry
249@SharedYcmd
250def Subcommands_GetDoc_Class_test( app ):
251  filepath = PathToTestFile( 'simple_eclipse_project',
252                             'src',
253                             'com',
254                             'test',
255                             'TestWidgetImpl.java' )
256  contents = ReadFile( filepath )
257
258  event_data = BuildRequest( filepath = filepath,
259                             filetype = 'java',
260                             line_num = 11,
261                             column_num = 7,
262                             contents = contents,
263                             command_arguments = [ 'GetDoc' ],
264                             completer_target = 'filetype_default' )
265
266  response = app.post_json( '/run_completer_command', event_data ).json
267
268  assert_that( response, has_entry( 'detailed_info',
269    'This is the actual code that matters. This concrete '
270    'implementation is the equivalent of the main function '
271    'in other languages' ) )
272
273
274@WithRetry
275@SharedYcmd
276def Subcommands_GetType_NoKnownType_test( app ):
277  filepath = PathToTestFile( 'simple_eclipse_project',
278                             'src',
279                             'com',
280                             'test',
281                             'TestWidgetImpl.java' )
282  contents = ReadFile( filepath )
283
284  event_data = BuildRequest( filepath = filepath,
285                             filetype = 'java',
286                             line_num = 28,
287                             column_num = 1,
288                             contents = contents,
289                             command_arguments = [ 'GetType' ],
290                             completer_target = 'filetype_default' )
291
292  response = app.post_json( '/run_completer_command',
293                            event_data,
294                            expect_errors = True )
295
296  assert_that( response.status_code,
297               equal_to( requests.codes.internal_server_error ) )
298
299  assert_that( response.json,
300               ErrorMatcher( RuntimeError, 'Unknown type' ) )
301
302
303@WithRetry
304@SharedYcmd
305def Subcommands_GetType_Class_test( app ):
306  filepath = PathToTestFile( 'simple_eclipse_project',
307                             'src',
308                             'com',
309                             'test',
310                             'TestWidgetImpl.java' )
311  contents = ReadFile( filepath )
312
313  event_data = BuildRequest( filepath = filepath,
314                             filetype = 'java',
315                             line_num = 11,
316                             column_num = 7,
317                             contents = contents,
318                             command_arguments = [ 'GetType' ],
319                             completer_target = 'filetype_default' )
320
321  response = app.post_json( '/run_completer_command', event_data ).json
322
323  assert_that( response, has_entry( 'message', 'com.test.TestWidgetImpl' ) )
324
325
326@WithRetry
327@SharedYcmd
328def Subcommands_GetType_Constructor_test( app ):
329  filepath = PathToTestFile( 'simple_eclipse_project',
330                             'src',
331                             'com',
332                             'test',
333                             'TestWidgetImpl.java' )
334  contents = ReadFile( filepath )
335
336  event_data = BuildRequest( filepath = filepath,
337                             filetype = 'java',
338                             line_num = 14,
339                             column_num = 3,
340                             contents = contents,
341                             command_arguments = [ 'GetType' ],
342                             completer_target = 'filetype_default' )
343
344  response = app.post_json( '/run_completer_command', event_data ).json
345
346  assert_that( response, has_entry(
347    'message', 'com.test.TestWidgetImpl.TestWidgetImpl(String info)' ) )
348
349
350@WithRetry
351@SharedYcmd
352def Subcommands_GetType_ClassMemberVariable_test( app ):
353  filepath = PathToTestFile( 'simple_eclipse_project',
354                             'src',
355                             'com',
356                             'test',
357                             'TestWidgetImpl.java' )
358  contents = ReadFile( filepath )
359
360  event_data = BuildRequest( filepath = filepath,
361                             filetype = 'java',
362                             line_num = 12,
363                             column_num = 18,
364                             contents = contents,
365                             command_arguments = [ 'GetType' ],
366                             completer_target = 'filetype_default' )
367
368  response = app.post_json( '/run_completer_command', event_data ).json
369
370  assert_that( response, has_entry( 'message', 'String info' ) )
371
372
373@WithRetry
374@SharedYcmd
375def Subcommands_GetType_MethodArgument_test( app ):
376  filepath = PathToTestFile( 'simple_eclipse_project',
377                             'src',
378                             'com',
379                             'test',
380                             'TestWidgetImpl.java' )
381  contents = ReadFile( filepath )
382
383  event_data = BuildRequest( filepath = filepath,
384                             filetype = 'java',
385                             line_num = 16,
386                             column_num = 17,
387                             contents = contents,
388                             command_arguments = [ 'GetType' ],
389                             completer_target = 'filetype_default' )
390
391  response = app.post_json( '/run_completer_command', event_data ).json
392
393  assert_that( response, has_entry(
394    'message', 'String info - com.test.TestWidgetImpl.TestWidgetImpl(String)'
395  ) )
396
397
398@WithRetry
399@SharedYcmd
400def Subcommands_GetType_MethodVariable_test( app ):
401  filepath = PathToTestFile( 'simple_eclipse_project',
402                             'src',
403                             'com',
404                             'test',
405                             'TestWidgetImpl.java' )
406  contents = ReadFile( filepath )
407
408  event_data = BuildRequest( filepath = filepath,
409                             filetype = 'java',
410                             line_num = 15,
411                             column_num = 9,
412                             contents = contents,
413                             command_arguments = [ 'GetType' ],
414                             completer_target = 'filetype_default' )
415
416  response = app.post_json( '/run_completer_command', event_data ).json
417
418  assert_that( response, has_entry(
419    'message', 'int a - com.test.TestWidgetImpl.TestWidgetImpl(String)' ) )
420
421
422@WithRetry
423@SharedYcmd
424def Subcommands_GetType_Method_test( app ):
425  filepath = PathToTestFile( 'simple_eclipse_project',
426                             'src',
427                             'com',
428                             'test',
429                             'TestWidgetImpl.java' )
430  contents = ReadFile( filepath )
431
432  event_data = BuildRequest( filepath = filepath,
433                             filetype = 'java',
434                             line_num = 20,
435                             column_num = 15,
436                             contents = contents,
437                             command_arguments = [ 'GetType' ],
438                             completer_target = 'filetype_default' )
439
440  response = app.post_json( '/run_completer_command', event_data ).json
441
442  assert_that( response, has_entry(
443    'message', 'void com.test.TestWidgetImpl.doSomethingVaguelyUseful()' ) )
444
445
446@WithRetry
447@SharedYcmd
448def Subcommands_GetType_Unicode_test( app ):
449  contents = ReadFile( TEST_JAVA )
450
451  app.post_json( '/event_notification',
452                 BuildRequest( filepath = TEST_JAVA,
453                               filetype = 'java',
454                               contents = contents,
455                               event_name = 'FileReadyToParse' ) )
456
457  event_data = BuildRequest( filepath = TEST_JAVA,
458                             filetype = 'java',
459                             line_num = 7,
460                             column_num = 17,
461                             contents = contents,
462                             command_arguments = [ 'GetType' ],
463                             completer_target = 'filetype_default' )
464
465  response = app.post_json( '/run_completer_command', event_data ).json
466
467  assert_that( response, has_entry(
468    'message', 'String whåtawîdgé - com.youcompleteme.Test.doUnicødeTes()' ) )
469
470
471@WithRetry
472@SharedYcmd
473def Subcommands_GetType_LiteralValue_test( app ):
474  filepath = PathToTestFile( 'simple_eclipse_project',
475                             'src',
476                             'com',
477                             'test',
478                             'TestWidgetImpl.java' )
479  contents = ReadFile( filepath )
480
481  event_data = BuildRequest( filepath = filepath,
482                             filetype = 'java',
483                             line_num = 15,
484                             column_num = 13,
485                             contents = contents,
486                             command_arguments = [ 'GetType' ],
487                             completer_target = 'filetype_default' )
488
489  response = app.post_json( '/run_completer_command',
490                            event_data,
491                            expect_errors = True )
492
493  assert_that( response.status_code,
494               equal_to( requests.codes.internal_server_error ) )
495
496  assert_that( response.json,
497               ErrorMatcher( RuntimeError, 'Unknown type' ) )
498
499
500@WithRetry
501@SharedYcmd
502def Subcommands_GoTo_NoLocation_test( app ):
503  filepath = PathToTestFile( 'simple_eclipse_project',
504                             'src',
505                             'com',
506                             'test',
507                             'AbstractTestWidget.java' )
508  contents = ReadFile( filepath )
509
510  event_data = BuildRequest( filepath = filepath,
511                             filetype = 'java',
512                             line_num = 18,
513                             column_num = 1,
514                             contents = contents,
515                             command_arguments = [ 'GoTo' ],
516                             completer_target = 'filetype_default' )
517
518  response = app.post_json( '/run_completer_command',
519                            event_data,
520                            expect_errors = True )
521
522  assert_that( response.status_code,
523               equal_to( requests.codes.internal_server_error ) )
524
525  assert_that( response.json,
526               ErrorMatcher( RuntimeError, 'Cannot jump to location' ) )
527
528
529@WithRetry
530@SharedYcmd
531def Subcommands_GoToReferences_NoReferences_test( app ):
532  filepath = PathToTestFile( 'simple_eclipse_project',
533                             'src',
534                             'com',
535                             'test',
536                             'AbstractTestWidget.java' )
537  contents = ReadFile( filepath )
538
539  event_data = BuildRequest( filepath = filepath,
540                             filetype = 'java',
541                             line_num = 2,
542                             column_num = 1,
543                             contents = contents,
544                             command_arguments = [ 'GoToReferences' ],
545                             completer_target = 'filetype_default' )
546
547  response = app.post_json( '/run_completer_command',
548                            event_data,
549                            expect_errors = True )
550
551  assert_that( response.status_code,
552               equal_to( requests.codes.internal_server_error ) )
553
554  assert_that( response.json,
555               ErrorMatcher( RuntimeError,
556                             'Cannot jump to location' ) )
557
558
559@WithRetry
560@IsolatedYcmd( {
561  'extra_conf_globlist': PathToTestFile( 'multiple_projects', '*' )
562} )
563def Subcommands_GoToReferences_MultipleProjects_test( app ):
564  filepath = PathToTestFile( 'multiple_projects',
565                             'src',
566                             'core',
567                             'java',
568                             'com',
569                             'puremourning',
570                             'widget',
571                             'core',
572                             'Utils.java' )
573  StartJavaCompleterServerWithFile( app, filepath )
574
575
576  RunTest( app, {
577    'description': 'GoToReferences works across multiple projects',
578    'request': {
579      'command': 'GoToReferences',
580      'filepath': filepath,
581      'line_num': 5,
582      'column_num': 22,
583    },
584    'expect': {
585      'response': requests.codes.ok,
586      'data': contains_inanyorder(
587        LocationMatcher( filepath, 8, 35 ),
588        LocationMatcher( PathToTestFile( 'multiple_projects',
589                                         'src',
590                                         'input',
591                                         'java',
592                                         'com',
593                                         'puremourning',
594                                         'widget',
595                                         'input',
596                                         'InputApp.java' ),
597                         8,
598                         16 )
599      )
600    }
601  } )
602
603
604
605@WithRetry
606@SharedYcmd
607def Subcommands_GoToReferences_test( app ):
608  filepath = PathToTestFile( 'simple_eclipse_project',
609                             'src',
610                             'com',
611                             'test',
612                             'AbstractTestWidget.java' )
613  contents = ReadFile( filepath )
614
615  event_data = BuildRequest( filepath = filepath,
616                             filetype = 'java',
617                             line_num = 10,
618                             column_num = 15,
619                             contents = contents,
620                             command_arguments = [ 'GoToReferences' ],
621                             completer_target = 'filetype_default' )
622
623  response = app.post_json( '/run_completer_command', event_data ).json
624
625  assert_that( response, contains_exactly( has_entries( {
626           'filepath': PathToTestFile( 'simple_eclipse_project',
627                                       'src',
628                                       'com',
629                                       'test',
630                                       'TestFactory.java' ),
631           'column_num': 9,
632           'description': "      w.doSomethingVaguelyUseful();",
633           'line_num': 28
634         } ),
635         has_entries( {
636           'filepath': PathToTestFile( 'simple_eclipse_project',
637                                       'src',
638                                       'com',
639                                       'test',
640                                       'TestLauncher.java' ),
641           'column_num': 11,
642           'description': "        w.doSomethingVaguelyUseful();",
643           'line_num': 32
644         } ) ) )
645
646
647@WithRetry
648@SharedYcmd
649def Subcommands_GoToSymbol_SingleSameFile_test( app ):
650  contents = ReadFile( TEST_JAVA )
651
652  event_data = BuildRequest( filepath = TEST_JAVA,
653                             filetype = 'java',
654                             line_num = 1,
655                             column_num = 1,
656                             contents = contents,
657                             command_arguments = [ 'GoToSymbol', 'TéstClass' ],
658                             completer_target = 'filetype_default' )
659
660  response = app.post_json( '/run_completer_command', event_data ).json
661
662  assert_that( response, has_entries( {
663    'filepath': TEST_JAVA,
664    'description': "Class: TéstClass",
665    'line_num': 20,
666    'column_num': 16,
667  } ) )
668
669
670@WithRetry
671@SharedYcmd
672def Subcommands_GoToSymbol_Multiple_test( app ):
673  contents = ReadFile( TEST_JAVA )
674
675  event_data = BuildRequest( filepath = TEST_JAVA,
676                             filetype = 'java',
677                             line_num = 1,
678                             column_num = 1,
679                             contents = contents,
680                             command_arguments = [ 'GoToSymbol', 'test' ],
681                             completer_target = 'filetype_default' )
682
683  response = app.post_json( '/run_completer_command', event_data ).json
684
685  assert_that( response, contains_inanyorder(
686    has_entries( {
687      'filepath': PathToTestFile( 'simple_eclipse_project',
688                                  'src',
689                                  'com',
690                                  'test',
691                                  'TestFactory.java' ) ,
692      'description': "Class: TestFactory",
693      'line_num': 12,
694      'column_num': 14,
695    } ),
696    has_entries( {
697      'filepath': PathToTestFile( 'simple_eclipse_project',
698                                  'src',
699                                  'com',
700                                  'test',
701                                  'TestWidgetImpl.java' ) ,
702      'description': "Class: TestWidgetImpl",
703      'line_num': 11,
704      'column_num': 7,
705    } ),
706    has_entries( {
707      'filepath': PathToTestFile( 'simple_eclipse_project',
708                                  'src',
709                                  'com',
710                                  'test',
711                                  'TestLauncher.java' ) ,
712      'description': "Class: TestLauncher",
713      'line_num': 6,
714      'column_num': 7,
715    } ),
716    has_entries( {
717      'filepath': PathToTestFile( 'simple_eclipse_project',
718                                  'src',
719                                  'com',
720                                  'youcompleteme',
721                                  'Test.java' ) ,
722      'description': "Class: Test",
723      'line_num': 3,
724      'column_num': 14,
725    } ),
726    has_entries( {
727      'filepath': PathToTestFile( 'simple_eclipse_project',
728                                  'src',
729                                  'com',
730                                  'test',
731                                  'TestWithDocumentation.java' ) ,
732      'description': "Class: TestWithDocumentation",
733      'line_num': 3,
734      'column_num': 14,
735    } )
736  ) )
737
738
739@WithRetry
740@SharedYcmd
741def Subcommands_GoToSymbol_None_test( app ):
742  contents = ReadFile( TEST_JAVA )
743
744  event_data = BuildRequest( filepath = TEST_JAVA,
745                             filetype = 'java',
746                             line_num = 1,
747                             column_num = 1,
748                             contents = contents,
749                             command_arguments = [ 'GoToSymbol', 'abcd' ],
750                             completer_target = 'filetype_default' )
751
752  response = app.post_json( '/run_completer_command',
753                            event_data,
754                            expect_errors = True )
755
756  assert_that( response.status_code,
757               equal_to( requests.codes.internal_server_error ) )
758
759  assert_that( response.json,
760               ErrorMatcher( RuntimeError, 'Symbol not found' ) )
761
762
763
764@WithRetry
765@SharedYcmd
766def Subcommands_RefactorRename_Simple_test( app ):
767  filepath = PathToTestFile( 'simple_eclipse_project',
768                             'src',
769                             'com',
770                             'test',
771                             'TestLauncher.java' )
772  RunTest( app, {
773    'description': 'RefactorRename works within a single scope/file',
774    'request': {
775      'command': 'RefactorRename',
776      'arguments': [ 'renamed_l' ],
777      'filepath': filepath,
778      'line_num': 28,
779      'column_num': 5,
780    },
781    'expect': {
782      'response': requests.codes.ok,
783      'data': has_entries( {
784        'fixits': contains_exactly( has_entries( {
785          'chunks': contains_exactly(
786              ChunkMatcher( 'renamed_l = new TestLauncher( 10 );'
787                            '\n    renamed_l',
788                            LocationMatcher( filepath, 27, 18 ),
789                            LocationMatcher( filepath, 28, 6 ) ),
790          ),
791          'location': LocationMatcher( filepath, 28, 5 )
792        } ) )
793      } )
794    }
795  } )
796
797
798@ExpectedFailure( 'Renaming does not work on overridden methods '
799                  'since jdt.ls 0.21.0',
800                  matches_regexp( 'No item matched:.*TestWidgetImpl.java' ) )
801@WithRetry
802@SharedYcmd
803def Subcommands_RefactorRename_MultipleFiles_test( app ):
804  AbstractTestWidget = PathToTestFile( 'simple_eclipse_project',
805                                       'src',
806                                       'com',
807                                       'test',
808                                       'AbstractTestWidget.java' )
809  TestFactory = PathToTestFile( 'simple_eclipse_project',
810                                'src',
811                                'com',
812                                'test',
813                                'TestFactory.java' )
814  TestLauncher = PathToTestFile( 'simple_eclipse_project',
815                                 'src',
816                                 'com',
817                                 'test',
818                                 'TestLauncher.java' )
819  TestWidgetImpl = PathToTestFile( 'simple_eclipse_project',
820                                   'src',
821                                   'com',
822                                   'test',
823                                   'TestWidgetImpl.java' )
824
825  RunTest( app, {
826    'description': 'RefactorRename works across files',
827    'request': {
828      'command': 'RefactorRename',
829      'arguments': [ 'a_quite_long_string' ],
830      'filepath': TestLauncher,
831      'line_num': 32,
832      'column_num': 13,
833    },
834    'expect': {
835      'response': requests.codes.ok,
836      'data': has_entries( {
837        'fixits': contains_exactly( has_entries( {
838          'chunks': contains_exactly(
839            ChunkMatcher(
840              'a_quite_long_string',
841              LocationMatcher( AbstractTestWidget, 10, 15 ),
842              LocationMatcher( AbstractTestWidget, 10, 39 ) ),
843            ChunkMatcher(
844              'a_quite_long_string',
845              LocationMatcher( TestFactory, 28, 9 ),
846              LocationMatcher( TestFactory, 28, 33 ) ),
847            ChunkMatcher(
848              'a_quite_long_string',
849              LocationMatcher( TestLauncher, 32, 11 ),
850              LocationMatcher( TestLauncher, 32, 35 ) ),
851            ChunkMatcher(
852              'a_quite_long_string',
853              LocationMatcher( TestWidgetImpl, 20, 15 ),
854              LocationMatcher( TestWidgetImpl, 20, 39 ) ),
855          ),
856          'location': LocationMatcher( TestLauncher, 32, 13 )
857        } ) )
858      } )
859    }
860  } )
861
862
863@WithRetry
864@SharedYcmd
865def Subcommands_RefactorRename_Missing_New_Name_test( app ):
866  filepath = PathToTestFile( 'simple_eclipse_project',
867                             'src',
868                             'com',
869                             'test',
870                             'TestLauncher.java' )
871  RunTest( app, {
872    'description': 'RefactorRename raises an error without new name',
873    'request': {
874      'command': 'RefactorRename',
875      'line_num': 15,
876      'column_num': 5,
877      'filepath': filepath,
878    },
879    'expect': {
880      'response': requests.codes.internal_server_error,
881      'data': ErrorMatcher( ValueError,
882                            'Please specify a new name to rename it to.\n'
883                            'Usage: RefactorRename <new name>' ),
884    }
885  } )
886
887
888@WithRetry
889@SharedYcmd
890def Subcommands_RefactorRename_Unicode_test( app ):
891  RunTest( app, {
892    'description': 'Rename works for unicode identifier',
893    'request': {
894      'command': 'RefactorRename',
895      'arguments': [ 'shorter' ],
896      'line_num': 7,
897      'column_num': 21,
898      'filepath': TEST_JAVA,
899    },
900    'expect': {
901      'response': requests.codes.ok,
902      'data': has_entries( {
903        'fixits': contains_exactly( has_entries( {
904          'chunks': contains_exactly(
905            ChunkMatcher(
906              'shorter = "Test";\n    return shorter',
907              LocationMatcher( TEST_JAVA, 7, 12 ),
908              LocationMatcher( TEST_JAVA, 8, 25 )
909            ),
910          ),
911        } ) ),
912      } ),
913    },
914  } )
915
916
917
918def RunFixItTest( app, description, filepath, line, col, fixits_for_line ):
919  RunTest( app, {
920    'description': description,
921    'request': {
922      'command': 'FixIt',
923      'line_num': line,
924      'column_num': col,
925      'filepath': filepath,
926    },
927    'expect': {
928      'response': requests.codes.ok,
929      'data': fixits_for_line,
930    }
931  } )
932
933
934@WithRetry
935@pytest.mark.parametrize( 'description,column', [
936  ( 'FixIt works at the firtst char of the line', 1 ),
937  ( 'FixIt works at the begin of the range of the diag.', 15 ),
938  ( 'FixIt works at the end of the range of the diag.', 20 ),
939  ( 'FixIt works at the end of the line', 34 ),
940] )
941@SharedYcmd
942def Subcommands_FixIt_SingleDiag_MultipleOption_Insertion_test( app,
943                                                                description,
944                                                                column ):
945  import os
946  wibble_path = PathToTestFile( 'simple_eclipse_project',
947                                'src',
948                                'com',
949                                'test',
950                                'Wibble.java' )
951  wibble_text = 'package com.test;{0}{0}public {1} Wibble {{{0}{0}}}{0}'
952  filepath = PathToTestFile( 'simple_eclipse_project',
953                             'src',
954                             'com',
955                             'test',
956                             'TestFactory.java' )
957
958  # Note: The code actions for creating variables are really not very useful.
959  # The import is, however, and the FixIt almost exactly matches the one
960  # supplied when completing 'CUTHBERT' and auto-inserting.
961  fixits_for_line = has_entries( {
962    'fixits': contains_inanyorder(
963      has_entries( {
964        'text': "Import 'Wibble' (com.test.wobble)",
965        'kind': 'quickfix',
966        'chunks': contains_exactly(
967          ChunkMatcher( 'package com.test;\n\n'
968                        'import com.test.wobble.Wibble;\n\n',
969                        LocationMatcher( filepath, 1, 1 ),
970                        LocationMatcher( filepath, 3, 1 ) ),
971        ),
972      } ),
973      has_entries( {
974        'text': "Create constant 'Wibble'",
975        'kind': 'quickfix',
976        'chunks': contains_exactly(
977          ChunkMatcher( '\n\nprivate static final String Wibble = null;',
978                        LocationMatcher( filepath, 16, 4 ),
979                        LocationMatcher( filepath, 16, 4 ) ),
980        ),
981      } ),
982      has_entries( {
983        'text': "Create class 'Wibble'",
984        'kind': 'quickfix',
985        'chunks': contains_exactly(
986          ChunkMatcher( wibble_text.format( os.linesep, 'class' ),
987                        LocationMatcher( wibble_path, 1, 1 ),
988                        LocationMatcher( wibble_path, 1, 1 ) ),
989        ),
990      } ),
991      has_entries( {
992        'text': "Create interface 'Wibble'",
993        'kind': 'quickfix',
994        'chunks': contains_exactly(
995          ChunkMatcher( wibble_text.format( os.linesep, 'interface' ),
996                        LocationMatcher( wibble_path, 1, 1 ),
997                        LocationMatcher( wibble_path, 1, 1 ) ),
998        ),
999      } ),
1000      has_entries( {
1001        'text': "Create enum 'Wibble'",
1002        'kind': 'quickfix',
1003        'chunks': contains_exactly(
1004          ChunkMatcher( wibble_text.format( os.linesep, 'enum' ),
1005                        LocationMatcher( wibble_path, 1, 1 ),
1006                        LocationMatcher( wibble_path, 1, 1 ) ),
1007        ),
1008      } ),
1009      has_entries( {
1010        'text': "Create local variable 'Wibble'",
1011        'kind': 'quickfix',
1012        'chunks': contains_exactly(
1013          ChunkMatcher( 'Object Wibble;\n\t',
1014                        LocationMatcher( filepath, 19, 5 ),
1015                        LocationMatcher( filepath, 19, 5 ) ),
1016        ),
1017      } ),
1018      has_entries( {
1019        'text': "Create field 'Wibble'",
1020        'kind': 'quickfix',
1021        'chunks': contains_exactly(
1022          ChunkMatcher( '\n\nprivate Object Wibble;',
1023                        LocationMatcher( filepath, 16, 4 ),
1024                        LocationMatcher( filepath, 16, 4 ) ),
1025        ),
1026      } ),
1027      has_entries( {
1028        'text': "Create parameter 'Wibble'",
1029        'kind': 'quickfix',
1030        'chunks': contains_exactly(
1031          ChunkMatcher( ', Object Wibble',
1032                        LocationMatcher( filepath, 18, 32 ),
1033                        LocationMatcher( filepath, 18, 32 ) ),
1034        ),
1035      } ),
1036      has_entries( {
1037        'text': 'Generate toString()...',
1038        'kind': 'source.generate.toString',
1039        'chunks': contains_exactly(
1040          ChunkMatcher( '\n\n@Override\npublic String toString() {'
1041                        '\n\treturn "TestFactory []";\n}',
1042                        LocationMatcher( filepath, 32, 4 ),
1043                        LocationMatcher( filepath, 32, 4 ) ),
1044        ),
1045      } ),
1046      has_entries( {
1047        'text': 'Organize imports',
1048        'kind': 'source.organizeImports',
1049        'chunks': contains_exactly(
1050          ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n',
1051                        LocationMatcher( filepath, 1, 18 ),
1052                        LocationMatcher( filepath, 3, 1 ) ),
1053        ),
1054      } ),
1055      has_entries( {
1056        'text': 'Change modifiers to final where possible',
1057        'kind': 'source.generate.finalModifiers',
1058        'chunks': contains_exactly(
1059          ChunkMatcher( 'final Wibble w ) {\n    if ( w == Wibble.CUTHBERT ) {'
1060                        '\n    }\n  }\n\n  public AbstractTestWidget getWidget'
1061                        '( final String info ) {\n    final AbstractTestWidget'
1062                        ' w = new TestWidgetImpl( info );\n    final ',
1063                        LocationMatcher( filepath, 18, 24 ),
1064                        LocationMatcher( filepath, 25, 5 ) ),
1065        ),
1066      } ),
1067    )
1068  } )
1069
1070  RunFixItTest( app, description, filepath, 19, column, fixits_for_line )
1071
1072
1073@WithRetry
1074@SharedYcmd
1075def Subcommands_FixIt_SingleDiag_SingleOption_Modify_test( app ):
1076  filepath = PathToTestFile( 'simple_eclipse_project',
1077                             'src',
1078                             'com',
1079                             'test',
1080                             'TestFactory.java' )
1081
1082  # TODO: As there is only one option, we automatically apply it.
1083  # In Java case this might not be the right thing. It's a code assist, not a
1084  # FixIt really. Perhaps we should change the client to always ask for
1085  # confirmation?
1086  fixits = has_entries( {
1087    'fixits': contains_inanyorder(
1088      has_entries( {
1089        'text': "Change type of 'test' to 'boolean'",
1090        'kind': 'quickfix',
1091        'chunks': contains_exactly(
1092          ChunkMatcher( 'boolean',
1093                        LocationMatcher( filepath, 14, 12 ),
1094                        LocationMatcher( filepath, 14, 15 ) ),
1095        ),
1096      } ),
1097      has_entries( {
1098        'text': 'Generate toString()...',
1099        'kind': 'source.generate.toString',
1100        'chunks': contains_exactly(
1101          ChunkMatcher( '\n\n@Override\npublic String toString() {'
1102                        '\n\treturn "TestFactory []";\n}',
1103                        LocationMatcher( filepath, 32, 4 ),
1104                        LocationMatcher( filepath, 32, 4 ) ),
1105        ),
1106      } ),
1107      has_entries( {
1108        'text': 'Organize imports',
1109        'kind': 'source.organizeImports',
1110        'chunks': contains_exactly(
1111          ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n',
1112                        LocationMatcher( filepath, 1, 18 ),
1113                        LocationMatcher( filepath, 3, 1 ) ),
1114        ),
1115      } ),
1116      has_entries( {
1117        'text': 'Change modifiers to final where possible',
1118        'kind': 'source.generate.finalModifiers',
1119        'chunks': contains_exactly(
1120          ChunkMatcher( 'final Wibble w ) {\n    if ( w == Wibble.CUTHBERT ) {'
1121                        '\n    }\n  }\n\n  public AbstractTestWidget getWidget'
1122                        '( final String info ) {\n    final AbstractTestWidget'
1123                        ' w = new TestWidgetImpl( info );\n    final ',
1124                        LocationMatcher( filepath, 18, 24 ),
1125                        LocationMatcher( filepath, 25, 5 ) ),
1126        ),
1127      } ),
1128    )
1129  } )
1130
1131  RunFixItTest( app, 'FixIts can change lines as well as add them',
1132                filepath, 27, 12, fixits )
1133
1134
1135@WithRetry
1136@SharedYcmd
1137def Subcommands_FixIt_SingleDiag_MultiOption_Delete_test( app ):
1138  filepath = PathToTestFile( 'simple_eclipse_project',
1139                             'src',
1140                             'com',
1141                             'test',
1142                             'TestFactory.java' )
1143
1144  fixits = has_entries( {
1145    'fixits': contains_inanyorder(
1146      has_entries( {
1147        'text': "Remove 'testString', keep assignments with side effects",
1148        'kind': 'quickfix',
1149        'chunks': contains_exactly(
1150          ChunkMatcher( '',
1151                        LocationMatcher( filepath, 14, 21 ),
1152                        LocationMatcher( filepath, 15, 30 ) ),
1153        ),
1154      } ),
1155      # The edit reported for this is huge and uninteresting really. Manual
1156      # testing can show that it works. This test is really about the previous
1157      # FixIt (and nonetheless, the previous tests ensure that we correctly
1158      # populate the chunks list; the contents all come from jdt.ls)
1159      has_entries( {
1160        'text': "Create getter and setter for 'testString'",
1161        'chunks': instance_of( list )
1162      } ),
1163      has_entries( {
1164        'text': "Organize imports",
1165        'chunks': instance_of( list )
1166      } ),
1167      has_entries( {
1168        'text': "Generate Getters and Setters",
1169        'chunks': instance_of( list )
1170      } ),
1171      has_entries( {
1172        'text': 'Change modifiers to final where possible',
1173        'chunks': instance_of( list )
1174      } ),
1175    )
1176  } )
1177
1178  RunFixItTest( app, 'FixIts can change lines as well as add them',
1179                filepath, 15, 29, fixits )
1180
1181
1182@WithRetry
1183@pytest.mark.parametrize( 'description,column,expect_fixits', [
1184  ( 'diags are merged in FixIt options - start of line', 1, 'MERGE' ),
1185  ( 'diags are not merged in FixIt options - start of diag 1', 10, 'FIRST' ),
1186  ( 'diags are not merged in FixIt options - end of diag 1', 15, 'FIRST' ),
1187  ( 'diags are not merged in FixIt options - start of diag 2', 23, 'SECOND' ),
1188  ( 'diags are not merged in FixIt options - end of diag 2', 46, 'SECOND' ),
1189  ( 'diags are merged in FixIt options - end of line', 55, 'MERGE' ),
1190] )
1191@SharedYcmd
1192def Subcommands_FixIt_MultipleDiags_test( app,
1193                                          description,
1194                                          column,
1195                                          expect_fixits ):
1196  filepath = PathToTestFile( 'simple_eclipse_project',
1197                             'src',
1198                             'com',
1199                             'test',
1200                             'TestFactory.java' )
1201
1202  FIRST = [
1203    has_entries( {
1204      'text': "Change type of 'test' to 'boolean'",
1205      'kind': 'quickfix',
1206      'chunks': contains_exactly(
1207        ChunkMatcher( 'boolean',
1208                      LocationMatcher( filepath, 14, 12 ),
1209                      LocationMatcher( filepath, 14, 15 ) ),
1210      ),
1211    } ),
1212  ]
1213  SECOND = [
1214    has_entries( {
1215      'text': "Remove argument to match 'doSomethingVaguelyUseful()'",
1216      'kind': 'quickfix',
1217      'chunks': contains_exactly(
1218        ChunkMatcher( '',
1219                      LocationMatcher( filepath, 30, 48 ),
1220                      LocationMatcher( filepath, 30, 50 ) ),
1221      ),
1222    } ),
1223    has_entries( {
1224      'text': "Change method 'doSomethingVaguelyUseful()': Add parameter "
1225      "'Bar'",
1226      'chunks': instance_of( list ),
1227    } ),
1228    has_entries( {
1229      'text': "Create method 'doSomethingVaguelyUseful(Bar)' in type "
1230      "'AbstractTestWidget'",
1231      'chunks': instance_of( list ),
1232    } ),
1233  ]
1234
1235  ACTIONS = [
1236    has_entries( {
1237      'text': "Generate toString()...",
1238      'chunks': instance_of( list ),
1239    } ),
1240    has_entries( {
1241      'text': "Organize imports",
1242      'chunks': instance_of( list ),
1243    } ),
1244    has_entries( {
1245      'text': 'Change modifiers to final where possible',
1246      'chunks': instance_of( list ),
1247    } ),
1248  ]
1249
1250  FIXITS = {
1251    'FIRST': FIRST + ACTIONS,
1252    'SECOND': SECOND + ACTIONS,
1253    'MERGE': FIRST + SECOND + ACTIONS,
1254  }
1255
1256  fixits = has_entries( {
1257    'fixits': contains_inanyorder( *FIXITS[ expect_fixits ] )
1258  } )
1259
1260  RunFixItTest( app, description, filepath, 30, column, fixits )
1261
1262
1263@SharedYcmd
1264def Subcommands_FixIt_Range_test( app ):
1265  filepath = PathToTestFile( 'simple_eclipse_project',
1266                             'src',
1267                             'com',
1268                             'test',
1269                             'TestLauncher.java' )
1270  RunTest( app, {
1271    'description': 'Formatting is applied on some part of the file '
1272                   'with tabs composed of 4 spaces',
1273    'request': {
1274      'command': 'FixIt',
1275      'filepath': filepath,
1276      'range': {
1277        'start': {
1278          'line_num': 34,
1279          'column_num': 28,
1280        },
1281        'end': {
1282          'line_num': 34,
1283          'column_num': 73
1284        }
1285      },
1286    },
1287    'expect': {
1288      'response': requests.codes.ok,
1289      'data': has_entries( {
1290        'fixits': contains_inanyorder(
1291          has_entries( {
1292            'text': 'Extract to field',
1293            'kind': 'refactor.extract.field',
1294            'chunks': contains_exactly(
1295              ChunkMatcher(
1296                matches_regexp(
1297                  'private String \\w+;\n'
1298                  '\n'
1299                  '\t@Override\n'
1300                  '      public void launch\\(\\) {\n'
1301                  '        AbstractTestWidget w = '
1302                  'factory.getWidget\\( "Test" \\);\n'
1303                  '        '
1304                  'w.doSomethingVaguelyUseful\\(\\);\n'
1305                  '\n'
1306                  '        \\w+ = "Did something '
1307                  'useful: " \\+ w.getWidgetInfo\\(\\);\n'
1308                  '\t\tSystem.out.println\\( \\w+' ),
1309                LocationMatcher( filepath, 29, 7 ),
1310                LocationMatcher( filepath, 34, 73 ) ),
1311            ),
1312          } ),
1313          has_entries( {
1314            'text': 'Extract to method',
1315            'kind': 'refactor.extract.function',
1316            'chunks': contains_exactly(
1317              # This one is a wall of text that rewrites 35 lines
1318              ChunkMatcher( instance_of( str ),
1319                            LocationMatcher( filepath, 1, 1 ),
1320                            LocationMatcher( filepath, 35, 8 ) ),
1321            ),
1322          } ),
1323          has_entries( {
1324            'text': 'Extract to local variable (replace all occurrences)',
1325            'kind': 'refactor.extract.variable',
1326            'chunks': contains_exactly(
1327              ChunkMatcher(
1328                matches_regexp(
1329                  'String \\w+ = "Did something '
1330                  'useful: " \\+ w.getWidgetInfo\\(\\);\n'
1331                  '\t\tSystem.out.println\\( \\w+' ),
1332                LocationMatcher( filepath, 34, 9 ),
1333                LocationMatcher( filepath, 34, 73 ) ),
1334            ),
1335          } ),
1336          has_entries( {
1337            'text': 'Extract to local variable',
1338            'kind': 'refactor.extract.variable',
1339            'chunks': contains_exactly(
1340              ChunkMatcher(
1341                matches_regexp(
1342                  'String \\w+ = "Did something '
1343                  'useful: " \\+ w.getWidgetInfo\\(\\);\n'
1344                  '\t\tSystem.out.println\\( \\w+' ),
1345                LocationMatcher( filepath, 34, 9 ),
1346                LocationMatcher( filepath, 34, 73 ) ),
1347            ),
1348          } ),
1349          has_entries( {
1350            'text': 'Introduce Parameter...',
1351            'kind': 'refactor.introduce.parameter',
1352            'chunks': contains_exactly(
1353              ChunkMatcher(
1354                'String string) {\n'
1355                '        AbstractTestWidget w = factory.getWidget( "Test" );\n'
1356                '        w.doSomethingVaguelyUseful();\n'
1357                '\n'
1358                '        System.out.println( string',
1359                LocationMatcher( filepath, 30, 26 ),
1360                LocationMatcher( filepath, 34, 73 ) ),
1361            ),
1362          } ),
1363          has_entries( {
1364            'text': 'Organize imports',
1365            'chunks': instance_of( list ),
1366          } ),
1367          has_entries( {
1368            'text': 'Change modifiers to final where possible',
1369            'chunks': instance_of( list ),
1370          } ),
1371        )
1372      } )
1373    }
1374  } )
1375
1376
1377
1378@WithRetry
1379@SharedYcmd
1380def Subcommands_FixIt_NoDiagnostics_test( app ):
1381  filepath = PathToTestFile( 'simple_eclipse_project',
1382                             'src',
1383                             'com',
1384                             'test',
1385                             'TestFactory.java' )
1386
1387  RunFixItTest( app, "no FixIts means you gotta code it yo' self",
1388                filepath, 1, 1, has_entries( {
1389                  'fixits': contains_inanyorder(
1390                    has_entries( {
1391                      'text': 'Change modifiers to final where possible',
1392                      'chunks': instance_of( list ) } ),
1393                    has_entries( { 'text': 'Organize imports',
1394                                   'chunks': instance_of( list ) } ),
1395                    has_entries( { 'text': 'Generate toString()...',
1396                                   'chunks': instance_of( list ) } ) ) } ) )
1397
1398
1399@WithRetry
1400@SharedYcmd
1401def Subcommands_FixIt_Unicode_test( app ):
1402  fixits = has_entries( {
1403    'fixits': contains_inanyorder(
1404      has_entries( {
1405        'text': "Remove argument to match 'doUnicødeTes()'",
1406        'kind': 'quickfix',
1407        'chunks': contains_exactly(
1408          ChunkMatcher( '',
1409                        LocationMatcher( TEST_JAVA, 13, 24 ),
1410                        LocationMatcher( TEST_JAVA, 13, 29 ) ),
1411        ),
1412      } ),
1413      has_entries( {
1414        'text': "Change method 'doUnicødeTes()': Add parameter 'String'",
1415        'kind': 'quickfix',
1416        'chunks': contains_exactly(
1417          ChunkMatcher( 'String test2',
1418                        LocationMatcher( TEST_JAVA, 6, 31 ),
1419                        LocationMatcher( TEST_JAVA, 6, 31 ) ),
1420        ),
1421      } ),
1422      has_entries( {
1423        'text': "Create method 'doUnicødeTes(String)'",
1424        'kind': 'quickfix',
1425        'chunks': contains_exactly(
1426          ChunkMatcher( 'private void doUnicødeTes(String test2) {\n}\n\n\n',
1427                        LocationMatcher( TEST_JAVA, 20, 3 ),
1428                        LocationMatcher( TEST_JAVA, 20, 3 ) ),
1429        ),
1430      } ),
1431      has_entries( {
1432        'text': 'Change modifiers to final where possible',
1433        'chunks': instance_of( list ),
1434      } ),
1435      has_entries( {
1436        'text': "Generate Getters and Setters",
1437        'chunks': instance_of( list ),
1438      } ),
1439    )
1440  } )
1441
1442  RunFixItTest( app, 'FixIts and diagnostics work with unicode strings',
1443                TEST_JAVA, 13, 1, fixits )
1444
1445
1446@WithRetry
1447@IsolatedYcmd()
1448def Subcommands_FixIt_InvalidURI_test( app ):
1449  filepath = PathToTestFile( 'simple_eclipse_project',
1450                             'src',
1451                             'com',
1452                             'test',
1453                             'TestFactory.java' )
1454
1455  fixits = has_entries( {
1456    'fixits': contains_inanyorder(
1457      has_entries( {
1458        'kind': 'quickfix',
1459        'text': "Change type of 'test' to 'boolean'",
1460        'chunks': contains_exactly(
1461          ChunkMatcher( 'boolean',
1462                        LocationMatcher( '', 14, 12 ),
1463                        LocationMatcher( '', 14, 15 ) ),
1464        ),
1465      } ),
1466      has_entries( {
1467        'text': 'Organize imports',
1468        'kind': 'source.organizeImports',
1469        'chunks': contains_exactly(
1470          ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n',
1471                        LocationMatcher( '', 1, 1 ),
1472                        LocationMatcher( '', 3, 1 ) ),
1473        ),
1474      } ),
1475      has_entries( {
1476        'text': 'Change modifiers to final where possible',
1477        'kind': 'source.generate.finalModifiers',
1478        'chunks': contains_exactly(
1479          ChunkMatcher( "final Wibble w ) {\n    if ( w == Wibble.CUTHBERT ) {"
1480                        "\n    }\n  }\n\n  public AbstractTestWidget getWidget"
1481                        "( final String info ) {\n    final AbstractTestWidget"
1482                        " w = new TestWidgetImpl( info );\n    final ",
1483                        LocationMatcher( '', 18, 24 ),
1484                        LocationMatcher( '', 25, 5 ) ),
1485        ),
1486      } ),
1487      has_entries( {
1488        'text': 'Generate toString()...',
1489        'kind': 'source.generate.toString',
1490        'chunks': contains_exactly(
1491          ChunkMatcher( '\n\n@Override\npublic String toString() {'
1492                        '\n\treturn "TestFactory []";\n}',
1493                        LocationMatcher( '', 32, 4 ),
1494                        LocationMatcher( '', 32, 4 ) ),
1495        ),
1496      } ),
1497    )
1498  } )
1499
1500  contents = ReadFile( filepath )
1501  # Wait for jdt.ls to have parsed the file and returned some diagnostics
1502  for tries in range( 0, 60 ):
1503    results = app.post_json( '/event_notification',
1504                             BuildRequest( filepath = filepath,
1505                                           filetype = 'java',
1506                                           contents = contents,
1507                                           event_name = 'FileReadyToParse' ) )
1508    if results.json:
1509      break
1510
1511    time.sleep( .25 )
1512
1513  with patch(
1514    'ycmd.completers.language_server.language_server_protocol.UriToFilePath',
1515    side_effect = lsp.InvalidUriException ):
1516    RunTest( app, {
1517      'description': 'Invalid URIs do not make us crash',
1518      'request': {
1519        'command': 'FixIt',
1520        'line_num': 27,
1521        'column_num': 12,
1522        'filepath': filepath,
1523      },
1524      'expect': {
1525        'response': requests.codes.ok,
1526        'data': fixits,
1527      }
1528    } )
1529
1530
1531@WithRetry
1532@SharedYcmd
1533def Subcommands_Format_WholeFile_Spaces_test( app ):
1534  RunTest( app, {
1535    'description': 'Formatting is applied on the whole file '
1536                   'with tabs composed of 4 spaces',
1537    'request': {
1538      'command': 'Format',
1539      'filepath': TEST_JAVA,
1540      'options': {
1541        'tab_size': 4,
1542        'insert_spaces': True
1543      }
1544    },
1545    'expect': {
1546      'response': requests.codes.ok,
1547      'data': has_entries( {
1548        'fixits': contains_exactly( has_entries( {
1549          'chunks': contains_exactly(
1550            ChunkMatcher( '\n    ',
1551                          LocationMatcher( TEST_JAVA,  3, 20 ),
1552                          LocationMatcher( TEST_JAVA,  4,  3 ) ),
1553            ChunkMatcher( '\n\n    ',
1554                          LocationMatcher( TEST_JAVA,  4, 22 ),
1555                          LocationMatcher( TEST_JAVA,  6,  3 ) ),
1556            ChunkMatcher( '\n        ',
1557                          LocationMatcher( TEST_JAVA,  6, 34 ),
1558                          LocationMatcher( TEST_JAVA,  7,  5 ) ),
1559            ChunkMatcher( '\n        ',
1560                          LocationMatcher( TEST_JAVA,  7, 35 ),
1561                          LocationMatcher( TEST_JAVA,  8,  5 ) ),
1562            ChunkMatcher( '',
1563                          LocationMatcher( TEST_JAVA,  8, 25 ),
1564                          LocationMatcher( TEST_JAVA,  8, 26 ) ),
1565            ChunkMatcher( '\n    ',
1566                          LocationMatcher( TEST_JAVA,  8, 27 ),
1567                          LocationMatcher( TEST_JAVA,  9,  3 ) ),
1568            ChunkMatcher( '\n\n    ',
1569                          LocationMatcher( TEST_JAVA,  9,  4 ),
1570                          LocationMatcher( TEST_JAVA, 11,  3 ) ),
1571            ChunkMatcher( '\n        ',
1572                          LocationMatcher( TEST_JAVA, 11, 29 ),
1573                          LocationMatcher( TEST_JAVA, 12,  5 ) ),
1574            ChunkMatcher( '\n        ',
1575                          LocationMatcher( TEST_JAVA, 12, 26 ),
1576                          LocationMatcher( TEST_JAVA, 13,  5 ) ),
1577            ChunkMatcher( '',
1578                          LocationMatcher( TEST_JAVA, 13, 24 ),
1579                          LocationMatcher( TEST_JAVA, 13, 25 ) ),
1580            ChunkMatcher( '',
1581                          LocationMatcher( TEST_JAVA, 13, 29 ),
1582                          LocationMatcher( TEST_JAVA, 13, 30 ) ),
1583            ChunkMatcher( '\n\n        ',
1584                          LocationMatcher( TEST_JAVA, 13, 32 ),
1585                          LocationMatcher( TEST_JAVA, 15,  5 ) ),
1586            ChunkMatcher( '\n        ',
1587                          LocationMatcher( TEST_JAVA, 15, 58 ),
1588                          LocationMatcher( TEST_JAVA, 16,  5 ) ),
1589            ChunkMatcher( '\n    ',
1590                          LocationMatcher( TEST_JAVA, 16, 42 ),
1591                          LocationMatcher( TEST_JAVA, 17,  3 ) ),
1592            ChunkMatcher( '\n\n    ',
1593                          LocationMatcher( TEST_JAVA, 17,  4 ),
1594                          LocationMatcher( TEST_JAVA, 20,  3 ) ),
1595            ChunkMatcher( '\n        ',
1596                          LocationMatcher( TEST_JAVA, 20, 28 ),
1597                          LocationMatcher( TEST_JAVA, 21,  5 ) ),
1598            ChunkMatcher( '\n        ',
1599                          LocationMatcher( TEST_JAVA, 21, 28 ),
1600                          LocationMatcher( TEST_JAVA, 22,  5 ) ),
1601            ChunkMatcher( '\n        ',
1602                          LocationMatcher( TEST_JAVA, 22, 30 ),
1603                          LocationMatcher( TEST_JAVA, 23,  5 ) ),
1604            ChunkMatcher( '\n        ',
1605                          LocationMatcher( TEST_JAVA, 23, 23 ),
1606                          LocationMatcher( TEST_JAVA, 24,  5 ) ),
1607            ChunkMatcher( '\n    ',
1608                          LocationMatcher( TEST_JAVA, 24, 27 ),
1609                          LocationMatcher( TEST_JAVA, 25,  3 ) ),
1610          )
1611        } ) )
1612      } )
1613    }
1614  } )
1615
1616
1617@WithRetry
1618@SharedYcmd
1619def Subcommands_Format_WholeFile_Tabs_test( app ):
1620  RunTest( app, {
1621    'description': 'Formatting is applied on the whole file '
1622                   'with tabs composed of 2 spaces',
1623    'request': {
1624      'command': 'Format',
1625      'filepath': TEST_JAVA,
1626      'options': {
1627        'tab_size': 4,
1628        'insert_spaces': False
1629      }
1630    },
1631    'expect': {
1632      'response': requests.codes.ok,
1633      'data': has_entries( {
1634        'fixits': contains_exactly( has_entries( {
1635          'chunks': contains_exactly(
1636            ChunkMatcher( '\n\t',
1637                          LocationMatcher( TEST_JAVA,  3, 20 ),
1638                          LocationMatcher( TEST_JAVA,  4,  3 ) ),
1639            ChunkMatcher( '\n\n\t',
1640                          LocationMatcher( TEST_JAVA,  4, 22 ),
1641                          LocationMatcher( TEST_JAVA,  6,  3 ) ),
1642            ChunkMatcher( '\n\t\t',
1643                          LocationMatcher( TEST_JAVA,  6, 34 ),
1644                          LocationMatcher( TEST_JAVA,  7,  5 ) ),
1645            ChunkMatcher( '\n\t\t',
1646                          LocationMatcher( TEST_JAVA,  7, 35 ),
1647                          LocationMatcher( TEST_JAVA,  8,  5 ) ),
1648            ChunkMatcher( '',
1649                          LocationMatcher( TEST_JAVA,  8, 25 ),
1650                          LocationMatcher( TEST_JAVA,  8, 26 ) ),
1651            ChunkMatcher( '\n\t',
1652                          LocationMatcher( TEST_JAVA,  8, 27 ),
1653                          LocationMatcher( TEST_JAVA,  9,  3 ) ),
1654            ChunkMatcher( '\n\n\t',
1655                          LocationMatcher( TEST_JAVA,  9,  4 ),
1656                          LocationMatcher( TEST_JAVA, 11,  3 ) ),
1657            ChunkMatcher( '\n\t\t',
1658                          LocationMatcher( TEST_JAVA, 11, 29 ),
1659                          LocationMatcher( TEST_JAVA, 12,  5 ) ),
1660            ChunkMatcher( '\n\t\t',
1661                          LocationMatcher( TEST_JAVA, 12, 26 ),
1662                          LocationMatcher( TEST_JAVA, 13,  5 ) ),
1663            ChunkMatcher( '',
1664                          LocationMatcher( TEST_JAVA, 13, 24 ),
1665                          LocationMatcher( TEST_JAVA, 13, 25 ) ),
1666            ChunkMatcher( '',
1667                          LocationMatcher( TEST_JAVA, 13, 29 ),
1668                          LocationMatcher( TEST_JAVA, 13, 30 ) ),
1669            ChunkMatcher( '\n\n\t\t',
1670                          LocationMatcher( TEST_JAVA, 13, 32 ),
1671                          LocationMatcher( TEST_JAVA, 15,  5 ) ),
1672            ChunkMatcher( '\n\t\t',
1673                          LocationMatcher( TEST_JAVA, 15, 58 ),
1674                          LocationMatcher( TEST_JAVA, 16,  5 ) ),
1675            ChunkMatcher( '\n\t',
1676                          LocationMatcher( TEST_JAVA, 16, 42 ),
1677                          LocationMatcher( TEST_JAVA, 17,  3 ) ),
1678            ChunkMatcher( '\n\n\t',
1679                          LocationMatcher( TEST_JAVA, 17,  4 ),
1680                          LocationMatcher( TEST_JAVA, 20,  3 ) ),
1681            ChunkMatcher( '\n\t\t',
1682                          LocationMatcher( TEST_JAVA, 20, 28 ),
1683                          LocationMatcher( TEST_JAVA, 21,  5 ) ),
1684            ChunkMatcher( '\n\t\t',
1685                          LocationMatcher( TEST_JAVA, 21, 28 ),
1686                          LocationMatcher( TEST_JAVA, 22,  5 ) ),
1687            ChunkMatcher( '\n\t\t',
1688                          LocationMatcher( TEST_JAVA, 22, 30 ),
1689                          LocationMatcher( TEST_JAVA, 23,  5 ) ),
1690            ChunkMatcher( '\n\t\t',
1691                          LocationMatcher( TEST_JAVA, 23, 23 ),
1692                          LocationMatcher( TEST_JAVA, 24,  5 ) ),
1693            ChunkMatcher( '\n\t',
1694                          LocationMatcher( TEST_JAVA, 24, 27 ),
1695                          LocationMatcher( TEST_JAVA, 25,  3 ) ),
1696          )
1697        } ) )
1698      } )
1699    }
1700  } )
1701
1702
1703@WithRetry
1704@SharedYcmd
1705def Subcommands_Format_Range_Spaces_test( app ):
1706  RunTest( app, {
1707    'description': 'Formatting is applied on some part of the file '
1708                   'with tabs composed of 4 spaces',
1709    'request': {
1710      'command': 'Format',
1711      'filepath': TEST_JAVA,
1712      'range': {
1713        'start': {
1714          'line_num': 20,
1715          'column_num': 1,
1716        },
1717        'end': {
1718          'line_num': 25,
1719          'column_num': 4
1720        }
1721      },
1722      'options': {
1723        'tab_size': 4,
1724        'insert_spaces': True
1725      }
1726    },
1727    'expect': {
1728      'response': requests.codes.ok,
1729      'data': has_entries( {
1730        'fixits': contains_exactly( has_entries( {
1731          'chunks': contains_exactly(
1732            ChunkMatcher( '  ',
1733                          LocationMatcher( TEST_JAVA, 20,  1 ),
1734                          LocationMatcher( TEST_JAVA, 20,  3 ) ),
1735            ChunkMatcher( '\n      ',
1736                          LocationMatcher( TEST_JAVA, 20, 28 ),
1737                          LocationMatcher( TEST_JAVA, 21,  5 ) ),
1738            ChunkMatcher( '\n      ',
1739                          LocationMatcher( TEST_JAVA, 21, 28 ),
1740                          LocationMatcher( TEST_JAVA, 22,  5 ) ),
1741            ChunkMatcher( '\n      ',
1742                          LocationMatcher( TEST_JAVA, 22, 30 ),
1743                          LocationMatcher( TEST_JAVA, 23,  5 ) ),
1744            ChunkMatcher( '\n      ',
1745                          LocationMatcher( TEST_JAVA, 23, 23 ),
1746                          LocationMatcher( TEST_JAVA, 24,  5 ) ),
1747          )
1748        } ) )
1749      } )
1750    }
1751  } )
1752
1753
1754@WithRetry
1755@SharedYcmd
1756def Subcommands_Format_Range_Tabs_test( app ):
1757  RunTest( app, {
1758    'description': 'Formatting is applied on some part of the file '
1759                   'with tabs instead of spaces',
1760    'request': {
1761      'command': 'Format',
1762      'filepath': TEST_JAVA,
1763      'range': {
1764        'start': {
1765          'line_num': 20,
1766          'column_num': 1,
1767        },
1768        'end': {
1769          'line_num': 25,
1770          'column_num': 4
1771        }
1772      },
1773      'options': {
1774        'tab_size': 4,
1775        'insert_spaces': False
1776      }
1777    },
1778    'expect': {
1779      'response': requests.codes.ok,
1780      'data': has_entries( {
1781        'fixits': contains_exactly( has_entries( {
1782          'chunks': contains_exactly(
1783            ChunkMatcher( '\t',
1784                          LocationMatcher( TEST_JAVA, 20,  1 ),
1785                          LocationMatcher( TEST_JAVA, 20,  3 ) ),
1786            ChunkMatcher( '\n\t\t',
1787                          LocationMatcher( TEST_JAVA, 20, 28 ),
1788                          LocationMatcher( TEST_JAVA, 21,  5 ) ),
1789            ChunkMatcher( '\n\t\t',
1790                          LocationMatcher( TEST_JAVA, 21, 28 ),
1791                          LocationMatcher( TEST_JAVA, 22,  5 ) ),
1792            ChunkMatcher( '\n\t\t',
1793                          LocationMatcher( TEST_JAVA, 22, 30 ),
1794                          LocationMatcher( TEST_JAVA, 23,  5 ) ),
1795            ChunkMatcher( '\n\t\t',
1796                          LocationMatcher( TEST_JAVA, 23, 23 ),
1797                          LocationMatcher( TEST_JAVA, 24,  5 ) ),
1798            ChunkMatcher( '\n\t',
1799                          LocationMatcher( TEST_JAVA, 24, 27 ),
1800                          LocationMatcher( TEST_JAVA, 25,  3 ) ),
1801          )
1802        } ) )
1803      } )
1804    }
1805  } )
1806
1807
1808@WithRetry
1809@SharedYcmd
1810def RunGoToTest( app, description, filepath, line, col, cmd, goto_response ):
1811  RunTest( app, {
1812    'description': description,
1813    'request': {
1814      'command': cmd,
1815      'line_num': line,
1816      'column_num': col,
1817      'filepath': filepath
1818    },
1819    'expect': {
1820      'response': requests.codes.ok,
1821      'data': goto_response,
1822    }
1823  } )
1824
1825
1826@pytest.mark.parametrize( 'test', [
1827    # Member function local variable
1828    { 'request': { 'line': 28, 'col': 5, 'filepath': TESTLAUNCHER_JAVA },
1829      'response': { 'line_num': 27, 'column_num': 18,
1830                    'filepath': TESTLAUNCHER_JAVA },
1831      'description': 'GoTo works for member local variable' },
1832    # Member variable
1833    { 'request': { 'line': 22, 'col': 7, 'filepath': TESTLAUNCHER_JAVA },
1834      'response': { 'line_num': 8, 'column_num': 16,
1835                    'filepath': TESTLAUNCHER_JAVA },
1836      'description': 'GoTo works for member variable' },
1837    # Method
1838    { 'request': { 'line': 28, 'col': 7, 'filepath': TESTLAUNCHER_JAVA },
1839      'response': { 'line_num': 21, 'column_num': 16,
1840                    'filepath': TESTLAUNCHER_JAVA },
1841      'description': 'GoTo works for method' },
1842    # Constructor
1843    { 'request': { 'line': 38, 'col': 26, 'filepath': TESTLAUNCHER_JAVA },
1844      'response': { 'line_num': 10, 'column_num': 10,
1845                    'filepath': TESTLAUNCHER_JAVA },
1846      'description': 'GoTo works for jumping to constructor' },
1847    # Jump to self - main()
1848    { 'request': { 'line': 26, 'col': 22, 'filepath': TESTLAUNCHER_JAVA },
1849      'response': { 'line_num': 26, 'column_num': 22,
1850                    'filepath': TESTLAUNCHER_JAVA },
1851      'description': 'GoTo works for jumping to the same position' },
1852    # Static method
1853    { 'request': { 'line': 37, 'col': 11, 'filepath': TESTLAUNCHER_JAVA },
1854      'response': { 'line_num': 13, 'column_num': 21,
1855                    'filepath': TESTLAUNCHER_JAVA },
1856      'description': 'GoTo works for static method' },
1857    # Static variable
1858    { 'request': { 'line': 14, 'col': 11, 'filepath': TESTLAUNCHER_JAVA },
1859      'response': { 'line_num': 12, 'column_num': 21,
1860                    'filepath': TESTLAUNCHER_JAVA },
1861      'description': 'GoTo works for static variable' },
1862    # Argument variable
1863    { 'request': { 'line': 23, 'col': 5, 'filepath': TESTLAUNCHER_JAVA },
1864      'response': { 'line_num': 21, 'column_num': 32,
1865                    'filepath': TESTLAUNCHER_JAVA },
1866      'description': 'GoTo works for argument variable' },
1867    # Class
1868    { 'request': { 'line': 27, 'col': 10, 'filepath': TESTLAUNCHER_JAVA },
1869      'response': { 'line_num': 6, 'column_num': 7,
1870                    'filepath': TESTLAUNCHER_JAVA },
1871      'description': 'GoTo works for jumping to class declaration' },
1872    # Unicode
1873    { 'request': { 'line': 8, 'col': 12, 'filepath': TEST_JAVA },
1874      'response': { 'line_num': 7, 'column_num': 12, 'filepath': TEST_JAVA },
1875      'description': 'GoTo works for unicode identifiers' }
1876  ] )
1877@pytest.mark.parametrize( 'command', [ 'GoTo',
1878                                       'GoToDefinition',
1879                                       'GoToDeclaration' ] )
1880@SharedYcmd
1881def Subcommands_GoTo_test( app, command, test ):
1882  RunGoToTest( app,
1883               test[ 'description' ],
1884               test[ 'request' ][ 'filepath' ],
1885               test[ 'request' ][ 'line' ],
1886               test[ 'request' ][ 'col' ],
1887               command,
1888               has_entries( test[ 'response' ] ) )
1889
1890
1891@pytest.mark.parametrize( 'test', [
1892    # Member function local variable
1893    { 'request': { 'line': 28, 'col': 5, 'filepath': TESTLAUNCHER_JAVA },
1894      'response': { 'line_num': 6, 'column_num': 7,
1895                    'filepath': TESTLAUNCHER_JAVA },
1896      'description': 'GoToType works for member local variable' },
1897    # Member variable
1898    { 'request': { 'line': 22, 'col': 7, 'filepath': TESTLAUNCHER_JAVA },
1899      'response': { 'line_num': 6, 'column_num': 14, 'filepath': TSET_JAVA },
1900      'description': 'GoToType works for member variable' },
1901  ] )
1902@SharedYcmd
1903def Subcommands_GoToType_test( app, test ):
1904  RunGoToTest( app,
1905               test[ 'description' ],
1906               test[ 'request' ][ 'filepath' ],
1907               test[ 'request' ][ 'line' ],
1908               test[ 'request' ][ 'col' ],
1909               'GoToType',
1910               has_entries( test[ 'response' ] ) )
1911
1912
1913@pytest.mark.parametrize( 'test', [
1914    # Interface
1915    { 'request': { 'line': 17, 'col': 25, 'filepath': TESTLAUNCHER_JAVA },
1916      'response': { 'line_num': 28, 'column_num': 16,
1917                    'filepath': TESTLAUNCHER_JAVA },
1918      'description': 'GoToImplementation on interface '
1919                     'jumps to its implementation' },
1920    # Interface reference
1921    { 'request': { 'line': 21, 'col': 30, 'filepath': TESTLAUNCHER_JAVA },
1922      'response': { 'line_num': 28, 'column_num': 16,
1923                    'filepath': TESTLAUNCHER_JAVA },
1924      'description': 'GoToImplementation on interface reference '
1925                     'jumpts to its implementation' },
1926  ] )
1927@SharedYcmd
1928def Subcommands_GoToImplementation_test( app, test ):
1929  RunGoToTest( app,
1930               test[ 'description' ],
1931               test[ 'request' ][ 'filepath' ],
1932               test[ 'request' ][ 'line' ],
1933               test[ 'request' ][ 'col' ],
1934               'GoToImplementation',
1935               has_entries( test[ 'response' ] ) )
1936
1937
1938@WithRetry
1939@SharedYcmd
1940def Subcommands_OrganizeImports_test( app ):
1941  RunTest( app, {
1942    'description': 'Imports are resolved and sorted, '
1943                   'and unused ones are removed',
1944    'request': {
1945      'command': 'OrganizeImports',
1946      'filepath': TESTLAUNCHER_JAVA
1947    },
1948    'expect': {
1949      'response': requests.codes.ok,
1950      'data': has_entries( {
1951        'fixits': contains_exactly( has_entries( {
1952          'chunks': contains_exactly(
1953            ChunkMatcher( 'import com.youcompleteme.Test;\n'
1954                          'import com.youcompleteme.testing.Tset;',
1955                          LocationMatcher( TESTLAUNCHER_JAVA, 3,  1 ),
1956                          LocationMatcher( TESTLAUNCHER_JAVA, 4, 54 ) ),
1957          )
1958        } ) )
1959      } )
1960    }
1961  } )
1962
1963
1964@WithRetry
1965@SharedYcmd
1966@patch( 'ycmd.completers.language_server.language_server_completer.'
1967        'REQUEST_TIMEOUT_COMMAND',
1968        5 )
1969def Subcommands_RequestTimeout_test( app ):
1970  with patch.object(
1971    handlers._server_state.GetFiletypeCompleter( [ 'java' ] ).GetConnection(),
1972    'WriteData' ):
1973    RunTest( app, {
1974      'description': 'Request timeout throws an error',
1975      'request': {
1976        'command': 'FixIt',
1977        'line_num': 1,
1978        'column_num': 1,
1979        'filepath': TEST_JAVA,
1980      },
1981      'expect': {
1982        'response': requests.codes.internal_server_error,
1983        'data': ErrorMatcher( ResponseTimeoutException, 'Response Timeout' )
1984      }
1985    } )
1986
1987
1988@WithRetry
1989@SharedYcmd
1990def Subcommands_RequestFailed_test( app ):
1991  connection = handlers._server_state.GetFiletypeCompleter(
1992    [ 'java' ] ).GetConnection()
1993
1994  def WriteJunkToServer( data ):
1995    junk = data.replace( bytes( b'textDocument/codeAction' ),
1996                         bytes( b'textDocument/codeFAILED' ) )
1997
1998    with connection._stdin_lock:
1999      connection._server_stdin.write( junk )
2000      connection._server_stdin.flush()
2001
2002
2003  with patch.object( connection, 'WriteData', side_effect = WriteJunkToServer ):
2004    RunTest( app, {
2005      'description': 'Response errors propagate to the client',
2006      'request': {
2007        'command': 'FixIt',
2008        'line_num': 1,
2009        'column_num': 1,
2010        'filepath': TEST_JAVA,
2011      },
2012      'expect': {
2013        'response': requests.codes.internal_server_error,
2014        'data': ErrorMatcher( ResponseFailedException )
2015      }
2016    } )
2017
2018
2019@WithRetry
2020@SharedYcmd
2021def Subcommands_IndexOutOfRange_test( app ):
2022  RunTest( app, {
2023    'description': 'Request with invalid position does not crash',
2024    'request': {
2025      'command': 'FixIt',
2026      'line_num': 99,
2027      'column_num': 99,
2028      'filepath': TEST_JAVA,
2029    },
2030    'expect': {
2031      'response': requests.codes.ok,
2032      'data': has_entries( { 'fixits': contains_exactly(
2033        has_entries( { 'text': 'Generate Getters and Setters',
2034          'chunks': instance_of( list ) } ),
2035        has_entries( { 'text': 'Change modifiers to final where possible',
2036          'chunks': instance_of( list ) } ),
2037      ) } ),
2038    }
2039  } )
2040
2041
2042@WithRetry
2043@SharedYcmd
2044def Subcommands_InvalidRange_test( app ):
2045  RunTest( app, {
2046    'description': 'Request with invalid visual range is rejected',
2047    'request': {
2048      'command': 'FixIt',
2049      'line_num': 99,
2050      'column_num': 99,
2051      'filepath': TEST_JAVA,
2052      'range': {
2053        'start': {
2054          'line_num': 99,
2055          'column_num': 99
2056        },
2057        'end': {
2058          'line_num': 100,
2059          'column_num': 100
2060        }
2061      }
2062    },
2063    'expect': {
2064      'response': requests.codes.internal_server_error,
2065      'data': ErrorMatcher( RuntimeError, 'Invalid range' ),
2066    }
2067  } )
2068
2069
2070@WithRetry
2071@SharedYcmd
2072def Subcommands_DifferentFileTypesUpdate_test( app ):
2073  RunTest( app, {
2074    'description': 'Request error handles the error',
2075    'request': {
2076      'command': 'FixIt',
2077      'line_num': 99,
2078      'column_num': 99,
2079      'filepath': TEST_JAVA,
2080      'file_data': {
2081        '!/bin/sh': {
2082          'filetypes': [],
2083          'contents': 'this should be ignored by the completer',
2084        },
2085        '/path/to/non/project/file': {
2086          'filetypes': [ 'c' ],
2087          'contents': 'this should be ignored by the completer',
2088        },
2089        TESTLAUNCHER_JAVA: {
2090          'filetypes': [ 'some', 'java', 'junk', 'also' ],
2091          'contents': ReadFile( TESTLAUNCHER_JAVA ),
2092        },
2093        '!/usr/bin/sh': {
2094          'filetypes': [ 'java' ],
2095          'contents': '\n',
2096        },
2097      }
2098    },
2099    'expect': {
2100      'response': requests.codes.ok,
2101      'data': has_entries( { 'fixits': contains_exactly(
2102        has_entries( { 'text': 'Generate Getters and Setters',
2103          'chunks': instance_of( list ) } ),
2104        has_entries( { 'text': 'Change modifiers to final where possible',
2105          'chunks': instance_of( list ) } ),
2106      ) } ),
2107    }
2108  } )
2109
2110
2111@WithRetry
2112@IsolatedYcmd( { 'extra_conf_globlist':
2113                 PathToTestFile( 'extra_confs', '*' ) } )
2114def Subcommands_ExtraConf_SettingsValid_test( app ):
2115  filepath = PathToTestFile( 'extra_confs',
2116                             'simple_extra_conf_project',
2117                             'src',
2118                             'ExtraConf.java' )
2119  RunTest( app, {
2120    'description': 'RefactorRename is disabled in extra conf.',
2121    'request': {
2122      'command': 'RefactorRename',
2123      'arguments': [ 'renamed_l' ],
2124      'filepath': filepath,
2125      'line_num': 1,
2126      'column_num': 7,
2127    },
2128    'expect': {
2129      'response': requests.codes.ok,
2130      'data': has_entries( {
2131        'fixits': contains_exactly( has_entries( {
2132          'chunks': empty(),
2133          'location': LocationMatcher( filepath, 1, 7 )
2134        } ) )
2135      } )
2136    }
2137  } )
2138
2139
2140@WithRetry
2141@IsolatedYcmd( { 'extra_conf_globlist':
2142                 PathToTestFile( 'extra_confs', '*' ) } )
2143def Subcommands_AdditionalFormatterOptions_test( app ):
2144  filepath = PathToTestFile( 'extra_confs',
2145                             'simple_extra_conf_project',
2146                             'src',
2147                             'ExtraConf.java' )
2148  RunTest( app, {
2149    'description': 'Format respects settings from extra conf.',
2150    'request': {
2151      'command': 'Format',
2152      'filepath': filepath,
2153      'options': {
2154        'tab_size': 4,
2155        'insert_spaces': True
2156      }
2157    },
2158    'expect': {
2159      'response': requests.codes.ok,
2160      'data': has_entries( {
2161        'fixits': contains_exactly( has_entries( {
2162          'chunks': contains_exactly(
2163            ChunkMatcher( '\n    ',
2164                          LocationMatcher( filepath,  1, 18 ),
2165                          LocationMatcher( filepath,  2,  3 ) ),
2166            ChunkMatcher( '\n            ',
2167                          LocationMatcher( filepath,  2, 20 ),
2168                          LocationMatcher( filepath,  2, 21 ) ),
2169            ChunkMatcher( '',
2170                          LocationMatcher( filepath,  2, 29 ),
2171                          LocationMatcher( filepath,  2, 30 ) ),
2172            ChunkMatcher( '\n    ',
2173                          LocationMatcher( filepath,  2, 33 ),
2174                          LocationMatcher( filepath,  2, 33 ) ),
2175            ChunkMatcher( '\n\n    ',
2176                          LocationMatcher( filepath,  2, 34 ),
2177                          LocationMatcher( filepath,  4,  3 ) ),
2178            ChunkMatcher( '\n            ',
2179                          LocationMatcher( filepath,  4, 27 ),
2180                          LocationMatcher( filepath,  4, 28 ) ),
2181            ChunkMatcher( '',
2182                          LocationMatcher( filepath,  4, 41 ),
2183                          LocationMatcher( filepath,  4, 42 ) ),
2184            ChunkMatcher( '\n        ',
2185                          LocationMatcher( filepath,  4, 45 ),
2186                          LocationMatcher( filepath,  5,  5 ) ),
2187            ChunkMatcher( '\n                ',
2188                          LocationMatcher( filepath,  5, 33 ),
2189                          LocationMatcher( filepath,  5, 34 ) ),
2190            ChunkMatcher( '',
2191                          LocationMatcher( filepath,  5, 36 ),
2192                          LocationMatcher( filepath,  5, 37 ) ),
2193            ChunkMatcher( '\n        ',
2194                          LocationMatcher( filepath,  5, 39 ),
2195                          LocationMatcher( filepath,  6,  5 ) ),
2196            ChunkMatcher( '\n                ',
2197                          LocationMatcher( filepath,  6, 33 ),
2198                          LocationMatcher( filepath,  6, 34 ) ),
2199            ChunkMatcher( '',
2200                          LocationMatcher( filepath,  6, 35 ),
2201                          LocationMatcher( filepath,  6, 36 ) ),
2202            ChunkMatcher( '\n        ',
2203                          LocationMatcher( filepath,  6, 38 ),
2204                          LocationMatcher( filepath,  7,  5 ) ),
2205            ChunkMatcher( '\n        ',
2206                          LocationMatcher( filepath,  7, 11 ),
2207                          LocationMatcher( filepath,  8,  5 ) ),
2208            ChunkMatcher( '\n    ',
2209                          LocationMatcher( filepath,  8, 11 ),
2210                          LocationMatcher( filepath,  9,  3 ) ),
2211          ),
2212          'location': LocationMatcher( filepath, 1, 1 )
2213        } ) )
2214      } )
2215    }
2216  } )
2217
2218
2219@WithRetry
2220@IsolatedYcmd()
2221def Subcommands_ExtraConf_SettingsValid_UnknownExtraConf_test( app ):
2222  filepath = PathToTestFile( 'extra_confs',
2223                             'simple_extra_conf_project',
2224                             'src',
2225                             'ExtraConf.java' )
2226  contents = ReadFile( filepath )
2227
2228  response = app.post_json( '/event_notification',
2229                            BuildRequest( **{
2230                              'event_name': 'FileReadyToParse',
2231                              'contents': contents,
2232                              'filepath': filepath,
2233                              'line_num': 1,
2234                              'column_num': 7,
2235                              'filetype': 'java',
2236                            } ),
2237                            expect_errors = True )
2238
2239  print( 'FileReadyToParse result: '
2240         f'{ json.dumps( response.json, indent = 2 ) }' )
2241
2242  assert_that( response.status_code,
2243               equal_to( requests.codes.internal_server_error ) )
2244  assert_that( response.json, ErrorMatcher( UnknownExtraConf ) )
2245
2246  app.post_json(
2247    '/ignore_extra_conf_file',
2248    { 'filepath': PathToTestFile( 'extra_confs', '.ycm_extra_conf.py' ) } )
2249
2250  RunTest( app, {
2251    'description': 'RefactorRename is disabled in extra conf but ignored.',
2252    'request': {
2253      'command': 'RefactorRename',
2254      'arguments': [ 'renamed_l' ],
2255      'filepath': filepath,
2256      'contents': contents,
2257      'line_num': 1,
2258      'column_num': 7,
2259    },
2260    'expect': {
2261      'response': requests.codes.ok,
2262      'data': has_entries( {
2263        'fixits': contains_exactly( has_entries( {
2264          # Just prove that we actually got a reasonable result
2265          'chunks': is_not( empty() ),
2266        } ) )
2267      } )
2268    }
2269  } )
2270
2271
2272@SharedYcmd
2273def Subcommands_ExecuteCommand_NoArguments_test( app ):
2274  RunTest( app, {
2275    'description': 'Running a command without args fails',
2276    'request': {
2277      'command': 'ExecuteCommand',
2278      'line_num': 1,
2279      'column_num': 1,
2280      'filepath': TEST_JAVA,
2281    },
2282    'expect': {
2283      'response': requests.codes.internal_server_error,
2284      'data': ErrorMatcher( ValueError,
2285                            'Must specify a command to execute' ),
2286    }
2287  } )
2288
2289
2290@SharedYcmd
2291def Subcommands_ExecuteCommand_test( app ):
2292  RunTest( app, {
2293    'description': 'Running a command does what it says it does',
2294    'request': {
2295      'command': 'ExecuteCommand',
2296      'arguments': [ 'java.edit.organizeImports' ],
2297      'line_num': 1,
2298      'column_num': 1,
2299      'filepath': TEST_JAVA,
2300    },
2301    'expect': {
2302      # We don't specify the path for import organize, and jdt.ls returns shrug
2303      'response': requests.codes.ok,
2304      'data': ''
2305    }
2306  } )
2307
2308
2309def Dummy_test():
2310  # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51
2311  assert True
2312