Test for new thread creation
- don't clear the stack trace on continue - track running status properly (ish) - mark threads (running) when the app is executing - indicate the "current" thread with a different icon TODO: - allow user to specify current thread? - track running status of threads individually? - allow to pause/continue specific threads?
This commit is contained in:
parent
e2ca9b5318
commit
e9e0e9e5b9
7 changed files with 495 additions and 110 deletions
|
|
@ -429,6 +429,9 @@ class DebugSession( object ):
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
self._stackTraceView.OnContinued()
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def StepInto( self ):
|
def StepInto( self ):
|
||||||
if self._stackTraceView.GetCurrentThreadId() is None:
|
if self._stackTraceView.GetCurrentThreadId() is None:
|
||||||
|
|
@ -440,6 +443,8 @@ class DebugSession( object ):
|
||||||
'threadId': self._stackTraceView.GetCurrentThreadId()
|
'threadId': self._stackTraceView.GetCurrentThreadId()
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
self._stackTraceView.OnContinued()
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def StepOut( self ):
|
def StepOut( self ):
|
||||||
|
|
@ -452,16 +457,39 @@ class DebugSession( object ):
|
||||||
'threadId': self._stackTraceView.GetCurrentThreadId()
|
'threadId': self._stackTraceView.GetCurrentThreadId()
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
self._stackTraceView.OnContinued()
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
def Continue( self ):
|
def Continue( self ):
|
||||||
if self._connection:
|
if not self._connection:
|
||||||
self._stackTraceView.Continue()
|
|
||||||
else:
|
|
||||||
self.Start()
|
self.Start()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._stackTraceView.GetCurrentThreadId() is None:
|
||||||
|
utils.UserMessage( 'No current thread', persist = True )
|
||||||
|
return
|
||||||
|
|
||||||
|
self._connection.DoRequest( None, {
|
||||||
|
'command': 'continue',
|
||||||
|
'arguments': {
|
||||||
|
'threadId': self._stackTraceView.GetCurrentThreadId(),
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
self._stackTraceView.OnContinued()
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def Pause( self ):
|
def Pause( self ):
|
||||||
self._stackTraceView.Pause()
|
if self._stackTraceView.GetCurrentThreadId() is None:
|
||||||
|
utils.UserMessage( 'No current thread', persist = True )
|
||||||
|
return
|
||||||
|
|
||||||
|
self._connection.DoRequest( None, {
|
||||||
|
'command': 'pause',
|
||||||
|
'arguments': {
|
||||||
|
'threadId': self._stackTraceView.GetCurrentThreadId(),
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def ExpandVariable( self ):
|
def ExpandVariable( self ):
|
||||||
|
|
@ -1098,6 +1126,7 @@ class DebugSession( object ):
|
||||||
def OnEvent_exited( self, message ):
|
def OnEvent_exited( self, message ):
|
||||||
utils.UserMessage( 'The debugee exited with status code: {}'.format(
|
utils.UserMessage( 'The debugee exited with status code: {}'.format(
|
||||||
message[ 'body' ][ 'exitCode' ] ) )
|
message[ 'body' ][ 'exitCode' ] ) )
|
||||||
|
self.SetCurrentFrame( None )
|
||||||
|
|
||||||
def OnEvent_process( self, message ):
|
def OnEvent_process( self, message ):
|
||||||
utils.UserMessage( 'The debugee was started: {}'.format(
|
utils.UserMessage( 'The debugee was started: {}'.format(
|
||||||
|
|
@ -1107,7 +1136,8 @@ class DebugSession( object ):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def OnEvent_continued( self, message ):
|
def OnEvent_continued( self, message ):
|
||||||
pass
|
self._stackTraceView.OnContinued()
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
def Clear( self ):
|
def Clear( self ):
|
||||||
self._codeView.Clear()
|
self._codeView.Clear()
|
||||||
|
|
@ -1142,6 +1172,7 @@ class DebugSession( object ):
|
||||||
def OnEvent_terminated( self, message ):
|
def OnEvent_terminated( self, message ):
|
||||||
# We will handle this when the server actually exists
|
# We will handle this when the server actually exists
|
||||||
utils.UserMessage( "Debugging was terminated by the server." )
|
utils.UserMessage( "Debugging was terminated by the server." )
|
||||||
|
self.SetCurrentFrame( None )
|
||||||
|
|
||||||
def OnEvent_output( self, message ):
|
def OnEvent_output( self, message ):
|
||||||
if self._outputView:
|
if self._outputView:
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,30 @@ import logging
|
||||||
from vimspector import utils
|
from vimspector import utils
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Need to do something a bit like the Variables stuff
|
||||||
|
#
|
||||||
|
# class Thread:
|
||||||
|
# PAUSED = 0
|
||||||
|
# RUNNING = 1
|
||||||
|
# state = RUNNING
|
||||||
|
#
|
||||||
|
# thread: dict
|
||||||
|
# stacktrace: list
|
||||||
|
#
|
||||||
|
# def __init__( self, thread ):
|
||||||
|
# self.thread = thread
|
||||||
|
# self.stacktrace = None
|
||||||
|
#
|
||||||
|
# def ShouldExpand( self, current_thread_id ):
|
||||||
|
# return self.thread[ 'id' ] == current_thread_id
|
||||||
|
|
||||||
|
|
||||||
class StackTraceView( object ):
|
class StackTraceView( object ):
|
||||||
|
class ThreadRequestState:
|
||||||
|
NO = 0
|
||||||
|
REQUESTING = 1
|
||||||
|
PENDING = 2
|
||||||
|
|
||||||
def __init__( self, session, win ):
|
def __init__( self, session, win ):
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger )
|
||||||
|
|
@ -48,14 +71,8 @@ class StackTraceView( object ):
|
||||||
self._line_to_frame = {}
|
self._line_to_frame = {}
|
||||||
self._line_to_thread = {}
|
self._line_to_thread = {}
|
||||||
|
|
||||||
# TODO: We really need a proper state model
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
#
|
self._pending_thread_request = None
|
||||||
# AWAIT_CONNECTION -- OnServerReady / RequestThreads --> REQUESTING_THREADS
|
|
||||||
# REQUESTING -- OnGotThreads / RequestScopes --> REQUESTING_SCOPES
|
|
||||||
#
|
|
||||||
# When we attach using gdbserver, this whole thing breaks because we request
|
|
||||||
# the threads over and over and get duff data back on later threads.
|
|
||||||
self._requesting_threads = False
|
|
||||||
|
|
||||||
|
|
||||||
def GetCurrentThreadId( self ):
|
def GetCurrentThreadId( self ):
|
||||||
|
|
@ -75,11 +92,12 @@ class StackTraceView( object ):
|
||||||
|
|
||||||
def ConnectionUp( self, connection ):
|
def ConnectionUp( self, connection ):
|
||||||
self._connection = connection
|
self._connection = connection
|
||||||
self._requesting_threads = False
|
|
||||||
|
|
||||||
def ConnectionClosed( self ):
|
def ConnectionClosed( self ):
|
||||||
self.Clear()
|
self.Clear()
|
||||||
self._connection = None
|
self._connection = None
|
||||||
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
|
self._pending_thread_request = None
|
||||||
|
|
||||||
def Reset( self ):
|
def Reset( self ):
|
||||||
self.Clear()
|
self.Clear()
|
||||||
|
|
@ -89,64 +107,82 @@ class StackTraceView( object ):
|
||||||
|
|
||||||
self._scratch_buffers = []
|
self._scratch_buffers = []
|
||||||
self._buf = None
|
self._buf = None
|
||||||
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
|
self._pending_thread_request = None
|
||||||
|
|
||||||
def LoadThreads( self, infer_current_frame ):
|
def LoadThreads( self, infer_current_frame, reason = '' ):
|
||||||
pending_request = False
|
if self._requesting_threads != StackTraceView.ThreadRequestState.NO:
|
||||||
if self._requesting_threads:
|
self._requesting_threads = StackTraceView.ThreadRequestState.PENDING
|
||||||
pending_request = True
|
self._pending_thread_request = ( infer_current_frame, reason )
|
||||||
return
|
return
|
||||||
|
|
||||||
def consume_threads( message ):
|
def consume_threads( message ):
|
||||||
self._requesting_threads = False
|
if self._requesting_threads == StackTraceView.ThreadRequestState.PENDING:
|
||||||
|
# We may have hit a thread event, so try again.
|
||||||
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
|
self.LoadThreads( *self._pending_thread_request )
|
||||||
|
return
|
||||||
|
|
||||||
if not message[ 'body' ][ 'threads' ]:
|
if not message[ 'body' ][ 'threads' ]:
|
||||||
if pending_request:
|
|
||||||
# We may have hit a thread event, so try again.
|
|
||||||
self.LoadThreads( infer_current_frame )
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# This is a protocol error. It is required to return at least one!
|
# This is a protocol error. It is required to return at least one!
|
||||||
utils.UserMessage( 'Server returned no threads. Is it running?',
|
utils.UserMessage( 'Protocol error: Server returned no threads',
|
||||||
persist = True )
|
persist = False,
|
||||||
|
error = True )
|
||||||
|
|
||||||
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
|
self._pending_thread_request = None
|
||||||
self._threads.clear()
|
self._threads.clear()
|
||||||
|
|
||||||
|
requesting = False
|
||||||
for thread in message[ 'body' ][ 'threads' ]:
|
for thread in message[ 'body' ][ 'threads' ]:
|
||||||
self._threads.append( thread )
|
self._threads.append( thread )
|
||||||
|
|
||||||
if infer_current_frame and thread[ 'id' ] == self._current_thread:
|
if infer_current_frame and thread[ 'id' ] == self._current_thread:
|
||||||
self._LoadStackTrace( thread, True )
|
self._LoadStackTrace( thread, True, reason )
|
||||||
|
requesting = True
|
||||||
elif infer_current_frame and self._current_thread is None:
|
elif infer_current_frame and self._current_thread is None:
|
||||||
self._current_thread = thread[ 'id' ]
|
self._current_thread = thread[ 'id' ]
|
||||||
self._LoadStackTrace( thread, True )
|
self._LoadStackTrace( thread, True, reason )
|
||||||
|
requesting = True
|
||||||
|
|
||||||
|
if not requesting:
|
||||||
self._DrawThreads()
|
self._DrawThreads()
|
||||||
|
|
||||||
def failure_handler( reason, msg ):
|
def failure_handler( reason, msg ):
|
||||||
# Make sure we request them again if the request fails
|
# Make sure we request them again if the request fails
|
||||||
self._requesting_threads = False
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
|
self._pending_thread_request = None
|
||||||
|
|
||||||
self._requesting_threads = True
|
self._requesting_threads = StackTraceView.ThreadRequestState.REQUESTING
|
||||||
self._connection.DoRequest( consume_threads, {
|
self._connection.DoRequest( consume_threads, {
|
||||||
'command': 'threads',
|
'command': 'threads',
|
||||||
}, failure_handler )
|
}, failure_handler )
|
||||||
|
|
||||||
def _DrawThreads( self ):
|
def _DrawThreads( self, running = False ):
|
||||||
self._line_to_frame.clear()
|
self._line_to_frame.clear()
|
||||||
self._line_to_thread.clear()
|
self._line_to_thread.clear()
|
||||||
|
|
||||||
with utils.ModifiableScratchBuffer( self._buf ):
|
with ( utils.ModifiableScratchBuffer( self._buf ),
|
||||||
|
utils.RestoreCursorPosition() ):
|
||||||
utils.ClearBuffer( self._buf )
|
utils.ClearBuffer( self._buf )
|
||||||
|
|
||||||
for thread in self._threads:
|
for thread in self._threads:
|
||||||
|
if self._current_thread == thread[ 'id' ]:
|
||||||
|
icon = '^' if '_frames' not in thread else '>'
|
||||||
|
else:
|
||||||
icon = '+' if '_frames' not in thread else '-'
|
icon = '+' if '_frames' not in thread else '-'
|
||||||
|
|
||||||
|
# FIXME: We probably need per-thread status here
|
||||||
|
if running:
|
||||||
|
status = ' (running)'
|
||||||
|
else:
|
||||||
|
status = ''
|
||||||
|
|
||||||
line = utils.AppendToBuffer(
|
line = utils.AppendToBuffer(
|
||||||
self._buf,
|
self._buf,
|
||||||
'{0} Thread: {1}'.format( icon, thread[ 'name' ] ) )
|
f'{icon} Thread: {thread["name"]}{status}' )
|
||||||
|
|
||||||
self._line_to_thread[ line ] = thread
|
self._line_to_thread[ line ] = thread
|
||||||
|
|
||||||
self._DrawStackTrace( thread )
|
self._DrawStackTrace( thread )
|
||||||
|
|
||||||
def _LoadStackTrace( self,
|
def _LoadStackTrace( self,
|
||||||
|
|
@ -181,7 +217,6 @@ class StackTraceView( object ):
|
||||||
thread = self._line_to_thread[ current_line ]
|
thread = self._line_to_thread[ current_line ]
|
||||||
if '_frames' in thread:
|
if '_frames' in thread:
|
||||||
del thread[ '_frames' ]
|
del thread[ '_frames' ]
|
||||||
with utils.RestoreCursorPosition():
|
|
||||||
self._DrawThreads()
|
self._DrawThreads()
|
||||||
else:
|
else:
|
||||||
self._LoadStackTrace( thread, False )
|
self._LoadStackTrace( thread, False )
|
||||||
|
|
@ -205,51 +240,28 @@ class StackTraceView( object ):
|
||||||
else:
|
else:
|
||||||
return do_jump()
|
return do_jump()
|
||||||
|
|
||||||
|
def OnContinued( self, threadId = None ):
|
||||||
|
# FIXME: This tends to create a very flickery stack trace when steppping.
|
||||||
|
# Maybe we shouldn't remove the frames, but just update the running status?
|
||||||
|
# for thread in self._threads:
|
||||||
|
# if threadId is None or thread[ 'id' ] == threadId:
|
||||||
|
# thread.pop( '_frames', None )
|
||||||
|
self._DrawThreads( running=True )
|
||||||
|
|
||||||
def OnStopped( self, event ):
|
def OnStopped( self, event ):
|
||||||
if 'threadId' in event:
|
if 'threadId' in event:
|
||||||
self._current_thread = event[ 'threadId' ]
|
self._current_thread = event[ 'threadId' ]
|
||||||
elif event.get( 'allThreadsStopped', False ) and self._threads:
|
elif event.get( 'allThreadsStopped', False ) and self._threads:
|
||||||
self._current_thread = self._threads[ 0 ][ 'id' ]
|
self._current_thread = self._threads[ 0 ][ 'id' ]
|
||||||
|
|
||||||
if self._current_thread is not None:
|
self.LoadThreads( True, 'stopped' )
|
||||||
for thread in self._threads:
|
|
||||||
if thread[ 'id' ] == self._current_thread:
|
|
||||||
self._LoadStackTrace( thread, True, 'stopped' )
|
|
||||||
return
|
|
||||||
|
|
||||||
self.LoadThreads( True )
|
|
||||||
|
|
||||||
def OnThreadEvent( self, event ):
|
def OnThreadEvent( self, event ):
|
||||||
if event[ 'reason' ] == 'started' and self._current_thread is None:
|
if event[ 'reason' ] == 'started' and self._current_thread is None:
|
||||||
self._current_thread = event[ 'threadId' ]
|
self._current_thread = event[ 'threadId' ]
|
||||||
self.LoadThreads( True )
|
self.LoadThreads( True )
|
||||||
|
else:
|
||||||
def Continue( self ):
|
self.LoadThreads( False )
|
||||||
if self._current_thread is None:
|
|
||||||
utils.UserMessage( 'No current thread', persist = True )
|
|
||||||
return
|
|
||||||
|
|
||||||
self._session._connection.DoRequest( None, {
|
|
||||||
'command': 'continue',
|
|
||||||
'arguments': {
|
|
||||||
'threadId': self._current_thread,
|
|
||||||
},
|
|
||||||
} )
|
|
||||||
|
|
||||||
self._session.ClearCurrentFrame()
|
|
||||||
self.LoadThreads( True )
|
|
||||||
|
|
||||||
def Pause( self ):
|
|
||||||
if self._current_thread is None:
|
|
||||||
utils.UserMessage( 'No current thread', persist = True )
|
|
||||||
return
|
|
||||||
|
|
||||||
self._session._connection.DoRequest( None, {
|
|
||||||
'command': 'pause',
|
|
||||||
'arguments': {
|
|
||||||
'threadId': self._current_thread,
|
|
||||||
},
|
|
||||||
} )
|
|
||||||
|
|
||||||
def _DrawStackTrace( self, thread ):
|
def _DrawStackTrace( self, thread ):
|
||||||
if '_frames' not in thread:
|
if '_frames' not in thread:
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ echo " * BASEDIR_CMD=$BASEDIR_CMD"
|
||||||
echo "%SETUP - Building test programs..."
|
echo "%SETUP - Building test programs..."
|
||||||
set -e
|
set -e
|
||||||
pushd tests/testdata/cpp/simple
|
pushd tests/testdata/cpp/simple
|
||||||
make clean all
|
make all
|
||||||
popd
|
popd
|
||||||
set +e
|
set +e
|
||||||
echo "%DONE - built test programs"
|
echo "%DONE - built test programs"
|
||||||
|
|
|
||||||
|
|
@ -91,3 +91,31 @@ endfunc
|
||||||
function! ThisTestIsFlaky()
|
function! ThisTestIsFlaky()
|
||||||
let g:test_is_flaky = v:true
|
let g:test_is_flaky = v:true
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! AssertMatchist( expected, actual ) abort
|
||||||
|
let ret = assert_equal( len( a:expected ), len( a:actual ) )
|
||||||
|
let len = min( [ len( a:expected ), len( a:actual ) ] )
|
||||||
|
let idx = 0
|
||||||
|
while idx < len
|
||||||
|
let ret += assert_match( a:expected[ idx ], a:actual[ idx ] )
|
||||||
|
let idx += 1
|
||||||
|
endwhile
|
||||||
|
return ret
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
|
||||||
|
function! GetBufLine( buf, start, end = '$' )
|
||||||
|
if type( a:start ) != v:t_string && a:start < 0
|
||||||
|
let start = getbufinfo( a:buf )[ 0 ].linecount + a:start
|
||||||
|
else
|
||||||
|
let start = a:start
|
||||||
|
endif
|
||||||
|
|
||||||
|
if type( a:end ) != v:t_string && a:end < 0
|
||||||
|
let end = getbufinfo( a:buf )[ 0 ].linecount + a:end
|
||||||
|
else
|
||||||
|
let end = a:end
|
||||||
|
endif
|
||||||
|
|
||||||
|
return getbufline( a:buf, start, end )
|
||||||
|
endfunction
|
||||||
|
|
|
||||||
|
|
@ -10,38 +10,338 @@ endfunction
|
||||||
|
|
||||||
function! s:StartDebugging()
|
function! s:StartDebugging()
|
||||||
exe 'edit ' . s:fn
|
exe 'edit ' . s:fn
|
||||||
call vimspector#SetLineBreakpoint( s:fn, 13 )
|
call vimspector#SetLineBreakpoint( s:fn, 15 )
|
||||||
call vimspector#Launch()
|
call vimspector#Launch()
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 13, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 15, 1 )
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! Test_Multiple_Threads()
|
function! Test_Multiple_Threads_Continue()
|
||||||
call vimspector#SetLineBreakpoint( s:fn, 41 )
|
|
||||||
call vimspector#SetLineBreakpoint( s:fn, 51 )
|
let thread_l = 67
|
||||||
|
let notify_l = 74
|
||||||
|
|
||||||
|
call vimspector#SetLineBreakpoint( s:fn, thread_l )
|
||||||
|
call vimspector#SetLineBreakpoint( s:fn, notify_l )
|
||||||
call s:StartDebugging()
|
call s:StartDebugging()
|
||||||
|
|
||||||
call vimspector#Continue()
|
call vimspector#Continue()
|
||||||
|
|
||||||
" As we step through the thread creation we should get Thread events
|
" As we step through the thread creation we should get Thread events
|
||||||
|
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 41, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call cursor( 1, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '> Thread: Thread #1',
|
||||||
|
\ ' .*: threads!main@threads.cpp:' . string( thread_l )
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ 1,
|
||||||
|
\ 2 )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
call vimspector#Continue()
|
call vimspector#Continue()
|
||||||
|
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 41, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call cursor( 1, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '> Thread: Thread #1',
|
||||||
|
\ ' .*: threads!main@threads.cpp:' . string( thread_l )
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ 1,
|
||||||
|
\ 2 )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
call vimspector#Continue()
|
call vimspector#Continue()
|
||||||
|
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 41, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call cursor( 1, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '> Thread: Thread #1',
|
||||||
|
\ ' .*: threads!main@threads.cpp:' . string( thread_l )
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ 1,
|
||||||
|
\ 2 )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
call vimspector#Continue()
|
call vimspector#Continue()
|
||||||
|
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 41, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call cursor( 1, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '> Thread: Thread #1',
|
||||||
|
\ ' .*: threads!main@threads.cpp:' . string( thread_l )
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ 1,
|
||||||
|
\ 2 )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #4',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
call vimspector#Continue()
|
call vimspector#Continue()
|
||||||
|
|
||||||
" This is the last one
|
" This is the last one
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 41, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call cursor( 1, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '> Thread: Thread #1',
|
||||||
|
\ ' .*: threads!main@threads.cpp:' . string( thread_l )
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ 1,
|
||||||
|
\ 2 )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #5',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
call vimspector#Continue()
|
call vimspector#Continue()
|
||||||
|
|
||||||
" So we break out of the loop
|
" So we break out of the loop
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 51, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, notify_l, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '> Thread: Thread #1',
|
||||||
|
\ ' .*: threads!main@threads.cpp:' . string( notify_l )
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ 1,
|
||||||
|
\ 2 )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #6',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
call vimspector#ClearBreakpoints()
|
||||||
|
call vimspector#test#setup#Reset()
|
||||||
|
%bwipe!
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! Test_Multiple_Threads_Step()
|
||||||
|
let thread_l = 67
|
||||||
|
let thread_n = thread_l + 1
|
||||||
|
let notify_l = 74
|
||||||
|
|
||||||
|
call vimspector#SetLineBreakpoint( s:fn, thread_l )
|
||||||
|
call vimspector#SetLineBreakpoint( s:fn, notify_l )
|
||||||
|
call s:StartDebugging()
|
||||||
|
call vimspector#Continue()
|
||||||
|
|
||||||
|
" As we step through the thread creation we should get Thread events
|
||||||
|
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '> Thread: Thread #1',
|
||||||
|
\ ' .*: threads!main@threads.cpp:' . string( thread_l )
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ 1,
|
||||||
|
\ 2 )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#StepOver()
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#Continue()
|
||||||
|
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#StepOver()
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ -1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#Continue()
|
||||||
|
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ -1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#StepOver()
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ '+ Thread: Thread #4',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ -2,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#Continue()
|
||||||
|
|
||||||
|
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ '+ Thread: Thread #4',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ -2,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#StepOver()
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ '+ Thread: Thread #4',
|
||||||
|
\ '+ Thread: Thread #5',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ -3,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#Continue()
|
||||||
|
|
||||||
|
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ '+ Thread: Thread #4',
|
||||||
|
\ '+ Thread: Thread #5',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ -3,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#StepOver()
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #2',
|
||||||
|
\ '+ Thread: Thread #3',
|
||||||
|
\ '+ Thread: Thread #4',
|
||||||
|
\ '+ Thread: Thread #5',
|
||||||
|
\ '+ Thread: Thread #6',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ -4,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
call vimspector#Continue()
|
||||||
|
|
||||||
|
|
||||||
|
" So we break out of the loop
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, notify_l, 1 )
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '+ Thread: Thread #6',
|
||||||
|
\ ],
|
||||||
|
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||||
|
\ '$',
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
call vimspector#ClearBreakpoints()
|
call vimspector#ClearBreakpoints()
|
||||||
call vimspector#test#setup#Reset()
|
call vimspector#test#setup#Reset()
|
||||||
|
|
|
||||||
29
tests/testdata/cpp/simple/threads.cpp
vendored
29
tests/testdata/cpp/simple/threads.cpp
vendored
|
|
@ -1,6 +1,8 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
@ -36,17 +38,40 @@ int main( int argc, char ** argv )
|
||||||
auto eng = std::default_random_engine() ;
|
auto eng = std::default_random_engine() ;
|
||||||
auto dist = std::uniform_int_distribution<int>( 250, 1000 );
|
auto dist = std::uniform_int_distribution<int>( 250, 1000 );
|
||||||
|
|
||||||
|
std::mutex m;
|
||||||
|
std::condition_variable v;
|
||||||
|
bool ready = false;
|
||||||
|
{
|
||||||
|
std::lock_guard l(m);
|
||||||
|
|
||||||
|
std::cout << "Preparing..." << '\n';
|
||||||
|
|
||||||
for ( int i = 0; i < numThreads; ++i )
|
for ( int i = 0; i < numThreads; ++i )
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
threads.emplace_back( [&,tnum=i]() {
|
auto tp = [&,tnum=i]() {
|
||||||
|
// Wait for the go-ahead
|
||||||
|
{
|
||||||
|
std::unique_lock l(m);
|
||||||
|
while (!ready) {
|
||||||
|
v.wait(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << "Started thread " << tnum << '\n';
|
std::cout << "Started thread " << tnum << '\n';
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(
|
||||||
5s + std::chrono::milliseconds( dist( eng ) ) );
|
5s + std::chrono::milliseconds( dist( eng ) ) );
|
||||||
std::cout << "Completed thread " << tnum << '\n';
|
std::cout << "Completed thread " << tnum << '\n';
|
||||||
});
|
};
|
||||||
|
|
||||||
|
threads.emplace_back( tp );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::cout << "Ready to go!" << '\n';
|
||||||
|
ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
v.notify_all();
|
||||||
|
|
||||||
for ( int i = 0; i < numThreads; ++i )
|
for ( int i = 0; i < numThreads; ++i )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,6 @@ function! ClearDown()
|
||||||
call vimspector#test#setup#ClearDown()
|
call vimspector#test#setup#ClearDown()
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:assert_match_list( expected, actual ) abort
|
|
||||||
let ret = assert_equal( len( a:expected ), len( a:actual ) )
|
|
||||||
let len = min( [ len( a:expected ), len( a:actual ) ] )
|
|
||||||
let idx = 0
|
|
||||||
while idx < len
|
|
||||||
let ret += assert_match( a:expected[ idx ], a:actual[ idx ] )
|
|
||||||
let idx += 1
|
|
||||||
endwhile
|
|
||||||
return ret
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:StartDebugging( ... )
|
function! s:StartDebugging( ... )
|
||||||
if a:0 == 0
|
if a:0 == 0
|
||||||
let config = #{
|
let config = #{
|
||||||
|
|
@ -222,7 +211,7 @@ function! Test_ExpandVariables()
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
|
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ '- Scope: Locals',
|
\ '- Scope: Locals',
|
||||||
\ ' \*- t (Test): {...}',
|
\ ' \*- t (Test): {...}',
|
||||||
|
|
@ -240,7 +229,7 @@ function! Test_ExpandVariables()
|
||||||
" Step - stays expanded
|
" Step - stays expanded
|
||||||
call vimspector#StepOver()
|
call vimspector#StepOver()
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ '- Scope: Locals',
|
\ '- Scope: Locals',
|
||||||
\ ' - t (Test): {...}',
|
\ ' - t (Test): {...}',
|
||||||
|
|
@ -289,7 +278,7 @@ function! Test_ExpandVariables()
|
||||||
call setpos( '.', [ 0, 2, 1 ] )
|
call setpos( '.', [ 0, 2, 1 ] )
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ '- Scope: Locals',
|
\ '- Scope: Locals',
|
||||||
\ ' - t (Test): {...}',
|
\ ' - t (Test): {...}',
|
||||||
|
|
@ -389,7 +378,7 @@ function! Test_ExpandWatch()
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
|
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ 'Watches: ----',
|
\ 'Watches: ----',
|
||||||
\ 'Expression: t',
|
\ 'Expression: t',
|
||||||
|
|
@ -408,7 +397,7 @@ function! Test_ExpandWatch()
|
||||||
" Step - stays expanded
|
" Step - stays expanded
|
||||||
call vimspector#StepOver()
|
call vimspector#StepOver()
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ 'Watches: ----',
|
\ 'Watches: ----',
|
||||||
\ 'Expression: t',
|
\ 'Expression: t',
|
||||||
|
|
@ -460,7 +449,7 @@ function! Test_ExpandWatch()
|
||||||
call setpos( '.', [ 0, 3, 1 ] )
|
call setpos( '.', [ 0, 3, 1 ] )
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ 'Watches: ----',
|
\ 'Watches: ----',
|
||||||
\ 'Expression: t',
|
\ 'Expression: t',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue