Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display source with exceptions #107

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
78f21cf
Display source with exceptions / assertion errors
blueyed Jan 6, 2017
96aa222
Store fpos with cases and use it in tracebacks
blueyed Jan 6, 2017
64b323d
s:read_vader: Include: call s:read_vader recursively
blueyed Jan 6, 2017
69883eb
Define real functions for SyntaxAt/SyntaxOf
blueyed Mar 10, 2017
4ae1846
Fix existing tests
blueyed Mar 10, 2017
3582769
Add test/feature/error-location.vader
blueyed Mar 10, 2017
7e81bb2
FEAT: move s:prepare into vader#window#execute
blueyed Mar 10, 2017
1e7e930
Handle E123 in s:get_source_linenr_from_tb_entry
blueyed Apr 9, 2017
faee92c
Better prefix with errors from autocommands
blueyed Apr 9, 2017
b0103e4
vader#window#execute: use a single tempfile
blueyed Oct 28, 2017
c06add5
vader#window#execute: keep refs to globals for profiling
blueyed Jan 7, 2018
e93eb7e
Set g:vader_current_file to the current file
blueyed Jul 7, 2018
c6721b9
Remove/inline s:print_throwpoint
blueyed Jul 7, 2018
575eaf3
More test coverage
blueyed Jul 7, 2018
c90d2ae
vader#window#execute: use scriptencoding utf-8
blueyed Aug 18, 2018
4967a6f
Add test for script-local functions
blueyed Aug 20, 2018
38d6a6b
Revert "vader#window#execute: use a single tempfile"
blueyed Aug 20, 2018
75651a2
vader#window#execute: do not delete temporary files
blueyed Aug 20, 2018
06736b6
Use `:verb function` to get function's source file
blueyed Nov 21, 2018
f7e0094
TODO: vader#window#append: add to s:console_buffered always
blueyed Nov 22, 2018
01b8810
rebuild
blueyed Dec 22, 2020
cd2d920
fixup! Add test/feature/error-location.vader
blueyed Dec 22, 2020
e9353e7
fixup! Handle E123 in s:get_source_linenr_from_tb_entry
blueyed Dec 22, 2020
15f387c
fixup! Use `:verb function` to get function's source file
blueyed Dec 22, 2020
b5112ea
fixup! Fix existing tests
blueyed Dec 22, 2020
5bc0851
Handle patch-8.2.1297 (more stack information)
blueyed Dec 22, 2020
803305a
ci: .travis.yml: exit with code 99 with non-bang use case
blueyed Dec 22, 2020
50521be
fixup! Handle patch-8.2.1297 (more stack information)
blueyed Dec 22, 2020
934a25c
ci: Travis: use dist=focal for Neovim stable PPA
blueyed Dec 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ matrix:
- env: ENV=nvim VADER_OUTPUT_FILE=/dev/stderr
- env: ENV=nvim TESTVIM_OPTS=--headless
- env: ENV=nvim-stable
dist: focal
addons:
apt:
sources:
Expand Down Expand Up @@ -58,7 +59,7 @@ script:
- |
covimerage run --append --source . --no-report $TESTVIM $TESTVIM_OPTS -Nu test/vimrc \
-c 'Vader test/*' \
-c 'exe get(get(g:, "vader_result", {}), "successful", 0) ? "qall!" : "cq"'
-c 'exe get(get(g:, "vader_result", {}), "successful", 0) ? "qall!" : "cq 99"'

# Report coverage. This should fail the build to detect any problems.
- covimerage -vv xml
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ The following syntax helper functions are provided:
at the first character of the nth match of the given pattern.
Return `''` if there was no match.

The path of the current `.vader` file can be accessed via `g:vader_file`.
The `.vader` file for the current test case path is available in
`g:vader_file`, which will not reflect included files (via `Include`).
The path of the actual file is available in `g:vader_current_file`.

In addition to plain Vimscript, you can also test Ruby/Python/Perl/Lua
interface with Execute block as follows:
Expand Down
172 changes: 127 additions & 45 deletions autoload/vader.vim
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ function! vader#run(bang, ...) range
endif

