view src/testdir/test_tagjump.vim @ 33815:08f9e1eac4cf v9.0.2123

patch 9.0.2123: Problem with initializing the length of range() lists Commit: https://github.com/vim/vim/commit/df63da98d8dc284b1c76cfe1b17fa0acbd6094d8 Author: Christian Brabandt <cb@256bit.org> Date: Thu Nov 23 20:14:28 2023 +0100 patch 9.0.2123: Problem with initializing the length of range() lists Problem: Problem with initializing the length of range() lists Solution: Set length explicitly when it shouldn't contain any items range() may cause a wrong calculation of list length, which may later then cause a segfault in list_find(). This is usually not a problem, because range_list_materialize() calculates the length, when it materializes the list. In addition, in list_find() when the length of the range was wrongly initialized, it may seem to be valid, so the check for list index out-of-bounds will not be true, because it is called before the list is actually materialized. And so we may eventually try to access a null pointer, causing a segfault. So this patch does 3 things: - In f_range(), when we know that the list should be empty, explicitly set the list->lv_len value to zero. This should happen, when start is larger than end (in case the stride is positive) or end is larger than start when the stride is negative. This should fix the underlying issue properly. However, - as a safety measure, let's check that the requested index is not out of range one more time, after the list has been materialized and return NULL in case it suddenly is. - add a few more tests to verify the behaviour. fixes: #13557 closes: #13563 Co-authored-by: Tim Pope <tpope@github.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Thu, 23 Nov 2023 20:30:07 +0100
parents dbec60b8c253
children d8c69a773456
line wrap: on
line source

" Tests for tagjump (tags and special searches)

source check.vim
source screendump.vim

" SEGV occurs in older versions.  (At least 7.4.1748 or older)
func Test_ptag_with_notagstack()
  CheckFeature quickfix

  set notagstack
  call assert_fails('ptag does_not_exist_tag_name', 'E433:')
  set tagstack&vim
endfunc

func Test_ptjump()
  CheckFeature quickfix

  set tags=Xpttags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXptfile\t1",
        \ "three\tXptfile\t3",
        \ "two\tXptfile\t2"],
        \ 'Xpttags', 'D')
  call writefile(['one', 'two', 'three'], 'Xptfile', 'D')

  %bw!
  ptjump two
  call assert_equal(2, winnr())
  wincmd p
  call assert_equal(1, &previewwindow)
  call assert_equal('Xptfile', expand("%:p:t"))
  call assert_equal(2, line('.'))
  call assert_equal(2, winnr('$'))
  call assert_equal(1, winnr())
  close
  call setline(1, ['one', 'two', 'three'])
  exe "normal 3G\<C-W>g}"
  call assert_equal(2, winnr())
  wincmd p
  call assert_equal(1, &previewwindow)
  call assert_equal('Xptfile', expand("%:p:t"))
  call assert_equal(3, line('.'))
  call assert_equal(2, winnr('$'))
  call assert_equal(1, winnr())
  close
  exe "normal 3G5\<C-W>\<C-G>}"
  wincmd p
  call assert_equal(5, winheight(0))
  close

  set tags&
endfunc

func Test_cancel_ptjump()
  CheckFeature quickfix

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "word\tfile1\tcmd1",
        \ "word\tfile2\tcmd2"],
        \ 'Xtags', 'D')

  only!
  call feedkeys(":ptjump word\<CR>\<CR>", "xt")
  help
  call assert_equal(2, winnr('$'))

  set tags&
  quit
endfunc

func Test_static_tagjump()
  set tags=Xtjtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXtjfile1\t/^one/;\"\tf\tfile:\tsignature:(void)",
        \ "word\tXtjfile2\tcmd2"],
        \ 'Xtjtags', 'D')
  new Xtjfile1
  call setline(1, ['empty', 'one()', 'empty'])
  write
  tag one
  call assert_equal(2, line('.'))

  bwipe!
  set tags&
  call delete('Xtjfile1')
endfunc

func Test_duplicate_tagjump()
  set tags=Xdttags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "thesame\tXdtfile1\t1;\"\td\tfile:",
        \ "thesame\tXdtfile1\t2;\"\td\tfile:",
        \ "thesame\tXdtfile1\t3;\"\td\tfile:",
        \ ],
        \ 'Xdttags', 'D')
  new Xdtfile1
  call setline(1, ['thesame one', 'thesame two', 'thesame three'])
  write
  tag thesame
  call assert_equal(1, line('.'))
  tnext
  call assert_equal(2, line('.'))
  tnext
  call assert_equal(3, line('.'))

  bwipe!
  set tags&
  call delete('Xdtfile1')
endfunc

func Test_tagjump_switchbuf()
  CheckFeature quickfix

  set tags=Xswtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "second\tXsbfile1\t2",
        \ "third\tXsbfile1\t3",],
        \ 'Xswtags', 'D')
  call writefile(['first', 'second', 'third'], 'Xsbfile1', 'D')

  enew | only
  set switchbuf=
  stag second
  call assert_equal(2, winnr('$'))
  call assert_equal(2, line('.'))
  stag third
  call assert_equal(3, winnr('$'))
  call assert_equal(3, line('.'))

  enew | only
  set switchbuf=useopen
  stag second
  call assert_equal(2, winnr('$'))
  call assert_equal(2, line('.'))
  stag third
  call assert_equal(2, winnr('$'))
  call assert_equal(3, line('.'))

  enew | only
  set switchbuf=usetab
  tab stag second
  call assert_equal(2, tabpagenr('$'))
  call assert_equal(2, line('.'))
  1tabnext | stag third
  call assert_equal(2, tabpagenr('$'))
  call assert_equal(3, line('.'))

  tabclose!
  enew | only
  set tags&
  set switchbuf&vim
endfunc

