view src/testdir/test_debugger.vim @ 34074:1629cc65d78d v9.1.0006

patch 9.1.0006: is*() and to*() function may be unsafe Commit: https://github.com/vim/vim/commit/184f71cc6868a240dc872ed2852542bbc1d43e28 Author: Keith Thompson <Keith.S.Thompson@gmail.com> Date: Thu Jan 4 21:19:04 2024 +0100 patch 9.1.0006: is*() and to*() function may be unsafe Problem: is*() and to*() function may be unsafe Solution: Add SAFE_* macros and start using those instead (Keith Thompson) Use SAFE_() macros for is*() and to*() functions The standard is*() and to*() functions declared in <ctype.h> have undefined behavior for negative arguments other than EOF. If plain char is signed, passing an unchecked value from argv for from user input to one of these functions has undefined behavior. Solution: Add SAFE_*() macros that cast the argument to unsigned char. Most implementations behave sanely for negative arguments, and most character values in practice are non-negative, but it's still best to avoid undefined behavior. The change from #13347 has been omitted, as this has already been separately fixed in commit ac709e2fc0db6d31abb7da96f743c40956b60c3a (v9.0.2054) fixes: #13332 closes: #13347 Signed-off-by: Keith Thompson <Keith.S.Thompson@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Thu, 04 Jan 2024 21:30:04 +0100
parents 695b50472e85
children
line wrap: on
line source

" Tests for the Vim script debug commands

source shared.vim
source screendump.vim
source check.vim

CheckRunVimInTerminal

func CheckCWD()
  " Check that the longer lines don't wrap due to the length of the script name
  " in cwd
  let script_len = len( getcwd() .. '/Xtest1.vim' )
  let longest_line = len( 'Breakpoint in "" line 1' )
  if script_len > ( 75 - longest_line )
    throw 'Skipped: Your CWD has too many characters'
  endif
endfunc
command! -nargs=0 -bar CheckCWD call CheckCWD()

" "options" argument can contain:
" 'msec' - time to wait for a match
" 'match' - "pattern" to use "lines" as pattern instead of text
def s:CheckDbgOutput(buf: number, lines: list<string>, options = {})
  # Verify the expected output
  var lnum = 20 - len(lines)
  var msec = get(options, 'msec', 1000)
  for l in lines
    if get(options, 'match', 'equal') ==# 'pattern'
      g:WaitForAssert(() => assert_match(l, term_getline(buf, lnum)), msec)
    else
      g:WaitForAssert(() => assert_equal(l, term_getline(buf, lnum)), msec)
    endif
    lnum += 1
  endfor
enddef

" Run a Vim debugger command
" If the expected output argument is supplied, then check for it.
def s:RunDbgCmd(buf: number, cmd: string, ...extra: list<any>)
  term_sendkeys(buf, cmd .. "\r")
  g:TermWait(buf)

  if len(extra) > 0
    var options = {match: 'equal'}
    if len(extra) > 1
      extend(options, extra[1])
    endif
    s:CheckDbgOutput(buf, extra[0], options)
  endif
enddef

