neovim

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

channel_spec.lua (11976B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local clear = n.clear
      6 local eq = t.eq
      7 local eval = n.eval
      8 local command = n.command
      9 local pcall_err = t.pcall_err
     10 local feed = n.feed
     11 local poke_eventloop = n.poke_eventloop
     12 local is_os = t.is_os
     13 local api = n.api
     14 local async_meths = n.async_meths
     15 local testprg = n.testprg
     16 local assert_alive = n.assert_alive
     17 
     18 describe('terminal channel is closed and later released if', function()
     19  local screen
     20 
     21  before_each(function()
     22    clear()
     23    screen = Screen.new()
     24  end)
     25 
     26  it('opened by nvim_open_term() and deleted by :bdelete!', function()
     27    command([[let id = nvim_open_term(0, {})]])
     28    local chans = eval('len(nvim_list_chans())')
     29    -- Channel hasn't been released yet.
     30    eq(
     31      "Vim(call):Can't send data to closed stream",
     32      pcall_err(command, [[bdelete! | call chansend(id, 'test')]])
     33    )
     34    -- Channel has been released after processing free_channel_event().
     35    eq(chans - 1, eval('len(nvim_list_chans())'))
     36 
     37    command('autocmd BufWipeout * ++once let id2 = nvim_open_term(str2nr(expand("<abuf>")), {})')
     38    -- Channel hasn't been released yet.
     39    eq(
     40      "Vim(call):Can't send data to closed stream",
     41      pcall_err(command, [[bdelete! | call chansend(id2, 'test')]])
     42    )
     43    -- Channel has been released after processing free_channel_event().
     44    eq(chans - 1, eval('len(nvim_list_chans())'))
     45  end)
     46 
     47  it('opened by nvim_open_term(), closed by chanclose(), and deleted by pressing a key', function()
     48    command('let id = nvim_open_term(0, {})')
     49    local chans = eval('len(nvim_list_chans())')
     50    -- Channel has been closed but not released.
     51    eq(
     52      "Vim(call):Can't send data to closed stream",
     53      pcall_err(command, [[call chanclose(id) | call chansend(id, 'test')]])
     54    )
     55    screen:expect({ any = '%[Terminal closed%]' })
     56    eq(chans, eval('len(nvim_list_chans())'))
     57    feed('i<CR>') -- Delete terminal.
     58    poke_eventloop() -- Process pending input.
     59    -- Channel has been released after processing free_channel_event().
     60    eq(chans - 1, eval('len(nvim_list_chans())'))
     61  end)
     62 
     63  it('opened by nvim_open_term(), closed by chanclose(), and deleted by :bdelete', function()
     64    command('let id = nvim_open_term(0, {})')
     65    local chans = eval('len(nvim_list_chans())')
     66    -- Channel has been closed but not released.
     67    eq(
     68      "Vim(call):Can't send data to closed stream",
     69      pcall_err(command, [[call chanclose(id) | call chansend(id, 'test')]])
     70    )
     71    screen:expect({ any = '%[Terminal closed%]' })
     72    eq(chans, eval('len(nvim_list_chans())'))
     73    -- Channel still hasn't been released yet.
     74    eq(
     75      "Vim(call):Can't send data to closed stream",
     76      pcall_err(command, [[bdelete | call chansend(id, 'test')]])
     77    )
     78    -- Channel has been released after processing free_channel_event().
     79    eq(chans - 1, eval('len(nvim_list_chans())'))
     80  end)
     81 
     82  it('opened by jobstart(…,{term=true}), exited, and deleted by pressing a key', function()
     83    command([[let id = jobstart('echo',{'term':v:true})]])
     84    local chans = eval('len(nvim_list_chans())')
     85    -- Wait for process to exit.
     86    screen:expect({ any = '%[Process exited 0%]' })
     87    -- Process has exited but channel has't been released.
     88    eq(
     89      "Vim(call):Can't send data to closed stream",
     90      pcall_err(command, [[call chansend(id, 'test')]])
     91    )
     92    eq(chans, eval('len(nvim_list_chans())'))
     93    feed('i<CR>') -- Delete terminal.
     94    poke_eventloop() -- Process pending input.
     95    poke_eventloop() -- Process term_delayed_free().
     96    -- Channel has been released after processing free_channel_event().
     97    eq(chans - 1, eval('len(nvim_list_chans())'))
     98  end)
     99 
    100  -- This indirectly covers #16264
    101  it('opened by jobstart(…,{term=true}), exited, and deleted by :bdelete', function()
    102    command([[let id = jobstart('echo', {'term':v:true})]])
    103    local chans = eval('len(nvim_list_chans())')
    104    -- Wait for process to exit.
    105    screen:expect({ any = '%[Process exited 0%]' })
    106    -- Process has exited but channel hasn't been released.
    107    eq(
    108      "Vim(call):Can't send data to closed stream",
    109      pcall_err(command, [[call chansend(id, 'test')]])
    110    )
    111    eq(chans, eval('len(nvim_list_chans())'))
    112    -- Channel still hasn't been released yet.
    113    eq(
    114      "Vim(call):Can't send data to closed stream",
    115      pcall_err(command, [[bdelete | call chansend(id, 'test')]])
    116    )
    117    poke_eventloop() -- Process term_delayed_free().
    118    -- Channel has been released after processing free_channel_event().
    119    eq(chans - 1, eval('len(nvim_list_chans())'))
    120  end)
    121 end)
    122 
    123 it('chansend sends lines to terminal channel in proper order', function()
    124  clear({ args = { '--cmd', 'set laststatus=2' } })
    125  local screen = Screen.new(100, 20)
    126  screen._default_attr_ids = nil
    127  local shells = is_os('win') and { 'cmd.exe', 'pwsh.exe -nop', 'powershell.exe -nop' } or { 'sh' }
    128  for _, sh in ipairs(shells) do
    129    command([[let id = jobstart(']] .. sh .. [[', {'term':v:true})]])
    130    -- On Windows this may fail if the shell hasn't fully started yet, so retry.
    131    t.retry(is_os('win') and 3 or 1, 5000, function()
    132      command([[call chansend(id, ['echo "hello"', 'echo "world"', ''])]])
    133      -- With PowerShell the command may be highlighted, so specify attr_ids = {}.
    134      screen:expect { any = [[echo "hello".*echo "world"]], attr_ids = {}, timeout = 2000 }
    135    end)
    136    command('bdelete!')
    137    screen:expect { any = '%[No Name%]' }
    138  end
    139 end)
    140 
    141 --- @param event string
    142 --- @param extra_tests fun(table, table)?
    143 local function test_autocmd_no_crash(event, extra_tests)
    144  local env = {}
    145  -- Use REPFAST for immediately output after start.
    146  local term_args = { testprg('shell-test'), 'REPFAST', '50', 'TEST' }
    147 
    148  before_each(function()
    149    clear()
    150    env.screen = Screen.new(60, 4)
    151    command([[file Xoldbuf | call setline(1, 'OLDBUF') | enew]])
    152    -- Wait before :bwipe to avoid closing PTY master before the child calls setsid(),
    153    -- as that will cause SIGHUP to be also sent to the parent.
    154    -- Use vim.uv.sleep() which blocks the event loop.
    155    n.exec([[
    156      func Wipe()
    157        lua vim.uv.sleep(5)
    158        bwipe!
    159      endfunc
    160    ]])
    161  end)
    162 
    163  local input_prompt_screen = [[
    164                                                                |
    165    {1:~                                                           }|*2
    166    ^                                                            |
    167  ]]
    168  local oldbuf_screen = [[
    169    ^OLDBUF                                                      |
    170    {1:~                                                           }|*2
    171                                                                |
    172  ]]
    173 
    174  it('processes job exit event when using jobstart(…,{term=true})', function()
    175    api.nvim_create_autocmd(event, { command = "call input('')" })
    176    async_meths.nvim_call_function('jobstart', { term_args, { term = true } })
    177    env.screen:expect(input_prompt_screen)
    178    vim.uv.sleep(20)
    179    feed('<CR>')
    180    env.screen:expect([[
    181      ^0: TEST                                                     |
    182      1: TEST                                                     |
    183      2: TEST                                                     |
    184                                                                  |
    185    ]])
    186    feed('i')
    187    env.screen:expect([[
    188      49: TEST                                                    |
    189                                                                  |
    190      [Process exited 0]^                                          |
    191      {5:-- TERMINAL --}                                              |
    192    ]])
    193    feed('<CR>')
    194    env.screen:expect(oldbuf_screen)
    195    assert_alive()
    196  end)
    197 
    198  it('wipes buffer and processes events when using jobstart(…,{term=true})', function()
    199    api.nvim_create_autocmd(event, { command = "call Wipe() | call input('')" })
    200    async_meths.nvim_call_function('jobstart', { term_args, { term = true } })
    201    env.screen:expect(input_prompt_screen)
    202    vim.uv.sleep(20)
    203    feed('<CR>')
    204    env.screen:expect(oldbuf_screen)
    205    assert_alive()
    206    eq('Xoldbuf', eval('bufname()'))
    207    eq(0, eval([[exists('b:term_title')]]))
    208  end)
    209 
    210  it('processes :bwipe from TermClose when using jobstart(…,{term=true})', function()
    211    local term_buf = api.nvim_get_current_buf()
    212    api.nvim_create_autocmd('TermClose', { command = ('bwipe! %d'):format(term_buf) })
    213    api.nvim_create_autocmd(event, { command = "call input('')", nested = true })
    214    async_meths.nvim_call_function('jobstart', { term_args, { term = true } })
    215    env.screen:expect(input_prompt_screen)
    216    vim.uv.sleep(20)
    217    feed('<CR>')
    218    env.screen:expect(oldbuf_screen)
    219    assert_alive()
    220    eq('Xoldbuf', eval('bufname()'))
    221    eq(0, eval([[exists('b:term_title')]]))
    222  end)
    223 
    224  it('only wipes buffer when using jobstart(…,{term=true})', function()
    225    api.nvim_create_autocmd(event, { command = 'call Wipe()' })
    226    async_meths.nvim_call_function('jobstart', { term_args, { term = true } })
    227    env.screen:expect(oldbuf_screen)
    228    assert_alive()
    229    eq('Xoldbuf', eval('bufname()'))
    230    eq(0, eval([[exists('b:term_title')]]))
    231  end)
    232 
    233  if extra_tests then
    234    extra_tests(env, term_args)
    235  end
    236 end
    237 
    238 describe('no crash when TermOpen autocommand', function()
    239  test_autocmd_no_crash('TermOpen', function(env, term_args)
    240    it('wipes buffer and processes events when using nvim_open_term()', function()
    241      api.nvim_create_autocmd('TermOpen', { command = "call Wipe() | call input('')" })
    242      async_meths.nvim_open_term(0, {})
    243      env.screen:expect([[
    244                                                                    |
    245        {1:~                                                           }|*2
    246        ^                                                            |
    247      ]])
    248      feed('<CR>')
    249      env.screen:expect([[
    250        ^OLDBUF                                                      |
    251        {1:~                                                           }|*2
    252                                                                    |
    253      ]])
    254      assert_alive()
    255    end)
    256 
    257    it('wipes buffer when using jobstart(…,{term=true}) during Nvim exit', function()
    258      n.expect_exit(n.exec_lua, function()
    259        vim.schedule(function()
    260          vim.fn.jobstart(term_args, { term = true })
    261        end)
    262        vim.api.nvim_create_autocmd('TermOpen', { command = 'call Wipe()' })
    263        vim.cmd('qall!')
    264      end)
    265    end)
    266  end)
    267 end)
    268 
    269 describe('no crash when BufFilePre autocommand', function()
    270  test_autocmd_no_crash('BufFilePre')
    271 end)
    272 
    273 describe('no crash when BufFilePost autocommand', function()
    274  test_autocmd_no_crash('BufFilePost')
    275 end)
    276 
    277 describe('nvim_open_term', function()
    278  local screen
    279 
    280  before_each(function()
    281    clear()
    282    screen = Screen.new(8, 10)
    283  end)
    284 
    285  it('with force_crlf=true converts newlines', function()
    286    local win = api.nvim_get_current_win()
    287    local buf = api.nvim_create_buf(false, true)
    288    local term = api.nvim_open_term(buf, { force_crlf = true })
    289    api.nvim_win_set_buf(win, buf)
    290    api.nvim_chan_send(term, 'here\nthere\nfoo\r\nbar\n\ntest')
    291    screen:expect([[
    292      ^here        |
    293      there       |
    294      foo         |
    295      bar         |
    296                  |
    297      test        |
    298                  |*4
    299    ]])
    300    api.nvim_chan_send(term, '\nfirst')
    301    screen:expect([[
    302      ^here        |
    303      there       |
    304      foo         |
    305      bar         |
    306                  |
    307      test        |
    308      first       |
    309                  |*3
    310    ]])
    311  end)
    312 
    313  it('with force_crlf=false does not convert newlines', function()
    314    local win = api.nvim_get_current_win()
    315    local buf = api.nvim_create_buf(false, true)
    316    local term = api.nvim_open_term(buf, { force_crlf = false })
    317    api.nvim_win_set_buf(win, buf)
    318    api.nvim_chan_send(term, 'here\nthere')
    319    screen:expect([[
    320      ^here        |
    321          there   |
    322                  |*8
    323    ]])
    324  end)
    325 end)