eval_spec.lua 8.2 KB

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