call vader#assert#reset()
call s:prepare()
try
let all_cases = []
let qfl = []
Expand Down Expand Up @@ -267,58 +266,126 @@ function! vader#restore(args)
endfor
endfunction

function! s:prepare()
command! -nargs=+ Log :call vader#log(<args>)
command! -nargs=+ Save :call vader#save(<q-args>)
command! -nargs=* Restore :call vader#restore(<q-args>)
command! -nargs=+ Assert :call vader#assert#true(<args>)
command! -nargs=+ AssertEqual :call vader#assert#equal(<args>)
command! -nargs=+ AssertNotEqual :call vader#assert#not_equal(<args>)
command! -nargs=+ AssertThrows :call vader#assert#throws(<q-args>)
let g:SyntaxAt = function('vader#helper#syntax_at')
let g:SyntaxOf = function('vader#helper#syntax_of')
endfunction

function! s:cleanup()
let s:register = {}
let s:register_undefined = []
delcommand Log
delcommand Save
delcommand Restore
delcommand Assert
delcommand AssertEqual
delcommand AssertNotEqual
delcommand AssertThrows
unlet g:SyntaxAt
unlet g:SyntaxOf
if exists(':Log') == 2
delcommand Log
delcommand Save
delcommand Restore
delcommand Assert
delcommand AssertEqual
delcommand AssertNotEqual
delcommand AssertThrows
delfunction SyntaxAt
delfunction SyntaxOf
endif
endfunction

function! s:comment(case, label)
return get(a:case.comment, a:label, '')
endfunction

function! s:execute(prefix, type, block, lang_if)
function! s:get_source_from_tb_entry(tb_entry)
if a:tb_entry =~# '\v^script '
let split_f_linenr = split(a:tb_entry[7:], ', line ')
let [f, l] = split_f_linenr
let source = readfile(f, "", l)
let floc = fnamemodify(f, ':~:.').':'.l
return [source[l-1], l, f, floc]
endif
let func_line = split(a:tb_entry, '\v[\[\]]')
if len(func_line) == 2
let [f, l] = func_line
else
let split_f_linenr = split(a:tb_entry, ', line ')
if len(split_f_linenr) == 2
let [f, l] = split_f_linenr
else
let f = split_f_linenr[0]
return ['', 0, f, '']
endif
endif
if f =~# '\v^\d+$'
let f = '{'.f.'}'
endif
try
call vader#window#execute(a:block, a:lang_if)
return 1
catch
call s:append(a:prefix, a:type, v:exception, 1)
call s:print_throwpoint()
return 0
if exists('*execute')
let func = execute('verb function '.f)
else
redir => func
silent exe 'verb function '.f
redir END
endif
catch /^Vim\%((\a\+)\)\=:E123/
return ['', l, f, '']
endtry

let func_lines = split(func, "\n")
" Vim patch v8.1.0362 will display source location of function.
let m = matchlist(func_lines[1], '\v^\s*Last set from (.*) line (\d+)$')
if empty(m)
let floc = ''
else
let floc = fnamemodify(m[1], ':~:.') . ':' . (m[2] + l)
endif

let source = map(filter(func_lines, "v:val =~# '\\v^".l."[^0-9]'"), "substitute(v:val, '\\v^\\d+\\s+', '', '')")
if len(source) != 1
throw printf('Internal error: could not find source of %s:%d (parsed function: %s, source: %s)', string(a:tb_entry), l, f, string(source))
endif
return [source[0], l, f, floc]
endfunction

function! s:print_throwpoint()
if v:throwpoint !~ 'vader#assert'
Log v:throwpoint
function! s:execute(prefix, type, block, fpos, lang_if)
let g:vader_current_file = a:fpos[0]
let [error, lines] = vader#window#execute(a:block, a:lang_if)
if empty(error)
return [1, []]
endif