" Debugger tests
def Test_Debugger()
  # Create a Vim script with some functions
  var lines =<< trim END
	func Foo()
	  let var1 = 1
	  let var2 = Bar(var1) + 9
	  return var2
	endfunc
	func Bar(var)
	  let var1 = 2 + a:var
	  let var2 = Bazz(var1) + 4
	  return var2
	endfunc
	func Bazz(var)
	  try
	    let var1 = 3 + a:var
	    let var3 = "another var"
	    let var3 = "value2"
	  catch
	    let var4 = "exception"
	  endtry
	  return var1
	endfunc
        def Vim9Func()
          for cmd in ['confirm', 'xxxxxxx']
            for _ in [1, 2]
              echo cmd
            endfor
          endfor
        enddef
  END
  writefile(lines, 'XtestDebug.vim', 'D')

  # Start Vim in a terminal
  var buf = g:RunVimInTerminal('-S XtestDebug.vim', {})

  # Start the Vim debugger
  s:RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])

  # Create a few stack frames by stepping through functions
  s:RunDbgCmd(buf, 'step', ['line 1: let var1 = 1'])
  s:RunDbgCmd(buf, 'step', ['line 2: let var2 = Bar(var1) + 9'])
  s:RunDbgCmd(buf, 'step', ['line 1: let var1 = 2 + a:var'])
  s:RunDbgCmd(buf, 'step', ['line 2: let var2 = Bazz(var1) + 4'])
  s:RunDbgCmd(buf, 'step', ['line 1: try'])
  s:RunDbgCmd(buf, 'step', ['line 2: let var1 = 3 + a:var'])
  s:RunDbgCmd(buf, 'step', ['line 3: let var3 = "another var"'])

  # check backtrace
  s:RunDbgCmd(buf, 'backtrace', [
	      '  2 function Foo[2]',
	      '  1 Bar[2]',
	      '->0 Bazz',
	      'line 3: let var3 = "another var"'])

  # Check variables in different stack frames
  s:RunDbgCmd(buf, 'echo var1', ['6'])

  s:RunDbgCmd(buf, 'up')
  s:RunDbgCmd(buf, 'back', [
	      '  2 function Foo[2]',
	      '->1 Bar[2]',
	      '  0 Bazz',
	      'line 3: let var3 = "another var"'])
  s:RunDbgCmd(buf, 'echo var1', ['3'])

  s:RunDbgCmd(buf, 'u')
  s:RunDbgCmd(buf, 'bt', [
	      '->2 function Foo[2]',
	      '  1 Bar[2]',
	      '  0 Bazz',
	      'line 3: let var3 = "another var"'])
  s:RunDbgCmd(buf, 'echo var1', ['1'])

  # Undefined variables
  s:RunDbgCmd(buf, 'step')
  s:RunDbgCmd(buf, 'frame 2')
  s:RunDbgCmd(buf, 'echo var3', [
	'Error detected while processing function Foo[2]..Bar[2]..Bazz:',
	'line    4:',
	'E121: Undefined variable: var3'])

  # var3 is defined in this level with some other value
  s:RunDbgCmd(buf, 'fr 0')
  s:RunDbgCmd(buf, 'echo var3', ['another var'])

  s:RunDbgCmd(buf, 'step')
  s:RunDbgCmd(buf, '')
  s:RunDbgCmd(buf, '')
  s:RunDbgCmd(buf, '')
  s:RunDbgCmd(buf, '')
  s:RunDbgCmd(buf, 'step', [
	      'function Foo[2]..Bar',
	      'line 3: End of function'])
  s:RunDbgCmd(buf, 'up')

  # Undefined var2
  s:RunDbgCmd(buf, 'echo var2', [
	      'Error detected while processing function Foo[2]..Bar:',
	      'line    3:',
	      'E121: Undefined variable: var2'])

  # Var2 is defined with 10
  s:RunDbgCmd(buf, 'down')
  s:RunDbgCmd(buf, 'echo var2', ['10'])

  # Backtrace movements
  s:RunDbgCmd(buf, 'b', [
	      '  1 function Foo[2]',
	      '->0 Bar',
	      'line 3: End of function'])

  # next command cannot go down, we are on bottom
  s:RunDbgCmd(buf, 'down', ['frame is zero'])
  s:RunDbgCmd(buf, 'up')

  # next command cannot go up, we are on top
  s:RunDbgCmd(buf, 'up', ['frame at highest level: 1'])
  s:RunDbgCmd(buf, 'where', [
	      '->1 function Foo[2]',
	      '  0 Bar',
	      'line 3: End of function'])

  # fil is not frame or finish, it is file
  s:RunDbgCmd(buf, 'fil', ['"[No Name]" --No lines in buffer--'])

  # relative backtrace movement
  s:RunDbgCmd(buf, 'fr -1')
  s:RunDbgCmd(buf, 'frame', [
	      '  1 function Foo[2]',
	      '->0 Bar',
	      'line 3: End of function'])

  s:RunDbgCmd(buf, 'fr +1')
  s:RunDbgCmd(buf, 'fram', [
	      '->1 function Foo[2]',
	      '  0 Bar',
	      'line 3: End of function'])

  # go beyond limits does not crash
  s:RunDbgCmd(buf, 'fr 100', ['frame at highest level: 1'])
  s:RunDbgCmd(buf, 'fra', [
	      '->1 function Foo[2]',
	      '  0 Bar',
	      'line 3: End of function'])

  s:RunDbgCmd(buf, 'frame -40', ['frame is zero'])
  s:RunDbgCmd(buf, 'fram', [
	      '  1 function Foo[2]',
	      '->0 Bar',
	      'line 3: End of function'])

  # final result 19
  s:RunDbgCmd(buf, 'cont', ['19'])

  # breakpoints tests

  # Start a debug session, so that reading the last line from the terminal
  # works properly.
  s:RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])

  # No breakpoints
  s:RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])

  # Place some breakpoints
  s:RunDbgCmd(buf, 'breaka func Bar')
  s:RunDbgCmd(buf, 'breaklis', ['  1  func Bar  line 1'])
  s:RunDbgCmd(buf, 'breakadd func 3 Bazz')
  s:RunDbgCmd(buf, 'breaklist', ['  1  func Bar  line 1',
	      '  2  func Bazz  line 3'])

  # Check whether the breakpoints are hit
  s:RunDbgCmd(buf, 'cont', [
	      'Breakpoint in "Bar" line 1',
	      'function Foo[2]..Bar',
	      'line 1: let var1 = 2 + a:var'])
  s:RunDbgCmd(buf, 'cont', [
	      'Breakpoint in "Bazz" line 3',
	      'function Foo[2]..Bar[2]..Bazz',
	      'line 3: let var3 = "another var"'])

  # Delete the breakpoints
  s:RunDbgCmd(buf, 'breakd 1')
  s:RunDbgCmd(buf, 'breakli', ['  2  func Bazz  line 3'])
  s:RunDbgCmd(buf, 'breakdel func 3 Bazz')
  s:RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])

  s:RunDbgCmd(buf, 'cont')

  # Make sure the breakpoints are removed
  s:RunDbgCmd(buf, ':echo Foo()', ['19'])

  # Delete a non-existing breakpoint
  s:RunDbgCmd(buf, ':breakdel 2', ['E161: Breakpoint not found: 2'])

  # Expression breakpoint
  s:RunDbgCmd(buf, ':breakadd func 2 Bazz')
  s:RunDbgCmd(buf, ':echo Bazz(1)', [
	      'Entering Debug mode.  Type "cont" to continue.',
	      'function Bazz',
	      'line 2: let var1 = 3 + a:var'])
  s:RunDbgCmd(buf, 'step')
  s:RunDbgCmd(buf, 'step')
  s:RunDbgCmd(buf, 'breaka expr var3')
  s:RunDbgCmd(buf, 'breakl', ['  3  func Bazz  line 2',
	      \ '  4  expr var3'])
  s:RunDbgCmd(buf, 'cont', ['Breakpoint in "Bazz" line 5',
	      'Oldval = "''another var''"',
	      'Newval = "''value2''"',
	      'function Bazz',
	      'line 5: catch'])

  s:RunDbgCmd(buf, 'breakdel *')
  s:RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])

  # Check for error cases
  s:RunDbgCmd(buf, 'breakadd abcd', [
	      'Error detected while processing function Bazz:',
	      'line    5:',
	      'E475: Invalid argument: abcd'])
  s:RunDbgCmd(buf, 'breakadd func', ['E475: Invalid argument: func'])
  s:RunDbgCmd(buf, 'breakadd func 2', ['E475: Invalid argument: func 2'])
  s:RunDbgCmd(buf, 'breaka func a()', ['E475: Invalid argument: func a()'])
  s:RunDbgCmd(buf, 'breakd abcd', ['E475: Invalid argument: abcd'])
  s:RunDbgCmd(buf, 'breakd func', ['E475: Invalid argument: func'])
  s:RunDbgCmd(buf, 'breakd func a()', ['E475: Invalid argument: func a()'])
  s:RunDbgCmd(buf, 'breakd func a', ['E161: Breakpoint not found: func a'])
  s:RunDbgCmd(buf, 'breakd expr', ['E475: Invalid argument: expr'])
  s:RunDbgCmd(buf, 'breakd expr x', ['E161: Breakpoint not found: expr x'])

  # finish the current function
  s:RunDbgCmd(buf, 'finish', [
	      'function Bazz',
	      'line 8: End of function'])
  s:RunDbgCmd(buf, 'cont')

  # Test for :next
  s:RunDbgCmd(buf, ':debug echo Bar(1)')
  s:RunDbgCmd(buf, 'step')
  s:RunDbgCmd(buf, 'next')
  s:RunDbgCmd(buf, '', [
	      'function Bar',
	      'line 3: return var2'])
  s:RunDbgCmd(buf, 'c')

  # Test for :interrupt
  s:RunDbgCmd(buf, ':debug echo Bazz(1)')
  s:RunDbgCmd(buf, 'step')
  s:RunDbgCmd(buf, 'step')
  s:RunDbgCmd(buf, 'interrupt', [
	      'Exception thrown: Vim:Interrupt',
	      'function Bazz',
	      'line 5: catch'])
  s:RunDbgCmd(buf, 'c')

  # Test showing local variable in :def function
  s:RunDbgCmd(buf, ':breakadd func 2 Vim9Func')
  s:RunDbgCmd(buf, ':call Vim9Func()', ['line 2:             for _ in [1, 2]'])
  s:RunDbgCmd(buf, 'next', ['line 2: for _ in [1, 2]'])
  s:RunDbgCmd(buf, 'echo cmd', ['confirm'])
  s:RunDbgCmd(buf, 'breakdel *')
  s:RunDbgCmd(buf, 'cont')

  # Test for :quit
  s:RunDbgCmd(buf, ':debug echo Foo()')
  s:RunDbgCmd(buf, 'breakdel *')
  s:RunDbgCmd(buf, 'breakadd func 3 Foo')
  s:RunDbgCmd(buf, 'breakadd func 3 Bazz')
  s:RunDbgCmd(buf, 'cont', [
	      'Breakpoint in "Bazz" line 3',
	      'function Foo[2]..Bar[2]..Bazz',
	      'line 3: let var3 = "another var"'])
  s:RunDbgCmd(buf, 'quit', [
	      'Breakpoint in "Foo" line 3',
	      'function Foo',
	      'line 3: return var2'])
  s:RunDbgCmd(buf, 'breakdel *')
  s:RunDbgCmd(buf, 'quit')
  s:RunDbgCmd(buf, 'enew! | only!')

  g:StopVimInTerminal(buf)
