1# Copyright (C) 2013-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
18from hamcrest import ( assert_that,
19                       contains_exactly,
20                       contains_inanyorder,
21                       empty,
22                       equal_to,
23                       has_entries,
24                       has_items )
25from unittest.mock import patch
26from ycmd.tests import IsolatedYcmd, SharedYcmd, PathToTestFile
27from ycmd.tests.test_utils import ( BuildRequest, CompletionEntryMatcher,
28                                    DummyCompleter, PatchCompleter )
29
30
31@SharedYcmd
32def GetCompletions_RequestValidation_NoLineNumException_test( app ):
33  response = app.post_json( '/semantic_completion_available', {
34    'column_num': 0,
35    'filepath': '/foo',
36    'file_data': {
37      '/foo': {
38        'filetypes': [ 'text' ],
39        'contents': 'zoo'
40      }
41    }
42  }, status = '5*', expect_errors = True )
43  response.mustcontain( 'missing', 'line_num' )
44
45
46@SharedYcmd
47def GetCompletions_IdentifierCompleter_Works_test( app ):
48  event_data = BuildRequest( contents = 'foo foogoo ba',
49                             event_name = 'FileReadyToParse' )
50
51  app.post_json( '/event_notification', event_data )
52
53  # query is 'oo'
54  completion_data = BuildRequest( contents = 'oo foo foogoo ba',
55                                  column_num = 3 )
56  response_data = app.post_json( '/completions', completion_data ).json
57
58  assert_that( 1, equal_to( response_data[ 'completion_start_column' ] ) )
59  assert_that(
60    response_data[ 'completions' ],
61    has_items( CompletionEntryMatcher( 'foo', '[ID]' ),
62               CompletionEntryMatcher( 'foogoo', '[ID]' ) )
63  )
64
65
66@IsolatedYcmd( { 'min_num_identifier_candidate_chars': 4 } )
67def GetCompletions_IdentifierCompleter_FilterShortCandidates_test( app ):
68  event_data = BuildRequest( contents = 'foo foogoo gooo',
69                             event_name = 'FileReadyToParse' )
70  app.post_json( '/event_notification', event_data )
71
72  completion_data = BuildRequest( contents = 'oo', column_num = 3 )
73  response = app.post_json( '/completions',
74                            completion_data ).json[ 'completions' ]
75
76  assert_that( response,
77               contains_inanyorder( CompletionEntryMatcher( 'foogoo' ),
78                                    CompletionEntryMatcher( 'gooo' ) ) )
79
80
81@SharedYcmd
82def GetCompletions_IdentifierCompleter_StartColumn_AfterWord_test( app ):
83  completion_data = BuildRequest( contents = 'oo foo foogoo ba',
84                                  column_num = 11 )
85  response_data = app.post_json( '/completions', completion_data ).json
86  assert_that( 8, equal_to( response_data[ 'completion_start_column' ] ) )
87
88
89@SharedYcmd
90def GetCompletions_IdentifierCompleter_WorksForSpecialIdentifierChars_test(
91  app ):
92  contents = """
93    textarea {
94      font-family: sans-serif;
95      font-size: 12px;
96    }"""
97  event_data = BuildRequest( contents = contents,
98                             filetype = 'css',
99                             event_name = 'FileReadyToParse' )
100
101  app.post_json( '/event_notification', event_data )
102
103  # query is 'fo'
104  completion_data = BuildRequest( contents = 'fo ' + contents,
105                                  filetype = 'css',
106                                  column_num = 3 )
107  results = app.post_json( '/completions',
108                           completion_data ).json[ 'completions' ]
109
110  assert_that(
111    results,
112    has_items( CompletionEntryMatcher( 'font-size', '[ID]' ),
113               CompletionEntryMatcher( 'font-family', '[ID]' ) )
114  )
115
116
117@SharedYcmd
118def GetCompletions_IdentifierCompleter_Unicode_InLine_test( app ):
119  contents = """
120    This is some text cøntaining unicøde
121  """
122
123  event_data = BuildRequest( contents = contents,
124                             filetype = 'css',
125                             event_name = 'FileReadyToParse' )
126
127  app.post_json( '/event_notification', event_data )
128
129  # query is 'tx'
130  completion_data = BuildRequest( contents = 'tx ' + contents,
131                                  filetype = 'css',
132                                  column_num = 3 )
133  results = app.post_json( '/completions',
134                           completion_data ).json[ 'completions' ]
135
136  assert_that(
137    results,
138    has_items( CompletionEntryMatcher( 'text', '[ID]' ) )
139  )
140
141
142@SharedYcmd
143def GetCompletions_IdentifierCompleter_UnicodeQuery_InLine_test( app ):
144  contents = """
145    This is some text cøntaining unicøde
146  """
147
148  event_data = BuildRequest( contents = contents,
149                             filetype = 'css',
150                             event_name = 'FileReadyToParse' )
151
152  app.post_json( '/event_notification', event_data )
153
154  # query is 'cø'
155  completion_data = BuildRequest( contents = 'cø ' + contents,
156                                  filetype = 'css',
157                                  column_num = 4 )
158  results = app.post_json( '/completions',
159                           completion_data ).json[ 'completions' ]
160
161  assert_that(
162    results,
163    has_items( CompletionEntryMatcher( 'cøntaining', '[ID]' ),
164               CompletionEntryMatcher( 'unicøde', '[ID]' ) )
165  )
166
167
168@IsolatedYcmd()
169def GetCompletions_IdentifierCompleter_Unicode_MultipleCodePoints_test( app ):
170  # The first ō is on one code point while the second is on two ("o" + combining
171  # macron character).
172  event_data = BuildRequest( contents = 'fōo\nfōo',
173                             event_name = 'FileReadyToParse' )
174
175  app.post_json( '/event_notification', event_data )
176
177  # query is 'fo'
178  completion_data = BuildRequest( contents = 'fōo\nfōo\nfo',
179                                  line_num = 3,
180                                  column_num = 3 )
181  response_data = app.post_json( '/completions', completion_data ).json
182
183  assert_that( 1, equal_to( response_data[ 'completion_start_column' ] ) )
184  assert_that(
185    response_data[ 'completions' ],
186    has_items( CompletionEntryMatcher( 'fōo', '[ID]' ),
187               CompletionEntryMatcher( 'fōo', '[ID]' ) )
188  )
189
190
191@SharedYcmd
192@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
193        return_value = [ 'foo', 'bar', 'qux' ] )
194def GetCompletions_ForceSemantic_Works_test( candidates, app ):
195  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
196    completion_data = BuildRequest( filetype = 'dummy_filetype',
197                                    force_semantic = True )
198
199    results = app.post_json( '/completions',
200                             completion_data ).json[ 'completions' ]
201    assert_that( results, has_items( CompletionEntryMatcher( 'foo' ),
202                                     CompletionEntryMatcher( 'bar' ),
203                                     CompletionEntryMatcher( 'qux' ) ) )
204
205
206@SharedYcmd
207def GetCompletions_ForceSemantic_NoSemanticCompleter_test( app, *args ):
208  event_data = BuildRequest( event_name = 'FileReadyToParse',
209                             filetype = 'dummy_filetype',
210                             contents = 'complete_this_word\ncom' )
211  app.post_json( '/event_notification', event_data )
212
213  completion_data = BuildRequest( filetype = 'dummy_filetype',
214                                  force_semantic = True,
215                                  contents = 'complete_this_word\ncom',
216                                  line_number = 2,
217                                  column_num = 4 )
218  results = app.post_json( '/completions', completion_data ).json
219  assert_that( results, has_entries( {
220    'completions': empty(),
221    'errors': empty(),
222  } ) )
223
224  # For proof, show that non-forced completion would return identifiers
225  completion_data = BuildRequest( filetype = 'dummy_filetype',
226                                  contents = 'complete_this_word\ncom',
227                                  line_number = 2,
228                                  column_num = 4 )
229  results = app.post_json( '/completions', completion_data ).json
230  assert_that( results, has_entries( {
231    'completions': contains_exactly(
232      CompletionEntryMatcher( 'com' ),
233      CompletionEntryMatcher( 'complete_this_word' ) ),
234    'errors': empty(),
235  } ) )
236
237
238@SharedYcmd
239def GetCompletions_IdentifierCompleter_SyntaxKeywordsAdded_test( app ):
240  event_data = BuildRequest( event_name = 'FileReadyToParse',
241                             syntax_keywords = [ 'foo', 'bar', 'zoo' ] )
242
243  app.post_json( '/event_notification', event_data )
244
245  completion_data = BuildRequest( contents = 'oo ',
246                                  column_num = 3 )
247
248  results = app.post_json( '/completions',
249                           completion_data ).json[ 'completions' ]
250  assert_that( results,
251               has_items( CompletionEntryMatcher( 'foo' ),
252                          CompletionEntryMatcher( 'zoo' ) ) )
253
254
255@SharedYcmd
256def GetCompletions_IdentifierCompleter_TagsAdded_test( app ):
257  event_data = BuildRequest( event_name = 'FileReadyToParse',
258                             tag_files = [ PathToTestFile( 'basic.tags' ) ] )
259  app.post_json( '/event_notification', event_data )
260
261  completion_data = BuildRequest( contents = 'oo',
262                                  column_num = 3,
263                                  filetype = 'cpp' )
264  results = app.post_json( '/completions',
265                           completion_data ).json[ 'completions' ]
266  assert_that( results,
267               has_items( CompletionEntryMatcher( 'foosy' ),
268                          CompletionEntryMatcher( 'fooaaa' ) ) )
269
270
271@SharedYcmd
272def GetCompletions_IdentifierCompleter_JustFinishedIdentifier_test( app ):
273  event_data = BuildRequest( event_name = 'CurrentIdentifierFinished',
274                             column_num = 4,
275                             contents = 'foo' )
276  app.post_json( '/event_notification', event_data )
277
278  completion_data = BuildRequest( contents = 'oo', column_num = 3 )
279  results = app.post_json( '/completions',
280                           completion_data ).json[ 'completions' ]
281  assert_that( results,
282               has_items( CompletionEntryMatcher( 'foo' ) ) )
283
284
285@IsolatedYcmd()
286def GetCompletions_IdentifierCompleter_IgnoreFinishedIdentifierInString_test(
287  app ):
288
289  event_data = BuildRequest( event_name = 'CurrentIdentifierFinished',
290                             column_num = 6,
291                             contents = '"foo"' )
292
293  app.post_json( '/event_notification', event_data )
294
295  completion_data = BuildRequest( contents = 'oo', column_num = 3 )
296  results = app.post_json( '/completions',
297                           completion_data ).json[ 'completions' ]
298  assert_that( results, empty() )
299
300
301@SharedYcmd
302def GetCompletions_IdentifierCompleter_IdentifierUnderCursor_test( app ):
303  event_data = BuildRequest( event_name = 'InsertLeave',
304                             column_num = 2,
305                             contents = 'foo' )
306  app.post_json( '/event_notification', event_data )
307
308  completion_data = BuildRequest( contents = 'oo', column_num = 3 )
309  results = app.post_json( '/completions',
310                           completion_data ).json[ 'completions' ]
311  assert_that( results,
312               has_items( CompletionEntryMatcher( 'foo' ) ) )
313
314
315@IsolatedYcmd()
316def GetCompletions_IdentifierCompleter_IgnoreCursorIdentifierInString_test(
317  app ):
318
319  event_data = BuildRequest( event_name = 'InsertLeave',
320                             column_num = 3,
321                             contents = '"foo"' )
322  app.post_json( '/event_notification', event_data )
323
324  completion_data = BuildRequest( contents = 'oo', column_num = 3 )
325  results = app.post_json( '/completions',
326                           completion_data ).json[ 'completions' ]
327  assert_that( results, empty() )
328
329
330@SharedYcmd
331def GetCompletions_FilenameCompleter_Works_test( app ):
332  filepath = PathToTestFile( 'filename_completer', 'test.foo' )
333  completion_data = BuildRequest( filepath = filepath,
334                                  contents = './',
335                                  column_num = 3 )
336  results = app.post_json( '/completions',
337                           completion_data ).json[ 'completions' ]
338  assert_that( results,
339               has_items( CompletionEntryMatcher( 'inner_dir', '[Dir]' ) ) )
340
341
342@SharedYcmd
343def GetCompletions_FilenameCompleter_FallBackToIdentifierCompleter_test( app ):
344  filepath = PathToTestFile( 'filename_completer', 'test.foo' )
345  event_data = BuildRequest( filepath = filepath,
346                             contents = './nonexisting_dir',
347                             filetype = 'foo',
348                             event_name = 'FileReadyToParse' )
349
350  app.post_json( '/event_notification', event_data )
351
352  completion_data = BuildRequest( filepath = filepath,
353                                  contents = './nonexisting_dir nd',
354                                  filetype = 'foo',
355                                  column_num = 21 )
356
357  assert_that(
358    app.post_json( '/completions', completion_data ).json[ 'completions' ],
359    has_items( CompletionEntryMatcher( 'nonexisting_dir', '[ID]' ) )
360  )
361
362
363@SharedYcmd
364def GetCompletions_UltiSnipsCompleter_Works_test( app ):
365  event_data = BuildRequest(
366    event_name = 'BufferVisit',
367    ultisnips_snippets = [
368        { 'trigger': 'foo', 'description': 'bar' },
369        { 'trigger': 'zoo', 'description': 'goo' },
370    ] )
371
372  app.post_json( '/event_notification', event_data )
373
374  completion_data = BuildRequest( contents = 'oo ',
375                                  column_num = 3 )
376
377  results = app.post_json( '/completions',
378                           completion_data ).json[ 'completions' ]
379  assert_that(
380    results,
381    has_items(
382      CompletionEntryMatcher( 'foo', extra_menu_info='<snip> bar' ),
383      CompletionEntryMatcher( 'zoo', extra_menu_info='<snip> goo' )
384    )
385  )
386
387
388@IsolatedYcmd( { 'use_ultisnips_completer': 0 } )
389def GetCompletions_UltiSnipsCompleter_UnusedWhenOffWithOption_test( app ):
390  event_data = BuildRequest(
391    event_name = 'BufferVisit',
392    ultisnips_snippets = [
393        { 'trigger': 'foo', 'description': 'bar' },
394        { 'trigger': 'zoo', 'description': 'goo' },
395    ] )
396
397  app.post_json( '/event_notification', event_data )
398
399  completion_data = BuildRequest( contents = 'oo ', column_num = 3 )
400
401  assert_that( app.post_json( '/completions', completion_data ).json,
402               has_entries( { 'completions': empty() } ) )
403
404
405@IsolatedYcmd( { 'semantic_triggers': { 'dummy_filetype': [ '_' ] } } )
406@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
407        return_value = [ 'some_candidate' ] )
408def GetCompletions_SemanticCompleter_WorksWhenTriggerIsIdentifier_test(
409  candidates, app ):
410  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
411    completion_data = BuildRequest( filetype = 'dummy_filetype',
412                                    contents = 'some_can',
413                                    column_num = 9 )
414
415    results = app.post_json( '/completions',
416                             completion_data ).json[ 'completions' ]
417    assert_that(
418      results,
419      has_items( CompletionEntryMatcher( 'some_candidate' ) )
420    )
421
422
423@SharedYcmd
424@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
425        return_value = True )
426@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
427        return_value = [ 'attribute' ] )
428def GetCompletions_CacheIsValid_test(
429  candidates_list, should_use, app ):
430  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
431    completion_data = BuildRequest( filetype = 'dummy_filetype',
432                                    contents = 'object.attr',
433                                    line_num = 1,
434                                    column_num = 12 )
435
436    results = app.post_json( '/completions',
437                             completion_data ).json[ 'completions' ]
438    assert_that(
439      results,
440      has_items( CompletionEntryMatcher( 'attribute' ) )
441    )
442
443    completion_data = BuildRequest( filetype = 'dummy_filetype',
444                                    contents = 'object.attri',
445                                    line_num = 1,
446                                    column_num = 13 )
447
448    results = app.post_json( '/completions',
449                             completion_data ).json[ 'completions' ]
450    assert_that(
451      results,
452      has_items( CompletionEntryMatcher( 'attribute' ) )
453    )
454
455    # We ask for candidates only once because of cache.
456    assert_that( candidates_list.call_count, equal_to( 1 ) )
457
458
459@SharedYcmd
460@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
461        return_value = True )
462@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
463        side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] )
464def GetCompletions_CacheIsNotValid_DifferentLineNumber_test(
465  candidates_list, should_use, app ):
466  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
467    completion_data = BuildRequest( filetype = 'dummy_filetype',
468                                    contents = 'objectA.attr\n'
469                                               'objectB.attr',
470                                    line_num = 1,
471                                    column_num = 12 )
472
473    results = app.post_json( '/completions',
474                             completion_data ).json[ 'completions' ]
475    assert_that(
476      results,
477      has_items( CompletionEntryMatcher( 'attributeA' ) )
478    )
479
480    completion_data = BuildRequest( filetype = 'dummy_filetype',
481                                    contents = 'objectA.\n'
482                                               'objectB.',
483                                    line_num = 2,
484                                    column_num = 12 )
485
486    results = app.post_json( '/completions',
487                             completion_data ).json[ 'completions' ]
488    assert_that(
489      results,
490      has_items( CompletionEntryMatcher( 'attributeB' ) )
491    )
492
493    # We ask for candidates twice because of cache invalidation:
494    # line numbers are different between requests.
495    assert_that( candidates_list.call_count, equal_to( 2 ) )
496
497
498@SharedYcmd
499@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
500        return_value = True )
501@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
502        side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] )
503def GetCompletions_CacheIsNotValid_DifferentStartColumn_test(
504  candidates_list, should_use, app ):
505  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
506    # Start column is 9
507    completion_data = BuildRequest( filetype = 'dummy_filetype',
508                                    contents = 'objectA.attr',
509                                    line_num = 1,
510                                    column_num = 12 )
511
512    results = app.post_json( '/completions',
513                             completion_data ).json[ 'completions' ]
514    assert_that(
515      results,
516      has_items( CompletionEntryMatcher( 'attributeA' ) )
517    )
518
519    # Start column is 8
520    completion_data = BuildRequest( filetype = 'dummy_filetype',
521                                    contents = 'object.attri',
522                                    line_num = 1,
523                                    column_num = 12 )
524
525    results = app.post_json( '/completions',
526                             completion_data ).json[ 'completions' ]
527    assert_that(
528      results,
529      has_items( CompletionEntryMatcher( 'attributeB' ) )
530    )
531
532    # We ask for candidates twice because of cache invalidation:
533    # start columns are different between requests.
534    assert_that( candidates_list.call_count, equal_to( 2 ) )
535
536
537@SharedYcmd
538@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
539        return_value = True )
540@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
541        side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] )
542def GetCompletions_CacheIsNotValid_DifferentForceSemantic_test(
543  candidates_list, should_use, app ):
544  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
545    completion_data = BuildRequest( filetype = 'dummy_filetype',
546                                    contents = 'objectA.attr',
547                                    line_num = 1,
548                                    column_num = 12,
549                                    force_semantic = True )
550
551    results = app.post_json( '/completions',
552                             completion_data ).json[ 'completions' ]
553    assert_that(
554      results,
555      has_items( CompletionEntryMatcher( 'attributeA' ) )
556    )
557
558    completion_data = BuildRequest( filetype = 'dummy_filetype',
559                                    contents = 'objectA.attr',
560                                    line_num = 1,
561                                    column_num = 12 )
562
563    results = app.post_json( '/completions',
564                             completion_data ).json[ 'completions' ]
565    assert_that(
566      results,
567      has_items( CompletionEntryMatcher( 'attributeB' ) )
568    )
569
570    # We ask for candidates twice because of cache invalidation:
571    # semantic completion is forced for one of the request, not the other.
572    assert_that( candidates_list.call_count, equal_to( 2 ) )
573
574
575@SharedYcmd
576@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
577        return_value = True )
578@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
579        side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] )
580def GetCompletions_CacheIsNotValid_DifferentContents_test(
581  candidates_list, should_use, app ):
582  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
583    completion_data = BuildRequest( filetype = 'dummy_filetype',
584                                    contents = 'objectA = foo\n'
585                                               'objectA.attr',
586                                    line_num = 2,
587                                    column_num = 12 )
588
589    results = app.post_json( '/completions',
590                             completion_data ).json[ 'completions' ]
591    assert_that(
592      results,
593      has_items( CompletionEntryMatcher( 'attributeA' ) )
594    )
595
596    completion_data = BuildRequest( filetype = 'dummy_filetype',
597                                    contents = 'objectA = bar\n'
598                                               'objectA.attr',
599                                    line_num = 2,
600                                    column_num = 12 )
601
602    results = app.post_json( '/completions',
603                             completion_data ).json[ 'completions' ]
604    assert_that(
605      results,
606      has_items( CompletionEntryMatcher( 'attributeB' ) )
607    )
608
609    # We ask for candidates twice because of cache invalidation:
610    # both requests have the same cursor position and current line but file
611    # contents are different.
612    assert_that( candidates_list.call_count, equal_to( 2 ) )
613
614
615@SharedYcmd
616@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
617        return_value = True )
618@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
619        side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] )
620def GetCompletions_CacheIsNotValid_DifferentNumberOfLines_test(
621  candidates_list, should_use, app ):
622  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
623    completion_data = BuildRequest( filetype = 'dummy_filetype',
624                                    contents = 'objectA.attr\n'
625                                               'objectB.attr',
626                                    line_num = 1,
627                                    column_num = 12 )
628
629    results = app.post_json( '/completions',
630                             completion_data ).json[ 'completions' ]
631    assert_that(
632      results,
633      has_items( CompletionEntryMatcher( 'attributeA' ) )
634    )
635
636    completion_data = BuildRequest( filetype = 'dummy_filetype',
637                                    contents = 'objectA.attr',
638                                    line_num = 1,
639                                    column_num = 12 )
640
641    results = app.post_json( '/completions',
642                             completion_data ).json[ 'completions' ]
643    assert_that(
644      results,
645      has_items( CompletionEntryMatcher( 'attributeB' ) )
646    )
647
648    # We ask for candidates twice because of cache invalidation:
649    # both requests have the same cursor position and current line but the
650    # number of lines in the current file is different.
651    assert_that( candidates_list.call_count, equal_to( 2 ) )
652
653
654@SharedYcmd
655@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
656        return_value = True )
657@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
658        side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] )
659def GetCompletions_CacheIsNotValid_DifferentFileData_test(
660  candidates_list, should_use, app ):
661  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
662    completion_data = BuildRequest( filetype = 'dummy_filetype',
663                                    contents = 'objectA.attr',
664                                    line_num = 1,
665                                    column_num = 12,
666                                    file_data = {
667                                      '/bar': {
668                                        'contents': 'objectA = foo',
669                                        'filetypes': [ 'dummy_filetype' ]
670                                      }
671                                    } )
672
673    results = app.post_json( '/completions',
674                             completion_data ).json[ 'completions' ]
675    assert_that(
676      results,
677      has_items( CompletionEntryMatcher( 'attributeA' ) )
678    )
679
680    completion_data = BuildRequest( filetype = 'dummy_filetype',
681                                    contents = 'objectA.attr',
682                                    line_num = 1,
683                                    column_num = 12,
684                                    file_data = {
685                                      '/bar': {
686                                        'contents': 'objectA = bar',
687                                        'filetypes': [ 'dummy_filetype' ]
688                                      }
689                                    } )
690
691    results = app.post_json( '/completions',
692                             completion_data ).json[ 'completions' ]
693    assert_that(
694      results,
695      has_items( CompletionEntryMatcher( 'attributeB' ) )
696    )
697
698    # We ask for candidates twice because of cache invalidation:
699    # both requests have the same cursor position and contents for the current
700    # file but different contents for another file.
701    assert_that( candidates_list.call_count, equal_to( 2 ) )
702
703
704@SharedYcmd
705@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
706        return_value = True )
707@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
708        side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] )
709def GetCompletions_CacheIsNotValid_DifferentExtraConfData_test(
710  candidates_list, should_use, app ):
711  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
712    completion_data = BuildRequest( filetype = 'dummy_filetype',
713                                    contents = 'objectA.attr',
714                                    line_num = 1,
715                                    column_num = 12 )
716
717    results = app.post_json( '/completions',
718                             completion_data ).json[ 'completions' ]
719    assert_that(
720      results,
721      has_items( CompletionEntryMatcher( 'attributeA' ) )
722    )
723
724    completion_data = BuildRequest( filetype = 'dummy_filetype',
725                                    contents = 'objectA.attr',
726                                    line_num = 1,
727                                    column_num = 12,
728                                    extra_conf_data = { 'key': 'value' } )
729
730    results = app.post_json( '/completions',
731                             completion_data ).json[ 'completions' ]
732    assert_that(
733      results,
734      has_items( CompletionEntryMatcher( 'attributeB' ) )
735    )
736
737    # We ask for candidates twice because of cache invalidation:
738    # both requests are identical except the extra conf data.
739    assert_that( candidates_list.call_count, equal_to( 2 ) )
740
741
742@SharedYcmd
743@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner',
744        return_value = True )
745@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList',
746        return_value = [ 'aba', 'cbc' ] )
747def GetCompletions_FilterThenReturnFromCache_test( candidates_list,
748                                                   should_use,
749                                                   app ):
750
751  with PatchCompleter( DummyCompleter, 'dummy_filetype' ):
752    # First, fill the cache with an empty query
753    completion_data = BuildRequest( filetype = 'dummy_filetype',
754                                    contents = 'objectA.',
755                                    line_num = 1,
756                                    column_num = 9 )
757
758    results = app.post_json( '/completions',
759                             completion_data ).json[ 'completions' ]
760    assert_that( results,
761                 has_items( CompletionEntryMatcher( 'aba' ),
762                            CompletionEntryMatcher( 'cbc' ) ) )
763
764    # Now, filter them. This causes them to be converted to bytes and back
765    completion_data = BuildRequest( filetype = 'dummy_filetype',
766                                    contents = 'objectA.c',
767                                    line_num = 1,
768                                    column_num = 10 )
769
770    results = app.post_json( '/completions',
771                             completion_data ).json[ 'completions' ]
772    assert_that( results,
773                 has_items( CompletionEntryMatcher( 'cbc' ) ) )
774
775    # Finally, request the original (unfiltered) set again. Ensure that we get
776    # proper results (not some bytes objects)
777    completion_data = BuildRequest( filetype = 'dummy_filetype',
778                                    contents = 'objectA.',
779                                    line_num = 1,
780                                    column_num = 9 )
781
782    results = app.post_json( '/completions',
783                             completion_data ).json[ 'completions' ]
784    assert_that( results,
785                 has_items( CompletionEntryMatcher( 'aba' ),
786                            CompletionEntryMatcher( 'cbc' ) ) )
787
788    assert_that( candidates_list.call_count, equal_to( 1 ) )
789
790
791def Dummy_test():
792  # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51
793  assert True
794