" 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. Need to subtract by 1 since Vim will still wrap the message if it
  " just fits.
  let script_len = len( getcwd() .. '/Xtest1.vim' )
  let longest_line = len( 'Breakpoint in "" line 1' )
  if script_len > ( 75 - longest_line - 1 )
    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