enddef

func Test_Debugger_breakadd()
  " Tests for :breakadd file and :breakadd here
  " Breakpoints should be set before sourcing the file

  let lines =<< trim END
	let var1 = 10
	let var2 = 20
	let var3 = 30
	let var4 = 40
  END
  call writefile(lines, 'XdebugBreakadd.vim', 'D')

  " Start Vim in a terminal
  let buf = RunVimInTerminal('XdebugBreakadd.vim', {})
  call s:RunDbgCmd(buf, ':breakadd file 2 XdebugBreakadd.vim')
  call s:RunDbgCmd(buf, ':4 | breakadd here')
  call s:RunDbgCmd(buf, ':source XdebugBreakadd.vim', ['line 2: let var2 = 20'])
  call s:RunDbgCmd(buf, 'cont', ['line 4: let var4 = 40'])
  call s:RunDbgCmd(buf, 'cont')

  call StopVimInTerminal(buf)

  %bw!

  call assert_fails('breakadd here', 'E32:')
  call assert_fails('breakadd file Xtest.vim /\)/', 'E55:')
endfunc

" Test for expression breakpoint set using ":breakadd expr <expr>"
func Test_Debugger_breakadd_expr()
  CheckCWD

  let lines =<< trim END
    let g:Xtest_var += 1
  END
  call writefile(lines, 'XdebugBreakExpr.vim', 'D')

  " Start Vim in a terminal
  let buf = RunVimInTerminal('XdebugBreakExpr.vim', {})
  call s:RunDbgCmd(buf, ':let g:Xtest_var = 10')
  call s:RunDbgCmd(buf, ':breakadd expr g:Xtest_var')
  call s:RunDbgCmd(buf, ':source %')
  let expected =<< trim eval END
    Oldval = "10"
    Newval = "11"
    {fnamemodify('XdebugBreakExpr.vim', ':p')}
    line 1: let g:Xtest_var += 1
  END
  call s:RunDbgCmd(buf, ':source %', expected)
  call s:RunDbgCmd(buf, 'cont')
  let expected =<< trim eval END
    Oldval = "11"
    Newval = "12"
    {fnamemodify('XdebugBreakExpr.vim', ':p')}
    line 1: let g:Xtest_var += 1
  END
  call s:RunDbgCmd(buf, ':source %', expected)

  call StopVimInTerminal(buf)
endfunc

def Test_Debugger_breakadd_vim9_expr()
  var lines =<< trim END
      vim9script
      func g:EarlyFunc()
      endfunc
      breakadd expr DoesNotExist()
      func g:LaterFunc()
      endfunc
      breakdel *
  END
  writefile(lines, 'XdebugBreak9expr.vim', 'D')

  # Start Vim in a terminal
  var buf = g:RunVimInTerminal('-S XdebugBreak9expr.vim', {wait_for_ruler: 0})
  call g:TermWait(buf, g:RunningWithValgrind() ? 1000 : 50)

  # Despite the failure the functions are defined
  s:RunDbgCmd(buf, ':function g:EarlyFunc',
     ['function EarlyFunc()', 'endfunction'], {match: 'pattern'})
  s:RunDbgCmd(buf, ':function g:LaterFunc',
     ['function LaterFunc()', 'endfunction'], {match: 'pattern'})

  call g:StopVimInTerminal(buf)
enddef

def Test_Debugger_break_at_return()
  var lines =<< trim END
      vim9script
      def g:GetNum(): number
        return 1
          + 2
          + 3
      enddef
      breakadd func GetNum
  END
  writefile(lines, 'XdebugBreakRet.vim', 'D')

  # Start Vim in a terminal
  var buf = g:RunVimInTerminal('-S XdebugBreakRet.vim', {wait_for_ruler: 0})
  call g:TermWait(buf, g:RunningWithValgrind() ? 1000 : 50)

  s:RunDbgCmd(buf, ':call GetNum()',
     ['line 1: return 1  + 2  + 3'], {match: 'pattern'})

  call g:StopVimInTerminal(buf)
