fake-lsp-server.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. local protocol = require 'vim.lsp.protocol'
  2. -- Logs to $NVIM_LOG_FILE.
  3. --
  4. -- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062
  5. local function log(loglevel, area, msg)
  6. vim.fn.writefile(
  7. {string.format('%s %s: %s', loglevel, area, msg)},
  8. vim.env.NVIM_LOG_FILE,
  9. 'a')
  10. end
  11. local function message_parts(sep, ...)
  12. local parts = {}
  13. for i = 1, select("#", ...) do
  14. local arg = select(i, ...)
  15. if arg ~= nil then
  16. table.insert(parts, arg)
  17. end
  18. end
  19. return table.concat(parts, sep)
  20. end
  21. -- Assert utility methods
  22. local function assert_eq(a, b, ...)
  23. if not vim.deep_equal(a, b) then
  24. error(message_parts(": ",
  25. ..., "assert_eq failed",
  26. string.format("left == %q, right == %q", vim.inspect(a), vim.inspect(b))
  27. ))
  28. end
  29. end
  30. local function format_message_with_content_length(encoded_message)
  31. return table.concat {
  32. 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
  33. encoded_message;
  34. }
  35. end
  36. local function read_message()
  37. local line = io.read("*l")
  38. local length = line:lower():match("content%-length:%s*(%d+)")
  39. return vim.json.decode(io.read(2 + length):sub(2))
  40. end
  41. local function send(payload)
  42. io.stdout:write(format_message_with_content_length(vim.json.encode(payload)))
  43. end
  44. local function respond(id, err, result)
  45. assert(type(id) == 'number', "id must be a number")
  46. send { jsonrpc = "2.0"; id = id, error = err, result = result }
  47. end
  48. local function notify(method, params)
  49. assert(type(method) == 'string', "method must be a string")
  50. send { method = method, params = params or {} }
  51. end
  52. local function expect_notification(method, params, ...)
  53. local message = read_message()
  54. assert_eq(method, message.method,
  55. ..., "expect_notification", "method")
  56. assert_eq(params, message.params,
  57. ..., "expect_notification", method, "params")
  58. assert_eq({jsonrpc = "2.0"; method=method, params=params}, message,
  59. ..., "expect_notification", "message")
  60. end
  61. local function expect_request(method, handler, ...)
  62. local req = read_message()
  63. assert_eq(method, req.method,
  64. ..., "expect_request", "method")
  65. local err, result = handler(req.params)
  66. respond(req.id, err, result)
  67. end
  68. io.stderr:setvbuf("no")
  69. local function skeleton(config)
  70. local on_init = assert(config.on_init)
  71. local body = assert(config.body)
  72. expect_request("initialize", function(params)
  73. return nil, on_init(params)
  74. end)
  75. expect_notification("initialized", {})
  76. body()
  77. expect_request("shutdown", function()
  78. return nil, {}
  79. end)
  80. expect_notification("exit", nil)
  81. end
  82. -- The actual tests.
  83. local tests = {}
  84. function tests.basic_init()
  85. skeleton {
  86. on_init = function(_params)
  87. return { capabilities = {} }
  88. end;
  89. body = function()
  90. notify('test')
  91. end;
  92. }
  93. end
  94. function tests.check_workspace_configuration()
  95. skeleton {
  96. on_init = function(_params)
  97. return { capabilities = {} }
  98. end;
  99. body = function()
  100. notify('start')
  101. notify('workspace/configuration', { items = {
  102. { section = "testSetting1" };
  103. { section = "testSetting2" };
  104. } })
  105. expect_notification('workspace/configuration', { true; vim.NIL})
  106. notify('shutdown')
  107. end;
  108. }
  109. end
  110. function tests.prepare_rename_nil()
  111. skeleton {
  112. on_init = function()
  113. return { capabilities = {
  114. renameProvider = true,
  115. } }
  116. end;
  117. body = function()
  118. notify('start')
  119. expect_request('textDocument/prepareRename', function()
  120. return nil, nil
  121. end)
  122. notify('shutdown')
  123. end;
  124. }
  125. end
  126. function tests.prepare_rename_placeholder()
  127. skeleton {
  128. on_init = function()
  129. return { capabilities = {
  130. renameProvider = true,
  131. } }
  132. end;
  133. body = function()
  134. notify('start')
  135. expect_request('textDocument/prepareRename', function()
  136. return nil, {placeholder = 'placeholder'}
  137. end)
  138. expect_request('textDocument/rename', function(params)
  139. assert_eq(params.newName, 'renameto')
  140. return nil, nil
  141. end)
  142. notify('shutdown')
  143. end;
  144. }
  145. end
  146. function tests.prepare_rename_range()
  147. skeleton {
  148. on_init = function()
  149. return { capabilities = {
  150. renameProvider = true,
  151. } }
  152. end;
  153. body = function()
  154. notify('start')
  155. expect_request('textDocument/prepareRename', function()
  156. return nil, {
  157. start = { line = 1, character = 8 },
  158. ['end'] = { line = 1, character = 12 },
  159. }
  160. end)
  161. expect_request('textDocument/rename', function(params)
  162. assert_eq(params.newName, 'renameto')
  163. return nil, nil
  164. end)
  165. notify('shutdown')
  166. end;
  167. }
  168. end
  169. function tests.prepare_rename_error()
  170. skeleton {
  171. on_init = function()
  172. return { capabilities = {
  173. renameProvider = true,
  174. } }
  175. end;
  176. body = function()
  177. notify('start')
  178. expect_request('textDocument/prepareRename', function()
  179. return {}, nil
  180. end)
  181. expect_request('textDocument/rename', function(params)
  182. assert_eq(params.newName, 'renameto')
  183. return nil, nil
  184. end)
  185. notify('shutdown')
  186. end;
  187. }
  188. end
  189. function tests.basic_check_capabilities()
  190. skeleton {
  191. on_init = function(params)
  192. local expected_capabilities = protocol.make_client_capabilities()
  193. assert_eq(params.capabilities, expected_capabilities)
  194. return {
  195. capabilities = {
  196. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  197. }
  198. }
  199. end;
  200. body = function()
  201. end;
  202. }
  203. end
  204. function tests.capabilities_for_client_supports_method()
  205. skeleton {
  206. on_init = function(params)
  207. local expected_capabilities = protocol.make_client_capabilities()
  208. assert_eq(params.capabilities, expected_capabilities)
  209. return {
  210. capabilities = {
  211. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  212. completionProvider = true;
  213. hoverProvider = true;
  214. definitionProvider = false;
  215. referencesProvider = false;
  216. codeLensProvider = { resolveProvider = true; };
  217. }
  218. }
  219. end;
  220. body = function()
  221. end;
  222. }
  223. end
  224. function tests.check_forward_request_cancelled()
  225. skeleton {
  226. on_init = function(_)
  227. return { capabilities = {} }
  228. end;
  229. body = function()
  230. expect_request("error_code_test", function()
  231. return {code = -32800}, nil, {method = "error_code_test", client_id=1}
  232. end)
  233. notify('finish')
  234. end;
  235. }
  236. end
  237. function tests.check_forward_content_modified()
  238. skeleton {
  239. on_init = function(_)
  240. return { capabilities = {} }
  241. end;
  242. body = function()
  243. expect_request("error_code_test", function()
  244. return {code = -32801}, nil, {method = "error_code_test", client_id=1}
  245. end)
  246. expect_notification('finish')
  247. notify('finish')
  248. end;
  249. }
  250. end
  251. function tests.check_pending_request_tracked()
  252. skeleton {
  253. on_init = function(_)
  254. return { capabilities = {} }
  255. end;
  256. body = function()
  257. local msg = read_message()
  258. assert_eq('slow_request', msg.method)
  259. expect_notification('release')
  260. respond(msg.id, nil, {})
  261. expect_notification('finish')
  262. notify('finish')
  263. end;
  264. }
  265. end
  266. function tests.check_cancel_request_tracked()
  267. skeleton {
  268. on_init = function(_)
  269. return { capabilities = {} }
  270. end;
  271. body = function()
  272. local msg = read_message()
  273. assert_eq('slow_request', msg.method)
  274. expect_notification('$/cancelRequest', {id=msg.id})
  275. expect_notification('release')
  276. respond(msg.id, {code = -32800}, nil)
  277. notify('finish')
  278. end;
  279. }
  280. end
  281. function tests.check_tracked_requests_cleared()
  282. skeleton {
  283. on_init = function(_)
  284. return { capabilities = {} }
  285. end;
  286. body = function()
  287. local msg = read_message()
  288. assert_eq('slow_request', msg.method)
  289. expect_notification('$/cancelRequest', {id=msg.id})
  290. expect_notification('release')
  291. respond(msg.id, nil, {})
  292. expect_notification('finish')
  293. notify('finish')
  294. end;
  295. }
  296. end
  297. function tests.basic_finish()
  298. skeleton {
  299. on_init = function(params)
  300. local expected_capabilities = protocol.make_client_capabilities()
  301. assert_eq(params.capabilities, expected_capabilities)
  302. return {
  303. capabilities = {
  304. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  305. }
  306. }
  307. end;
  308. body = function()
  309. expect_notification("finish")
  310. notify('finish')
  311. end;
  312. }
  313. end
  314. function tests.basic_check_buffer_open()
  315. skeleton {
  316. on_init = function(params)
  317. local expected_capabilities = protocol.make_client_capabilities()
  318. assert_eq(params.capabilities, expected_capabilities)
  319. return {
  320. capabilities = {
  321. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  322. }
  323. }
  324. end;
  325. body = function()
  326. notify('start')
  327. expect_notification('textDocument/didOpen', {
  328. textDocument = {
  329. languageId = "";
  330. text = table.concat({"testing"; "123"}, "\n") .. '\n';
  331. uri = "file://";
  332. version = 0;
  333. };
  334. })
  335. expect_notification("finish")
  336. notify('finish')
  337. end;
  338. }
  339. end
  340. function tests.basic_check_buffer_open_and_change()
  341. skeleton {
  342. on_init = function(params)
  343. local expected_capabilities = protocol.make_client_capabilities()
  344. assert_eq(params.capabilities, expected_capabilities)
  345. return {
  346. capabilities = {
  347. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  348. }
  349. }
  350. end;
  351. body = function()
  352. notify('start')
  353. expect_notification('textDocument/didOpen', {
  354. textDocument = {
  355. languageId = "";
  356. text = table.concat({"testing"; "123"}, "\n") .. '\n';
  357. uri = "file://";
  358. version = 0;
  359. };
  360. })
  361. expect_notification('textDocument/didChange', {
  362. textDocument = {
  363. uri = "file://";
  364. version = 3;
  365. };
  366. contentChanges = {
  367. { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
  368. }
  369. })
  370. expect_notification("finish")
  371. notify('finish')
  372. end;
  373. }
  374. end
  375. function tests.basic_check_buffer_open_and_change_noeol()
  376. skeleton {
  377. on_init = function(params)
  378. local expected_capabilities = protocol.make_client_capabilities()
  379. assert_eq(params.capabilities, expected_capabilities)
  380. return {
  381. capabilities = {
  382. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  383. }
  384. }
  385. end;
  386. body = function()
  387. notify('start')
  388. expect_notification('textDocument/didOpen', {
  389. textDocument = {
  390. languageId = "";
  391. text = table.concat({"testing"; "123"}, "\n");
  392. uri = "file://";
  393. version = 0;
  394. };
  395. })
  396. expect_notification('textDocument/didChange', {
  397. textDocument = {
  398. uri = "file://";
  399. version = 3;
  400. };
  401. contentChanges = {
  402. { text = table.concat({"testing"; "boop"}, "\n"); };
  403. }
  404. })
  405. expect_notification("finish")
  406. notify('finish')
  407. end;
  408. }
  409. end
  410. function tests.basic_check_buffer_open_and_change_multi()
  411. skeleton {
  412. on_init = function(params)
  413. local expected_capabilities = protocol.make_client_capabilities()
  414. assert_eq(params.capabilities, expected_capabilities)
  415. return {
  416. capabilities = {
  417. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  418. }
  419. }
  420. end;
  421. body = function()
  422. notify('start')
  423. expect_notification('textDocument/didOpen', {
  424. textDocument = {
  425. languageId = "";
  426. text = table.concat({"testing"; "123"}, "\n") .. '\n';
  427. uri = "file://";
  428. version = 0;
  429. };
  430. })
  431. expect_notification('textDocument/didChange', {
  432. textDocument = {
  433. uri = "file://";
  434. version = 3;
  435. };
  436. contentChanges = {
  437. { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
  438. }
  439. })
  440. expect_notification('textDocument/didChange', {
  441. textDocument = {
  442. uri = "file://";
  443. version = 4;
  444. };
  445. contentChanges = {
  446. { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
  447. }
  448. })
  449. expect_notification("finish")
  450. notify('finish')
  451. end;
  452. }
  453. end
  454. function tests.basic_check_buffer_open_and_change_multi_and_close()
  455. skeleton {
  456. on_init = function(params)
  457. local expected_capabilities = protocol.make_client_capabilities()
  458. assert_eq(params.capabilities, expected_capabilities)
  459. return {
  460. capabilities = {
  461. textDocumentSync = protocol.TextDocumentSyncKind.Full;
  462. }
  463. }
  464. end;
  465. body = function()
  466. notify('start')
  467. expect_notification('textDocument/didOpen', {
  468. textDocument = {
  469. languageId = "";
  470. text = table.concat({"testing"; "123"}, "\n") .. '\n';
  471. uri = "file://";
  472. version = 0;
  473. };
  474. })
  475. expect_notification('textDocument/didChange', {
  476. textDocument = {
  477. uri = "file://";
  478. version = 3;
  479. };
  480. contentChanges = {
  481. { text = table.concat({"testing"; "321"}, "\n") .. '\n'; };
  482. }
  483. })
  484. expect_notification('textDocument/didChange', {
  485. textDocument = {
  486. uri = "file://";
  487. version = 4;
  488. };
  489. contentChanges = {
  490. { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; };
  491. }
  492. })
  493. expect_notification('textDocument/didClose', {
  494. textDocument = {
  495. uri = "file://";
  496. };
  497. })
  498. expect_notification("finish")
  499. notify('finish')
  500. end;
  501. }
  502. end
  503. function tests.basic_check_buffer_open_and_change_incremental()
  504. skeleton {
  505. on_init = function(params)
  506. local expected_capabilities = protocol.make_client_capabilities()
  507. assert_eq(params.capabilities, expected_capabilities)
  508. return {
  509. capabilities = {
  510. textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
  511. }
  512. }
  513. end;
  514. body = function()
  515. notify('start')
  516. expect_notification('textDocument/didOpen', {
  517. textDocument = {
  518. languageId = "";
  519. text = table.concat({"testing"; "123"}, "\n") .. '\n';
  520. uri = "file://";
  521. version = 0;
  522. };
  523. })
  524. expect_notification('textDocument/didChange', {
  525. textDocument = {
  526. uri = "file://";
  527. version = 3;
  528. };
  529. contentChanges = {
  530. {
  531. range = {
  532. start = { line = 1; character = 3; };
  533. ["end"] = { line = 1; character = 3; };
  534. };
  535. rangeLength = 0;
  536. text = "boop";
  537. };
  538. }
  539. })
  540. expect_notification("finish")
  541. notify('finish')
  542. end;
  543. }
  544. end
  545. function tests.basic_check_buffer_open_and_change_incremental_editing()
  546. skeleton {
  547. on_init = function(params)
  548. local expected_capabilities = protocol.make_client_capabilities()
  549. assert_eq(params.capabilities, expected_capabilities)
  550. return {
  551. capabilities = {
  552. textDocumentSync = protocol.TextDocumentSyncKind.Incremental;
  553. }
  554. }
  555. end;
  556. body = function()
  557. notify('start')
  558. expect_notification('textDocument/didOpen', {
  559. textDocument = {
  560. languageId = "";
  561. text = table.concat({"testing"; "123"}, "\n");
  562. uri = "file://";
  563. version = 0;
  564. };
  565. })
  566. expect_notification('textDocument/didChange', {
  567. textDocument = {
  568. uri = "file://";
  569. version = 3;
  570. };
  571. contentChanges = {
  572. {
  573. range = {
  574. start = { line = 0; character = 0; };
  575. ["end"] = { line = 1; character = 0; };
  576. };
  577. rangeLength = 4;
  578. text = "testing\n\n";
  579. };
  580. }
  581. })
  582. expect_notification("finish")
  583. notify('finish')
  584. end;
  585. }
  586. end
  587. function tests.invalid_header()
  588. io.stdout:write("Content-length: \r\n")
  589. end
  590. function tests.decode_nil()
  591. skeleton {
  592. on_init = function(_)
  593. return { capabilities = {} }
  594. end;
  595. body = function()
  596. notify('start')
  597. notify("workspace/executeCommand", {
  598. arguments = { "EXTRACT_METHOD", {metadata = {field = vim.NIL}}, 3, 0, 6123, vim.NIL },
  599. command = "refactor.perform",
  600. title = "EXTRACT_METHOD"
  601. })
  602. notify('finish')
  603. end;
  604. }
  605. end
  606. function tests.code_action_with_resolve()
  607. skeleton {
  608. on_init = function()
  609. return {
  610. capabilities = {
  611. codeActionProvider = {
  612. resolveProvider = true
  613. }
  614. }
  615. }
  616. end;
  617. body = function()
  618. notify('start')
  619. local cmd = {
  620. title = 'Command 1',
  621. command = 'dummy1'
  622. }
  623. expect_request('textDocument/codeAction', function()
  624. return nil, { cmd, }
  625. end)
  626. expect_request('codeAction/resolve', function()
  627. return nil, cmd
  628. end)
  629. notify('shutdown')
  630. end;
  631. }
  632. end
  633. function tests.clientside_commands()
  634. skeleton {
  635. on_init = function()
  636. return {
  637. capabilities = {}
  638. }
  639. end;
  640. body = function()
  641. notify('start')
  642. notify('shutdown')
  643. end;
  644. }
  645. end
  646. -- Tests will be indexed by TEST_NAME
  647. local kill_timer = vim.loop.new_timer()
  648. kill_timer:start(_G.TIMEOUT or 1e3, 0, function()
  649. kill_timer:stop()
  650. kill_timer:close()
  651. log('ERROR', 'LSP', 'TIMEOUT')
  652. io.stderr:write("TIMEOUT")
  653. os.exit(100)
  654. end)
  655. local test_name = _G.TEST_NAME -- lualint workaround
  656. assert(type(test_name) == 'string', 'TEST_NAME must be specified.')
  657. local status, err = pcall(assert(tests[test_name], "Test not found"))
  658. kill_timer:stop()
  659. kill_timer:close()
  660. if not status then
  661. log('ERROR', 'LSP', tostring(err))
  662. io.stderr:write(err)
  663. os.exit(101)
  664. end
  665. os.exit(0)