neovim

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

eval_spec.lua (8384B)


      1 -- Tests for core Vimscript "eval" behavior.
      2 --
      3 -- See also:
      4 --    let_spec.lua
      5 --    null_spec.lua
      6 --    operators_spec.lua
      7 --
      8 -- Tests for the Vimscript |vimscript-functions| library should live in:
      9 --    test/functional/vimscript/<funcname>_spec.lua
     10 --    test/functional/vimscript/functions_spec.lua
     11 
     12 local t = require('test.testutil')
     13 local n = require('test.functional.testnvim')()
     14 local Screen = require('test.functional.ui.screen')
     15 
     16 local mkdir = t.mkdir
     17 local clear = n.clear
     18 local eq = t.eq
     19 local exec = n.exec
     20 local exc_exec = n.exc_exec
     21 local exec_lua = n.exec_lua
     22 local exec_capture = n.exec_capture
     23 local eval = n.eval
     24 local command = n.command
     25 local write_file = t.write_file
     26 local api = n.api
     27 local fn = n.fn
     28 local sleep = vim.uv.sleep
     29 local assert_alive = n.assert_alive
     30 local poke_eventloop = n.poke_eventloop
     31 local feed = n.feed
     32 local expect_exit = n.expect_exit
     33 
     34 describe('Up to MAX_FUNC_ARGS arguments are handled by', function()
     35  local max_func_args = 20 -- from eval.h
     36  local range = n.fn.range
     37 
     38  before_each(clear)
     39 
     40  it('printf()', function()
     41    local printf = n.fn.printf
     42    local rep = n.fn['repeat']
     43    local expected = '2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,'
     44    eq(expected, printf(rep('%d,', max_func_args - 1), unpack(range(2, max_func_args))))
     45    local ret = exc_exec('call printf("", 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)')
     46    eq('Vim(call):E740: Too many arguments for function printf', ret)
     47  end)
     48 
     49  it('rpcnotify()', function()
     50    local rpcnotify = n.fn.rpcnotify
     51    local ret = rpcnotify(0, 'foo', unpack(range(3, max_func_args)))
     52    eq(1, ret)
     53    ret = exc_exec('call rpcnotify(0, "foo", 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)')
     54    eq('Vim(call):E740: Too many arguments for function rpcnotify', ret)
     55  end)
     56 end)
     57 
     58 describe('backtick expansion', function()
     59  setup(function()
     60    clear()
     61    mkdir('test-backticks')
     62    write_file('test-backticks/file1', 'test file 1')
     63    write_file('test-backticks/file2', 'test file 2')
     64    write_file('test-backticks/file3', 'test file 3')
     65    mkdir('test-backticks/subdir')
     66    write_file('test-backticks/subdir/file4', 'test file 4')
     67    -- Long path might cause "Press ENTER" prompt; use :silent to avoid it.
     68    command('silent cd test-backticks')
     69  end)
     70 
     71  teardown(function()
     72    n.rmdir('test-backticks')
     73  end)
     74 
     75  it("with default 'shell'", function()
     76    if t.is_os('win') then
     77      command(':silent args `dir /b *2`')
     78    else
     79      command(':silent args `echo ***2`')
     80    end
     81    eq({ 'file2' }, eval('argv()'))
     82    if t.is_os('win') then
     83      command(':silent args `dir /s/b *4`')
     84      eq({ 'subdir\\file4' }, eval('map(argv(), \'fnamemodify(v:val, ":.")\')'))
     85    else
     86      command(':silent args `echo */*4`')
     87      eq({ 'subdir/file4' }, eval('argv()'))
     88    end
     89  end)
     90 
     91  it('with shell=fish', function()
     92    t.skip(fn.executable('fish') == 0, 'missing "fish" command')
     93 
     94    command('set shell=fish')
     95    command(':silent args `echo ***2`')
     96    eq({ 'file2' }, eval('argv()'))
     97    command(':silent args `echo */*4`')
     98    eq({ 'subdir/file4' }, eval('argv()'))
     99  end)
    100 end)
    101 
    102 describe('List support code', function()
    103  local dur
    104  local min_dur = 8
    105  local len = 131072
    106 
    107  if not pending('does not actually allows interrupting with just got_int', function() end) then
    108    return
    109  end
    110  -- The following tests are confirmed to work with os_breakcheck() just before
    111  -- `if (got_int) {break;}` in tv_list_copy and list_join_inner() and not to
    112  -- work without.
    113  setup(function()
    114    clear()
    115    dur = 0
    116    while true do
    117      command(([[
    118        let rt = reltime()
    119        let bl = range(%u)
    120        let dur = reltimestr(reltime(rt))
    121      ]]):format(len))
    122      dur = tonumber(api.nvim_get_var('dur'))
    123      if dur >= min_dur then
    124        -- print(('Using len %u, dur %g'):format(len, dur))
    125        break
    126      else
    127        len = len * 2
    128      end
    129    end
    130  end)
    131  it('allows interrupting copy', function()
    132    feed(':let t_rt = reltime()<CR>:let t_bl = copy(bl)<CR>')
    133    sleep(min_dur / 16 * 1000)
    134    feed('<C-c>')
    135    poke_eventloop()
    136    command('let t_dur = reltimestr(reltime(t_rt))')
    137    local t_dur = tonumber(api.nvim_get_var('t_dur'))
    138    if t_dur >= dur / 8 then
    139      eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8))
    140    end
    141  end)
    142  it('allows interrupting join', function()
    143    feed(':let t_rt = reltime()<CR>:let t_j = join(bl)<CR>')
    144    sleep(min_dur / 16 * 1000)
    145    feed('<C-c>')
    146    poke_eventloop()
    147    command('let t_dur = reltimestr(reltime(t_rt))')
    148    local t_dur = tonumber(api.nvim_get_var('t_dur'))
    149    print(('t_dur: %g'):format(t_dur))
    150    if t_dur >= dur / 8 then
    151      eq(nil, ('Took too long to cancel: %g >= %g'):format(t_dur, dur / 8))
    152    end
    153  end)
    154 end)
    155 
    156 describe('uncaught exception', function()
    157  before_each(clear)
    158 
    159  it('is not forgotten #13490', function()
    160    command('autocmd BufWinEnter * throw "i am error"')
    161    eq('i am error', exc_exec('try | new | endtry'))
    162 
    163    -- Like Vim, throwing here aborts the processing of the script, but does not stop :runtime!
    164    -- from processing the others.
    165    -- Only the first thrown exception should be rethrown from the :try below, though.
    166    for i = 1, 3 do
    167      write_file(
    168        'throw' .. i .. '.vim',
    169        ([[
    170        let result ..= '%d'
    171        throw 'throw%d'
    172        let result ..= 'X'
    173      ]]):format(i, i)
    174      )
    175    end
    176    finally(function()
    177      for i = 1, 3 do
    178        os.remove('throw' .. i .. '.vim')
    179      end
    180    end)
    181 
    182    command('set runtimepath+=. | let result = ""')
    183    eq('throw1', exc_exec('try | runtime! throw*.vim | endtry'))
    184    eq('123', eval('result'))
    185  end)
    186 
    187  it('multiline exception remains multiline #25350', function()
    188    local screen = Screen.new(80, 11)
    189    exec_lua([[
    190      function _G.Oops()
    191        error("oops")
    192      end
    193    ]])
    194    feed(':try\rlua _G.Oops()\rendtry\r')
    195    screen:expect([[
    196      {3:                                                                                }|
    197      :try                                                                            |
    198      :  lua _G.Oops()                                                                |
    199      :  endtry                                                                       |
    200      {9:Error in :}                                                                      |
    201      {9:E5108: Lua: [string "<nvim>"]:2: oops}                                           |
    202      {9:stack traceback:}                                                                |
    203      {9:        [C]: in function 'error'}                                                |
    204      {9:        [string "<nvim>"]:2: in function 'Oops'}                                 |
    205      {9:        [string ":lua"]:1: in main chunk}                                        |
    206      {6:Press ENTER or type command to continue}^                                         |
    207    ]])
    208  end)
    209 end)
    210 
    211 describe('listing functions using :function', function()
    212  before_each(clear)
    213 
    214  it('works for lambda functions with <lambda> #20466', function()
    215    command('let A = {-> 1}')
    216    local num = exec_capture('echo A'):match("function%('<lambda>(%d+)'%)")
    217    eq(
    218      ([[
    219   function <lambda>%s(...)
    220 1  return 1
    221   endfunction]]):format(num),
    222      exec_capture(('function <lambda>%s'):format(num))
    223    )
    224  end)
    225 end)
    226 
    227 it('no double-free in garbage collection #16287', function()
    228  clear()
    229  -- Don't use exec() here as using a named script reproduces the issue better.
    230  write_file(
    231    'Xgarbagecollect.vim',
    232    [[
    233    func Foo() abort
    234      let s:args = [a:000]
    235      let foo0 = ""
    236      let foo1 = ""
    237      let foo2 = ""
    238      let foo3 = ""
    239      let foo4 = ""
    240      let foo5 = ""
    241      let foo6 = ""
    242      let foo7 = ""
    243      let foo8 = ""
    244      let foo9 = ""
    245      let foo10 = ""
    246      let foo11 = ""
    247      let foo12 = ""
    248      let foo13 = ""
    249      let foo14 = ""
    250    endfunc
    251 
    252    set updatetime=1
    253    call Foo()
    254    call Foo()
    255  ]]
    256  )
    257  finally(function()
    258    os.remove('Xgarbagecollect.vim')
    259  end)
    260  command('source Xgarbagecollect.vim')
    261  sleep(10)
    262  assert_alive()
    263 end)
    264 
    265 it('no heap-use-after-free with EXITFREE and partial as prompt callback', function()
    266  clear()
    267  exec([[
    268    func PromptCallback(text)
    269    endfunc
    270    setlocal buftype=prompt
    271    call prompt_setcallback('', funcref('PromptCallback'))
    272  ]])
    273  expect_exit(command, 'qall!')
    274 end)