tests: improve robustness of immediate successes in screen tests

This commit is contained in:
Björn Linse 2017-06-26 14:49:15 +02:00
parent 8fd092f3ff
commit c8810a51a3
20 changed files with 314 additions and 147 deletions

View File

@ -20,15 +20,15 @@ describe("update_menu notification", function()
end) end)
local function expect_sent(expected) local function expect_sent(expected)
screen:wait(function() screen:expect{condition=function()
if screen.update_menu ~= expected then if screen.update_menu ~= expected then
if expected then if expected then
return 'update_menu was expected but not sent' error('update_menu was expected but not sent')
else else
return 'update_menu was sent unexpectedly' error('update_menu was sent unexpectedly')
end end
end end
end) end, unchanged=(not expected)}
end end
it("should be sent when adding a menu", function() it("should be sent when adding a menu", function()

View File

@ -161,13 +161,13 @@ describe('execute()', function()
eq('42', eval('g:mes')) eq('42', eval('g:mes'))
command('let g:mes = execute("echon 13", "silent")') command('let g:mes = execute("echon 13", "silent")')
screen:expect([[ screen:expect{grid=[[
^ | ^ |
~ | ~ |
~ | ~ |
~ | ~ |
| |
]]) ]], unchanged=true}
eq('13', eval('g:mes')) eq('13', eval('g:mes'))
end) end)

View File

@ -150,13 +150,13 @@ describe('input()', function()
{T:Foo>}Bar^ | {T:Foo>}Bar^ |
]]) ]])
command('redraw!') command('redraw!')
screen:expect([[ screen:expect{grid=[[
| |
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{T:Foo>}Bar^ | {T:Foo>}Bar^ |
]]) ]], reset=true}
feed('<BS>') feed('<BS>')
screen:expect([[ screen:expect([[
| |
@ -166,13 +166,13 @@ describe('input()', function()
{T:Foo>}Ba^ | {T:Foo>}Ba^ |
]]) ]])
command('redraw!') command('redraw!')
screen:expect([[ screen:expect{grid=[[
| |
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{T:Foo>}Ba^ | {T:Foo>}Ba^ |
]]) ]], reset=true}
end) end)
it('allows omitting everything with dictionary argument', function() it('allows omitting everything with dictionary argument', function()
command('echohl Test') command('echohl Test')
@ -348,13 +348,13 @@ describe('inputdialog()', function()
{T:Foo>}Bar^ | {T:Foo>}Bar^ |
]]) ]])
command('redraw!') command('redraw!')
screen:expect([[ screen:expect{grid=[[
| |
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{T:Foo>}Bar^ | {T:Foo>}Bar^ |
]]) ]], reset=true}
feed('<BS>') feed('<BS>')
screen:expect([[ screen:expect([[
| |
@ -364,13 +364,13 @@ describe('inputdialog()', function()
{T:Foo>}Ba^ | {T:Foo>}Ba^ |
]]) ]])
command('redraw!') command('redraw!')
screen:expect([[ screen:expect{grid=[[
| |
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
{T:Foo>}Ba^ | {T:Foo>}Ba^ |
]]) ]], reset=true}
end) end)
it('allows omitting everything with dictionary argument', function() it('allows omitting everything with dictionary argument', function()
command('echohl Test') command('echohl Test')

View File

@ -113,7 +113,6 @@ describe('timers', function()
^ | ^ |
]]) ]])
screen:sleep(200)
screen:expect([[ screen:expect([[
ITEM 1 | ITEM 1 |
ITEM 2 | ITEM 2 |
@ -200,13 +199,14 @@ describe('timers', function()
screen:attach() screen:attach()
screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} ) screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} )
source([[ source([[
let g:val = 0
func! MyHandler(timer) func! MyHandler(timer)
echo "evil" echo "evil"
let g:val = 1
endfunc endfunc
]]) ]])
command("call timer_start(100, 'MyHandler', {'repeat': 1})") command("call timer_start(100, 'MyHandler', {'repeat': 1})")
feed(":good") feed(":good")
screen:sleep(200)
screen:expect([[ screen:expect([[
| |
{0:~ }| {0:~ }|
@ -215,6 +215,17 @@ describe('timers', function()
{0:~ }| {0:~ }|
:good^ | :good^ |
]]) ]])
screen:expect{grid=[[
|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
:good^ |
]], intermediate=true, timeout=200}
eq(1, eval('g:val'))
end) end)
end) end)

View File

