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)