enddef

func Test_Backtrace_Through_Source()
  CheckCWD
  let file1 =<< trim END
    func SourceAnotherFile()
      source Xtest2.vim
    endfunc

    func CallAFunction()
      call SourceAnotherFile()
      call File2Function()
    endfunc

    func GlobalFunction()
      call CallAFunction()
    endfunc
  END
  call writefile(file1, 'Xtest1.vim', 'D')

  let file2 =<< trim END
    func DoAThing()
      echo "DoAThing"
    endfunc

    func File2Function()
      call DoAThing()
    endfunc

    call File2Function()
  END
  call writefile(file2, 'Xtest2.vim', 'D')

  let buf = RunVimInTerminal('-S Xtest1.vim', {})

  call s:RunDbgCmd(buf,
                \ ':debug call GlobalFunction()',
                \ ['cmd: call GlobalFunction()'])
  call s:RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])

  call s:RunDbgCmd(buf, 'backtrace', ['>backtrace',
                                    \ '->0 function GlobalFunction',
                                    \ 'line 1: call CallAFunction()'])

  call s:RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
  call s:RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])

  call s:RunDbgCmd(buf, 'backtrace', ['>backtrace',
                                    \ '  2 function GlobalFunction[1]',
                                    \ '  1 CallAFunction[1]',
                                    \ '->0 SourceAnotherFile',
                                    \ 'line 1: source Xtest2.vim'])

  " Step into the 'source' command. Note that we print the full trace all the
  " way though the source command.
  call s:RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()'])

  call s:RunDbgCmd( buf, 'up' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '->1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'up' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 function GlobalFunction[1]',
        \ '->2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'up' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '->3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'up', [ 'frame at highest level: 3' ] )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '->3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 function GlobalFunction[1]',
        \ '->2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '->1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down', [ 'frame is zero' ] )

  " step until we have another meaningful trace
  call s:RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
  call s:RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 9: call File2Function()'])

  call s:RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
  call s:RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  5 function GlobalFunction[1]',
        \ '  4 CallAFunction[1]',
        \ '  3 SourceAnotherFile[1]',
        \ '  2 script ' .. getcwd() .. '/Xtest2.vim[9]',
        \ '  1 function File2Function[1]',
        \ '->0 DoAThing',
        \ 'line 1: echo "DoAThing"'])

  " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
  call s:RunDbgCmd(buf, 'step', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'step', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
  call s:RunDbgCmd(buf, 'step', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  1 function GlobalFunction[1]',
        \ '->0 CallAFunction',
        \ 'line 2: call File2Function()'])

  call s:RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  2 function GlobalFunction[1]',
        \ '  1 CallAFunction[2]',
        \ '->0 File2Function',
        \ 'line 1: call DoAThing()'])

  call StopVimInTerminal(buf)
endfunc

func Test_Backtrace_Autocmd()
  CheckCWD
  let file1 =<< trim END
    func SourceAnotherFile()
      source Xtest2.vim
    endfunc

    func CallAFunction()
      call SourceAnotherFile()
      call File2Function()
    endfunc

    func GlobalFunction()
      call CallAFunction()
    endfunc

    au User TestGlobalFunction :call GlobalFunction() | echo "Done"
  END
  call writefile(file1, 'Xtest1.vim', 'D')

  let file2 =<< trim END
    func DoAThing()
      echo "DoAThing"
    endfunc

    func File2Function()
      call DoAThing()
    endfunc

    call File2Function()
  END
  call writefile(file2, 'Xtest2.vim', 'D')

  let buf = RunVimInTerminal('-S Xtest1.vim', {})

  call s:RunDbgCmd(buf,
                \ ':debug doautocmd User TestGlobalFunction',
                \ ['cmd: doautocmd User TestGlobalFunction'])
  call s:RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"'])

  " At this point the only thing in the stack is the autocommand
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '->0 User Autocommands for "TestGlobalFunction"',
        \ 'cmd: call GlobalFunction() | echo "Done"'])

  " And now we're back into the call stack
  call s:RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  1 User Autocommands for "TestGlobalFunction"',
        \ '->0 function GlobalFunction',
        \ 'line 1: call CallAFunction()'])

  call s:RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
  call s:RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])

  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 User Autocommands for "TestGlobalFunction"',
        \ '  2 function GlobalFunction[1]',
        \ '  1 CallAFunction[1]',
        \ '->0 SourceAnotherFile',
        \ 'line 1: source Xtest2.vim'])

  " Step into the 'source' command. Note that we print the full trace all the
  " way though the source command.
  call s:RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()'])

  call s:RunDbgCmd( buf, 'up' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '->1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'up' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '->2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'up' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '->3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'up' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '->4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'up', [ 'frame at highest level: 4' ] )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '->4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '->3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )


  call s:RunDbgCmd( buf, 'down' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '->2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '->1 SourceAnotherFile[1]',
        \ '  0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down' )
  call s:RunDbgCmd( buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 1: func DoAThing()' ] )

  call s:RunDbgCmd( buf, 'down', [ 'frame is zero' ] )

  " step until we have another meaningful trace
  call s:RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
  call s:RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  4 User Autocommands for "TestGlobalFunction"',
        \ '  3 function GlobalFunction[1]',
        \ '  2 CallAFunction[1]',
        \ '  1 SourceAnotherFile[1]',
        \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ 'line 9: call File2Function()'])

  call s:RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
  call s:RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  6 User Autocommands for "TestGlobalFunction"',
        \ '  5 function GlobalFunction[1]',
        \ '  4 CallAFunction[1]',
        \ '  3 SourceAnotherFile[1]',
        \ '  2 script ' .. getcwd() .. '/Xtest2.vim[9]',
        \ '  1 function File2Function[1]',
        \ '->0 DoAThing',
        \ 'line 1: echo "DoAThing"'])

  " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
  call s:RunDbgCmd(buf, 'step', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'step', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
  call s:RunDbgCmd(buf, 'step', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  2 User Autocommands for "TestGlobalFunction"',
        \ '  1 function GlobalFunction[1]',
        \ '->0 CallAFunction',
        \ 'line 2: call File2Function()'])

  call s:RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 User Autocommands for "TestGlobalFunction"',
        \ '  2 function GlobalFunction[1]',
        \ '  1 CallAFunction[2]',
        \ '->0 File2Function',
        \ 'line 1: call DoAThing()'])


  " Now unwind so that we get back to the original autocommand (and the second
  " cmd echo "Done")
  call s:RunDbgCmd(buf, 'finish', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  3 User Autocommands for "TestGlobalFunction"',
        \ '  2 function GlobalFunction[1]',
        \ '  1 CallAFunction[2]',
        \ '->0 File2Function',
        \ 'line 1: End of function'])

  call s:RunDbgCmd(buf, 'finish', ['line 2: End of function'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  2 User Autocommands for "TestGlobalFunction"',
        \ '  1 function GlobalFunction[1]',
        \ '->0 CallAFunction',
        \ 'line 2: End of function'])

  call s:RunDbgCmd(buf, 'finish', ['line 1: End of function'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  1 User Autocommands for "TestGlobalFunction"',
        \ '->0 function GlobalFunction',
        \ 'line 1: End of function'])

  call s:RunDbgCmd(buf, 'step', ['cmd: echo "Done"'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '->0 User Autocommands for "TestGlobalFunction"',
        \ 'cmd: echo "Done"'])

  call StopVimInTerminal(buf)