@ -92,6 +92,7 @@ describe('search cmdline', function()
9 {inc:the}se | 9 {inc:the}se |
/the^ | /the^ |
]]) ]])
screen.bell = false
feed('<C-G>') feed('<C-G>')
if wrapscan == 'wrapscan' then if wrapscan == 'wrapscan' then
screen:expect([[ screen:expect([[
@ -100,11 +101,13 @@ describe('search cmdline', function()
/the^ | /the^ |
]]) ]])
else else
screen:expect([[ screen:expect{grid=[[
8 them | 8 them |
9 {inc:the}se | 9 {inc:the}se |
/the^ | /the^ |
]]) ]], condition=function()
eq(true, screen.bell)
end}
feed('<CR>') feed('<CR>')
eq({0, 0, 0, 0}, funcs.getpos('"')) eq({0, 0, 0, 0}, funcs.getpos('"'))
end end
@ -120,6 +123,7 @@ describe('search cmdline', function()
10 foobar | 10 foobar |
?the^ | ?the^ |
]]) ]])
screen.bell = false
if wrapscan == 'wrapscan' then if wrapscan == 'wrapscan' then
feed('<C-G>') feed('<C-G>')
screen:expect([[ screen:expect([[
@ -135,11 +139,13 @@ describe('search cmdline', function()
]]) ]])
else else
feed('<C-G>') feed('<C-G>')
screen:expect([[ screen:expect{grid=[[
9 {inc:the}se | 9 {inc:the}se |
10 foobar | 10 foobar |
?the^ | ?the^ |
]]) ]], condition=function()
eq(true, screen.bell)
end}
feed('<CR>') feed('<CR>')
screen:expect([[ screen:expect([[
9 ^these | 9 ^these |
@ -173,6 +179,7 @@ describe('search cmdline', function()
3 the | 3 the |
?the^ | ?the^ |
]]) ]])
screen.bell = false
feed('<C-T>') feed('<C-T>')
if wrapscan == 'wrapscan' then if wrapscan == 'wrapscan' then
screen:expect([[ screen:expect([[
@ -181,11 +188,13 @@ describe('search cmdline', function()
?the^ | ?the^ |
]]) ]])
else else
screen:expect([[ screen:expect{grid=[[
2 {inc:the}se | 2 {inc:the}se |
3 the | 3 the |
?the^ | ?the^ |
]]) ]], condition=function()
eq(true, screen.bell)
end}
end end
end end

View File

@ -874,20 +874,23 @@ describe('put command', function()
local function bell_test(actions, should_ring) local function bell_test(actions, should_ring)
local screen = Screen.new() local screen = Screen.new()
screen:attach() screen:attach()
if should_ring then
-- check bell is not set by nvim before the action
screen:sleep(50)
end
helpers.ok(not screen.bell and not screen.visualbell) helpers.ok(not screen.bell and not screen.visualbell)
actions() actions()
helpers.wait() screen:expect{condition=function()
screen:wait(function()
if should_ring then if should_ring then
if not screen.bell and not screen.visualbell then if not screen.bell and not screen.visualbell then
return 'Bell was not rung after action' error('Bell was not rung after action')
end end
else else
if screen.bell or screen.visualbell then if screen.bell or screen.visualbell then
return 'Bell was rung after action' error('Bell was rung after action')
end end
end end
end) end, unchanged=(not should_ring)}
screen:detach() screen:detach()
end end

View File

@ -36,6 +36,13 @@ describe("'fillchars'", function()
]]) ]])
end) end)
it('supports whitespace', function() it('supports whitespace', function()
screen:expect([[
^ |
~ |
~ |
~ |
|
]])
command('set fillchars=eob:\\ ') command('set fillchars=eob:\\ ')
screen:expect([[ screen:expect([[
^ | ^ |

View File

@ -426,9 +426,8 @@ describe("'scrollback' option", function()
curbufmeths.set_option('scrollback', 200) curbufmeths.set_option('scrollback', 200)
-- Wait for prompt. -- Wait for prompt.
screen:expect{any='$'} screen:expect{any='%$'}
wait()
if iswin() then if iswin() then
feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r') feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r')
else else

View File

@ -370,7 +370,7 @@ describe('tui FocusGained/FocusLost', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]])
feed_data('\027[O') feed_data('\027[O')
screen:expect([[ screen:expect{grid=[[
| |
{4:~ }| {4:~ }|
{4:~ }| {4:~ }|
@ -378,7 +378,7 @@ describe('tui FocusGained/FocusLost', function()
{5:[No Name] }| {5:[No Name] }|
:{1: } | :{1: } |
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]], unchanged=true}
end) end)
it('in cmdline-mode', function() it('in cmdline-mode', function()

View File

@ -267,7 +267,7 @@ describe('Command-line coloring', function()
:echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ |
]]) ]])
redraw_input() redraw_input()
screen:expect([[ screen:expect{grid=[[
| |
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
@ -276,7 +276,7 @@ describe('Command-line coloring', function()
{EOB:~ }| {EOB:~ }|
{EOB:~ }| {EOB:~ }|
:echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ |
]]) ]], reset=true}
end) end)
for _, func_part in ipairs({'', 'n', 'msg'}) do for _, func_part in ipairs({'', 'n', 'msg'}) do
it('disables :echo' .. func_part .. ' messages', function() it('disables :echo' .. func_part .. ' messages', function()
@ -855,17 +855,6 @@ describe('Ex commands coloring support', function()
{EOB:~ }| {EOB:~ }|
| |
]]) ]])
feed('<CR>')
screen:expect([[
^ |
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
|
]])
eq('Error detected while processing :\nE605: Exception not caught: 42', eq('Error detected while processing :\nE605: Exception not caught: 42',
meths.command_output('messages')) meths.command_output('messages'))
end) end)

