testutil.lua 6.0 KB

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