endif
endfunc
-" Trace ruler liveness on demand.
if !empty($VIM_SYNTAX_TEST_LOG) && filewritable($VIM_SYNTAX_TEST_LOG)
- def s:TraceRulerLiveness(context: string, times: number, tail: string)
+ " Trace liveness.
+ def s:TraceLiveness(context: string, times: number, tail: string)
writefile([printf('%s: %4d: %s', context, times, tail)],
$VIM_SYNTAX_TEST_LOG,
'a')
enddef
+
+ " Anticipate rendering idiosyncrasies (see #16559).
+ def s:CanFindRenderedFFFDChars(
+ buf: number,
+ in_name_and_out_name: string,
+ times: number): bool
+ if CannotUseRealEstate(in_name_and_out_name)
+ return false
+ endif
+ # Expect a 20-line terminal buffer (see "term_util#RunVimInTerminal()"),
+ # where the bottom, reserved line is of the default "&cmdheight".
+ var lines: list<number> = []
+ for lnum: number in range(1, 19)
+ if stridx(term_getline(buf, lnum), "\xef\xbf\xbd") >= 0
+ add(lines, lnum)
+ endif
+ endfor
+ TraceLiveness('F', times, string(lines))
+ return !empty(lines)
+ enddef
else
- def s:TraceRulerLiveness(_: string, _: number, _: string)
+ " Do not trace liveness.
+ def s:TraceLiveness(_: string, _: number, _: string)
+ enddef
+
+ " Anticipate rendering idiosyncrasies (see #16559).
+ def s:CanFindRenderedFFFDChars(
+ buf: number,
+ in_name_and_out_name: string,
+ _: number): bool
+ if CannotUseRealEstate(in_name_and_out_name)
+ return false
+ endif
+ # Expect a 20-line terminal buffer (see "term_util#RunVimInTerminal()"),
+ # where the bottom, reserved line is of the default "&cmdheight".
+ for lnum: number in range(1, 19)
+ if stridx(term_getline(buf, lnum), "\xef\xbf\xbd") >= 0
+ return true
+ endif
+ endfor
+ return false
enddef
endif
get(term_getcursor(buf), 0) != 20)
enddef
+def s:CannotUseRealEstate(in_name_and_out_name: string): bool
+ # Expect defaults from "term_util#RunVimInTerminal()".
+ if winwidth(1) != 75 || winheight(1) != 20
+ ch_log(printf('Aborting for %s: (75 x 20) != (%d x %d)',
+ in_name_and_out_name,
+ winwidth(1),
+ winheight(1)))
+ return true
+ endif
+ return false
+enddef
+
+" Throw an "FFFD" string if U+FFFD characters are found in the terminal buffer
+" during a non-last test round; otherwise, generate a screendump and proceed
+" with its verification.
+def s:VerifyScreenDumpOrThrowFFFD(
+ buf: number,
+ which_page: string,
+ in_name_and_out_name: string,
+ aborted_count: number,
+ max_aborted_count: number,
+ basename: string,
+ opts: dict<any>,
+ page_quota: dict<number>,
+ seen_pages: list<number>,
+ page_nr: number): number
+ if !has_key(page_quota, page_nr)
+ # Constrain management of unseen pages to the product of "wait" times
+ # "max_aborted_count" (see "opts" below). When _test repetition_ and
+ # _line rewriting_ FAIL page verification, the page gets to keep its
+ # unseen mark; when _test repetition_ is FAILING for a later page, all
+ # earlier unseen pages get another chance at _test repetition_ etc. before
+ # further progress can be made for the later page.
+ page_quota[page_nr] = max_aborted_count
+ endif
+ const with_fffd: bool = CanFindRenderedFFFDChars(
+ buf,
+ in_name_and_out_name,
+ (max_aborted_count - aborted_count + 1))
+ if with_fffd && aborted_count > 1
+ throw 'FFFD'
+ endif
+ ch_log(which_page .. ' screendump for ' .. in_name_and_out_name)
+ # Generate a screendump of every 19 lines of "buf", reusing the bottom line
+ # (or the bottom six or so lines for "*_01.dump") from the previous dump as
+ # the top line(s) in the next dump for continuity. Constrain generation of
+ # unseen pages for the last test round (via "wait").
+ const status: number = g:VerifyScreenDump(
+ buf,
+ basename,
+ (aborted_count != max_aborted_count)
+ ? extend({wait: max_aborted_count}, opts, 'keep')
+ : opts)
+ if !with_fffd || (!status || !page_quota[page_nr])
+ add(seen_pages, page_nr)
+ else
+ TraceLiveness('Q', (max_aborted_count - aborted_count + 1), string(page_quota))
+ endif
+ page_quota[page_nr] -= 1
+ return status
+enddef
+
" Poll for updates of the cursor position in the terminal buffer occupying the
" first window. (ALWAYS call the function or its equivalent before calling
" "VerifyScreenDump()" *and* after calling any number of "term_sendkeys()".)
CannotDumpPage: func, # (TYPE FOR LEGACY CONTEXT CALL SITES.)
buf: number,
in_name_and_out_name: string): list<string>
- # Expect defaults from "term_util#RunVimInTerminal()".
- if winwidth(1) != 75 || winheight(1) != 20
- ch_log(printf('Aborting for %s: (75 x 20) != (%d x %d)',
- in_name_and_out_name,
- winwidth(1),
- winheight(1)))
+ if CannotUseRealEstate(in_name_and_out_name)
return ['0,0-1', 'All']
endif
# A two-fold role for redrawing:
redraw
endif
endwhile
- TraceRulerLiveness('P', (2048 - times), in_name_and_out_name)
+ TraceLiveness('P', (2048 - times), in_name_and_out_name)
return ruler
enddef
" the terminal buffer. (Call the function before calling "VerifyScreenDump()"
" for the first time.)
def s:TermWaitAndPollRuler(buf: number, in_name_and_out_name: string): list<string>
- # Expect defaults from "term_util#RunVimInTerminal()".
- if winwidth(1) != 75 || winheight(1) != 20
- ch_log(printf('Aborting for %s: (75 x 20) != (%d x %d)',
- in_name_and_out_name,
- winwidth(1),
- winheight(1)))
+ if CannotUseRealEstate(in_name_and_out_name)
return ['0,0-1', 'All']
endif
# The contents of "ruler".
redraw
endif
endwhile
- TraceRulerLiveness('W', (32768 - times), in_name_and_out_name)
+ TraceLiveness('W', (32768 - times), in_name_and_out_name)
if strpart(ruler, 0, 8) !=# 'is_nonce'
# Retain any of "b:is_(bash|dash|kornshell|posix|sh)" entries and let
# "CannotDumpShellFirstPage()" win the cursor race.
redraw!
endfunc
+ def CollectFFFDChars()
+ const fffd: string = "\xef\xbf\xbd"
+ const flags: string = 'eW'
+ const pos: list<number> = getpos('.')
+ var fffds: list<list<number>> = []
+ try
+ cursor(1, 1)
+ var prev: list<number> = [0, 0]
+ var next: list<number> = [0, 0]
+ next = searchpos(fffd, 'c' .. flags)
+ while next[0] > 0 && prev != next
+ add(fffds, next)
+ prev = next
+ next = searchpos(fffd, flags)
+ endwhile
+ finally
+ setpos('.', pos)
+ endtry
+ if !empty(fffds)
+ # Use "actions/upload-artifact@v4" of ci.yml for delivery.
+ writefile(
+ [printf('%s: %s', bufname('%'), string(fffds))],
+ 'failed/10-FFFDS',
+ 'a')
+ endif
+ redraw!
+ enddef
+
def s:AssertCursorForwardProgress(): bool
const curnum: number = line('.')
if curnum <= cursor
return AssertCursorForwardProgress()
enddef
END
+ let MAX_ABORTED_COUNT = 5
let MAX_FAILED_COUNT = 5
- let DUMP_OPTS = exists("$VIM_SYNTAX_TEST_WAIT_TIME") &&
+ let DUMP_OPTS = extend(
+ \ exists("$VIM_SYNTAX_TEST_WAIT_TIME") &&
\ !empty($VIM_SYNTAX_TEST_WAIT_TIME)
- \ ? {'wait': max([1, str2nr($VIM_SYNTAX_TEST_WAIT_TIME)])}
- \ : {}
- lockvar DUMP_OPTS MAX_FAILED_COUNT XTESTSCRIPT
+ \ ? {'wait': max([1, str2nr($VIM_SYNTAX_TEST_WAIT_TIME)])}
+ \ : {},
+ \ {'FileComparisonPreAction':
+ \ function('g:ScreenDumpDiscardFFFDChars'),
+ \ 'NonEqualLineComparisonPostAction':
+ \ function('g:ScreenDumpLookForFFFDChars')})
+ lockvar DUMP_OPTS MAX_FAILED_COUNT MAX_ABORTED_COUNT XTESTSCRIPT
let ok_count = 0
let disused_pages = []
let failed_tests = []
let args = !empty(path)
\ ? prefix .. ' -S ' .. path
\ : prefix
- let buf = RunVimInTerminal(args, {})
- " edit the file only after catching the SwapExists event
- call term_sendkeys(buf, ":edit " .. fname .. "\<CR>")
- " set up the testing environment
- call term_sendkeys(buf, ":call SetUpVim()\<CR>")
- " load filetype specific settings
- call term_sendkeys(buf, ":call LoadFiletype('" .. filetype .. "')\<CR>")
-
- " Make a synchronisation point between buffers by requesting to echo
- " a known token in the terminal buffer and asserting its availability
- " with "s:TermWaitAndPollRuler()".
- if filetype == 'sh'
- call term_sendkeys(buf, ":call ShellInfo()\<CR>")
- else
- call term_sendkeys(buf, ":echo 'is_nonce'\<CR>")
- endif
+ let fail = 0
- let root_00 = root .. '_00'
- let in_name_and_out_name = fname .. ': failed/' .. root_00 .. '.dump'
- " Queue up all "term_sendkeys()"es and let them finish before returning
- " from "s:TermWaitAndPollRuler()".
- let ruler = s:TermWaitAndPollRuler(buf, in_name_and_out_name)
- call ch_log('First screendump for ' .. in_name_and_out_name)
- " Make a screendump at the start of the file: failed/root_00.dump
- let fail = VerifyScreenDump(buf, root_00, DUMP_OPTS)
- let nr = 0
-
- " Accommodate the next code block to "buf"'s contingency for self
- " wipe-out.
try
- let keys_a = ":call ScrollToSecondPage((18 * 75 + 1), 19, 5) | redraw!\<CR>"
- let keys_b = ":call ScrollToNextPage((18 * 75 + 1), 19, 5) | redraw!\<CR>"
- while s:CannotSeeLastLine(ruler)
- call term_sendkeys(buf, keys_a)
- let keys_a = keys_b
- let nr += 1
- let root_next = printf('%s_%02d', root, nr)
- let in_name_and_out_name = fname .. ': failed/' .. root_next .. '.dump'
- let ruler = s:TermPollRuler(
- \ function('s:CannotDumpNextPage', [buf, ruler]),
- \ buf,
- \ in_name_and_out_name)
- call ch_log('Next screendump for ' .. in_name_and_out_name)
- " Make a screendump of every 18 lines of the file: failed/root_NN.dump
- let fail += VerifyScreenDump(buf, root_next, DUMP_OPTS)
+ let aborted_count = MAX_ABORTED_COUNT
+ let collected_count = 0
+ let seen_pages = []
+ let page_quota = {}
+
+ " See #16559. For each processed page, repeat pre-verification steps
+ " from scratch (subject to page cacheing) whenever U+FFFD characters
+ " are found in the terminal buffer with "term_getline()", i.e. treat
+ " these pages as if they were distinct test files. U+FFFD characters
+ " found at the last attempt (see "MAX_ABORTED_COUNT") will be ignored
+ " and "VerifyScreenDump()" will take over with own filtering.
+ while aborted_count > 0
+ let buf = RunVimInTerminal(args, {})
+ try
+ " edit the file only after catching the SwapExists event
+ call term_sendkeys(buf, ":edit " .. fname .. "\<CR>")
+ " set up the testing environment
+ call term_sendkeys(buf, ":call SetUpVim()\<CR>")
+ " load filetype specific settings
+ call term_sendkeys(buf, ":call LoadFiletype('" .. filetype .. "')\<CR>")
+
+ " Collect all *non-spurious* U+FFFD characters for scrutiny.
+ if aborted_count == 1 && collected_count != 1
+ let collected_count = 1
+ call term_sendkeys(buf, ":call CollectFFFDChars()\<CR>")
+ endif
+
+ " Make a synchronisation point between a terminal buffer and
+ " another buffer by requesting to echo a known token in the former
+ " and asserting its availability with "s:TermWaitAndPollRuler()"
+ " from the latter.
+ if filetype == 'sh'
+ call term_sendkeys(buf, ":call ShellInfo()\<CR>")
+ else
+ call term_sendkeys(buf, ":echo 'is_nonce'\<CR>")
+ endif
+
+ let page_nr = 0
+ let root_00 = root .. '_00'
+ let in_name_and_out_name = fname .. ': failed/' .. root_00 .. '.dump'
+ " Queue up all "term_sendkeys()"es and let them finish before
+ " returning from "s:TermWaitAndPollRuler()".
+ let ruler = s:TermWaitAndPollRuler(buf, in_name_and_out_name)
+ if index(seen_pages, page_nr) < 0
+ let fail += s:VerifyScreenDumpOrThrowFFFD(
+ \ buf,
+ \ 'First',
+ \ in_name_and_out_name,
+ \ aborted_count,
+ \ MAX_ABORTED_COUNT,
+ \ root_00,
+ \ DUMP_OPTS,
+ \ page_quota,
+ \ seen_pages,
+ \ page_nr)
+ " Reset "aborted_count" for another page.
+ let aborted_count = MAX_ABORTED_COUNT
+ endif
+ let keys_a = ":call ScrollToSecondPage((18 * 75 + 1), 19, 5) | redraw!\<CR>"
+ let keys_b = ":call ScrollToNextPage((18 * 75 + 1), 19, 5) | redraw!\<CR>"
+
+ while s:CannotSeeLastLine(ruler)
+ call term_sendkeys(buf, keys_a)
+ let keys_a = keys_b
+ let page_nr += 1
+ let root_next = printf('%s_%02d', root, page_nr)
+ let in_name_and_out_name = fname .. ': failed/' .. root_next .. '.dump'
+ let ruler = s:TermPollRuler(
+ \ function('s:CannotDumpNextPage', [buf, ruler]),
+ \ buf,
+ \ in_name_and_out_name)
+ if index(seen_pages, page_nr) < 0
+ let fail += s:VerifyScreenDumpOrThrowFFFD(
+ \ buf,
+ \ 'Next',
+ \ in_name_and_out_name,
+ \ aborted_count,
+ \ MAX_ABORTED_COUNT,
+ \ root_next,
+ \ DUMP_OPTS,
+ \ page_quota,
+ \ seen_pages,
+ \ page_nr)
+ " Reset "aborted_count" for another page.
+ let aborted_count = MAX_ABORTED_COUNT
+ endif
+ endwhile
+ call StopVimInTerminal(buf)
+ break
+ catch /^FFFD$/
+ " Clear out.
+ call StopVimInTerminal(buf)
+ while winnr('$') > 1
+ close
+ endwhile
+ let aborted_count -= 1
+ endtry
endwhile
- call StopVimInTerminal(buf)
finally
call delete('Xtestscript')
endtry
- let nr += 1
- let pagename = printf('dumps/%s_%02d.dump', root, nr)
+ let page_nr += 1
+ let pagename = printf('dumps/%s_%02d.dump', root, page_nr)
while filereadable(pagename)
call add(disused_pages, pagename)
- let nr += 1
- let pagename = printf('dumps/%s_%02d.dump', root, nr)
+ let page_nr += 1
+ let pagename = printf('dumps/%s_%02d.dump', root, page_nr)
endwhile
" redraw here to avoid the following messages to get mixed up with screen