" Tests for [ CTRL-I and CTRL-W CTRL-I commands
function Test_keyword_jump()
  call writefile(["#include Xinclude", "",
	      \ "",
	      \ "/* test text test tex start here",
	      \ "		some text",
	      \ "		test text",
	      \ "		start OK if found this line",
	      \ "	start found wrong line",
	      \ "test text"], 'Xtestfile', 'D')
  call writefile(["/* test text test tex start here",
	      \ "		some text",
	      \ "		test text",
	      \ "		start OK if found this line",
	      \ "	start found wrong line",
	      \ "test text"], 'Xinclude', 'D')
  new Xtestfile
  call cursor(1,1)
  call search("start")
  exe "normal! 5[\<C-I>"
  call assert_equal("		start OK if found this line", getline('.'))
  call cursor(1,1)
  call search("start")
  exe "normal! 5\<C-W>\<C-I>"
  call assert_equal("		start OK if found this line", getline('.'))

  " invalid tag search pattern
  call assert_fails('tag /\%(/', 'E426:')

  enew! | only
endfunction

" Test for jumping to a tag with 'hidden' set, with symbolic link in path of
" tag.  This only works for Unix, because of the symbolic link.
func Test_tag_symbolic()
  CheckUnix

  set hidden
  call delete("Xtest.dir", "rf")
  call system("ln -s . Xtest.dir")
  " Create a tags file with the current directory name inserted.
  call writefile([
        \ "SECTION_OFF	" . getcwd() . "/Xtest.dir/Xtest.c	/^#define  SECTION_OFF  3$/",
        \ '',
        \ ], 'Xsymtags', 'D')
  call writefile(['#define  SECTION_OFF  3',
        \ '#define  NUM_SECTIONS 3'], 'Xtest.c', 'D')

  " Try jumping to a tag, but with a path that contains a symbolic link.  When
  " wrong, this will give the ATTENTION message.  The next space will then be
  " eaten by hit-return, instead of moving the cursor to 'd'.
  set tags=Xsymtags
  enew!
  call append(0, 'SECTION_OFF')
  call cursor(1,1)
  exe "normal \<C-]> "
  call assert_equal('Xtest.c', expand('%:t'))
  call assert_equal(2, col('.'))

  set hidden&
  set tags&
  enew!
  call delete("Xtest.dir", "rf")
  %bwipe!
endfunc

" Tests for tag search with !_TAG_FILE_ENCODING.
func Test_tag_file_encoding()
  if has('vms')
    throw 'Skipped: does not work on VMS'
  endif

  if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21"
    throw 'Skipped: iconv does not work'
  endif

  let save_enc = &encoding
  set encoding=utf8

  let content = ['text for tags1', 'abcdefghijklmnopqrs']
  call writefile(content, 'Xtags1.txt', 'D')
  let content = ['text for tags2', 'ABC']
  call writefile(content, 'Xtags2.txt', 'D')
  let content = ['text for tags3', 'ABC']
  call writefile(content, 'Xtags3.txt', 'D')
  let content = ['!_TAG_FILE_ENCODING	utf-8	//', 'abcdefghijklmnopqrs	Xtags1.txt	/abcdefghijklmnopqrs']
  call writefile(content, 'Xtags1', 'D')

  " case1:
  new
  set tags=Xtags1
  tag abcdefghijklmnopqrs
  call assert_equal('Xtags1.txt', expand('%:t'))
  call assert_equal('abcdefghijklmnopqrs', getline('.'))
  close

  " case2:
  new
  let content = ['!_TAG_FILE_ENCODING	cp932	//',
        \ "\x82`\x82a\x82b	Xtags2.txt	/\x82`\x82a\x82b"]
  call writefile(content, 'Xenctags')
  set tags=Xenctags
  tag /.BC
  call assert_equal('Xtags2.txt', expand('%:t'))
  call assert_equal('ABC', getline('.'))
  call delete('Xenctags')
  close

  " case3:
  new
  let contents = [
      \ "!_TAG_FILE_SORTED	1	//",
      \ "!_TAG_FILE_ENCODING	cp932	//"]
  for i in range(1, 100)
      call add(contents, 'abc' .. i
            \ .. "	Xtags3.txt	/\x82`\x82a\x82b")
  endfor
  call writefile(contents, 'Xenctags', 'D')
  set tags=Xenctags
  tag abc50
  call assert_equal('Xtags3.txt', expand('%:t'))
  call assert_equal('ABC', getline('.'))
  close

  set tags&
  let &encoding = save_enc
endfunc

" Test for emacs-style tags file (TAGS)
func Test_tagjump_etags()
  CheckFeature emacs_tags

  call writefile([
        \ "void foo() {}",
        \ "int main(int argc, char **argv)",
        \ "{",
        \ "\tfoo();",
        \ "\treturn 0;",
        \ "}",
        \ ], 'Xmain.c', 'D')

  call writefile([
	\ "\x0c",
        \ "Xmain.c,64",
        \ "void foo() {}\x7ffoo\x011,0",
        \ "int main(int argc, char **argv)\x7fmain\x012,14",
	\ ], 'Xtetags', 'D')
  set tags=Xtetags
  ta foo
  call assert_equal('void foo() {}', getline('.'))

  " Test for including another tags file
  call writefile([
        \ "\x0c",
        \ "Xmain.c,64",
        \ "void foo() {}\x7ffoo\x011,0",
        \ "\x0c",
        \ "Xnonexisting,include",
        \ "\x0c",
        \ "Xtags2,include"
        \ ], 'Xtetags')
  call writefile([
        \ "\x0c",
        \ "Xmain.c,64",
        \ "int main(int argc, char **argv)\x7fmain\x012,14",
        \ ], 'Xtags2', 'D')
  tag main
  call assert_equal(2, line('.'))
  call assert_fails('tag bar', 'E426:')

  " corrupted tag line
  call writefile([
        \ "\x0c",
        \ "Xmain.c,8",
        \ "int main"
        \ ], 'Xtetags', 'b')
  call assert_fails('tag foo', 'E426:')

  " invalid line number
  call writefile([
	\ "\x0c",
        \ "Xmain.c,64",
        \ "void foo() {}\x7ffoo\x0abc,0",
	\ ], 'Xtetags')
  call assert_fails('tag foo', 'E426:')

  " invalid tag name
  call writefile([
	\ "\x0c",
        \ "Xmain.c,64",
        \ ";;;;\x7f1,0",
	\ ], 'Xtetags')
  call assert_fails('tag foo', 'E431:')

  " end of file after a CTRL-L line
  call writefile([
	\ "\x0c",
        \ "Xmain.c,64",
        \ "void foo() {}\x7ffoo\x011,0",
	\ "\x0c",
	\ ], 'Xtetags')
  call assert_fails('tag main', 'E426:')

  " error in an included tags file
  call writefile([
        \ "\x0c",
        \ "Xtags2,include"
        \ ], 'Xtetags')
  call writefile([
        \ "\x0c",
        \ "Xmain.c,64",
        \ "void foo() {}",
        \ ], 'Xtags2')
  call assert_fails('tag foo', 'E431:')

  set tags&
  bwipe!