View File

@ -253,9 +253,6 @@ local function test_cmdline(linegrid)
]], cmdline=expectation} ]], cmdline=expectation}
-- erase information, so we check if it is retransmitted -- erase information, so we check if it is retransmitted
-- TODO(bfredl): when we add a flag to screen:expect{}
-- to explicitly check redraw!, it should also do this
screen.cmdline = {}
command("redraw!") command("redraw!")
screen:expect{grid=[[ screen:expect{grid=[[
^ | ^ |
@ -263,7 +260,7 @@ local function test_cmdline(linegrid)
{1:~ }| {1:~ }|
{1:~ }| {1:~ }|
| |
]], cmdline=expectation} ]], cmdline=expectation, reset=true}
feed('<cr>') feed('<cr>')
@ -323,7 +320,6 @@ local function test_cmdline(linegrid)
{{' line1'}}, {{' line1'}},
}} }}
screen.cmdline_block = {}
command("redraw!") command("redraw!")
screen:expect{grid=[[ screen:expect{grid=[[
^ | ^ |
@ -339,7 +335,7 @@ local function test_cmdline(linegrid)
}}, cmdline_block = { }}, cmdline_block = {
{{'function Foo()'}}, {{'function Foo()'}},
{{' line1'}}, {{' line1'}},
}} }, reset=true}
feed('endfunction<cr>') feed('endfunction<cr>')
screen:expect{grid=[[ screen:expect{grid=[[
@ -415,7 +411,6 @@ local function test_cmdline(linegrid)
pos = 4, pos = 4,
}}} }}}
screen.cmdline = {}
command("redraw!") command("redraw!")
screen:expect{grid=[[ screen:expect{grid=[[
| |
@ -427,7 +422,7 @@ local function test_cmdline(linegrid)
firstc = ":", firstc = ":",
content = {{"yank"}}, content = {{"yank"}},
pos = 4, pos = 4,
}}} }}, reset=true}
feed("<c-c>") feed("<c-c>")
screen:expect{grid=[[ screen:expect{grid=[[

View File

@ -870,7 +870,7 @@ describe("'winhighlight' highlight", function()
eq('Vim(set):E474: Invalid argument: winhl=xxx:yyy', eq('Vim(set):E474: Invalid argument: winhl=xxx:yyy',
exc_exec("set winhl=xxx:yyy")) exc_exec("set winhl=xxx:yyy"))
eq('Normal:Background1', eval('&winhl')) eq('Normal:Background1', eval('&winhl'))
screen:expect([[ screen:expect{grid=[[
{1:^ }| {1:^ }|
{2:~ }| {2:~ }|
{2:~ }| {2:~ }|
@ -879,7 +879,7 @@ describe("'winhighlight' highlight", function()
{2:~ }| {2:~ }|
{2:~ }| {2:~ }|
| |
]]) ]], unchanged=true}
end) end)

View File

@ -495,6 +495,18 @@ describe(":substitute, 'inccommand' preserves undo", function()
for _, case in pairs(cases) do for _, case in pairs(cases) do
clear() clear()
common_setup(screen, case, default_text) common_setup(screen, case, default_text)
screen:expect([[
Inc substitution on |
two lines |
^ |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
|
]])
feed_command("set undolevels=1") feed_command("set undolevels=1")
feed("1G0") feed("1G0")
@ -757,8 +769,23 @@ describe(":substitute, inccommand=split", function()
-- non-modifier prefix -- non-modifier prefix
feed(':silent tabedit %s/tw/to') feed(':silent tabedit %s/tw/to')
screen:expect{any=[[two lines]]} screen:expect([[
feed('<Esc>') Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:silent tabedit %s/tw/to^ |
]])
end) end)
it('shows split window when typing the pattern', function() it('shows split window when typing the pattern', function()
@ -866,7 +893,6 @@ describe(":substitute, inccommand=split", function()
it('does not show split window for :s/', function() it('does not show split window for :s/', function()
feed("2gg") feed("2gg")
feed(":s/tw") feed(":s/tw")
screen:sleep(1)
screen:expect([[ screen:expect([[
Inc substitution on | Inc substitution on |
{12:tw}o lines | {12:tw}o lines |
@ -1234,8 +1260,18 @@ describe("inccommand=nosplit", function()
-- non-modifier prefix -- non-modifier prefix
feed(':silent tabedit %s/tw/to') feed(':silent tabedit %s/tw/to')
screen:expect{any=[[two lines]]} screen:expect([[
feed('<Esc>') two lines |
Inc substitution on |
two lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:silent tabedit %s/t|
w/to^ |
]])
end) end)
it("does not show window after toggling :set inccommand", function() it("does not show window after toggling :set inccommand", function()

View File

@ -168,13 +168,13 @@ describe('ui/mouse/input', function()
| |
]]) ]])
feed('<LeftMouse><11,0>') feed('<LeftMouse><11,0>')
screen:expect([[ screen:expect{grid=[[
{tab: + foo }{sel: + bar }{fill: }{tab:X}| {tab: + foo }{sel: + bar }{fill: }{tab:X}|
this is ba^r | this is ba^r |
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
| |
]]) ]], unchanged=true}
feed('<LeftDrag><6,0>') feed('<LeftDrag><6,0>')
screen:expect([[ screen:expect([[
{sel: + bar }{tab: + foo }{fill: }{tab:X}| {sel: + bar }{tab: + foo }{fill: }{tab:X}|
@ -236,13 +236,13 @@ describe('ui/mouse/input', function()
| |
]]) ]])
feed('<LeftDrag><4,1>') feed('<LeftDrag><4,1>')
screen:expect([[ screen:expect{grid=[[
{sel: + foo }{tab: + bar }{fill: }{tab:X}| {sel: + foo }{tab: + bar }{fill: }{tab:X}|
this is fo^o | this is fo^o |
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
| |
]]) ]], unchanged=true}
feed('<LeftDrag><14,1>') feed('<LeftDrag><14,1>')
screen:expect([[ screen:expect([[
{tab: + bar }{sel: + foo }{fill: }{tab:X}| {tab: + bar }{sel: + foo }{fill: }{tab:X}|
@ -254,13 +254,6 @@ describe('ui/mouse/input', function()
end) end)
it('out of tabline to the left moves tab left', function() it('out of tabline to the left moves tab left', function()
if helpers.skip_fragile(pending,
os.getenv("TRAVIS") and (helpers.os_name() == "osx"
or os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN")) -- #4874
then
return
end
feed_command('%delete') feed_command('%delete')
insert('this is foo') insert('this is foo')
feed_command('silent file foo | tabnew | file bar') feed_command('silent file foo | tabnew | file bar')
@ -273,21 +266,21 @@ describe('ui/mouse/input', function()
| |
]]) ]])
feed('<LeftMouse><11,0>') feed('<LeftMouse><11,0>')
screen:expect([[ screen:expect{grid=[[
{tab: + foo }{sel: + bar }{fill: }{tab:X}| {tab: + foo }{sel: + bar }{fill: }{tab:X}|
this is ba^r | this is ba^r |
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
| |
]]) ]], unchanged=true}
feed('<LeftDrag><11,1>') feed('<LeftDrag><11,1>')
screen:expect([[ screen:expect{grid=[[
{tab: + foo }{sel: + bar }{fill: }{tab:X}| {tab: + foo }{sel: + bar }{fill: }{tab:X}|
this is ba^r | this is ba^r |
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
| |
]]) ]], unchanged=true}
feed('<LeftDrag><6,1>') feed('<LeftDrag><6,1>')
screen:expect([[ screen:expect([[
{sel: + bar }{tab: + foo }{fill: }{tab:X}| {sel: + bar }{tab: + foo }{fill: }{tab:X}|
@ -319,13 +312,13 @@ describe('ui/mouse/input', function()
| |
]]) ]])
feed('<LeftDrag><4,1>') feed('<LeftDrag><4,1>')
screen:expect([[ screen:expect{grid=[[
{sel: + foo }{tab: + bar }{fill: }{tab:X}| {sel: + foo }{tab: + bar }{fill: }{tab:X}|
this is fo^o | this is fo^o |
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
| |
]]) ]], unchanged=true}
feed('<LeftDrag><7,1>') feed('<LeftDrag><7,1>')
screen:expect([[ screen:expect([[
{tab: + bar }{sel: + foo }{fill: }{tab:X}| {tab: + bar }{sel: + foo }{fill: }{tab:X}|

View File

@ -51,25 +51,20 @@ describe("shell command :!", function()
end) end)
it("throttles shell-command output greater than ~10KB", function() it("throttles shell-command output greater than ~10KB", function()
if os.getenv("TRAVIS") and helpers.os_name() == "osx" then
pending("[Unreliable on Travis macOS.]", function() end)
return
end
screen.timeout = 20000 -- Avoid false failure on slow systems.
child_session.feed_data( child_session.feed_data(
":!for i in $(seq 2 3000); do echo XXXXXXXXXX $i; done\n") ":!for i in $(seq 2 30000); do echo XXXXXXXXXX $i; done\n")
-- If we observe any line starting with a dot, then throttling occurred. -- If we observe any line starting with a dot, then throttling occurred.
screen:expect{any="\n."} -- Avoid false failure on slow systems.
screen:expect{any="\n%.", timeout=20000}
-- Final chunk of output should always be displayed, never skipped. -- Final chunk of output should always be displayed, never skipped.
-- (Throttling is non-deterministic, this test is merely a sanity check.) -- (Throttling is non-deterministic, this test is merely a sanity check.)
screen:expect([[ screen:expect([[
XXXXXXXXXX 2997 | XXXXXXXXXX 29997 |
XXXXXXXXXX 2998 | XXXXXXXXXX 29998 |
XXXXXXXXXX 2999 | XXXXXXXXXX 29999 |
XXXXXXXXXX 3000 | XXXXXXXXXX 30000 |
| |
{10:Press ENTER or type command to continue}{1: } | {10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} | {3:-- TERMINAL --} |

View File

@ -89,15 +89,17 @@ Screen.__index = Screen
local debug_screen local debug_screen
local default_screen_timeout = 3500 local default_timeout_factor = 1
if os.getenv('VALGRIND') then if os.getenv('VALGRIND') then
default_screen_timeout = default_screen_timeout * 3 default_timeout_factor = default_timeout_factor * 3
end end
if os.getenv('CI') then if os.getenv('CI') then
default_screen_timeout = default_screen_timeout * 3 default_timeout_factor = default_timeout_factor * 3
end end
local default_screen_timeout = default_timeout_factor * 3500
do do
local spawn, nvim_prog = helpers.spawn, helpers.nvim_prog local spawn, nvim_prog = helpers.spawn, helpers.nvim_prog
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--embed'}) local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--embed'})
@ -160,12 +162,13 @@ function Screen.new(width, height)
_attr_table = {[0]={{},{}}}, _attr_table = {[0]={{},{}}},
_clear_attrs = {}, _clear_attrs = {},
_new_attrs = false, _new_attrs = false,
_width = width,
_height = height,
_cursor = { _cursor = {
row = 1, col = 1 row = 1, col = 1
}, },
_busy = false _busy = false
}, Screen) }, Screen)
self:_handle_resize(width, height)
return self return self
end end
@ -190,6 +193,7 @@ function Screen:attach(options)
end end
self._options = options self._options = options
self._clear_attrs = (options.ext_linegrid and {{},{}}) or {} self._clear_attrs = (options.ext_linegrid and {{},{}}) or {}
self:_handle_resize(self._width, self._height)
uimeths.attach(self._width, self._height, options) uimeths.attach(self._width, self._height, options)
if self._options.rgb == nil then if self._options.rgb == nil then
-- nvim defaults to rgb=true internally, -- nvim defaults to rgb=true internally,
@ -243,8 +247,27 @@ local ext_keys = {
-- nothing is ignored. -- nothing is ignored.
-- condition: Function asserting some arbitrary condition. Return value is -- condition: Function asserting some arbitrary condition. Return value is
-- ignored, throw an error (use eq() or similar) to signal failure. -- ignored, throw an error (use eq() or similar) to signal failure.
-- any: Lua pattern string expected to match a screen line. -- any: Lua pattern string expected to match a screen line. NB: the
-- following chars are magic characters
-- ( ) . % + - * ? [ ^ $
-- and must be escaped with a preceding % for a literal match.
-- mode: Expected mode as signaled by "mode_change" event -- mode: Expected mode as signaled by "mode_change" event
-- unchanged: Test that the screen state is unchanged since the previous
-- expect(...). Any flush event resulting in a different state is
-- considered an error. Not observing any events until timeout
-- is acceptable.
-- intermediate:Test that the final state is the same as the previous expect,
-- but expect an intermediate state that is different. If possible
-- it is better to use an explicit screen:expect(...) for this
-- intermediate state.
-- reset: Reset the state internal to the test Screen before starting to
-- receive updates. This should be used after command("redraw!")
-- or some other mechanism that will invoke "redraw!", to check
-- that all screen state is transmitted again. This includes
-- state related to ext_ features as mentioned below.
-- timeout: maximum time that will be waited until the expected state is
-- seen (or maximum time to observe an incorrect change when
-- `unchanged` flag is used)
-- --
-- The following keys should be used to expect the state of various ext_ -- The following keys should be used to expect the state of various ext_
-- features. Note that an absent key will assert that the item is currently -- features. Note that an absent key will assert that the item is currently
@ -262,7 +285,8 @@ function Screen:expect(expected, attr_ids, attr_ignore)
if type(expected) == "table" then if type(expected) == "table" then
assert(not (attr_ids ~= nil or attr_ignore ~= nil)) assert(not (attr_ids ~= nil or attr_ignore ~= nil))
local is_key = {grid=true, attr_ids=true, attr_ignore=true, condition=true, local is_key = {grid=true, attr_ids=true, attr_ignore=true, condition=true,
any=true, mode=true} any=true, mode=true, unchanged=true, intermediate=true,
reset=true, timeout=true}
for _, v in ipairs(ext_keys) do for _, v in ipairs(ext_keys) do
is_key[v] = true is_key[v] = true
end end
@ -304,7 +328,7 @@ function Screen:expect(expected, attr_ids, attr_ignore)
attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids or {}) attr_state.id_to_index = self:hlstate_check_attrs(attr_state.ids or {})
end end
self._new_attrs = false self._new_attrs = false
self:wait(function() self:_wait(function()
if condition ~= nil then if condition ~= nil then
local status, res = pcall(condition) local status, res = pcall(condition)
if not status then if not status then
@ -361,7 +385,7 @@ screen:redraw_debug() to show all intermediate screen states. ]])
-- Extension features. The default expectations should cover the case of -- Extension features. The default expectations should cover the case of
-- the ext_ feature being disabled, or the feature currently not activated -- the ext_ feature being disabled, or the feature currently not activated
-- (for instance no external cmdline visible). Some extensions require -- (for instance no external cmdline visible). Some extensions require
-- preprocessing to prepresent highlights in a reproducible way. -- preprocessing to represent highlights in a reproducible way.
local extstate = self:_extstate_repr(attr_state) local extstate = self:_extstate_repr(attr_state)
-- convert assertion errors into invalid screen state descriptions -- convert assertion errors into invalid screen state descriptions
@ -379,14 +403,48 @@ screen:redraw_debug() to show all intermediate screen states. ]])
if not status then if not status then
return tostring(res) return tostring(res)
end end
end) end, expected)
end end
function Screen:wait(check, timeout) function Screen:_wait(check, flags)
local err, checked = false local err, checked = false, false
local success_seen = false local success_seen = false
local failure_after_success = false local failure_after_success = false
local did_flush = true local did_flush = true
local warn_immediate = not (flags.unchanged or flags.intermediate)
if flags.intermediate and flags.unchanged then
error("Choose only one of 'intermediate' and 'unchanged', not both")
end
if flags.reset then
-- throw away all state, we expect it to be retransmitted
self:_reset()
end
-- Maximum timeout, after which a incorrect state will be regarded as a
-- failure
local timeout = flags.timeout or self.timeout
-- Minimal timeout before the loop is allowed to be stopped so we
-- always do some check for failure after success.
local minimal_timeout = default_timeout_factor * 2
local immediate_seen, intermediate_seen = false, false
if not check() then
minimal_timeout = default_timeout_factor * 20
immediate_seen = true
end
-- for an unchanged test, flags.timeout means the time during the state is
-- expected to be unchanged, so always wait this full time.
if (flags.unchanged or flags.intermediate) and flags.timeout ~= nil then
minimal_timeout = timeout
end
assert(timeout >= minimal_timeout)
local did_miminal_timeout = false
local function notification_cb(method, args) local function notification_cb(method, args)
assert(method == 'redraw') assert(method == 'redraw')
did_flush = self:_redraw(args) did_flush = self:_redraw(args)
@ -395,9 +453,15 @@ function Screen:wait(check, timeout)
end end
err = check() err = check()
checked = true checked = true
if err and immediate_seen then
intermediate_seen = true
end
if not err then if not err then
success_seen = true success_seen = true
if did_miminal_timeout then
helpers.stop() helpers.stop()
end
elseif success_seen and #args > 0 then elseif success_seen and #args > 0 then
failure_after_success = true failure_after_success = true
--print(require('inspect')(args)) --print(require('inspect')(args))
@ -405,35 +469,83 @@ function Screen:wait(check, timeout)
return true return true
end end
run(nil, notification_cb, nil, timeout or self.timeout) run(nil, notification_cb, nil, minimal_timeout)
if not did_flush then if not did_flush then
err = "no flush received" err = "no flush received"
elseif not checked then elseif not checked then
err = check() err = check()
if not err and flags.unchanged then
-- expecting NO screen change: use a shorter timout
success_seen = true
end
end
if not success_seen then
did_miminal_timeout = true
run(nil, notification_cb, nil, timeout-minimal_timeout)
end
local did_warn = false
if warn_immediate and immediate_seen then
print([[
Warning: A screen test has immediate success. Try to avoid this unless the
purpose of the test really requires it.]])
if intermediate_seen then
print([[
There are intermediate states between the two identical expects.
Use screen:snapshot_util() or screen:redraw_debug() to find them, and add them
to the test if they make sense.
]])
else
print([[If necessary, silence this warning by
supplying the 'unchanged' argument to screen:expect.]])
end
did_warn = true
end end
if failure_after_success then if failure_after_success then
print([[ print([[
Warning: Screen changes were received after the expected state. This indicates Warning: Screen changes were received after the expected state. This indicates
indeterminism in the test. Try adding wait() (or screen:expect(...)) between indeterminism in the test. Try adding screen:expect(...) (or wait()) between
asynchronous (feed(), nvim_input()) and synchronous API calls. asynchronous (feed(), nvim_input()) and synchronous API calls.
- Use Screen:redraw_debug() to investigate the problem. - Use Screen:redraw_debug() to investigate the problem. It might find
relevant intermediate states that should be added to the test to make it
more robust.
- If the point of the test is to assert the state after some user input
sent with feed(...), also adding an screen:expect(...) before the feed(...)
will help ensure the input is sent to nvim when nvim is in a predictable
state. This is preferable to using wait(), as it is more closely emulates
real user interaction.
- wait() can trigger redraws and consequently generate more indeterminism. - wait() can trigger redraws and consequently generate more indeterminism.
In that case try removing every wait(). In that case try removing every wait().
]]) ]])
did_warn = true
end
if err then
assert(false, err)
elseif did_warn then
local tb = debug.traceback() local tb = debug.traceback()
local index = string.find(tb, '\n%s*%[C]') local index = string.find(tb, '\n%s*%[C]')
print(string.sub(tb,1,index)) print(string.sub(tb,1,index))
end end
if err then if flags.intermediate then
assert(false, err) assert(intermediate_seen, "expected intermediate screen state before final screen state")
elseif flags.unchanged then
assert(not intermediate_seen, "expected screen state to be unchanged")
end end
end end
function Screen:sleep(ms) function Screen:sleep(ms)
pcall(function() self:wait(function() return "error" end, ms) end) local function notification_cb(method, args)
assert(method == 'redraw')
self:_redraw(args)
end
run(nil, notification_cb, nil, ms)
end end
function Screen:_redraw(updates) function Screen:_redraw(updates)
@ -486,12 +598,23 @@ end
function Screen:_handle_flush() function Screen:_handle_flush()
end end
function Screen:_handle_grid_resize(grid, width, height) function Screen:_handle_grid_resize(grid, width, height)
assert(grid == 1) assert(grid == 1)
self:_handle_resize(width, height) self:_handle_resize(width, height)
end end
function Screen:_reset()
-- TODO: generalize to multigrid later
self:_handle_grid_clear(1)
-- TODO: share with initialization, so it generalizes?
self.popupmenu = nil
self.cmdline = {}
self.cmdline_block = {}
self.wildmenu_items = nil
self.wildmenu_pos = nil
end
function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
self._cursor_style_enabled = cursor_style_enabled self._cursor_style_enabled = cursor_style_enabled

View File

@ -394,7 +394,7 @@ local function screen_tests(linegrid)
end) end)
it('redraws properly with :tab split right after scroll', function() it('redraws properly with :tab split right after scroll', function()
feed('30Ofoo<esc>gg') feed('15Ofoo<esc>15Obar<esc>gg')
command('vsplit') command('vsplit')
screen:expect([[ screen:expect([[
@ -420,18 +420,17 @@ local function screen_tests(linegrid)
foo {3:}foo | foo {3:}foo |
foo {3:}foo | foo {3:}foo |
foo {3:}foo | foo {3:}foo |
foo {3:}foo | bar {3:}foo |
foo {3:}foo | bar {3:}foo |
foo {3:}foo | bar {3:}foo |
foo {3:}foo | bar {3:}foo |
foo {3:}foo | bar {3:}foo |
foo {3:}foo | bar {3:}foo |
foo {3:}foo | bar {3:}foo |
foo {3:}foo | bar {3:}foo |
{1:[No Name] [+] }{3:[No Name] [+] }| {1:[No Name] [+] }{3:[No Name] [+] }|
| |
]]) ]])
command('tab split') command('tab split')
screen:expect([[ screen:expect([[
{4: }{5:2}{4:+ [No Name] }{2: + [No Name] }{3: }{4:X}| {4: }{5:2}{4:+ [No Name] }{2: + [No Name] }{3: }{4:X}|
@ -439,14 +438,14 @@ local function screen_tests(linegrid)
foo | foo |
foo | foo |
foo | foo |
foo | bar |
foo | bar |
foo | bar |
foo | bar |
foo | bar |
foo | bar |
foo | bar |
foo | bar |
| |
]]) ]])
end) end)

View File

@ -324,7 +324,17 @@ describe('search highlighting', function()
]]) ]])
-- same, for C-t -- same, for C-t
feed('<ESC>/<C-t>') feed('<ESC>')
screen:expect([[
the first line |
in a ^little file |
|
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
feed('/<C-t>')
screen:expect([[ screen:expect([[
the first line | the first line |
in a little file | in a little file |

View File

@ -1,3 +1,5 @@
local global_helpers = require('test.helpers')
local shallowcopy = global_helpers.shallowcopy
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen') local Screen = require('test.functional.ui.screen')
local clear, feed, command = helpers.clear, helpers.feed, helpers.command local clear, feed, command = helpers.clear, helpers.feed, helpers.command
@ -18,6 +20,14 @@ describe("'wildmenu'", function()
screen:detach() screen:detach()
end) end)
-- expect the screen stayed unchanged some time after first seen success
local function expect_stay_unchanged(args)
screen:expect(args)
args = shallowcopy(args)
args.unchanged = true
screen:expect(args)
end
it(':sign <tab> shows wildmenu completions', function() it(':sign <tab> shows wildmenu completions', function()
command('set wildmode=full') command('set wildmode=full')
command('set wildmenu') command('set wildmenu')
@ -76,10 +86,6 @@ describe("'wildmenu'", function()
end) end)
it('is preserved during :terminal activity', function() it('is preserved during :terminal activity', function()
-- Because this test verifies a _lack_ of activity after screen:sleep(), we
-- must wait the full timeout. So make it reasonable.
screen.timeout = 1000
command('set wildmenu wildmode=full') command('set wildmenu wildmode=full')
command('set scrollback=4') command('set scrollback=4')
if iswin() then if iswin() then
@ -90,26 +96,24 @@ describe("'wildmenu'", function()
feed([[<C-\><C-N>gg]]) feed([[<C-\><C-N>gg]])
feed([[:sign <Tab>]]) -- Invoke wildmenu. feed([[:sign <Tab>]]) -- Invoke wildmenu.
screen:sleep(50) -- Allow some terminal output. expect_stay_unchanged{grid=[[
screen:expect([[
foo | foo |
foo | foo |
foo | foo |
define jump list > | define jump list > |
:sign define^ | :sign define^ |
]]) ]]}
-- cmdline CTRL-D display should also be preserved. -- cmdline CTRL-D display should also be preserved.
feed([[<C-\><C-N>]]) feed([[<C-\><C-N>]])
feed([[:sign <C-D>]]) -- Invoke cmdline CTRL-D. feed([[:sign <C-D>]]) -- Invoke cmdline CTRL-D.
screen:sleep(50) -- Allow some terminal output. expect_stay_unchanged{grid=[[
screen:expect([[
:sign | :sign |
define place | define place |
jump undefine | jump undefine |
list unplace | list unplace |
:sign ^ | :sign ^ |
]]) ]]}
-- Exiting cmdline should show the buffer. -- Exiting cmdline should show the buffer.
feed([[<C-\><C-N>]]) feed([[<C-\><C-N>]])
@ -123,22 +127,17 @@ describe("'wildmenu'", function()
end) end)
it('ignores :redrawstatus called from a timer #7108', function() it('ignores :redrawstatus called from a timer #7108', function()
-- Because this test verifies a _lack_ of activity after screen:sleep(), we
-- must wait the full timeout. So make it reasonable.
screen.timeout = 1000
command('set wildmenu wildmode=full') command('set wildmenu wildmode=full')
command([[call timer_start(10, {->execute('redrawstatus')}, {'repeat':-1})]]) command([[call timer_start(10, {->execute('redrawstatus')}, {'repeat':-1})]])
feed([[<C-\><C-N>]]) feed([[<C-\><C-N>]])
feed([[:sign <Tab>]]) -- Invoke wildmenu. feed([[:sign <Tab>]]) -- Invoke wildmenu.
screen:sleep(30) -- Allow some timer activity. expect_stay_unchanged{grid=[[
screen:expect([[
| |
~ | ~ |
~ | ~ |
define jump list > | define jump list > |
:sign define^ | :sign define^ |
]]) ]]}
end) end)
it('with laststatus=0, :vsplit, :term #2255', function() it('with laststatus=0, :vsplit, :term #2255', function()
@ -164,10 +163,9 @@ describe("'wildmenu'", function()
feed([[<C-\><C-N>]]) feed([[<C-\><C-N>]])
feed([[:<Tab>]]) -- Invoke wildmenu. feed([[:<Tab>]]) -- Invoke wildmenu.
screen:sleep(10) -- Flush
-- Check only the last 2 lines, because the shell output is -- Check only the last 2 lines, because the shell output is
-- system-dependent. -- system-dependent.
screen:expect{any='! # & < = > @ > \n:!^'} expect_stay_unchanged{any='! # & < = > @ > \n:!^'}
end) end)
end) end)

View File

@ -757,7 +757,7 @@ describe('completion', function()
eval('1 + 1') eval('1 + 1')
-- popupmenu still visible -- popupmenu still visible
screen:expect([[ screen:expect{grid=[[
foobar fooegg | foobar fooegg |
fooegg^ | fooegg^ |
{1:foobar }{0: }| {1:foobar }{0: }|
@ -766,7 +766,7 @@ describe('completion', function()
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
{3:-- Keyword completion (^N^P) }{4:match 1 of 2} | {3:-- Keyword completion (^N^P) }{4:match 1 of 2} |
]]) ]], unchanged=true}
feed('<c-p>') feed('<c-p>')
-- Didn't restart completion: old matches still used -- Didn't restart completion: old matches still used