123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- local protocol = require 'vim.lsp.protocol'
- -- Logs to $NVIM_LOG_FILE.
- --
- -- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062
- local function log(loglevel, area, msg)
- vim.fn.writefile(
- {string.format('%s %s: %s', loglevel, area, msg)},
- vim.env.NVIM_LOG_FILE,
- 'a')
- end
- local function message_parts(sep, ...)
- local parts = {}
- for i = 1, select("#", ...) do
- local arg = select(i, ...)
- if arg ~= nil then
- table.insert(parts, arg)
- end
- end
- return table.concat(parts, sep)
- end
- -- Assert utility methods
- local function assert_eq(a, b, ...)
- if not vim.deep_equal(a, b) then
- error(message_parts(": ",
- ..., "assert_eq failed",
- string.format("left == %q, right == %q", vim.inspect(a), vim.inspect(b))
- ))
- end
- end
- local function format_message_with_content_length(encoded_message)
- return table.concat {
- 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
- encoded_message;
- }
- end
- local function read_message()
- local line = io.read("*l")
- local length = line:lower():match("content%-length:%s*(%d+)")
- return vim.json.decode(io.read(2 + length):sub(2))
- end
- local function send(payload)
- io.stdout:write(format_message_with_content_length(vim.json.encode(payload)))
- end
- local function respond(id, err, result)
- assert(type(id) == 'number', "id must be a number")
- send { jsonrpc = "2.0"; id = id, error = err, result = result }
- end
- local function notify(method, params)
- assert(type(method) == 'string', "method must be a string")
- send { method = method, params = params or {} }
- end
- local function expect_notification(method, params, ...)
- local message = read_message()
- assert_eq(method, message.method,
- ..., "expect_notification", "method")
- assert_eq(params, message.params,
- ..., "expect_notification", method, "params")
- assert_eq({jsonrpc = "2.0"; method=method, params=params}, message,
- ..., "expect_notification", "message")
- end
- local function expect_request(method, handler, ...)
- local req = read_message()
- assert_eq(method, req.method,
- ..., "expect_request", "method")
- local err, result = handler(req.params)
- respond(req.id, err, result)
- end
- io.stderr:setvbuf("no")
- local function skeleton(config)
- local on_init = assert(config.on_init)
- local body = assert(config.body)
- expect_request("initialize", function(params)
- return nil, on_init(params)
- end)
- expect_notification("initialized", {})
- body()
- expect_request("shutdown", function()
- return nil, {}
- end)
- expect_notification("exit", nil)
- end
- -- The actual tests.
- local tests = {}
- function tests.basic_init()
- skeleton {
- on_init = function(_params)
- return { capabilities = {} }
- end;
- body = function()
- notify('test')
- end;
- }
- end
- function tests.check_workspace_configuration()
- skeleton {
- on_init = function(_params)
- return { capabilities = {} }
- end;
- body = function()
- notify('start')
- notify('workspace/configuration', { items = {
- { section = "testSetting1" };
- { section = "testSetting2" };
- } })
- expect_notification('workspace/configuration', { true; vim.NIL})
- notify('shutdown')
- end;
- }
- end
- function tests.prepare_rename_nil()
- skeleton {
- on_init = function()
- return { capabilities = {
- renameProvider = true,
- } }
- end;
- body = function()
- notify('start')
- expect_request('textDocument/prepareRename', function()
- return nil, nil
- end)
- notify('shutdown')
- end;
- }
- end
- function tests.prepare_rename_placeholder()
- skeleton {
- on_init = function()
- return { capabilities = {
- renameProvider = true,
- } }
- end;
- body = function()
- notify('start')
- expect_request('textDocument/prepareRename', function()
- return nil, {placeholder = 'placeholder'}
- end)
- expect_request('textDocument/rename', function(params)
- assert_eq(params.newName, 'renameto')
- return nil, nil
- end)
- notify('shutdown')
- end;
- }
- end
- function tests.prepare_rename_range()
- skeleton {
- on_init = function()
- return { capabilities = {
- renameProvider = true,
- } }
- end;
- body = function()
- notify('start')
- expect_request('textDocument/prepareRename', function()
- return nil, {
- start = { line = 1, character = 8 },
- ['end'] = { line = 1, character = 12 },
- }
- end)
- expect_request('textDocument/rename', function(params)
- assert_eq(params.newName, 'renameto')
- return nil, nil
- end)
- notify('shutdown')
- end;
- }
- end
- function tests.prepare_rename_error()
- skeleton {
- on_init = function()
- return { capabilities = {
- renameProvider = true,
- } }
- end;
- body = function()
- notify('start')
- expect_request('textDocument/prepareRename', function()
- return {}, nil
- end)
- expect_request('textDocument/rename', function(params)
- assert_eq(params.newName, 'renameto')
- return nil, nil
- end)
- notify('shutdown')
- end;
- }
- end
- function tests.basic_check_capabilities()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- }
- }
- end;
- body = function()
- end;
- }
- end
- function tests.capabilities_for_client_supports_method()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- completionProvider = true;
- hoverProvider = true;
- definitionProvider = false;
- referencesProvider = false;
- codeLensProvider = { resolveProvider = true; };
- }
- }
- end;
- body = function()
- end;
- }
- end
- function tests.check_forward_request_cancelled()
- skeleton {
- on_init = function(_)
- return { capabilities = {} }
- end;
- body = function()
- expect_request("error_code_test", function()
- return {code = -32800}, nil, {method = "error_code_test", client_id=1}
- end)
- notify('finish')
- end;
- }
- end
- function tests.check_forward_content_modified()
- skeleton {
- on_init = function(_)
- return { capabilities = {} }
- end;
- body = function()
- expect_request("error_code_test", function()
- return {code = -32801}, nil, {method = "error_code_test", client_id=1}
- end)
- expect_notification('finish')
- notify('finish')
- end;
- }
- end
- function tests.check_pending_request_tracked()
- skeleton {
- on_init = function(_)
- return { capabilities = {} }
- end;
- body = function()
- local msg = read_message()
- assert_eq('slow_request', msg.method)
- expect_notification('release')
- respond(msg.id, nil, {})
- expect_notification('finish')
- notify('finish')
- end;
- }
- end
- function tests.check_cancel_request_tracked()
- skeleton {
- on_init = function(_)
- return { capabilities = {} }
- end;
- body = function()
- local msg = read_message()
- assert_eq('slow_request', msg.method)
- expect_notification('$/cancelRequest', {id=msg.id})
- expect_notification('release')
- respond(msg.id, {code = -32800}, nil)
- notify('finish')
- end;
- }
- end
- function tests.check_tracked_requests_cleared()
- skeleton {
- on_init = function(_)
- return { capabilities = {} }
- end;
- body = function()
- local msg = read_message()
- assert_eq('slow_request', msg.method)
- expect_notification('$/cancelRequest', {id=msg.id})
- expect_notification('release')
- respond(msg.id, nil, {})
- expect_notification('finish')
- notify('finish')
- end;
- }
- end
- function tests.basic_finish()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- }
- }
- end;
- body = function()
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.basic_check_buffer_open()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- }
- }
- end;
- body = function()
- notify('start')
- expect_notification('textDocument/didOpen', {
- textDocument = {
- languageId = "";
- text = table.concat({"testing"; "123"}, "\n") .. '\n';
- uri = "file://";
- version = 0;
- };
- })
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.basic_check_buffer_open_and_change()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- }
- }
- end;
- body = function()
- notify('start')
- expect_notification('textDocument/didOpen', {
- textDocument = {
- languageId = "";
- text = table.concat({"testing"; "123"}, "\n") .. '\n';
- uri = "file://";
- version = 0;
- };
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 3;
- };
- contentChanges = {
- { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
- }
- })
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.basic_check_buffer_open_and_change_noeol()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- }
- }
- end;
- body = function()
- notify('start')
- expect_notification('textDocument/didOpen', {
- textDocument = {
- languageId = "";
- text = table.concat({"testing"; "123"}, "\n");
- uri = "file://";
- version = 0;
- };
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 3;
- };
- contentChanges = {
- { text = table.concat({"testing"; "boop"}, "\n"); };
- }
- })
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.basic_check_buffer_open_and_change_multi()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- }
- }
- end;
- body = function()
- notify('start')
- expect_notification('textDocument/didOpen', {
- textDocument = {
- languageId = "";
- text = table.concat({"testing"; "123"}, "\n") .. '\n';
- uri = "file://";
- version = 0;
- };
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 3;
- };
- contentChanges = {
- { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
- }
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 4;
- };
- contentChanges = {
- { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
- }
- })
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.basic_check_buffer_open_and_change_multi_and_close()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Full;
- }
- }
- end;
- body = function()
- notify('start')
- expect_notification('textDocument/didOpen', {
- textDocument = {
- languageId = "";
- text = table.concat({"testing"; "123"}, "\n") .. '\n';
- uri = "file://";
- version = 0;
- };
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 3;
- };
- contentChanges = {
- { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
- }
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 4;
- };
- contentChanges = {
- { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
- }
- })
- expect_notification('textDocument/didClose', {
- textDocument = {
- uri = "file://";
- };
- })
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.basic_check_buffer_open_and_change_incremental()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
- }
- }
- end;
- body = function()
- notify('start')
- expect_notification('textDocument/didOpen', {
- textDocument = {
- languageId = "";
- text = table.concat({"testing"; "123"}, "\n") .. '\n';
- uri = "file://";
- version = 0;
- };
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 3;
- };
- contentChanges = {
- {
- range = {
- start = { line = 1; character = 3; };
- ["end"] = { line = 1; character = 3; };
- };
- rangeLength = 0;
- text = "boop";
- };
- }
- })
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.basic_check_buffer_open_and_change_incremental_editing()
- skeleton {
- on_init = function(params)
- local expected_capabilities = protocol.make_client_capabilities()
- assert_eq(params.capabilities, expected_capabilities)
- return {
- capabilities = {
- textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
- }
- }
- end;
- body = function()
- notify('start')
- expect_notification('textDocument/didOpen', {
- textDocument = {
- languageId = "";
- text = table.concat({"testing"; "123"}, "\n");
- uri = "file://";
- version = 0;
- };
- })
- expect_notification('textDocument/didChange', {
- textDocument = {
- uri = "file://";
- version = 3;
- };
- contentChanges = {
- {
- range = {
- start = { line = 0; character = 0; };
- ["end"] = { line = 1; character = 0; };
- };
- rangeLength = 4;
- text = "testing\n\n";
- };
- }
- })
- expect_notification("finish")
- notify('finish')
- end;
- }
- end
- function tests.invalid_header()
- io.stdout:write("Content-length: \r\n")
- end
- function tests.decode_nil()
- skeleton {
- on_init = function(_)
- return { capabilities = {} }
- end;
- body = function()
- notify('start')
- notify("workspace/executeCommand", {
- arguments = { "EXTRACT_METHOD", {metadata = {field = vim.NIL}}, 3, 0, 6123, vim.NIL },
- command = "refactor.perform",
- title = "EXTRACT_METHOD"
- })
- notify('finish')
- end;
- }
- end
- function tests.code_action_with_resolve()
- skeleton {
- on_init = function()
- return {
- capabilities = {
- codeActionProvider = {
- resolveProvider = true
- }
- }
- }
- end;
- body = function()
- notify('start')
- local cmd = {
- title = 'Command 1',
- command = 'dummy1'
- }
- expect_request('textDocument/codeAction', function()
- return nil, { cmd, }
- end)
- expect_request('codeAction/resolve', function()
- return nil, cmd
- end)
- notify('shutdown')
- end;
- }
- end
- function tests.clientside_commands()
- skeleton {
- on_init = function()
- return {
- capabilities = {}
- }
- end;
- body = function()
- notify('start')
- notify('shutdown')
- end;
- }
- end
- -- Tests will be indexed by TEST_NAME
- local kill_timer = vim.loop.new_timer()
- kill_timer:start(_G.TIMEOUT or 1e3, 0, function()
- kill_timer:stop()
- kill_timer:close()
- log('ERROR', 'LSP', 'TIMEOUT')
- io.stderr:write("TIMEOUT")
- os.exit(100)
- end)
- local test_name = _G.TEST_NAME -- lualint workaround
- assert(type(test_name) == 'string', 'TEST_NAME must be specified.')
- local status, err = pcall(assert(tests[test_name], "Test not found"))
- kill_timer:stop()
- kill_timer:close()
- if not status then
- log('ERROR', 'LSP', tostring(err))
- io.stderr:write(err)
- os.exit(101)
- end
- os.exit(0)
|