endfunc

" Test for getting and modifying the tag stack
func Test_getsettagstack()
  call writefile(['line1', 'line2', 'line3'], 'Xstsfile1', 'D')
  call writefile(['line1', 'line2', 'line3'], 'Xstsfile2', 'D')
  call writefile(['line1', 'line2', 'line3'], 'Xstsfile3', 'D')

  enew | only
  call settagstack(1, {'items' : []})
  call assert_equal(0, gettagstack(1).length)
  call assert_equal([], 1->gettagstack().items)
  " Error cases
  call assert_equal({}, gettagstack(100))
  call assert_equal(-1, settagstack(100, {'items' : []}))
  call assert_fails('call settagstack(1, [1, 10])', 'E1206:')
  call assert_fails("call settagstack(1, {'items' : 10})", 'E714:')
  call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E1174:')
  call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962:')
  call assert_equal(-1, settagstack(0, test_null_dict()))

  set tags=Xtsttags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXstsfile1\t1",
        \ "three\tXstsfile3\t3",
        \ "two\tXstsfile2\t2"],
        \ 'Xtsttags', 'D')

  let stk = []
  call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'one',
	\ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
  tag one
  call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'two',
	\ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
  tag two
  call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'three',
	\ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
  tag three
  call assert_equal(3, gettagstack(1).length)
  call assert_equal(stk, gettagstack(1).items)
  " Check for default - current window
  call assert_equal(3, gettagstack().length)
  call assert_equal(stk, gettagstack().items)

  " Try to set current index to invalid values
  call settagstack(1, {'curidx' : -1})
  call assert_equal(1, gettagstack().curidx)
  eval {'curidx' : 50}->settagstack(1)
  call assert_equal(4, gettagstack().curidx)

  " Try pushing invalid items onto the stack
  call settagstack(1, {'items' : []})
  call settagstack(1, {'items' : ["plate"]}, 'a')
  call assert_equal(0, gettagstack().length)
  call assert_equal([], gettagstack().items)
  call settagstack(1, {'items' : [{"tagname" : "abc"}]}, 'a')
  call assert_equal(0, gettagstack().length)
  call assert_equal([], gettagstack().items)
  call settagstack(1, {'items' : [{"from" : 100}]}, 'a')
  call assert_equal(0, gettagstack().length)
  call assert_equal([], gettagstack().items)
  call settagstack(1, {'items' : [{"from" : [2, 1, 0, 0]}]}, 'a')
  call assert_equal(0, gettagstack().length)
  call assert_equal([], gettagstack().items)

  " Push one item at a time to the stack
  call settagstack(1, {'items' : []})
  call settagstack(1, {'items' : [stk[0]]}, 'a')
  call settagstack(1, {'items' : [stk[1]]}, 'a')
  call settagstack(1, {'items' : [stk[2]]}, 'a')
  call settagstack(1, {'curidx' : 4})
  call assert_equal({'length' : 3, 'curidx' : 4, 'items' : stk},
        \ gettagstack(1))

  " Try pushing items onto a full stack
  for i in range(7)
    call settagstack(1, {'items' : stk}, 'a')
  endfor
  call assert_equal(20, gettagstack().length)
  call settagstack(1,
        \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a')
  call assert_equal('abc', gettagstack().items[19].tagname)

  " truncate the tag stack
  call settagstack(1,
        \ {'curidx' : 9,
        \  'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
  let t = gettagstack()
  call assert_equal(9, t.length)
  call assert_equal(10, t.curidx)

  " truncate the tag stack without pushing any new items
  call settagstack(1, {'curidx' : 5}, 't')
  let t = gettagstack()
  call assert_equal(4, t.length)
  call assert_equal(5, t.curidx)

  " truncate an empty tag stack and push new items
  call settagstack(1, {'items' : []})
  call settagstack(1,
        \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
  let t = gettagstack()
  call assert_equal(1, t.length)
  call assert_equal(2, t.curidx)

  " Tag with multiple matches
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "two\tXstsfile1\t1",
        \ "two\tXstsfile2\t3",
        \ "two\tXstsfile3\t2"],
        \ 'Xtsttags')
  call settagstack(1, {'items' : []})
  tag two
  tnext
  tnext
  call assert_equal(1, gettagstack().length)
  call assert_equal(3, gettagstack().items[0].matchnr)

  " Memory allocation failures
  call test_alloc_fail(GetAllocId('tagstack_items'), 0, 0)
  call assert_fails('call gettagstack()', 'E342:')
  call test_alloc_fail(GetAllocId('tagstack_from'), 0, 0)
  call assert_fails('call gettagstack()', 'E342:')
  call test_alloc_fail(GetAllocId('tagstack_details'), 0, 0)
  call assert_fails('call gettagstack()', 'E342:')

  call settagstack(1, {'items' : []})
  set tags&
endfunc

func Test_tag_with_count()
  call writefile([
	\ 'test	Xtest.h	/^void test();$/;"	p	typeref:typename:void	signature:()',
	\ ], 'Xtags', 'D')
  call writefile([
	\ 'main	Xtest.c	/^int main()$/;"	f	typeref:typename:int	signature:()',
	\ 'test	Xtest.c	/^void test()$/;"	f	typeref:typename:void	signature:()',
	\ ], 'Ytags', 'D')
  cal writefile([
	\ 'int main()',
	\ 'void test()',
	\ ], 'Xtest.c', 'D')
  cal writefile([
	\ 'void test();',
	\ ], 'Xtest.h', 'D')
  set tags=Xtags,Ytags

  new Xtest.c
  let tl = taglist('test', 'Xtest.c')
  call assert_equal(tl[0].filename, 'Xtest.c')
  call assert_equal(tl[1].filename, 'Xtest.h')

  tag test
  call assert_equal(bufname('%'), 'Xtest.c')
  1tag test
  call assert_equal(bufname('%'), 'Xtest.c')
  2tag test
  call assert_equal(bufname('%'), 'Xtest.h')

  set tags&
  bwipe Xtest.h
  bwipe Xtest.c
endfunc

func Test_tagnr_recall()
  call writefile([
	\ 'test	Xtest.h	/^void test();$/;"	p',
	\ 'main	Xtest.c	/^int main()$/;"	f',
	\ 'test	Xtest.c	/^void test()$/;"	f',
	\ ], 'Xtags', 'D')
  cal writefile([
	\ 'int main()',
	\ 'void test()',
	\ ], 'Xtest.c', 'D')
  cal writefile([
	\ 'void test();',
	\ ], 'Xtest.h', 'D')
  set tags=Xtags

  new Xtest.c
  let tl = taglist('test', 'Xtest.c')
  call assert_equal(tl[0].filename, 'Xtest.c')
  call assert_equal(tl[1].filename, 'Xtest.h')

  2tag test
  call assert_equal(bufname('%'), 'Xtest.h')
  pop
  call assert_equal(bufname('%'), 'Xtest.c')
  tag
  call assert_equal(bufname('%'), 'Xtest.h')

  set tags&
  bwipe Xtest.h
  bwipe Xtest.c
endfunc

func Test_tag_line_toolong()
  call writefile([
	\ '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678	django/contrib/admin/templates/admin/edit_inline/stacked.html	16;"	j	line:16	language:HTML'
	\ ], 'Xtags', 'D')
  set tags=Xtags
  let old_vbs = &verbose
  set verbose=5
  " ":tjump" should give "tag not found" not "Format error in tags file"
  call assert_fails('tj /foo', 'E426:')
  try
    tj /foo
  catch /^Vim\%((\a\+)\)\=:E431/
    call assert_report(v:exception)
  catch /.*/
  endtry
  call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])

  call writefile([
	\ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567	django/contrib/admin/templates/admin/edit_inline/stacked.html	16;"	j	line:16	language:HTML'
	\ ], 'Xtags')
  call assert_fails('tj /foo', 'E426:')
  try
    tj /foo
  catch /^Vim\%((\a\+)\)\=:E431/
    call assert_report(v:exception)
  catch /.*/
  endtry
  call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])

  " binary search works in file with long line
  call writefile([
        \ 'asdfasfd	nowhere	16',
	\ 'foobar	Xsomewhere	3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567',
        \ 'zasdfasfd	nowhere	16',
	\ ], 'Xtags')
  call writefile([
        \ 'one',
        \ 'two',
        \ 'trhee',
        \ 'four',
        \ ], 'Xsomewhere', 'D')
  tag foobar
  call assert_equal('Xsomewhere', expand('%'))
  call assert_equal(3, getcurpos()[1])

  " expansion on command line works with long lines when &wildoptions contains
  " 'tagfile'
  set wildoptions=tagfile
  call writefile([
	\ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa	file	/^pattern$/;"	f'
	\ ], 'Xtags')
  call feedkeys(":tag \<Tab>", 'tx')
  " Should not crash
  call assert_true(v:true)

  set tags&
  let &verbose = old_vbs
