neovim/test/functional/vimscript/timer_spec.lua
bfredl e61228a214 fix(tests): needing two calls to setup a screen is cringe
Before calling "attach" a screen object is just a dummy container for
(row, col) values whose purpose is to be sent as part of the "attach"
function call anyway.

Just create the screen in an attached state directly. Keep the complete
(row, col, options) config together. It is still completely valid to
later detach and re-attach as needed, including to another session.
2024-11-14 12:40:57 +01:00

269 lines
7.8 KiB
Lua

local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local feed, eq, eval, ok = n.feed, t.eq, n.eval, t.ok
local source, async_meths, run = n.source, n.async_meths, n.run
local clear, command, fn = n.clear, n.command, n.fn
local exc_exec = n.exc_exec
local api = n.api
local load_adjust = n.load_adjust
local retry = t.retry
describe('timers', function()
before_each(function()
clear()
source([[
let g:val = 0
func MyHandler(timer)
let g:val += 1
endfunc
]])
end)
it('works one-shot', function()
eq(0, eval("[timer_start(10, 'MyHandler'), g:val][1]"))
run(nil, nil, nil, load_adjust(100))
eq(1, eval('g:val'))
end)
it('works one-shot when repeat=0', function()
eq(0, eval("[timer_start(10, 'MyHandler', {'repeat': 0}), g:val][1]"))
run(nil, nil, nil, load_adjust(100))
eq(1, eval('g:val'))
end)
it('works with repeat two', function()
eq(0, eval("[timer_start(10, 'MyHandler', {'repeat': 2}), g:val][1]"))
run(nil, nil, nil, load_adjust(20))
retry(nil, load_adjust(300), function()
eq(2, eval('g:val'))
end)
end)
it('are triggered during sleep', function()
source([[
let g:val = -1
func! MyHandler(timer)
if g:val >= 0
let g:val += 1
if g:val == 2
call timer_stop(a:timer)
endif
endif
endfunc
]])
eval("timer_start(10, 'MyHandler', {'repeat': -1})")
async_meths.nvim_command('sleep 10')
eq(-1, eval('g:val')) -- timer did nothing yet.
async_meths.nvim_command('let g:val = 0')
run(nil, nil, nil, load_adjust(20))
retry(nil, nil, function()
eq(2, eval('g:val'))
end)
end)
it('works with zero timeout', function()
-- timer_start does still not invoke the callback immediately
eq(0, eval("[timer_start(0, 'MyHandler', {'repeat': 1000}), g:val][1]"))
retry(nil, nil, function()
eq(1000, eval('g:val'))
end)
end)
it('can be started during sleep', function()
async_meths.nvim_command('sleep 10')
-- this also tests that remote requests works during sleep
eq(0, eval("[timer_start(10, 'MyHandler', {'repeat': 2}), g:val][1]"))
run(nil, nil, nil, load_adjust(20))
retry(nil, load_adjust(300), function()
eq(2, eval('g:val'))
end)
end)
it('are paused when event processing is disabled', function()
command("call timer_start(5, 'MyHandler', {'repeat': -1})")
run(nil, nil, nil, load_adjust(10))
local count = eval('g:val')
-- shows two line error message and thus invokes the return prompt.
-- if we start to allow event processing here, we need to change this test.
feed(':throw "fatal error"<CR>')
run(nil, nil, nil, load_adjust(30))
feed('<cr>')
local diff = eval('g:val') - count
assert(0 <= diff and diff <= 4, 'expected (0 <= diff <= 4), got: ' .. tostring(diff))
end)
it('are triggered in blocking getchar() call', function()
command("call timer_start(5, 'MyHandler', {'repeat': -1})")
async_meths.nvim_command('let g:val = 0 | let g:c = getchar()')
retry(nil, nil, function()
local val = eval('g:val')
ok(val >= 2, '>= 2', tostring(val))
eq(0, eval('getchar(1)'))
end)
feed('c')
eq(99, eval('g:c'))
end)
it('can invoke redraw in blocking getchar() call', function()
local screen = Screen.new(40, 6)
api.nvim_buf_set_lines(0, 0, -1, true, { 'ITEM 1', 'ITEM 2' })
source([[
let g:cont = 0
func! AddItem(timer)
if !g:cont
return
endif
call timer_stop(a:timer)
call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3'])
" Meant to test for what Vim tests in Test_peek_and_get_char.
call getchar(1)
redraw
endfunc
]])
async_meths.nvim_command('let g:c2 = getchar()')
async_meths.nvim_command(
'call timer_start(' .. load_adjust(100) .. ", 'AddItem', {'repeat': -1})"
)
screen:expect([[
^ITEM 1 |
ITEM 2 |
{1:~ }|*3
|
]])
async_meths.nvim_command('let g:cont = 1')
screen:expect([[
^ITEM 1 |
ITEM 2 |
ITEM 3 |
{1:~ }|*2
|
]])
feed('3')
eq(51, eval('g:c2'))
screen:expect {
grid = [[
^ITEM 1 |
ITEM 2 |
ITEM 3 |
{1:~ }|*2
|
]],
unchanged = true,
}
end)
it('can be stopped', function()
local t_init_val = eval("[timer_start(5, 'MyHandler', {'repeat': -1}), g:val]")
eq(0, t_init_val[2])
run(nil, nil, nil, load_adjust(30))
fn.timer_stop(t_init_val[1])
local count = eval('g:val')
run(nil, load_adjust(300), nil, load_adjust(30))
local count2 = eval('g:val')
-- when count is eval:ed after timer_stop this should be non-racy
eq(count, count2)
end)
it('can be stopped from the handler', function()
source([[
func! MyHandler(timer)
let g:val += 1
if g:val == 3
call timer_stop(a:timer)
" check double stop is ignored
call timer_stop(a:timer)
endif
endfunc
]])
eq(0, eval('g:val'))
command("call timer_start(10, 'MyHandler', {'repeat': -1})")
retry(nil, nil, function()
eq(3, eval('g:val'))
end)
end)
it('can have two timers', function()
source([[
let g:val2 = 0
func! MyHandler2(timer)
let g:val2 += 1
endfunc
]])
command("call timer_start(2, 'MyHandler', {'repeat': 3})")
command("call timer_start(4, 'MyHandler2', {'repeat': 2})")
retry(nil, nil, function()
eq(3, eval('g:val'))
eq(2, eval('g:val2'))
end)
end)
it('do not crash when processing events in the handler', function()
source([[
let g:val = 0
func! MyHandler(timer)
call timer_stop(a:timer)
sleep 10m
let g:val += 1
endfunc
]])
command("call timer_start(5, 'MyHandler', {'repeat': 1})")
run(nil, nil, nil, load_adjust(20))
retry(nil, load_adjust(150), function()
eq(1, eval('g:val'))
end)
end)
it("doesn't mess up the cmdline", function()
local screen = Screen.new(40, 6)
source([[
let g:val = 0
func! MyHandler(timer)
while !g:val
return
endwhile
call timer_stop(a:timer)
echo "evil"
redraw
let g:val = 2
endfunc
]])
command("call timer_start(100, 'MyHandler', {'repeat': -1})")
feed(':good')
screen:expect([[
|
{1:~ }|*4
:good^ |
]])
command('let g:val = 1')
screen:expect_unchanged(true, load_adjust(200))
eq(2, eval('g:val'))
end)
it("timer_start can't be used in the sandbox", function()
source [[
function! Scary(timer) abort
call execute('echo ''execute() should be disallowed''', '')
endfunction
]]
eq('Vim(call):E48: Not allowed in sandbox', exc_exec("sandbox call timer_start(0, 'Scary')"))
end)
it('can be triggered after an empty string <expr> mapping #17257', function()
local screen = Screen.new(40, 6)
command([=[imap <expr> <F2> [timer_start(0, { _ -> execute("throw 'x'", "") }), ''][-1]]=])
feed('i<F2>')
screen:expect({ any = 'E605: Exception not caught: x' })
end)
end)