testutil.lua (6703B)
1 local n = require('test.functional.testnvim')() 2 3 local clear = n.clear 4 local exec_lua = n.exec_lua 5 local run = n.run 6 local stop = n.stop 7 local api = n.api 8 local NIL = vim.NIL 9 10 local M = {} 11 12 function M.clear_notrace() 13 -- problem: here be dragons 14 -- solution: don't look too closely for dragons 15 clear { 16 env = { 17 NVIM_LUA_NOTRACK = '1', 18 NVIM_APPNAME = 'nvim_lsp_test', 19 VIMRUNTIME = os.getenv 'VIMRUNTIME', 20 }, 21 } 22 end 23 24 M.create_tcp_echo_server = function() 25 --- Create a TCP server that echos the first message it receives. 26 --- @param host string 27 --- @return integer 28 function _G._create_tcp_server(host) 29 local uv = vim.uv 30 local server = assert(uv.new_tcp()) 31 local on_read = require('vim.lsp.rpc').create_read_loop( 32 function(body) 33 vim.rpcnotify(1, 'body', body) 34 end, 35 nil, 36 function(err, code) 37 vim.rpcnotify(1, 'error', err, code) 38 end 39 ) 40 server:bind(host, 0) 41 server:listen(127, function(e) 42 assert(not e, e) 43 local socket = assert(uv.new_tcp()) 44 server:accept(socket) 45 socket:read_start(function(err, chunk) 46 on_read(err, chunk) 47 socket:shutdown() 48 socket:close() 49 server:shutdown() 50 server:close() 51 end) 52 end) 53 return server:getsockname().port 54 end 55 function _G._send_msg_to_server(msg) 56 local port = _G._create_tcp_server('127.0.0.1') 57 local client = assert(vim.uv.new_tcp()) 58 client:connect('127.0.0.1', port, function() 59 client:write(msg, function() 60 client:shutdown() 61 client:close() 62 end) 63 end) 64 end 65 end 66 67 M.create_server_definition = function() 68 function _G._create_server(opts) 69 opts = opts or {} 70 local server = {} 71 server.messages = {} 72 73 function server.cmd(dispatchers, _config) 74 local closing = false 75 local handlers = opts.handlers or {} 76 local srv = {} 77 78 function srv.request(method, params, callback) 79 table.insert(server.messages, { 80 method = method, 81 params = params, 82 }) 83 local handler = handlers[method] 84 if handler then 85 handler(method, params, callback) 86 elseif method == 'initialize' then 87 callback(nil, { 88 capabilities = opts.capabilities or {}, 89 }) 90 elseif method == 'shutdown' then 91 callback(nil, nil) 92 end 93 local request_id = #server.messages 94 return true, request_id 95 end 96 97 function srv.notify(method, params) 98 table.insert(server.messages, { 99 method = method, 100 params = params, 101 }) 102 if method == 'exit' then 103 dispatchers.on_exit(0, 15) 104 end 105 end 106 107 function srv.is_closing() 108 return closing 109 end 110 111 function srv.terminate() 112 closing = true 113 dispatchers.on_exit(0, 15) 114 end 115 116 return srv 117 end 118 119 return server 120 end 121 end 122 123 -- Fake LSP server. 124 M.fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua' 125 M.fake_lsp_logfile = 'Xtest-fake-lsp.log' 126 127 local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) 128 exec_lua(function(fake_lsp_code, fake_lsp_logfile, timeout) 129 options = options or {} 130 settings = settings or {} 131 _G.lsp = require('vim.lsp') 132 _G.TEST_RPC_CLIENT_ID = _G.lsp.start_client { 133 cmd_env = { 134 NVIM_LOG_FILE = fake_lsp_logfile, 135 NVIM_LUA_NOTRACK = '1', 136 NVIM_APPNAME = 'nvim_lsp_test', 137 }, 138 cmd = { 139 vim.v.progpath, 140 '-l', 141 fake_lsp_code, 142 test_name, 143 tostring(timeout), 144 }, 145 handlers = setmetatable({}, { 146 __index = function(_t, _method) 147 return function(...) 148 return vim.rpcrequest(1, 'handler', ...) 149 end 150 end, 151 }), 152 workspace_folders = { 153 { 154 uri = 'file://' .. vim.uv.cwd(), 155 name = 'test_folder', 156 }, 157 }, 158 before_init = function(_params, _config) 159 vim.schedule(function() 160 vim.rpcrequest(1, 'setup') 161 end) 162 end, 163 on_init = function(client, result) 164 _G.TEST_RPC_CLIENT = client 165 vim.rpcrequest(1, 'init', result) 166 end, 167 flags = { 168 allow_incremental_sync = options.allow_incremental_sync or false, 169 debounce_text_changes = options.debounce_text_changes or 0, 170 }, 171 settings = settings, 172 on_exit = function(...) 173 vim.rpcnotify(1, 'exit', ...) 174 end, 175 } 176 end, M.fake_lsp_code, M.fake_lsp_logfile, timeout_ms or 1e3) 177 end 178 179 --- @class test.lsp.Config 180 --- @field test_name string 181 --- @field timeout_ms? integer 182 --- @field options? table 183 --- @field settings? table 184 --- 185 --- @field on_setup? fun() 186 --- @field on_init? fun(client: vim.lsp.Client, ...) 187 --- @field on_handler? fun(...) 188 --- @field on_exit? fun(code: integer, signal: integer) 189 190 --- @param config test.lsp.Config 191 function M.test_rpc_server(config) 192 if config.test_name then 193 M.clear_notrace() 194 fake_lsp_server_setup( 195 config.test_name, 196 config.timeout_ms or 1e3, 197 config.options, 198 config.settings 199 ) 200 end 201 local client = setmetatable({}, { 202 __index = function(t, name) 203 -- Workaround for not being able to yield() inside __index for Lua 5.1 :( 204 -- Otherwise I would just return the value here. 205 return function(arg1, ...) 206 local ismethod = arg1 == t 207 return exec_lua(function(...) 208 local client = _G.TEST_RPC_CLIENT 209 if type(client[name]) == 'function' then 210 return client[name](ismethod and client or arg1, ...) 211 end 212 return client[name] 213 end, ...) 214 end 215 end, 216 }) 217 --- @type integer, integer 218 local code, signal 219 local busy = 0 220 local exited = false 221 local function on_request(method, args) 222 busy = busy + 1 223 if method == 'setup' and config.on_setup then 224 config.on_setup() 225 end 226 if method == 'init' and config.on_init then 227 config.on_init(client, unpack(args)) 228 end 229 if method == 'handler' and config.on_handler then 230 config.on_handler(unpack(args)) 231 end 232 busy = busy - 1 233 if busy == 0 and exited then 234 stop() 235 end 236 return NIL 237 end 238 local function on_notify(method, args) 239 if method == 'exit' then 240 code, signal = unpack(args) 241 exited = true 242 if busy == 0 then 243 stop() 244 end 245 end 246 return NIL 247 end 248 -- TODO specify timeout? 249 -- run(on_request, on_notify, nil, 1000) 250 run(on_request, on_notify, nil) 251 if config.on_exit then 252 config.on_exit(code, signal) 253 end 254 stop() 255 if config.test_name then 256 api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) 257 end 258 end 259 260 return M