endfunc

" Check that using :tselect does not run into the hit-enter prompt.
" Requires a terminal to trigger that prompt.
func Test_tselect()
  CheckScreendump

  call writefile([
	\ 'main	Xtest.h	/^void test();$/;"	f',
	\ 'main	Xtest.c	/^int main()$/;"	f',
	\ 'main	Xtest.x	/^void test()$/;"	f',
	\ ], 'Xtags', 'D')
  cal writefile([
	\ 'int main()',
	\ 'void test()',
	\ ], 'Xtest.c', 'D')

  let lines =<< trim [SCRIPT]
    set tags=Xtags
  [SCRIPT]
  call writefile(lines, 'XTest_tselect', 'D')
  let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50})

  call TermWait(buf, 50)
  call term_sendkeys(buf, ":tselect main\<CR>2\<CR>")
  call VerifyScreenDump(buf, 'Test_tselect_1', {})

  call StopVimInTerminal(buf)
endfunc

func Test_tagline()
  call writefile([
	\ 'provision	Xtest.py	/^    def provision(self, **kwargs):$/;"	m	line:1	language:Python class:Foo',
	\ 'provision	Xtest.py	/^    def provision(self, **kwargs):$/;"	m	line:3	language:Python class:Bar',
	\], 'Xtags', 'D')
  call writefile([
	\ '    def provision(self, **kwargs):',
	\ '        pass',
	\ '    def provision(self, **kwargs):',
	\ '        pass',
	\], 'Xtest.py', 'D')

  set tags=Xtags

  1tag provision
  call assert_equal(line('.'), 1)
  2tag provision
  call assert_equal(line('.'), 3)

  set tags&
endfunc

" Test for expanding environment variable in a tag file name
func Test_tag_envvar()
  call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags', 'D')
  set tags=Xtags

  let $FOO='TagTestEnv'

  let caught_exception = v:false
  try
    tag Func1
  catch /E429:/
    call assert_match('E429:.*"TagTestEnv".*', v:exception)
    let caught_exception = v:true
  endtry
  call assert_true(caught_exception)

  set tags&
  unlet $FOO
endfunc