endfunc

func Test_Backtrace_CmdLine()
  CheckCWD
  let file1 =<< trim END
    func SourceAnotherFile()
      source Xtest2.vim
    endfunc

    func CallAFunction()
      call SourceAnotherFile()
      call File2Function()
    endfunc

    func GlobalFunction()
      call CallAFunction()
    endfunc

    au User TestGlobalFunction :call GlobalFunction() | echo "Done"
  END
  call writefile(file1, 'Xtest1.vim', 'D')

  let file2 =<< trim END
    func DoAThing()
      echo "DoAThing"
    endfunc

    func File2Function()
      call DoAThing()
    endfunc

    call File2Function()
  END
  call writefile(file2, 'Xtest2.vim', 'D')

  let buf = RunVimInTerminal(
        \ '-S Xtest1.vim -c "debug call GlobalFunction()"',
        \ {'wait_for_ruler': 0})

  " Need to wait for the vim-in-terminal to be ready.
  " With valgrind this can take quite long.
  call s:CheckDbgOutput(buf, ['command line',
                            \ 'cmd: call GlobalFunction()'], #{msec: 5000})

  " At this point the only thing in the stack is the cmdline
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '->0 command line',
        \ 'cmd: call GlobalFunction()'])

  " And now we're back into the call stack
  call s:RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '>backtrace',
        \ '  1 command line',
        \ '->0 function GlobalFunction',
        \ 'line 1: call CallAFunction()'])

  call StopVimInTerminal(buf)
endfunc

