summaryrefslogtreecommitdiffstatshomepage
path: root/test/functional/plugin/lsp/testutil.lua
AgeCommit message (Collapse)AuthorFiles
2026-02-19test(lsp): fix fake LSP server timeout not working (#37970)zeertzjq1
Problem: Fake LSP server does not timeout or respond to SIGTERM as it does not run the event loop. Solution: Instead of io.read(), use stdioopen()'s on_stdin callback to accumulate input and use vim.wait() to wait for input. Also, in the test suite, don't stop a session when it's not running, as calling uv.stop() outside uv.run() will instead cause the next uv.run() to stop immediately, which cancels the next RPC request.
2025-11-16feat(lsp): skip invalid header lines #36402tao1
Problem: Some servers write log to stdout and there's no way to avoid it. See https://github.com/neovim/neovim/pull/35743#pullrequestreview-3379705828 Solution: We can extract `content-length` field byte by byte and skip invalid lines via a simple state machine (name/colon/value/invalid), with minimal performance impact. I chose byte parsing here instead of pattern. Although it's a bit more complex, it provides more stable performance and allows for more accurate error info when needed. Here is a bench result and script: parse header1 by pattern: 59.52377ms 45 parse header1 by byte: 7.531128ms 45 parse header2 by pattern: 26.06936ms 45 parse header2 by byte: 5.235724ms 45 parse header3 by pattern: 9.348495ms 45 parse header3 by byte: 3.452389ms 45 parse header4 by pattern: 9.73156ms 45 parse header4 by byte: 3.638386ms 45 Script: ```lua local strbuffer = require('string.buffer') --- @param header string local function get_content_length(header) for line in header:gmatch('(.-)\r?\n') do if line == '' then break end local key, value = line:match('^%s*(%S+)%s*:%s*(%d+)%s*$') if key and key:lower() == 'content-length' then return assert(tonumber(value)) end end error('Content-Length not found in header: ' .. header) end --- @param header string local function get_content_length_by_byte(header) local state = 'name' local i, len = 1, #header local j, name = 1, 'content-length' local buf = strbuffer.new() local digit = true while i <= len do local c = header:byte(i) if state == 'name' then if c >= 65 and c <= 90 then -- lower case c = c + 32 end if (c == 32 or c == 9) and j == 1 then -- skip OWS for compatibility only elseif c == name:byte(j) then j = j + 1 elseif c == 58 and j == 15 then state = 'colon' else state = 'invalid' end elseif state == 'colon' then if c ~= 32 and c ~= 9 then -- skip OWS normally state = 'value' i = i - 1 end elseif state == 'value' then if c == 13 and header:byte(i + 1) == 10 then -- must end with \r\n local value = buf:get() return assert(digit and tonumber(value), 'value of Content-Length is not number: ' .. value) else buf:put(string.char(c)) end if c < 48 and c ~= 32 and c ~= 9 or c > 57 then digit = false end elseif state == 'invalid' then if c == 10 then -- reset for next line state, j = 'name', 1 end end i = i + 1 end error('Content-Length not found in header: ' .. header) end --- @param fn fun(header: string): number local function bench(label, header, fn, count) local start = vim.uv.hrtime() local value --- @type number for _ = 1, count do value = fn(header) end local elapsed = (vim.uv.hrtime() - start) / 1e6 print(label .. ':', elapsed .. 'ms', value) end -- header starting with log lines local header1 = 'WARN: no common words file defined for Khmer - this language might not be correctly auto-detected\nWARN: no common words file defined for Japanese - this language might not be correctly auto-detected\nContent-Length: 45 \r\n\r\n' -- header starting with content-type local header2 = 'Content-Type: application/json-rpc; charset=utf-8\r\nContent-Length: 45 \r\n' -- regular header local header3 = ' Content-Length: 45\r\n' -- regular header ending with content-type local header4 = ' Content-Length: 45 \r\nContent-Type: application/json-rpc; charset=utf-8\r\n' local count = 10000 collectgarbage('collect') bench('parse header1 by pattern', header1, get_content_length, count) collectgarbage('collect') bench('parse header1 by byte', header1, get_content_length_by_byte, count) collectgarbage('collect') bench('parse header2 by pattern', header2, get_content_length, count) collectgarbage('collect') bench('parse header2 by byte', header2, get_content_length_by_byte, count) collectgarbage('collect') bench('parse header3 by pattern', header3, get_content_length, count) collectgarbage('collect') bench('parse header3 by byte', header3, get_content_length_by_byte, count) collectgarbage('collect') bench('parse header4 by pattern', header4, get_content_length, count) collectgarbage('collect') bench('parse header4 by byte', header4, get_content_length_by_byte, count) ``` Also, I removed an outdated test https://github.com/neovim/neovim/blob/accd392f4d14a114e378f84dc15cb24bc34a370a/test/functional/plugin/lsp_spec.lua#L1950 and tweaked the boilerplate in two other tests for reusability while keeping the final assertions the same. https://github.com/neovim/neovim/blob/accd392f4d14a114e378f84dc15cb24bc34a370a/test/functional/plugin/lsp_spec.lua#L5704 https://github.com/neovim/neovim/blob/accd392f4d14a114e378f84dc15cb24bc34a370a/test/functional/plugin/lsp_spec.lua#L5721
2025-09-17test(lsp): make async format test work properly (#35794)zeertzjq1
Overriding vim.lsp.handlers['textDocument/formatting'] doesn't work here because fake_lsp_server_setup() uses a table with __index to specify client handlers, which takes priority over vim.lsp.handlers[], and as a result the overridden handler is never called, and the test ends before the vim.wait() even finishes. Instead, set a global variable from the handler that is actually reached (by vim.rpcrequest() from client handler), and avoid stopping the event loop too early.
2025-06-18feat(lsp): pass resolved config to cmd() #34550Julian Visser1
Problem: In LSP configs, the function form of `cmd()` cannot easily get the resolved root dir (workspace). One of the main use-cases of a dynamic `cmd()` is to be able to start a new server whose binary may be located *in the workspace* ([example](https://github.com/neovim/nvim-lspconfig/pull/3912)). Compare `reuse_client()`, which also receives the resolved config. Solution: Pass the resolved config to `cmd()`. Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2024-11-20feat(lsp): deprecate non-method client functionsLewis Russell1
Deprecated: - `client.request()` -> `client:request()` - `client.request_sync()` -> `client:request_sync()` - `client.notify()` -> `client:notify()` - `client.cancel_request()` -> `client:cancel_request()` - `client.stop()` -> `client:stop()` - `client.is_stopped()` `client:is_stopped()` - `client.supports_method()` -> `client:supports_method()` - `client.on_attach()` -> `client:on_attach()` Fixed docgen to link class fields to the full function doc.
2024-09-21test: support upvalues in exec_luaLewis Russell1
2024-09-03feat(lsp): support hostname in rpc.connect #30238Tristan Knight1
Updated the `rpc.connect` function to support connecting to LSP servers using hostnames, not just IP addresses. This change includes updates to the documentation and additional test cases to verify the new functionality. - Modified `connect` function to resolve hostnames. - Updated documentation to reflect the change. - Added test case for connecting using hostname. Added a TCP echo server utility function to the LSP test suite. This server echoes the first message it receives and is used in tests to verify LSP server connections via both IP address and hostname. Refactored existing tests to use the new utility function.
2024-08-11test(lsp): refactor and tidyLewis Russell1
- Merge all the top level 'LSP' describe blocks - Refactor text edit tests - Fix typing errors - Add linebreaks between tests
2024-05-23fix(lsp): check if buffer was detached in on_init callback (#28914)Ilia Choly1
Co-authored-by: Jongwook Choi <wookayin@gmail.com>
2024-04-23test: improve test conventionsdundargoc1
Specifically, functions that are run in the context of the test runner are put in module `test/testutil.lua` while the functions that are run in the context of the test session are put in `test/functional/testnvim.lua`. Closes https://github.com/neovim/neovim/issues/27004.
2024-04-11test: remove unnecessary nil argument to testutil (#28270)zeertzjq1
2024-04-08test: improve test conventionsdundargoc1
Work on https://github.com/neovim/neovim/issues/27004.