" Test for :ptag
func Test_tag_preview()
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "second\tXtpfile1\t2",
        \ "third\tXtpfile1\t3",],
        \ 'Xtags', 'D')
  set tags=Xtags
  call writefile(['first', 'second', 'third'], 'Xtpfile1', 'D')

  enew | only
  ptag third
  call assert_equal(2, winnr())
  call assert_equal(2, winnr('$'))
  call assert_equal(1, getwinvar(1, '&previewwindow'))
  call assert_equal(0, getwinvar(2, '&previewwindow'))
  wincmd P
  call assert_equal(3, line('.'))

  " jump to the tag again
  wincmd w
  ptag third
  wincmd P
  call assert_equal(3, line('.'))

  " jump to the newer tag
  wincmd w
  ptag
  wincmd P
  call assert_equal(3, line('.'))

  " close the preview window
  pclose
  call assert_equal(1, winnr('$'))

  set tags&
endfunc

" Tests for guessing the tag location
func Test_tag_guess()
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "func1\tXfoo\t/^int func1(int x)/",
        \ "func2\tXfoo\t/^int func2(int y)/",
        \ "func3\tXfoo\t/^func3/",
        \ "func4\tXfoo\t/^func4/"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]

    int FUNC1  (int x) { }
    int
    func2   (int y) { }
    int * func3 () { }

  [CODE]
  call writefile(code, 'Xfoo', 'D')

  let v:statusmsg = ''
  ta func1
  call assert_match('E435:', v:statusmsg)
  call assert_equal(2, line('.'))
  let v:statusmsg = ''
  ta func2
  call assert_match('E435:', v:statusmsg)
  call assert_equal(4, line('.'))
  let v:statusmsg = ''
  ta func3
  call assert_match('E435:', v:statusmsg)
  call assert_equal(5, line('.'))
  call assert_fails('ta func4', 'E434:')

  set tags&
endfunc

" Test for an unsorted tags file
func Test_tag_sort()
  let l = [
        \ "first\tXfoo\t1",
        \ "ten\tXfoo\t3",
        \ "six\tXfoo\t2"]
  call writefile(l, 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
    int six() {}
    int ten() {}
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  call assert_fails('tag first', 'E432:')

  " When multiple tag files are not sorted, then message should be displayed
  " multiple times
  call writefile(l, 'Xtags2', 'D')
  set tags=Xtags,Xtags2
  call assert_fails('tag first', ['E432:', 'E432:'])

  set tags&
  %bwipe
endfunc

" Test for an unsorted tags file
func Test_tag_fold()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/",
        \ "first\tXfoo\t1",
        \ "second\tXfoo\t2",
        \ "third\tXfoo\t3"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
    int second() {}
    int third() {}
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  enew
  tag second
  call assert_equal('Xfoo', bufname(''))
  call assert_equal(2, line('.'))

  set tags&
  %bwipe
endfunc

" Test for the :ltag command
func Test_ltag()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "first\tXfoo\t1",
        \ "second\tXfoo\t/^int second() {}$/",
        \ "third\tXfoo\t3"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
    int second() {}
    int third() {}
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  enew
  call setloclist(0, [], 'f')
  ltag third
  call assert_equal('Xfoo', bufname(''))
  call assert_equal(3, line('.'))
  call assert_equal([{'lnum': 3, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'),
        \ 'col': 0, 'end_col': 0, 'pattern': '', 'valid': 1, 'vcol': 0,
        \ 'nr': 0, 'type': '', 'module': '', 'text': 'third'}], getloclist(0))

  ltag second
  call assert_equal(2, line('.'))
  call assert_equal([{'lnum': 0, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'),
        \ 'col': 0, 'end_col': 0, 'pattern': '^\Vint second() {}\$',
        \ 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', 'module': '',
        \ 'text': 'second'}], getloclist(0))

  set tags&
  %bwipe
endfunc

" Test for setting the last search pattern to the tag search pattern
" when cpoptions has 't'
func Test_tag_last_search_pat()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "first\tXfoo\t/^int first() {}/",
        \ "second\tXfoo\t/^int second() {}/",
        \ "third\tXfoo\t/^int third() {}/"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
    int second() {}
    int third() {}
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  enew
  let save_cpo = &cpo
  set cpo+=t
  let @/ = ''
  tag second
  call assert_equal('^int second() {}', @/)
  let &cpo = save_cpo

  set tags&
  %bwipe
endfunc

" Tag stack tests
func Test_tag_stack()
  let l = []
  for i in range(10, 31)
    let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"]
  endfor
  call writefile(l, 'Xtags', 'D')
  set tags=Xtags

  let l = []
  for i in range(10, 31)
    let l += ["int var" .. i .. ";"]
  endfor
  call writefile(l, 'Xfoo', 'D')

  " Jump to a tag when the tag stack is full. Oldest entry should be removed.
  enew
  for i in range(10, 30)
    exe "tag var" .. i
  endfor
  let l = gettagstack()
  call assert_equal(20, l.length)
  call assert_equal('var11', l.items[0].tagname)
  tag var31
  let l = gettagstack()
  call assert_equal('var12', l.items[0].tagname)
  call assert_equal('var31', l.items[19].tagname)

  " Use tnext with a single match
  call assert_fails('tnext', 'E427:')

  " Jump to newest entry from the top of the stack
  call assert_fails('tag', 'E556:')

  " Pop with zero count from the top of the stack
  call assert_fails('0pop', 'E556:')

  " Pop from an unsaved buffer
  enew!
  call append(1, "sample text")
  call assert_fails('pop', 'E37:')
  call assert_equal(21, gettagstack().curidx)
  enew!

  " Pop all the entries in the tag stack
  call assert_fails('30pop', 'E555:')

  " Pop with a count when already at the bottom of the stack
  call assert_fails('exe "normal 4\<C-T>"', 'E555:')
  call assert_equal(1, gettagstack().curidx)

  " Jump to newest entry from the bottom of the stack with zero count
  call assert_fails('0tag', 'E555:')

  " Pop the tag stack when it is empty
  call settagstack(1, {'items' : []})
  call assert_fails('pop', 'E73:')

  set tags&
  %bwipe
endfunc

" Test for browsing multiple matching tags
func Test_tag_multimatch()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "first\tXfoo\t1",
        \ "first\tXfoo\t2",
        \ "first\tXfoo\t3"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
    int first() {}
    int first() {}
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  call settagstack(1, {'items' : []})
  tag first
  tlast
  call assert_equal(3, line('.'))
  call assert_fails('tnext', 'E428:')
  tfirst
  call assert_equal(1, line('.'))
  call assert_fails('tprev', 'E425:')

  tlast
  call feedkeys("5\<CR>", 't')
  tselect first
  call assert_equal(2, gettagstack().curidx)

  set ignorecase
  tag FIRST
  tnext
  call assert_equal(2, line('.'))
  tlast
  tprev
  call assert_equal(2, line('.'))
  tNext
  call assert_equal(1, line('.'))
  set ignorecase&

  set tags&
  %bwipe
endfunc

" Test for previewing multiple matching tags
func Test_preview_tag_multimatch()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "first\tXfoo\t1",
        \ "first\tXfoo\t2",
        \ "first\tXfoo\t3"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
    int first() {}
    int first() {}
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  enew | only
  ptag first
  ptlast
  wincmd P
  call assert_equal(3, line('.'))
  wincmd w
  call assert_fails('ptnext', 'E428:')
  ptprev
  wincmd P
  call assert_equal(2, line('.'))
  wincmd w
  ptfirst
  wincmd P
  call assert_equal(1, line('.'))
  wincmd w
  call assert_fails('ptprev', 'E425:')
  ptnext
  wincmd P
  call assert_equal(2, line('.'))
  wincmd w
  ptlast
  call feedkeys("5\<CR>", 't')
  ptselect first
  wincmd P
  call assert_equal(3, line('.'))

  pclose

  set tags&
  %bwipe
endfunc

" Test for jumping to multiple matching tags across multiple :tags commands
func Test_tnext_multimatch()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "first\tXfoo1\t1",
        \ "first\tXfoo2\t1",
        \ "first\tXfoo3\t1"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
  [CODE]
  call writefile(code, 'Xfoo1', 'D')
  call writefile(code, 'Xfoo2', 'D')
  call writefile(code, 'Xfoo3', 'D')

  tag first
  tag first
  pop
  tnext
  tnext
  call assert_fails('tnext', 'E428:')

  set tags&
  %bwipe
endfunc

" Test for jumping to multiple matching tags in non-existing files
func Test_multimatch_non_existing_files()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "first\tXfoo1\t1",
        \ "first\tXfoo2\t1",
        \ "first\tXfoo3\t1"],
        \ 'Xtags', 'D')
  set tags=Xtags

  call settagstack(1, {'items' : []})
  call assert_fails('tag first', 'E429:')
  call assert_equal(3, gettagstack().items[0].matchnr)

  set tags&
  %bwipe