func Test_Backtrace_DefFunction()
  CheckCWD
  let file1 =<< trim END
    vim9script
    import './Xtest2.vim' as imp

    def SourceAnotherFile()
      source Xtest2.vim
    enddef

    def CallAFunction()
      SourceAnotherFile()
      imp.File2Function()
    enddef

    def g:GlobalFunction()
      var some = "some var"
      CallAFunction()
    enddef

    defcompile
  END
  call writefile(file1, 'Xtest1.vim', 'D')

  let file2 =<< trim END
    vim9script

    def DoAThing(): number
      var a = 100 * 2
      a += 3
      return a
    enddef

    export def File2Function()
      DoAThing()
    enddef

    defcompile
    File2Function()
  END
  call writefile(file2, 'Xtest2.vim', 'D')

  let buf = RunVimInTerminal('-S Xtest1.vim', {})

  call s:RunDbgCmd(buf,
                \ ':debug call GlobalFunction()',
                \ ['cmd: call GlobalFunction()'])

  call s:RunDbgCmd(buf, 'step', ['line 1: var some = "some var"'])
  call s:RunDbgCmd(buf, 'step', ['line 2: CallAFunction()'])
  call s:RunDbgCmd(buf, 'echo some', ['some var'])

  call s:RunDbgCmd(buf, 'backtrace', [
        \ '\V>backtrace',
        \ '\V->0 function GlobalFunction',
        \ '\Vline 2: CallAFunction()',
        \ ],
        \ #{match: 'pattern'})

  call s:RunDbgCmd(buf, 'step', ['line 1: SourceAnotherFile()'])
  call s:RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
  " Repeated line, because we first are in the compiled function before the
  " EXEC and then in do_cmdline() before the :source command.
  call s:RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
  call s:RunDbgCmd(buf, 'step', ['line 1: vim9script'])
  call s:RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number'])
  call s:RunDbgCmd(buf, 'step', ['line 9: export def File2Function()'])
  call s:RunDbgCmd(buf, 'step', ['line 13: defcompile'])
  call s:RunDbgCmd(buf, 'step', ['line 14: File2Function()'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '\V>backtrace',
        \ '\V  3 function GlobalFunction[2]',
        \ '\V  2 <SNR>\.\*_CallAFunction[1]',
        \ '\V  1 <SNR>\.\*_SourceAnotherFile[1]',
        \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ '\Vline 14: File2Function()'],
        \ #{match: 'pattern'})

  " Don't step into compiled functions...
  call s:RunDbgCmd(buf, 'next', ['line 15: End of sourced file'])
  call s:RunDbgCmd(buf, 'backtrace', [
        \ '\V>backtrace',
        \ '\V  3 function GlobalFunction[2]',
        \ '\V  2 <SNR>\.\*_CallAFunction[1]',
        \ '\V  1 <SNR>\.\*_SourceAnotherFile[1]',
        \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
        \ '\Vline 15: End of sourced file'],
        \ #{match: 'pattern'})

  call StopVimInTerminal(buf)
endfunc

func Test_DefFunction_expr()
  CheckCWD
  let file3 =<< trim END
      vim9script
      g:someVar = "foo"
      def g:ChangeVar()
        g:someVar = "bar"
        echo "changed"
      enddef
      defcompile
  END
  call writefile(file3, 'Xtest3.vim', 'D')
  let buf = RunVimInTerminal('-S Xtest3.vim', {})

  call s:RunDbgCmd(buf, ':breakadd expr g:someVar')
  call s:RunDbgCmd(buf, ':call g:ChangeVar()', ['Oldval = "''foo''"', 'Newval = "''bar''"', 'function ChangeVar', 'line 2: echo "changed"'])

  call StopVimInTerminal(buf)
endfunc

func Test_debug_def_and_legacy_function()
  CheckCWD
  let file =<< trim END
    vim9script
    def g:SomeFunc()
      echo "here"
      echo "and"
      echo "there"
      breakadd func 2 LocalFunc
      LocalFunc()
    enddef

    def LocalFunc()
      echo "first"
      echo "second"
      breakadd func LegacyFunc
      LegacyFunc()
    enddef

    func LegacyFunc()
      echo "legone"
      echo "legtwo"
    endfunc

    breakadd func 2 g:SomeFunc
  END
  call writefile(file, 'XtestDebug.vim', 'D')

  let buf = RunVimInTerminal('-S XtestDebug.vim', {})

  call s:RunDbgCmd(buf,':call SomeFunc()', ['line 2: echo "and"'])
  call s:RunDbgCmd(buf,'next', ['line 3: echo "there"'])
  call s:RunDbgCmd(buf,'next', ['line 4: breakadd func 2 LocalFunc'])

  " continue, next breakpoint is in LocalFunc()
  call s:RunDbgCmd(buf,'cont', ['line 2: echo "second"'])

  " continue, next breakpoint is in LegacyFunc()
  call s:RunDbgCmd(buf,'cont', ['line 1: echo "legone"'])

  call s:RunDbgCmd(buf, 'cont')

  call StopVimInTerminal(buf)
endfunc

func Test_debug_def_function()
  CheckCWD
  let file =<< trim END
    vim9script
    def g:Func()
      var n: number
      def Closure(): number
          return n + 3
      enddef
      n += Closure()
      echo 'result: ' .. n
    enddef

    def g:FuncWithArgs(text: string, nr: number, ...items: list<number>)
      echo text .. nr
      for it in items
        echo it
      endfor
      echo "done"
    enddef

    def g:FuncWithDict()
      var d = {
         a: 1,
         b: 2,
         }
         # comment
         def Inner()
           eval 1 + 2
         enddef
    enddef

    def g:FuncComment()
      # comment
      echo "first"
         .. "one"
      # comment
      echo "second"
    enddef

    def g:FuncForLoop()
      eval 1 + 2
      for i in [11, 22, 33]
        eval i + 2
      endfor
      echo "done"
    enddef

    def g:FuncWithSplitLine()
        eval 1 + 2
           | eval 2 + 3
    enddef
  END
  call writefile(file, 'Xtest.vim', 'D')

  let buf = RunVimInTerminal('-S Xtest.vim', {})

  call s:RunDbgCmd(buf,
                \ ':debug call Func()',
                \ ['cmd: call Func()'])
  call s:RunDbgCmd(buf, 'next', ['result: 3'])
  call term_sendkeys(buf, "\r")
  call s:RunDbgCmd(buf, 'cont')

  call s:RunDbgCmd(buf,
                \ ':debug call FuncWithArgs("asdf", 42, 1, 2, 3)',
                \ ['cmd: call FuncWithArgs("asdf", 42, 1, 2, 3)'])
  call s:RunDbgCmd(buf, 'step', ['line 1: echo text .. nr'])
  call s:RunDbgCmd(buf, 'echo text', ['asdf'])
  call s:RunDbgCmd(buf, 'echo nr', ['42'])
  call s:RunDbgCmd(buf, 'echo items', ['[1, 2, 3]'])
  call s:RunDbgCmd(buf, 'step', ['asdf42', 'function FuncWithArgs', 'line 2:   for it in items'])
  call s:RunDbgCmd(buf, 'step', ['function FuncWithArgs', 'line 2: for it in items'])
  call s:RunDbgCmd(buf, 'echo it', ['0'])
  call s:RunDbgCmd(buf, 'step', ['line 3: echo it'])
  call s:RunDbgCmd(buf, 'echo it', ['1'])
  call s:RunDbgCmd(buf, 'step', ['1', 'function FuncWithArgs', 'line 4: endfor'])
  call s:RunDbgCmd(buf, 'step', ['line 2: for it in items'])
  call s:RunDbgCmd(buf, 'echo it', ['1'])
  call s:RunDbgCmd(buf, 'step', ['line 3: echo it'])
  call s:RunDbgCmd(buf, 'step', ['2', 'function FuncWithArgs', 'line 4: endfor'])
  call s:RunDbgCmd(buf, 'step', ['line 2: for it in items'])
  call s:RunDbgCmd(buf, 'echo it', ['2'])
  call s:RunDbgCmd(buf, 'step', ['line 3: echo it'])
  call s:RunDbgCmd(buf, 'step', ['3', 'function FuncWithArgs', 'line 4: endfor'])
  call s:RunDbgCmd(buf, 'step', ['line 2: for it in items'])
  call s:RunDbgCmd(buf, 'step', ['line 5: echo "done"'])
  call s:RunDbgCmd(buf, 'cont')

  call s:RunDbgCmd(buf,
                \ ':debug call FuncWithDict()',
                \ ['cmd: call FuncWithDict()'])
  call s:RunDbgCmd(buf, 'step', ['line 1: var d = {  a: 1,  b: 2,  }'])
  call s:RunDbgCmd(buf, 'step', ['line 6: def Inner()'])
  call s:RunDbgCmd(buf, 'cont')

  call s:RunDbgCmd(buf, ':breakadd func 1 FuncComment')
  call s:RunDbgCmd(buf, ':call FuncComment()', ['function FuncComment', 'line 2: echo "first"  .. "one"'])
  call s:RunDbgCmd(buf, ':breakadd func 3 FuncComment')
  call s:RunDbgCmd(buf, 'cont', ['function FuncComment', 'line 5: echo "second"'])
  call s:RunDbgCmd(buf, 'cont')

  call s:RunDbgCmd(buf, ':breakadd func 2 FuncForLoop')
  call s:RunDbgCmd(buf, ':call FuncForLoop()', ['function FuncForLoop', 'line 2:   for i in [11, 22, 33]'])
  call s:RunDbgCmd(buf, 'step', ['line 2: for i in [11, 22, 33]'])
  call s:RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 3: eval i + 2'])
  call s:RunDbgCmd(buf, 'echo i', ['11'])
  call s:RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 4: endfor'])
  call s:RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 2: for i in [11, 22, 33]'])
  call s:RunDbgCmd(buf, 'next', ['line 3: eval i + 2'])
  call s:RunDbgCmd(buf, 'echo i', ['22'])

  call s:RunDbgCmd(buf, 'breakdel *')
  call s:RunDbgCmd(buf, 'cont')

  call s:RunDbgCmd(buf, ':breakadd func FuncWithSplitLine')
  call s:RunDbgCmd(buf, ':call FuncWithSplitLine()', ['function FuncWithSplitLine', 'line 1: eval 1 + 2 | eval 2 + 3'])

  call s:RunDbgCmd(buf, 'cont')
  call StopVimInTerminal(buf)
endfunc

func Test_debug_def_function_with_lambda()
  CheckCWD
  let lines =<< trim END
     vim9script
     def g:Func()
       var s = 'a'
       ['b']->map((_, v) => s)
       echo "done"
     enddef
     breakadd func 2 g:Func
  END
  call writefile(lines, 'XtestLambda.vim', 'D')

  let buf = RunVimInTerminal('-S XtestLambda.vim', {})

  call s:RunDbgCmd(buf,
                \ ':call g:Func()',
                \ ['function Func', 'line 2: [''b'']->map((_, v) => s)'])
  call s:RunDbgCmd(buf,
                \ 'next',
                \ ['function Func', 'line 3: echo "done"'])

  call s:RunDbgCmd(buf, 'cont')
  call StopVimInTerminal(buf)
endfunc

def Test_debug_backtrace_level()
  CheckCWD
  var lines =<< trim END
    let s:file1_var = 'file1'
    let g:global_var = 'global'

    func s:File1Func( arg )
      let s:file1_var .= a:arg
      let local_var = s:file1_var .. ' test1'
      let g:global_var .= local_var
      source Xtest2.vim
    endfunc

    call s:File1Func( 'arg1' )
  END
  writefile(lines, 'Xtest1.vim', 'D')

  lines =<< trim END
    let s:file2_var = 'file2'

    func s:File2Func( arg )
      let s:file2_var .= a:arg
      let local_var = s:file2_var .. ' test2'
      let g:global_var .= local_var
    endfunc

    call s:File2Func( 'arg2' )
  END
  writefile(lines, 'Xtest2.vim', 'D')

  var file1 = getcwd() .. '/Xtest1.vim'
  var file2 = getcwd() .. '/Xtest2.vim'

  # set a breakpoint and source file1.vim
  var buf = g:RunVimInTerminal(
        '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim',
        {wait_for_ruler: 0})

  s:CheckDbgOutput(buf, [
        'Breakpoint in "' .. file1 .. '" line 1',
        'Entering Debug mode.  Type "cont" to continue.',
        'command line..script ' .. file1,
        'line 1: let s:file1_var = ''file1'''
        ], {msec: 5000})

  # step through the initial declarations
  s:RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] )
  s:RunDbgCmd(buf, 'step', [ 'line 4: func s:File1Func( arg )' ] )
  s:RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
  s:RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
  s:RunDbgCmd(buf, 'echo global_var', [ 'global' ] )

  # step in to the first function
  s:RunDbgCmd(buf, 'step', [ 'line 11: call s:File1Func( ''arg1'' )' ] )
  s:RunDbgCmd(buf, 'step', [ 'line 1: let s:file1_var .= a:arg' ] )
  s:RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
  s:RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
  s:RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
  s:RunDbgCmd(buf,
                'echo global_var',
                [ 'E121: Undefined variable: global_var' ] )
  s:RunDbgCmd(buf,
                'echo local_var',
                [ 'E121: Undefined variable: local_var' ] )
  s:RunDbgCmd(buf,
                'echo l:local_var',
                [ 'E121: Undefined variable: l:local_var' ] )

  # backtrace up
  s:RunDbgCmd(buf, 'backtrace', [
        '\V>backtrace',
        '\V  2 command line',
        '\V  1 script ' .. file1 .. '[11]',
        '\V->0 function <SNR>\.\*_File1Func',
        '\Vline 1: let s:file1_var .= a:arg',
        ],
        { match: 'pattern' } )
  s:RunDbgCmd(buf, 'up', [ '>up' ] )

  s:RunDbgCmd(buf, 'backtrace', [
        '\V>backtrace',
        '\V  2 command line',
        '\V->1 script ' .. file1 .. '[11]',
        '\V  0 function <SNR>\.\*_File1Func',
        '\Vline 1: let s:file1_var .= a:arg',
        ],
        { match: 'pattern' } )

  # Expression evaluation in the script frame (not the function frame)
  # FIXME: Unexpected in this scope (a: should not be visible)
  s:RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
  s:RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
  s:RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
  # FIXME: Unexpected in this scope (global should be found)
  s:RunDbgCmd(buf,
                'echo global_var',
                [ 'E121: Undefined variable: global_var' ] )
  s:RunDbgCmd(buf,
                'echo local_var',
                [ 'E121: Undefined variable: local_var' ] )
  s:RunDbgCmd(buf,
                'echo l:local_var',
                [ 'E121: Undefined variable: l:local_var' ] )


  # step while backtraced jumps to the latest frame
  s:RunDbgCmd(buf, 'step', [
        'line 2: let local_var = s:file1_var .. '' test1''' ] )
  s:RunDbgCmd(buf, 'backtrace', [
        '\V>backtrace',
        '\V  2 command line',
        '\V  1 script ' .. file1 .. '[11]',
        '\V->0 function <SNR>\.\*_File1Func',
        '\Vline 2: let local_var = s:file1_var .. '' test1''',
        ],
        { match: 'pattern' } )

  s:RunDbgCmd(buf, 'step', [ 'line 3: let g:global_var .= local_var' ] )
  s:RunDbgCmd(buf, 'echo local_var', [ 'file1arg1 test1' ] )
  s:RunDbgCmd(buf, 'echo l:local_var', [ 'file1arg1 test1' ] )

  s:RunDbgCmd(buf, 'step', [ 'line 4: source Xtest2.vim' ] )
  s:RunDbgCmd(buf, 'step', [ 'line 1: let s:file2_var = ''file2''' ] )
  s:RunDbgCmd(buf, 'backtrace', [
        '\V>backtrace',
        '\V  3 command line',
        '\V  2 script ' .. file1 .. '[11]',
        '\V  1 function <SNR>\.\*_File1Func[4]',
        '\V->0 script ' .. file2,
        '\Vline 1: let s:file2_var = ''file2''',
        ],
        { match: 'pattern' } )

  # Expression evaluation in the script frame file2 (not the function frame)
  s:RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
  s:RunDbgCmd(buf,
        'echo s:file1_var',
        [ 'E121: Undefined variable: s:file1_var' ] )
  s:RunDbgCmd(buf, 'echo g:global_var', [ 'globalfile1arg1 test1' ] )
  s:RunDbgCmd(buf, 'echo global_var', [ 'globalfile1arg1 test1' ] )
  s:RunDbgCmd(buf,
                'echo local_var',
                [ 'E121: Undefined variable: local_var' ] )
  s:RunDbgCmd(buf,
                'echo l:local_var',
                [ 'E121: Undefined variable: l:local_var' ] )
  s:RunDbgCmd(buf,
                'echo s:file2_var',
                [ 'E121: Undefined variable: s:file2_var' ] )

  s:RunDbgCmd(buf, 'step', [ 'line 3: func s:File2Func( arg )' ] )
  s:RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )

  # Up the stack to the other script context
  s:RunDbgCmd(buf, 'up')
  s:RunDbgCmd(buf, 'backtrace', [
        '\V>backtrace',
        '\V  3 command line',
        '\V  2 script ' .. file1 .. '[11]',
        '\V->1 function <SNR>\.\*_File1Func[4]',
        '\V  0 script ' .. file2,
        '\Vline 3: func s:File2Func( arg )',
        ],
        { match: 'pattern' } )
  # FIXME: Unexpected. Should see the a: and l: dicts from File1Func
  s:RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
  s:RunDbgCmd(buf,
        'echo l:local_var',
        [ 'E121: Undefined variable: l:local_var' ] )

  s:RunDbgCmd(buf, 'up')
  s:RunDbgCmd(buf, 'backtrace', [
        '\V>backtrace',
        '\V  3 command line',
        '\V->2 script ' .. file1 .. '[11]',
        '\V  1 function <SNR>\.\*_File1Func[4]',
        '\V  0 script ' .. file2,
        '\Vline 3: func s:File2Func( arg )',
        ],
        { match: 'pattern' } )

  # FIXME: Unexpected (wrong script vars are used)
  s:RunDbgCmd(buf,
        'echo s:file1_var',
        [ 'E121: Undefined variable: s:file1_var' ] )
  s:RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )

  s:RunDbgCmd(buf, 'cont')
  g:StopVimInTerminal(buf)