" Get line number from wrapper function or throwpoint.
let throwpoint = matchstr(error[1], '\v<function \zs\<SNR\>\d+_vader_wrapper.*')
if empty(throwpoint)
call s:append(a:prefix, a:type, 'Error: '.error[0]. ' (in '.error[1].')', 1)
return [0, []]
endif

call s:append(a:prefix, a:type, error[0], 1)

let tb_entries = reverse(split(throwpoint, '\.\.'))
let tb_first = remove(tb_entries, -1)
call filter(tb_entries, "v:val !~# '\\vvader#assert#[^,]+, line \\d+$'")
for tb_entry in tb_entries
let [source, l, f, floc] = s:get_source_from_tb_entry(tb_entry)
if l
if empty(floc)
let floc = 'line '.l
endif
call vader#log('in '.f.' ('.floc.')')
if len(source)
call vader#log(' '.source)
endif
else
call vader#log('in '.f)
endif
endfor
let [source, l, _, _] = s:get_source_from_tb_entry(tb_first)
let errpos = [a:fpos[0], l + a:fpos[1]]
if len(source)
call vader#log(errpos[0].':'.(errpos[1]).': '.source)
else
call vader#log(errpos[0].':'.(errpos[1]))
endif

return [0, errpos]
endfunction

function! s:run(filename, cases, options)
let given = []
let before = []
let after = []
let then = []
let given = { 'lines': [] }
let before = {}
let after = {}
let then = {}
let comment = { 'given': '', 'before': '', 'after': '' }
let total = len(a:cases)
let just = len(string(total))
Expand All @@ -338,39 +405,46 @@ function! s:run(filename, cases, options)

for label in ['given', 'before', 'after', 'then']
if has_key(case, label)
execute 'let '.label.' = case[label]'
execute 'let '.label." = {'lines': case[label], 'fpos': case.fpos[label]}"
let comment[label] = get(case.comment, label, '')
endif
endfor

if !empty(given)
if !empty(given.lines)
call s:append(prefix, 'given', comment.given)
endif
call vader#window#prepare(given, get(case, 'type', ''))
call vader#window#prepare(given.lines, get(case, 'type', ''))

if !empty(before)
let s:indent = 2
let ok = ok && s:execute(prefix, 'before', before, '')
let [ok, errpos] = s:execute(prefix, 'before', before.lines, before.fpos, '')
endif

let s:indent = 3
if has_key(case, 'execute')
call s:append(prefix, 'execute', s:comment(case, 'execute'))
let ok = ok && s:execute(prefix, 'execute', case.execute, get(case, 'lang_if', ''))
if ok
let [ok, errpos] = s:execute(prefix, 'execute', case.execute, case.fpos.execute, get(case, 'lang_if', ''))
endif
elseif has_key(case, 'do')
call s:append(prefix, 'do', s:comment(case, 'do'))
try
call vader#window#replay(case.do)
catch
call s:append(prefix, 'do', v:exception, 1)
call s:print_throwpoint()
if v:throwpoint !~ 'vader#assert'
call vader#log(v:throwpoint)
endif
let ok = 0
let errpos = case.fpos.do
endtry
endif

if has_key(case, 'then')
call s:append(prefix, 'then', s:comment(case, 'then'))
let ok = ok && s:execute(prefix, 'then', then, '')
if ok
let [ok, errpos] = s:execute(prefix, 'then', then.lines, then.fpos, '')
endif
endif

if has_key(case, 'expect')
Expand All @@ -381,6 +455,7 @@ function! s:run(filename, cases, options)
else
let begin = s:append(prefix, 'expect', s:comment(case, 'expect'), 1)
let ok = 0
let errpos = case.fpos.expect
let data = { 'type': get(case, 'type', ''), 'got': result, 'expect': case.expect }
call vader#window#append('- Expected:', 3)
for line in case.expect
Expand All @@ -397,7 +472,11 @@ function! s:run(filename, cases, options)
if !empty(after)
let s:indent = 2
let g:vader_case_ok = ok
let ok = s:execute(prefix, 'after', after, '') && ok
let [after_ok, after_errpos] = s:execute(prefix, 'after', after.lines, after.fpos, '')
let ok = after_ok && ok
if empty(errpos)
let errpos = after_errpos
endif
endif

if ok
Expand All @@ -410,7 +489,10 @@ function! s:run(filename, cases, options)
\ get(case.comment, 'then', ''),
\ get(case.comment, 'expect', '')], '!empty(v:val)'), ' / ') .
\ ' (#'.s:error_line.')'
call add(qfl, { 'type': 'E', 'filename': a:filename, 'lnum': case.lnum, 'text': description })
if empty(errpos)
let errpos = [a:filename, case.lnum]
endif
call add(qfl, { 'type': 'E', 'filename': fnamemodify(errpos[0], ':~:.'), 'lnum': errpos[1], 'text': description })
if exitfirst && !case.pending
call vader#window#append('Stopping after first failure.', 2)
break
Expand Down
28 changes: 18 additions & 10 deletions autoload/vader/parser.vim
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function! s:flush_buffer(cases, case, fn, lnum, raw, label, newlabel, buffer, fi
\ a:newlabel == 'given' ||
\ index(['before', 'after', 'do', 'execute'], a:newlabel) >= 0 && fulfilled
call add(a:cases, deepcopy(a:case))
let new = { 'comment': {}, 'lnum': a:lnum, 'pending': 0 }
let new = { 'comment': {}, 'lnum': a:lnum, 'pending': 0, 'fpos': {} }
if !empty(get(a:case, 'type', ''))
let new.type = a:case.type " To reuse Given block with type
endif
Expand All @@ -81,9 +81,9 @@ function! s:read_vader(fn, line1, line2)
let line = remove(remains, 0)
let m = matchlist(line, '^Include\(\s*(.*)\s*\)\?:\s*\(.\{-}\)\s*$')
if !empty(m)
let file = findfile(m[2], fnamemodify(a:fn, ':h'))
if empty(file)
echoerr "Cannot find ".m[2]
let file = fnamemodify(a:fn, ':h').'/'.m[2]
if !filereadable(file)
echoerr 'Cannot find '.m[2].' ('.file.')'
endif
if reserved > 0
let depth += 1
Expand All @@ -92,9 +92,8 @@ function! s:read_vader(fn, line1, line2)
endif
let reserved -= 1
endif
let included = readfile(file)
let reserved += len(included)
call extend(remains, included, 0)
call extend(lines, s:read_vader(file, 1, 0))
let lnum += 1
continue
end

Expand All @@ -116,16 +115,24 @@ function! s:parse_vader(lines, line1)
let newlabel = ''
let buffer = []
let cases = []
let case = { 'lnum': a:line1, 'comment': {}, 'pending': 0, 'raw': 0 }
let case = { 'lnum': a:line1, 'comment': {}, 'pending': 0, 'raw': 0, 'fpos': {} }

if empty(a:lines)
return []
endif

for [fn, lnum, line] in a:lines
" Comment / separators
" Keep them to maintain line numbers for Execute, but escape/comment them
" for s:flush_buffer.
if !case.raw && line =~ '^[#"=~*^-]'
continue
if index(['execute', 'then', 'before', 'after'], label) == -1
continue
endif
if line[0] != '"'
let line = '" '.line
endif
let line = '" '.line
endif

let matched = 0
Expand All @@ -134,6 +141,7 @@ function! s:parse_vader(lines, line1)
if !empty(m)
let newlabel = tolower(l)
call s:flush_buffer(cases, case, fn, lnum, m[3] == ';', label, newlabel, buffer, 0)
let case.fpos[newlabel] = [fn, lnum]

let label = newlabel
let arg = m[1]
Expand All @@ -159,7 +167,7 @@ function! s:parse_vader(lines, line1)
if matched | continue | endif

" Continuation
if !empty(line) && !case.raw && line !~ '^ '
if !empty(line) && line[0] != '"' && !case.raw && line !~ '^ '
throw 'Syntax error (line does not start with two spaces): ' . line
endif
if !empty(label)
Expand Down
Loading