endfunc

func Test_tselect_listing()
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:",
        \ "first\tXfoo\t2" .. ';"' .. "\tkind:v\ttyperef:typename:char\tfile:"],
        \ 'Xtags', 'D')
  set tags=Xtags

  let code =<< trim [CODE]
    static int first;
    static char first;
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  call feedkeys("\<CR>", "t")
  let l = split(execute("tselect first"), "\n")
  let expected =<< [DATA]
  # pri kind tag               file
  1 FS  v    first             Xfoo
               typeref:typename:int 
               1
  2 FS  v    first             Xfoo
               typeref:typename:char 
               2
Type number and <Enter> (q or empty cancels): 
[DATA]
  call assert_equal(expected, l)

  set tags&
  %bwipe
endfunc

" Test for :isearch, :ilist, :ijump and :isplit commands
" Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands
func Test_inc_search()
  new
  call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo', '==='])
  call cursor(3, 1)

  " Test for [i and ]i
  call assert_equal('1:foo', execute('normal [i'))
  call assert_equal('2:foo', execute('normal 2[i'))
  call assert_fails('normal 3[i', 'E387:')
  call assert_equal('3:foo', execute('normal ]i'))
  call assert_equal('4:foo', execute('normal 2]i'))
  call assert_fails('normal 3]i', 'E389:')
  call assert_fails('normal G]i', 'E349:')
  call assert_fails('normal [i', 'E349:')
  call cursor(3, 1)

  " Test for :isearch
  call assert_equal('1:foo', execute('isearch foo'))
  call assert_equal('3:foo', execute('isearch 4 /foo/'))
  call assert_fails('isearch 3 foo', 'E387:')
  call assert_equal('3:foo', execute('+1,$isearch foo'))
  call assert_fails('1,.-1isearch 3 foo', 'E389:')
  call assert_fails('isearch bar', 'E389:')
  call assert_fails('isearch /foo/3', 'E488:')

  " Test for [I and ]I
  call assert_equal([
        \ '  1:    1 1:foo',
        \ '  2:    2 2:foo',
        \ '  3:    3 foo',
        \ '  4:    4 3:foo',
        \ '  5:    5 4:foo'], split(execute('normal [I'), "\n"))
  call assert_equal([
        \ '  1:    4 3:foo',
        \ '  2:    5 4:foo'], split(execute('normal ]I'), "\n"))
  call assert_fails('normal G]I', 'E349:')
  call assert_fails('normal [I', 'E349:')
  call cursor(3, 1)

  " Test for :ilist
  call assert_equal([
        \ '  1:    1 1:foo',
        \ '  2:    2 2:foo',
        \ '  3:    3 foo',
        \ '  4:    4 3:foo',
        \ '  5:    5 4:foo'], split(execute('ilist foo'), "\n"))
  call assert_equal([
        \ '  1:    4 3:foo',
        \ '  2:    5 4:foo'], split(execute('+1,$ilist /foo/'), "\n"))
  call assert_fails('ilist bar', 'E389:')

  " Test for [ CTRL-I and ] CTRL-I
  exe "normal [\t"
  call assert_equal([1, 3], [line('.'), col('.')])
  exe "normal 2j4[\t"
  call assert_equal([4, 3], [line('.'), col('.')])
  call assert_fails("normal k3[\t", 'E387:')
  call assert_fails("normal 6[\t", 'E389:')
  exe "normal ]\t"
  call assert_equal([4, 3], [line('.'), col('.')])
  exe "normal k2]\t"
  call assert_equal([5, 3], [line('.'), col('.')])
  call assert_fails("normal 2k3]\t", 'E389:')
  call assert_fails("normal G[\t", 'E349:')
  call assert_fails("normal ]\t", 'E349:')
  call cursor(3, 1)

  " Test for :ijump
  call cursor(3, 1)
  ijump foo
  call assert_equal([1, 3], [line('.'), col('.')])
  call cursor(3, 1)
  ijump 4 /foo/
  call assert_equal([4, 3], [line('.'), col('.')])
  call cursor(3, 1)
  call assert_fails('ijump 3 foo', 'E387:')
  +,$ijump 2 foo
  call assert_equal([5, 3], [line('.'), col('.')])
  call assert_fails('ijump bar', 'E389:')

  " Test for CTRL-W i
  call cursor(3, 1)
  wincmd i
  call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')])
  close
  5wincmd i
  call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')])
  close
  call assert_fails('3wincmd i', 'E387:')
  call assert_fails('6wincmd i', 'E389:')
  call assert_fails("normal G\<C-W>i", 'E349:')
  call cursor(3, 1)

  " Test for :isplit
  isplit foo
  call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')])
  close
  isplit 5 /foo/
  call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')])
  close
  call assert_fails('isplit 3 foo', 'E387:')
  call assert_fails('isplit 6 foo', 'E389:')
  call assert_fails('isplit bar', 'E389:')

  close!