enddef

" Test for setting a breakpoint on a :endif where the :if condition is false
" and then quit the script. This should generate an interrupt.
func Test_breakpt_endif_intr()
  func F()
    let g:Xpath ..= 'a'
    if v:false
      let g:Xpath ..= 'b'
    endif
    invalid_command
  endfunc

  let g:Xpath = ''
  breakadd func 4 F
  try
    let caught_intr = 0
    debuggreedy
    call feedkeys(":call F()\<CR>quit\<CR>", "xt")
  catch /^Vim:Interrupt$/
    call assert_match('\.F, line 4', v:throwpoint)
    let caught_intr = 1
  endtry
  0debuggreedy
  call assert_equal(1, caught_intr)
  call assert_equal('a', g:Xpath)
  breakdel *
  delfunc F
endfunc

" Test for setting a breakpoint on a :else where the :if condition is false
" and then quit the script. This should generate an interrupt.
func Test_breakpt_else_intr()
  func F()
    let g:Xpath ..= 'a'
    if v:false
      let g:Xpath ..= 'b'
    else
      invalid_command
    endif
    invalid_command
  endfunc

  let g:Xpath = ''
  breakadd func 4 F
  try
    let caught_intr = 0
    debuggreedy
    call feedkeys(":call F()\<CR>quit\<CR>", "xt")
  catch /^Vim:Interrupt$/
    call assert_match('\.F, line 4', v:throwpoint)
    let caught_intr = 1
  endtry
  0debuggreedy
  call assert_equal(1, caught_intr)
  call assert_equal('a', g:Xpath)
  breakdel *
  delfunc F
