def GetCompletions_MoreThan10_NoResolve_ThenResolveCacheBad_test(app): ClearCompletionsCache() request, response = RunTest( app, { 'description': "More than 10 candiates after filtering, don't resolve", 'request': { 'filetype': 'java', 'filepath': ProjectPath('MethodsWithDocumentation.java'), 'line_num': 33, 'column_num': 7, }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completions': has_item( CompletionEntryMatcher( 'useAString', 'MethodsWithDocumentation.useAString(String s) : void', { 'kind': 'Method', # This is the un-resolved info (no documentation) 'detailed_info': 'useAString(String s) : void\n\n', 'extra_data': has_entries({'resolve': instance_of(int)}) }), ), 'completion_start_column': 7, 'errors': empty(), }) }, }) # We know the item we want is there, pull out the resolve ID resolve = None for item in response['completions']: if item['insertion_text'] == 'useAString': resolve = item['extra_data']['resolve'] break assert resolve is not None request['resolve'] = resolve # Use a different position - should mean the cache is not valid for request request['column_num'] = 20 response = app.post_json('/resolve_completion', request).json print(f"Resolve response: { pformat( response ) }") assert_that( response, has_entries({ 'completion': None, 'errors': contains_exactly(ErrorMatcher(CompletionsChanged)) }))
# We ignore errors here and we check the response code ourself. # This is to allow testing of requests returning errors. request = CombineRequest(test['request'], {'contents': contents}) response = app.post_json('/completions', request, expect_errors=True) print(f'completer response: { pformat( response.json ) }') assert_that(response.status_code, equal_to(test['expect']['response'])) assert_that(response.json, test['expect']['data']) return request, response.json PUBLIC_OBJECT_METHODS = [ CompletionEntryMatcher('equals', 'Object.equals(Object arg0) : boolean', {'kind': 'Method'}), CompletionEntryMatcher('getClass', 'Object.getClass() : Class<?>', {'kind': 'Method'}), CompletionEntryMatcher('hashCode', 'Object.hashCode() : int', {'kind': 'Method'}), CompletionEntryMatcher('notify', 'Object.notify() : void', {'kind': 'Method'}), CompletionEntryMatcher('notifyAll', 'Object.notifyAll() : void', {'kind': 'Method'}), CompletionEntryMatcher('toString', 'Object.toString() : String', {'kind': 'Method'}), CompletionEntryMatcher( 'wait', 'Object.wait(long arg0, int arg1) : void', { 'menu_text': matches_regexp('wait\\(long .*, int .*\\) : void'), 'kind': 'Method', }),
def GetCompletions_MoreThan10_NoResolve_ThenResolve_test(app): ClearCompletionsCache() request, response = RunTest( app, { 'description': "More than 10 candiates after filtering, don't resolve", 'request': { 'filetype': 'java', 'filepath': ProjectPath('MethodsWithDocumentation.java'), 'line_num': 33, 'column_num': 7, }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completions': has_item( CompletionEntryMatcher( 'useAString', 'MethodsWithDocumentation.useAString(String s) : void', { 'kind': 'Method', # This is the un-resolved info (no documentation) 'detailed_info': 'useAString(String s) : void\n\n', 'extra_data': has_entries({'resolve': instance_of(int)}) }), ), 'completion_start_column': 7, 'errors': empty(), }) }, }) # We know the item we want is there, pull out the resolve ID resolve = None for item in response['completions']: if item['insertion_text'] == 'useAString': resolve = item['extra_data']['resolve'] break assert resolve is not None request['resolve'] = resolve # Do this twice to prove that the request is idempotent for i in range(2): response = app.post_json('/resolve_completion', request).json print(f"Resolve response: { pformat( response ) }") nl = os.linesep assert_that( response, has_entries({ 'completion': CompletionEntryMatcher( 'useAString', 'MethodsWithDocumentation.useAString(String s) : void', { 'kind': 'Method', # This is the resolved info (no documentation) 'detailed_info': 'useAString(String s) : void\n' '\n' f'Multiple lines of description here.{ nl }' f'{ nl }' f' * **Parameters:**{ nl }' f' { nl }' f' * **s** a string' }), 'errors': empty(), })) # The item is resoled assert_that(response['completion'], is_not(has_key('resolve'))) assert_that(response['completion'], is_not(has_key('item')))
def GetCompletions_ClientDataGivenToExtraConf_Cache_test(app): app.post_json( '/load_extra_conf_file', {'filepath': PathToTestFile('client_data', '.ycm_extra_conf.py')}) filepath = PathToTestFile('client_data', 'macro.cpp') contents = ReadFile(filepath) request = { 'filetype': 'cpp', 'filepath': filepath, 'contents': contents, 'line_num': 11, 'column_num': 8 } # Complete with flags from the client. completion_request = CombineRequest( request, {'extra_conf_data': { 'flags': ['-DSOME_MACRO'] }}) assert_that( app.post_json('/completions', completion_request).json, has_entries({ 'completions': has_item(CompletionEntryMatcher('macro_defined')), 'errors': empty() })) # Complete at the same position but for a different set of flags from the # client. completion_request = CombineRequest( request, {'extra_conf_data': { 'flags': ['-Wall'] }}) assert_that( app.post_json('/completions', completion_request).json, has_entries({ 'completions': has_item(CompletionEntryMatcher('macro_not_defined')), 'errors': empty() })) # Finally, complete once again at the same position but no flags are given by # the client. An empty list of flags is returned by the extra conf file in # that case. completion_request = CombineRequest(request, {}) assert_that( app.post_json('/completions', completion_request).json, has_entries({ 'completions': empty(), 'errors': contains( ErrorMatcher(RuntimeError, 'Still no compile flags, no completions yet.')) }))
# This is to allow testing of requests returning errors. response = app.post_json( '/completions', CombineRequest( test[ 'request' ], { 'contents': contents } ), expect_errors = True ) print( 'completer response: {0}'.format( pformat( response.json ) ) ) eq_( response.status_code, test[ 'expect' ][ 'response' ] ) assert_that( response.json, test[ 'expect' ][ 'data' ] ) PUBLIC_OBJECT_METHODS = [ CompletionEntryMatcher( 'equals', 'Object', { 'kind': 'Method' } ), CompletionEntryMatcher( 'getClass', 'Object', { 'kind': 'Method' } ), CompletionEntryMatcher( 'hashCode', 'Object', { 'kind': 'Method' } ), CompletionEntryMatcher( 'notify', 'Object', { 'kind': 'Method' } ), CompletionEntryMatcher( 'notifyAll', 'Object', { 'kind': 'Method' } ), CompletionEntryMatcher( 'toString', 'Object', { 'kind': 'Method' } ), CompletionEntryMatcher( 'wait', 'Object', { 'menu_text': matches_regexp( 'wait\\(long .*, int .*\\) : void' ), 'kind': 'Method', } ), CompletionEntryMatcher( 'wait', 'Object', { 'menu_text': matches_regexp( 'wait\\(long .*\\) : void' ), 'kind': 'Method', } ), CompletionEntryMatcher( 'wait', 'Object', { 'menu_text': 'wait() : void',
def GetCompletions_SupportExtraConf_test(app): RunTest( app, { 'description': 'Flags for foo.cpp from extra conf file are used', 'request': { 'filetype': 'cpp', 'filepath': PathToTestFile('extra_conf', 'foo.cpp'), 'line_num': 5, 'column_num': 15 }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completion_start_column': 15, 'completions': contains_exactly(CompletionEntryMatcher('member_foo')), 'errors': empty(), }) } }) RunTest( app, { 'description': 'Same flags are used again for foo.cpp', 'request': { 'filetype': 'cpp', 'filepath': PathToTestFile('extra_conf', 'foo.cpp'), 'line_num': 5, 'column_num': 15 }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completion_start_column': 15, 'completions': contains_exactly(CompletionEntryMatcher('member_foo')), 'errors': empty(), }) } }) RunTest( app, { 'description': 'Flags for bar.cpp from extra conf file are used', 'request': { 'filetype': 'cpp', 'filepath': PathToTestFile('extra_conf', 'bar.cpp'), 'line_num': 5, 'column_num': 15 }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completion_start_column': 15, 'completions': contains_exactly(CompletionEntryMatcher('member_bar')), 'errors': empty(), }) } })
def GetCompletions_IncludeMultiFileType_test(app): trivial1 = { 'filetypes': ['python', 'javascript'], 'contents': ReadFile(PathToTestFile('trivial.js')), } trivial2 = { 'filetypes': ['javascript'], 'contents': ReadFile(PathToTestFile('trivial2.js')), } request = { 'line_num': 1, 'column_num': 3, 'file_data': { PathToTestFile('trivial.js'): trivial1, PathToTestFile('trivial2.js'): trivial2, }, } app.post_json( '/event_notification', CombineRequest( request, { 'filepath': PathToTestFile('trivial2.js'), 'event_name': 'FileReadyToParse', })) response = app.post_json( '/completions', CombineRequest( request, { 'filepath': PathToTestFile('trivial2.js'), # We must force the use of semantic engine because the previous test would # have entered 'empty' results into the completion cache. 'force_semantic': True, })).json print('completer response: {0}'.format(pformat(response, indent=2))) assert_that( response, has_entries({ 'completion_start_column': 3, # Note: This time, we *do* see the completions, becuase one of the 2 # filetypes for trivial.js is javascript. 'completions': contains_inanyorder( CompletionEntryMatcher('y', 'string'), CompletionEntryMatcher('z', 'string'), CompletionEntryMatcher('toString', 'fn() -> string'), CompletionEntryMatcher('toLocaleString', 'fn() -> string'), CompletionEntryMatcher('valueOf', 'fn() -> number'), CompletionEntryMatcher('hasOwnProperty', 'fn(prop: string) -> bool'), CompletionEntryMatcher('isPrototypeOf', 'fn(obj: ?) -> bool'), CompletionEntryMatcher('propertyIsEnumerable', 'fn(prop: string) -> bool'), ), 'errors': empty(), }))
def test_GetCompletions_Basic(self, app): RunTest( app, { 'description': 'Extra and detailed info when completions are methods', 'request': { 'line_num': 17, 'column_num': 6, 'filepath': PathToTestFile('test.ts') }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completions': contains_inanyorder( CompletionEntryMatcher( 'methodA', '(method) Foo.methodA(): void', extra_params={ 'kind': 'method', 'detailed_info': '(method) Foo.methodA(): void\n\n' 'Unicode string: 说话' }), CompletionEntryMatcher( 'methodB', '(method) Foo.methodB(): void', extra_params={ 'kind': 'method', 'detailed_info': '(method) Foo.methodB(): void' }), CompletionEntryMatcher( 'methodC', '(method) Foo.methodC(a: { foo: string; bar: number; }): void', extra_params={ 'kind': 'method', 'detailed_info': '(method) Foo.methodC(a: {\n' ' foo: string;\n' ' bar: number;\n' '}): void' })) }) } }) RunTest( app, { 'description': 'Filtering works', 'request': { 'line_num': 17, 'column_num': 7, 'filepath': PathToTestFile('test.ts') }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completions': contains_inanyorder( CompletionEntryMatcher( 'methodA', '(method) Foo.methodA(): void', extra_params={ 'kind': 'method', 'detailed_info': '(method) Foo.methodA(): void\n\n' 'Unicode string: 说话' })) }) } })
def EventNotification_OnBufferUnload_CloseFile_test(app): # Open main.ts file in a buffer. main_filepath = PathToTestFile('buffer_unload', 'main.ts') main_contents = ReadFile(main_filepath) event_data = BuildRequest(filepath=main_filepath, filetype='typescript', contents=main_contents, event_name='BufferVisit') app.post_json('/event_notification', event_data) # Complete in main.ts buffer an object defined in imported.ts. completion_data = BuildRequest(filepath=main_filepath, filetype='typescript', contents=main_contents, line_num=3, column_num=10) response = app.post_json('/completions', completion_data) assert_that( response.json, has_entries({ 'completions': contains_exactly(CompletionEntryMatcher('method')) })) # Open imported.ts file in another buffer. imported_filepath = PathToTestFile('buffer_unload', 'imported.ts') imported_contents = ReadFile(imported_filepath) event_data = BuildRequest(filepath=imported_filepath, filetype='typescript', contents=imported_contents, event_name='BufferVisit') app.post_json('/event_notification', event_data) # Modify imported.ts buffer without writing the changes to disk. modified_imported_contents = imported_contents.replace( 'method', 'modified_method') # FIXME: TypeScript completer should not rely on the FileReadyToParse events # to synchronize the contents of dirty buffers but use instead the file_data # field of the request. event_data = BuildRequest(filepath=imported_filepath, filetype='typescript', contents=modified_imported_contents, event_name='FileReadyToParse') app.post_json('/event_notification', event_data) # Complete at same location in main.ts buffer. imported_data = { imported_filepath: { 'filetypes': ['typescript'], 'contents': modified_imported_contents } } completion_data = BuildRequest(filepath=main_filepath, filetype='typescript', contents=main_contents, line_num=3, column_num=10, file_data=imported_data) response = app.post_json('/completions', completion_data) assert_that( response.json, has_entries({ 'completions': contains_exactly(CompletionEntryMatcher('modified_method')) })) # Unload imported.ts buffer. event_data = BuildRequest(filepath=imported_filepath, filetype='typescript', contents=imported_contents, event_name='BufferUnload') app.post_json('/event_notification', event_data) # Complete at same location in main.ts buffer. completion_data = BuildRequest(filepath=main_filepath, filetype='typescript', contents=main_contents, line_num=3, column_num=10) response = app.post_json('/completions', completion_data) assert_that( response.json, has_entries({ 'completions': contains_exactly(CompletionEntryMatcher('method')) }))
def GetCompletions_WithFixIt_test(app): filepath = ProjectPath('TestFactory.java') RunTest( app, { 'description': 'semantic completion with when additional textEdit', 'request': { 'filetype': 'java', 'filepath': filepath, 'line_num': 19, 'column_num': 25, }, 'expect': { 'response': requests.codes.ok, 'data': has_entries({ 'completion_start_column': 22, 'completions': contains_inanyorder( CompletionEntryMatcher( 'CUTHBERT', 'com.test.wobble.Wibble', { 'kind': 'Field', 'extra_data': has_entries({ 'fixits': contains( has_entries({ 'chunks': contains( # For some reason, jdtls feels it's OK to replace the text # before the cursor. Perhaps it does this to canonicalise the # path ? ChunkMatcher( 'Wibble', LocationMatcher( filepath, 19, 15), LocationMatcher( filepath, 19, 21)), # When doing an import, eclipse likes to add two newlines # after the package. I suppose this is config in real eclipse, # but there's no mechanism to configure this in jdtl afaik. ChunkMatcher( '\n\n', LocationMatcher( filepath, 1, 18), LocationMatcher( filepath, 1, 18)), # OK, so it inserts the import ChunkMatcher( 'import com.test.wobble.Wibble;', LocationMatcher( filepath, 1, 18), LocationMatcher( filepath, 1, 18)), # More newlines. Who doesn't like newlines?! ChunkMatcher( '\n\n', LocationMatcher( filepath, 1, 18), LocationMatcher( filepath, 1, 18)), # For reasons known only to the eclipse JDT developers, it # seems to want to delete the lines after the package first. ChunkMatcher( '', LocationMatcher( filepath, 1, 18), LocationMatcher( filepath, 3, 1)), ), })), }), }), ), 'errors': empty(), }) }, })