endfunc

" this was using a line from ml_get() freed by the regexp
func Test_isearch_copy_line()
  new
  norm o
  norm 0
  0norm o
  sil! norm bc0
  sil! isearch \%')
  bwipe!
endfunc

" Test for :dsearch, :dlist, :djump and :dsplit commands
" Test for [d, ]d, [D, ]D, [ CTRL-D, ] CTRL-D and CTRL-W d commands
func Test_macro_search()
  new
  call setline(1, ['#define FOO 1', '#define FOO 2', '#define FOO 3',
        \ '#define FOO 4', '#define FOO 5'])
  call cursor(3, 9)

  " Test for [d and ]d
  call assert_equal('#define FOO 1', execute('normal [d'))
  call assert_equal('#define FOO 2', execute('normal 2[d'))
  call assert_fails('normal 3[d', 'E387:')
  call assert_equal('#define FOO 4', execute('normal ]d'))
  call assert_equal('#define FOO 5', execute('normal 2]d'))
  call assert_fails('normal 3]d', 'E388:')

  " Test for :dsearch
  call assert_equal('#define FOO 1', execute('dsearch FOO'))
  call assert_equal('#define FOO 5', execute('dsearch 5 /FOO/'))
  call assert_fails('dsearch 3 FOO', 'E387:')
  call assert_equal('#define FOO 4', execute('+1,$dsearch FOO'))
  call assert_fails('1,.-1dsearch 3 FOO', 'E388:')
  call assert_fails('dsearch BAR', 'E388:')

  " Test for [D and ]D
  call assert_equal([
        \ '  1:    1 #define FOO 1',
        \ '  2:    2 #define FOO 2',
        \ '  3:    3 #define FOO 3',
        \ '  4:    4 #define FOO 4',
        \ '  5:    5 #define FOO 5'], split(execute('normal [D'), "\n"))
  call assert_equal([
        \ '  1:    4 #define FOO 4',
        \ '  2:    5 #define FOO 5'], split(execute('normal ]D'), "\n"))

  " Test for :dlist
  call assert_equal([
        \ '  1:    1 #define FOO 1',
        \ '  2:    2 #define FOO 2',
        \ '  3:    3 #define FOO 3',
        \ '  4:    4 #define FOO 4',
        \ '  5:    5 #define FOO 5'], split(execute('dlist FOO'), "\n"))
  call assert_equal([
        \ '  1:    4 #define FOO 4',
        \ '  2:    5 #define FOO 5'], split(execute('+1,$dlist /FOO/'), "\n"))
  call assert_fails('dlist BAR', 'E388:')

  " Test for [ CTRL-D and ] CTRL-D
  exe "normal [\<C-D>"
  call assert_equal([1, 9], [line('.'), col('.')])
  exe "normal 2j4[\<C-D>"
  call assert_equal([4, 9], [line('.'), col('.')])
  call assert_fails("normal k3[\<C-D>", 'E387:')
  call assert_fails("normal 6[\<C-D>", 'E388:')
  exe "normal ]\<C-D>"
  call assert_equal([4, 9], [line('.'), col('.')])
  exe "normal k2]\<C-D>"
  call assert_equal([5, 9], [line('.'), col('.')])
  call assert_fails("normal 2k3]\<C-D>", 'E388:')

  " Test for :djump
  call cursor(3, 9)
  djump FOO
  call assert_equal([1, 9], [line('.'), col('.')])
  call cursor(3, 9)
  djump 4 /FOO/
  call assert_equal([4, 9], [line('.'), col('.')])
  call cursor(3, 9)
  call assert_fails('djump 3 FOO', 'E387:')
  +,$djump 2 FOO
  call assert_equal([5, 9], [line('.'), col('.')])
  call assert_fails('djump BAR', 'E388:')

  " Test for CTRL-W d
  call cursor(3, 9)
  wincmd d
  call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')])
  close
  5wincmd d
  call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')])
  close
  call assert_fails('3wincmd d', 'E387:')
  call assert_fails('6wincmd d', 'E388:')
  new
  call assert_fails("normal \<C-W>d", 'E349:')
  call assert_fails("normal \<C-W>\<C-D>", 'E349:')
  close

  " Test for :dsplit
  dsplit FOO
  call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')])
  close
  dsplit 5 /FOO/
  call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')])
  close
  call assert_fails('dsplit 3 FOO', 'E387:')
  call assert_fails('dsplit 6 FOO', 'E388:')
  call assert_fails('dsplit BAR', 'E388:')

  close!
