Before: Save g:ale_run_synchronously let g:ale_run_synchronously = 1 unlet! g:ale_run_synchronously_callbacks unlet! g:ale_run_synchronously_emulate_commands runtime autoload/ale/lsp.vim runtime autoload/ale/lsp_linter.vim runtime autoload/ale/engine.vim runtime autoload/ale/job.vim runtime autoload/ale/socket.vim let g:job_map = {} let g:emulate_job_failure = 0 let g:next_job_id = 1 let g:socket_map = {} let g:emulate_socket_failure = 0 let g:next_channel_id = 0 let g:message_buffer = '' let g:calls = [] function! ale#engine#IsExecutable(buffer, executable) abort return !empty(a:executable) endfunction function! ale#job#HasOpenChannel(job_id) abort return has_key(g:job_map, a:job_id) endfunction function! ale#job#Stop(job_id) abort if has_key(g:job_map, a:job_id) call remove(g:job_map, a:job_id) endif endfunction function! ale#job#Start(command, options) abort if g:emulate_job_failure return 0 endif let l:job_id = g:next_job_id let g:next_job_id += 1 let g:job_map[l:job_id] = [a:command, a:options] return l:job_id endfunction function! ale#job#SendRaw(job_id, data) abort let g:message_buffer .= a:data endfunction function! ale#socket#IsOpen(channel_id) abort return has_key(g:socket_map, a:channel_id) endfunction function! ale#socket#Close(channel_id) abort if has_key(g:socket_map, a:channel_id) call remove(g:socket_map, a:channel_id) endif endfunction function! ale#socket#Open(address, options) abort if g:emulate_socket_failure return -1 endif let l:channel_id = g:next_channel_id let g:next_channel_id += 1 let g:socket_map[l:channel_id] = [a:address, a:options] return l:channel_id endfunction function! ale#socket#Send(channel_id, data) abort let g:message_buffer .= a:data endfunction function! PopMessages() abort let l:message_list = [] for l:line in split(g:message_buffer, '\(\r\|\n\|Content-Length\)\+') if l:line[:0] is '{' let l:data = json_decode(l:line) call add(l:message_list, l:data) endif endfor let g:message_buffer = '' return l:message_list endfunction function! SendMessage(message) abort let l:conn_id = keys(ale#lsp#GetConnections())[0] let l:body = json_encode(a:message) let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body call ale#lsp#HandleMessage(l:conn_id, l:data) endfunction function! Start() abort let l:linter = values(ale#linter#GetLintersLoaded())[0][0] return ale#lsp_linter#StartLSP( \ bufnr(''), \ l:linter, \ {linter, details -> add(g:calls, [linter.name, details])}, \) endfunction function! AssertInitSuccess(linter_name, conn_prefix, language, root, command) abort let l:messages = PopMessages() if a:linter_name is# 'tsserver' AssertEqual \ [ \ { \ 'seq': v:null, \ 'arguments': { \ 'file': expand('%:p'), \ }, \ 'type': 'request', \ 'command': 'open', \ }, \ ], \ l:messages else AssertEqual \ [ \ { \ 'method': 'initialize', \ 'jsonrpc': '2.0', \ 'id': 1, \ 'params': { \ 'initializationOptions': {}, \ 'rootUri': ale#path#ToURI(a:root), \ 'rootPath': a:root, \ 'processId': getpid(), \ 'capabilities': { \ 'workspace': { \ 'applyEdit': v:false, \ 'didChangeConfiguration': { \ 'dynamicRegistration': v:false, \ }, \ 'symbol': { \ 'dynamicRegistration': v:false, \ }, \ 'workspaceFolders': v:false, \ 'configuration': v:false, \ }, \ 'textDocument': { \ 'synchronization': { \ 'dynamicRegistration': v:false, \ 'willSave': v:false, \ 'willSaveWaitUntil': v:false, \ 'didSave': v:true, \ }, \ 'completion': { \ 'dynamicRegistration': v:false, \ 'completionItem': { \ 'snippetSupport': v:false, \ 'commitCharactersSupport': v:false, \ 'documentationFormat': ['plaintext'], \ 'deprecatedSupport': v:false, \ 'preselectSupport': v:false, \ }, \ 'contextSupport': v:false, \ }, \ 'hover': { \ 'dynamicRegistration': v:false, \ 'contentFormat': ['plaintext'], \ }, \ 'references': { \ 'dynamicRegistration': v:false, \ }, \ 'documentSymbol': { \ 'dynamicRegistration': v:false, \ 'hierarchicalDocumentSymbolSupport': v:false, \ }, \ 'definition': { \ 'dynamicRegistration': v:false, \ 'linkSupport': v:false, \ }, \ 'typeDefinition': { \ 'dynamicRegistration': v:false, \ }, \ 'publishDiagnostics': { \ 'relatedInformation': v:true, \ }, \ 'codeAction': { \ 'dynamicRegistration': v:false, \ }, \ 'rename': { \ 'dynamicRegistration': v:false, \ }, \ }, \ }, \ }, \ }, \ ], \ l:messages call SendMessage({ \ 'jsonrpc': '2.0', \ 'id': 1, \ 'result': { \ 'capabilities': { \ 'renameProvider': v:true, \ 'executeCommandProvider': { \ 'commands': [], \ }, \ 'hoverProvider': v:true, \ 'documentSymbolProvider': v:true, \ 'documentRangeFormattingProvider': v:true, \ 'codeLensProvider': { \ 'resolveProvider': v:false \ }, \ 'referencesProvider': v:true, \ 'textDocumentSync': 2, \ 'documentFormattingProvider': v:true, \ 'codeActionProvider': v:true, \ 'signatureHelpProvider': { \ 'triggerCharacters': ['(', ','], \ }, \ 'completionProvider': { \ 'triggerCharacters': ['.'], \ 'resolveProvider': v:false \ }, \ 'definitionProvider': v:true, \ 'experimental': {}, \ 'documentHighlightProvider': v:true, \ 'workspaceSymbolProvider': v:true, \ }, \ }, \}) let l:messages = PopMessages() AssertEqual \ [ \ { \ 'method': 'initialized', \ 'jsonrpc': '2.0', \ 'params': {}, \ }, \ { \ 'method': 'textDocument/didOpen', \ 'jsonrpc': '2.0', \ 'params': { \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('%:p')), \ 'version': ale#lsp#message#GetNextVersionID() - 1, \ 'languageId': a:language, \ 'text': "\n", \ }, \ }, \ }, \ ], \ l:messages endif AssertEqual \ [ \ [ \ a:linter_name, \ { \ 'connection_id': a:conn_prefix . ':' . a:root, \ 'project_root': a:root, \ 'buffer': bufnr(''), \ 'command': !empty(a:command) ? ale#job#PrepareCommand(bufnr(''), a:command) : '', \ }, \ ], \ ], \ g:calls endfunction function! AssertInitFailure() abort let l:messages = PopMessages() AssertEqual [], l:messages AssertEqual [], g:calls endfunction call ale#linter#Reset() After: Restore call ale#linter#Reset() call ale#lsp#ResetConnections() unlet! g:ale_run_synchronously_callbacks unlet! g:job_map unlet! g:emulate_job_failure unlet! g:next_job_id unlet! g:socket_map unlet! g:emulate_socket_failure unlet! g:next_channel_id unlet! g:message_buffer unlet! g:calls delfunction PopMessages delfunction Start delfunction AssertInitSuccess delfunction AssertInitFailure runtime autoload/ale/engine.vim runtime autoload/ale/job.vim runtime autoload/ale/socket.vim Execute(tsserver should be started correctly): runtime ale_linters/typescript/tsserver.vim Assert Start() call AssertInitSuccess('tsserver', 'tsserver', '', '', ale#Escape('tsserver')) Execute(tsserver failures should be handled appropriately): runtime ale_linters/typescript/tsserver.vim let g:emulate_job_failure = 1 Assert !Start() call AssertInitFailure() Execute(LSP jobs should start correctly): call ale#linter#Define('foobar', { \ 'name': 'foo', \ 'lsp': 'stdio', \ 'executable': 'foo', \ 'command': 'foo', \ 'project_root': '/foo/bar', \ 'initialization_options': {}, \}) Assert Start() call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', 'foo') Execute(LSP job failures should be handled): call ale#linter#Define('foobar', { \ 'name': 'foo', \ 'lsp': 'stdio', \ 'executable': 'foo', \ 'command': 'foo', \ 'project_root': '/foo/bar', \ 'initialization_options': {}, \}) let g:emulate_job_failure = 1 Assert !Start() call AssertInitFailure() Execute(LSP TCP connections should start correctly): call ale#linter#Define('foobar', { \ 'name': 'foo', \ 'lsp': 'socket', \ 'address': 'foo', \ 'project_root': '/foo/bar', \ 'initialization_options': {}, \}) Assert Start() call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '') Execute(LSP TCP connection failures should be handled): call ale#linter#Define('foobar', { \ 'name': 'foo', \ 'lsp': 'socket', \ 'address': 'foo', \ 'project_root': '/foo/bar', \ 'initialization_options': {}, \}) let g:emulate_socket_failure = 1 Assert !Start() call AssertInitFailure() Execute(Deferred executables should be handled correctly): call ale#linter#Define('foobar', { \ 'name': 'foo', \ 'lsp': 'stdio', \ 'executable': {b -> ale#command#Run(b, 'echo', {-> 'foo'})}, \ 'command': '%e -c', \ 'project_root': '/foo/bar', \ 'initialization_options': {}, \}) Assert Start() call ale#test#FlushJobs() call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c') Execute(Deferred commands should be handled correctly): call ale#linter#Define('foobar', { \ 'name': 'foo', \ 'lsp': 'stdio', \ 'executable': 'foo', \ 'command': {b -> ale#command#Run(b, 'echo', {-> '%e -c'})}, \ 'project_root': '/foo/bar', \ 'initialization_options': {}, \}) Assert Start() call ale#test#FlushJobs() call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c') Execute(Deferred addresses should be handled correctly): call ale#linter#Define('foobar', { \ 'name': 'foo', \ 'lsp': 'socket', \ 'address': {b -> ale#command#Run(b, 'echo', {-> 'localhost:1234'})}, \ 'project_root': '/foo/bar', \ 'initialization_options': {}, \}) Assert Start() call ale#test#FlushJobs() call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '')