neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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