endfunc

func Test_define_search()
  " this was accessing freed memory
  new
  call setline(1, ['first line', '', '#define something 0'])
  sil norm o0
  sil! norm 
  bwipe!

  new somefile
  call setline(1, ['first line', '', '#define something 0'])
  sil norm 0o0
  sil! norm ]d
  bwipe!
endfunc

" Test for [*, [/, ]* and ]/
func Test_comment_search()
  new
  call setline(1, ['', '/*', ' *', ' *', ' */'])
  normal! 4gg[/
  call assert_equal([2, 1], [line('.'), col('.')])
  normal! 3gg[*
  call assert_equal([2, 1], [line('.'), col('.')])
  normal! 3gg]/
  call assert_equal([5, 3], [line('.'), col('.')])
  normal! 3gg]*
  call assert_equal([5, 3], [line('.'), col('.')])
  %d
  call setline(1, ['', '/*', ' *', ' *'])
  call assert_beeps('normal! 3gg]/')
  %d
  call setline(1, ['', ' *', ' *', ' */'])
  call assert_beeps('normal! 4gg[/')
  %d
  call setline(1, '        /* comment */')
  normal! 15|[/
  call assert_equal(9, col('.'))
  normal! 15|]/
  call assert_equal(21, col('.'))
  call setline(1, '         comment */')
  call assert_beeps('normal! 15|[/')
  call setline(1, '        /* comment')
  call assert_beeps('normal! 15|]/')
  close!
endfunc

" Test for the 'taglength' option
func Test_tag_length()
  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "tame\tXtlfile1\t1;",
        \ "tape\tXtlfile2\t1;"], 'Xtags', 'D')
  call writefile(['tame'], 'Xtlfile1', 'D')
  call writefile(['tape'], 'Xtlfile2', 'D')

  " Jumping to the tag 'tape', should instead jump to 'tame'
  new
  set taglength=2
  tag tape
  call assert_equal('Xtlfile1', @%)
  " Tag search should jump to the right tag
  enew
  tag /^tape$
  call assert_equal('Xtlfile2', @%)

  set tags& taglength&
endfunc

" Tests for errors in a tags file
func Test_tagfile_errors()
  set tags=Xtags

  " missing search pattern or line number for a tag
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "foo\tXfile\t"], 'Xtags', 'bD')
  call writefile(['foo'], 'Xfile', 'D')

  enew
  tag foo
  call assert_equal('', @%)
  let caught_431 = v:false
  try
    eval taglist('.*')
  catch /:E431:/
    let caught_431 = v:true
  endtry
  call assert_equal(v:true, caught_431)

  " tag name and file name are not separated by a tab
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "foo Xfile 1"], 'Xtags')
  call assert_fails('tag foo', 'E431:')

  " file name and search pattern are not separated by a tab
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "foo\tXfile 1;"], 'Xtags')
  call assert_fails('tag foo', 'E431:')

  set tags&
endfunc

" When :stag fails to open the file, should close the new window
func Test_stag_close_window_on_error()
  new | only
  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "foo\tXfile\t1"], 'Xtags', 'D')
  call writefile(['foo'], 'Xfile', 'D')
  call writefile([], '.Xfile.swp', 'D')
  " Remove the catch-all that runtest.vim adds
  au! SwapExists
  augroup StagTest
    au!
    autocmd SwapExists Xfile let v:swapchoice='q'
  augroup END

  stag foo
  call assert_equal(1, winnr('$'))
  call assert_equal('', @%)

  augroup StagTest
    au!
  augroup END
  set tags&
endfunc

" Test for 'tagbsearch' (binary search)
func Test_tagbsearch()
  " If a tags file header says the tags are sorted, but the tags are actually
  " unsorted, then binary search should fail and linear search should work.
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/",
        \ "third\tXfoo\t3",
        \ "second\tXfoo\t2",
        \ "first\tXfoo\t1"],
        \ 'Xtags', 'D')
  set tags=Xtags
  let code =<< trim [CODE]
    int first() {}
    int second() {}
    int third() {}
  [CODE]
  call writefile(code, 'Xfoo', 'D')

  enew
  set tagbsearch
  call assert_fails('tag first', 'E426:')
  call assert_equal('', bufname())
  call assert_fails('tag second', 'E426:')
  call assert_equal('', bufname())
  tag third
  call assert_equal('Xfoo', bufname())
  call assert_equal(3, line('.'))
  %bw!

  set notagbsearch
  tag first
  call assert_equal('Xfoo', bufname())
  call assert_equal(1, line('.'))
  enew
  tag second
  call assert_equal('Xfoo', bufname())
  call assert_equal(2, line('.'))
  enew
  tag third
  call assert_equal('Xfoo', bufname())
  call assert_equal(3, line('.'))
  %bw!

  " If a tags file header says the tags are unsorted, but the tags are
  " actually sorted, then binary search should work.
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "!_TAG_FILE_SORTED\t0\t/0=unsorted, 1=sorted, 2=foldcase/",
        \ "first\tXfoo\t1",
        \ "second\tXfoo\t2",
        \ "third\tXfoo\t3"],
        \ 'Xtags')

  set tagbsearch
  tag first
  call assert_equal('Xfoo', bufname())
  call assert_equal(1, line('.'))
  enew
  tag second
  call assert_equal('Xfoo', bufname())
  call assert_equal(2, line('.'))
  enew
  tag third
  call assert_equal('Xfoo', bufname())
  call assert_equal(3, line('.'))
  %bw!

  " Binary search fails on EOF
  call writefile([
        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/",
        \ "bar\tXfoo\t1",
        \ "foo\tXfoo\t2"],
        \ 'Xtags')
  call assert_fails('tag bbb', 'E426:')

  set tags& tagbsearch&
endfunc

" vim: shiftwidth=2 sts=2 expandtab