endfunc

" Test for setting a breakpoint on a :endwhile where the :while condition is
" false and then quit the script. This should generate an interrupt.
func Test_breakpt_endwhile_intr()
  func F()
    let g:Xpath ..= 'a'
    while v:false
      let g:Xpath ..= 'b'
    endwhile
    invalid_command
  endfunc

  let g:Xpath = ''
  breakadd func 4 F
  try
    let caught_intr = 0
    debuggreedy
    call feedkeys(":call F()\<CR>quit\<CR>", "xt")
  catch /^Vim:Interrupt$/
    call assert_match('\.F, line 4', v:throwpoint)
    let caught_intr = 1
  endtry
  0debuggreedy
  call assert_equal(1, caught_intr)
  call assert_equal('a', g:Xpath)
  breakdel *
  delfunc F
endfunc

" Test for setting a breakpoint on a script local function
func Test_breakpt_scriptlocal_func()
  let g:Xpath = ''
  func s:G()
    let g:Xpath ..= 'a'
  endfunc

  let funcname = expand("<SID>") .. "G"
  exe "breakadd func 1 " .. funcname
  debuggreedy
  redir => output
  call feedkeys(":call " .. funcname .. "()\<CR>c\<CR>", "xt")
  redir END
  0debuggreedy
  call assert_match('Breakpoint in "' .. funcname .. '" line 1', output)
  call assert_equal('a', g:Xpath)
  breakdel *
  exe "delfunc " .. funcname
endfunc

" vim: shiftwidth=2 sts=2 expandtab