Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Commit

Permalink
#72: Provide a way to set breakpoint suspension policy.
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Jul 19, 2018
1 parent bf83758 commit e08d53c
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -253,14 +253,18 @@ def process_net_command(py_db, cmd_id, seq, text):
# func name: 'None': match anything. Empty: match global, specified: only method context.
# command to add some breakpoint.
# text is file\tline. Add to breakpoints dictionary
suspend_policy = "NONE"
suspend_policy = "NONE" # Can be 'NONE' or 'ALL'
is_logpoint = False
hit_condition = None
if py_db._set_breakpoints_with_id:
try:
breakpoint_id, type, file, line, func_name, condition, expression, hit_condition, is_logpoint = text.split('\t', 8)
try:
breakpoint_id, type, file, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy = text.split('\t', 9)
except ValueError: # not enough values to unpack
# No suspend_policy passed (use default).
breakpoint_id, type, file, line, func_name, condition, expression, hit_condition, is_logpoint = text.split('\t', 8)
is_logpoint = is_logpoint == 'True'
except Exception:
except ValueError: # not enough values to unpack
breakpoint_id, type, file, line, func_name, condition, expression = text.split('\t', 6)

breakpoint_id = int(breakpoint_id)
Expand All @@ -274,8 +278,8 @@ def process_net_command(py_db, cmd_id, seq, text):
expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').\
replace("@_@TAB_CHAR@_@", '\t').strip()
else:
#Note: this else should be removed after PyCharm migrates to setting
#breakpoints by id (and ideally also provides func_name).
# Note: this else should be removed after PyCharm migrates to setting
# breakpoints by id (and ideally also provides func_name).
type, file, line, func_name, suspend_policy, condition, expression = text.split('\t', 6)
# If we don't have an id given for each breakpoint, consider
# the id to be the line.
Expand Down
32 changes: 24 additions & 8 deletions ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,16 +502,25 @@ def wait_for_breakpoint_hit(self, *args, **kwargs):

def wait_for_breakpoint_hit_with_suspend_type(self, reason=REASON_STOP_ON_BREAKPOINT, get_line=False, get_name=False):
'''
108 is over
109 is return
111 is breakpoint
108 is over
109 is return
111 is breakpoint
:param reason: may be the actual reason (int or string) or a list of reasons.
'''
self.log.append('Start: wait_for_breakpoint_hit')
# wait for hit breakpoint
last = self.reader_thread.get_next_message('wait_for_breakpoint_hit. reason=%s' % (reason,))
while not ('stop_reason="%s"' % reason) in last:
if not isinstance(reason, (list, tuple)):
reason = (reason,)
while True:
last = self.reader_thread.get_next_message('wait_for_breakpoint_hit. reason=%s' % (reason,))

found = False
for r in reason:
if ('stop_reason="%s"' % (r,)) in last:
found = True
break
if found:
break

# we have something like <xml><thread id="12152656" stop_reason="111"><frame id="12453120" name="encode" ...
splitted = last.split('"')
Expand Down Expand Up @@ -620,14 +629,21 @@ def write_version(self):
def get_main_filename(self):
return self.TEST_FILE

def write_add_breakpoint(self, line, func, filename=None):
def write_add_breakpoint(self, line, func, filename=None, hit_condition=None, is_logpoint=False, suspend_policy=None):
'''
@param line: starts at 1
'''
if filename is None:
filename = self.get_main_filename()
breakpoint_id = self.next_breakpoint_id()
self.write("111\t%s\t%s\t%s\t%s\t%s\t%s\tNone\tNone" % (self.next_seq(), breakpoint_id, 'python-line', filename, line, func))
if hit_condition is None and not is_logpoint and suspend_policy is None:
# Format kept for backward compatibility tests
self.write("%s\t%s\t%s\t%s\t%s\t%s\t%s\tNone\tNone" % (
CMD_SET_BREAK, self.next_seq(), breakpoint_id, 'python-line', filename, line, func))
else:
# Format: breakpoint_id, type, file, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy
self.write("%s\t%s\t%s\t%s\t%s\t%s\t%s\tNone\tNone\t%s\t%s\t%s" % (
CMD_SET_BREAK, self.next_seq(), breakpoint_id, 'python-line', filename, line, func, hit_condition, is_logpoint, suspend_policy))
self.log.append('write_add_breakpoint: %s line: %s func: %s' % (breakpoint_id, line, func))
return breakpoint_id

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
if __name__ == '__main__':
import os
import sys
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))

if root_dirname not in sys.path:
sys.path.append(root_dirname)

import pydevd
print('before pydevd.settrace')
pydevd.settrace(port=8787)
pydevd.settrace(port=port)
print('after pydevd.settrace')
print('TEST SUCEEDED!')

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import os
import _debugger_case_remote_2
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))

if root_dirname not in sys.path:
Expand All @@ -12,7 +13,7 @@

print('before pydevd.settrace')
sys.stdout.flush()
pydevd.settrace(port=8787, patch_multiprocessing=True)
pydevd.settrace(port=port, patch_multiprocessing=True)
print('after pydevd.settrace')
sys.stdout.flush()
f = _debugger_case_remote_2.__file__
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
if __name__ == '__main__':
import os
import sys
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))

if root_dirname not in sys.path:
sys.path.append(root_dirname)

import pydevd
print('before pydevd.settrace')
pydevd.settrace(port=8787)
pydevd.settrace(port=port)
print('after pydevd.settrace')
raise ValueError('TEST SUCEEDED!')

Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
if __name__ == '__main__':
import os
import sys
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))

if root_dirname not in sys.path:
sys.path.append(root_dirname)

import pydevd
print('before pydevd.settrace')
pydevd.settrace(port=8787)
pydevd.settrace(port=port)
print('after pydevd.settrace')
for i in range(2):
try:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import threading

semaphore1 = threading.Semaphore(0)
lock = threading.Lock()
proceed = False


def thread_target():
semaphore1.release()
import time

while True:
with lock:
if proceed:
break
time.sleep(1 / 30.)


for i in range(2):
t = threading.Thread(target=thread_target)
t.start()

semaphore1.acquire() # let first thread run
semaphore1.acquire() # let second thread run

# At this point we know both other threads are already running.
print('break here')

with lock:
proceed = True

print('TEST SUCEEDED!')
64 changes: 50 additions & 14 deletions ptvsd/_vendored/pydevd/tests_python/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1330,17 +1330,22 @@ class WriterThreadCaseModuleWithEntryPoint(WriterThreadCaseMSwitch):
def get_main_filename(self):
return debugger_unittest._get_debugger_test_file('_debugger_case_module_entry_point.py')

class AbstractRemoteWriterThread(debugger_unittest.AbstractWriterThread):


def update_command_line_args(self, args):
ret = debugger_unittest.AbstractWriterThread.update_command_line_args(self, args)
ret.append(str(self.port))
return ret

#=======================================================================================================================
# WriterThreadCaseRemoteDebugger
#=======================================================================================================================
class WriterThreadCaseRemoteDebugger(debugger_unittest.AbstractWriterThread):
class WriterThreadCaseRemoteDebugger(AbstractRemoteWriterThread):

TEST_FILE = debugger_unittest._get_debugger_test_file('_debugger_case_remote.py')

def run(self):
self.start_socket(8787)
self.start_socket()

self.log.append('making initial run')
self.write_make_initial_run()
Expand All @@ -1364,21 +1369,21 @@ def run(self):
#=======================================================================================================================
# WriterThreadCaseRemoteDebuggerUnhandledExceptions
#=======================================================================================================================
class WriterThreadCaseRemoteDebuggerUnhandledExceptions(debugger_unittest.AbstractWriterThread):
class WriterThreadCaseRemoteDebuggerUnhandledExceptions(AbstractRemoteWriterThread):

TEST_FILE = debugger_unittest._get_debugger_test_file('_debugger_case_remote_unhandled_exceptions.py')

@overrides(debugger_unittest.AbstractWriterThread.check_test_suceeded_msg)
@overrides(AbstractRemoteWriterThread.check_test_suceeded_msg)
def check_test_suceeded_msg(self, stdout, stderr):
return 'TEST SUCEEDED' in ''.join(stderr)

@overrides(debugger_unittest.AbstractWriterThread.additional_output_checks)
@overrides(AbstractRemoteWriterThread.additional_output_checks)
def additional_output_checks(self, stdout, stderr):
# Don't call super as we have an expected exception
assert 'ValueError: TEST SUCEEDED' in stderr

def run(self):
self.start_socket(8787) # Wait for it to connect back at this port.
self.start_socket() # Wait for it to connect back at this port.

self.log.append('making initial run')
self.write_make_initial_run()
Expand All @@ -1401,21 +1406,21 @@ def run(self):
#=======================================================================================================================
# WriterThreadCaseRemoteDebuggerUnhandledExceptions2
#=======================================================================================================================
class WriterThreadCaseRemoteDebuggerUnhandledExceptions2(debugger_unittest.AbstractWriterThread):
class WriterThreadCaseRemoteDebuggerUnhandledExceptions2(AbstractRemoteWriterThread):

TEST_FILE = debugger_unittest._get_debugger_test_file('_debugger_case_remote_unhandled_exceptions2.py')

@overrides(debugger_unittest.AbstractWriterThread.check_test_suceeded_msg)
@overrides(AbstractRemoteWriterThread.check_test_suceeded_msg)
def check_test_suceeded_msg(self, stdout, stderr):
return 'TEST SUCEEDED' in ''.join(stderr)

@overrides(debugger_unittest.AbstractWriterThread.additional_output_checks)
@overrides(AbstractRemoteWriterThread.additional_output_checks)
def additional_output_checks(self, stdout, stderr):
# Don't call super as we have an expected exception
assert 'ValueError: TEST SUCEEDED' in stderr

def run(self):
self.start_socket(8787) # Wait for it to connect back at this port.
self.start_socket() # Wait for it to connect back at this port.

self.log.append('making initial run')
self.write_make_initial_run()
Expand Down Expand Up @@ -1474,7 +1479,7 @@ def run(self):
#=======================================================================================================================
# WriterThreadCaseRemoteDebuggerMultiProc
#=======================================================================================================================
class WriterThreadCaseRemoteDebuggerMultiProc(debugger_unittest.AbstractWriterThread):
class WriterThreadCaseRemoteDebuggerMultiProc(AbstractRemoteWriterThread):

# It seems sometimes it becomes flaky on the ci because the process outlives the writer thread...
# As we're only interested in knowing if a second connection was received, just kill the related
Expand All @@ -1484,7 +1489,7 @@ class WriterThreadCaseRemoteDebuggerMultiProc(debugger_unittest.AbstractWriterTh
TEST_FILE = debugger_unittest._get_debugger_test_file('_debugger_case_remote_1.py')

def run(self):
self.start_socket(8787)
self.start_socket()

self.log.append('making initial run')
self.write_make_initial_run()
Expand Down Expand Up @@ -2052,6 +2057,30 @@ def run(self):

self.finished_ok = True

#=======================================================================================================================
# WriterCaseBreakpointSuspensionPolicy
#======================================================================================================================
class WriterCaseBreakpointSuspensionPolicy(debugger_unittest.AbstractWriterThread):

TEST_FILE = debugger_unittest._get_debugger_test_file('_debugger_case_suspend_policy.py')

def run(self):
self.start_socket()
self.write_add_breakpoint(27, '', filename=self.TEST_FILE, hit_condition='', is_logpoint=False, suspend_policy='ALL')
self.write_make_initial_run()

thread_ids = []
for i in range(3):
self.log.append('Waiting for thread %s of 3 to stop' % (i + 1,))
# One thread is suspended with a breakpoint hit and the other 2 as thread suspended.
thread_id, _frame_id = self.wait_for_breakpoint_hit((REASON_STOP_ON_BREAKPOINT, REASON_THREAD_SUSPEND))
thread_ids.append(thread_id)

for thread_id in thread_ids:
self.write_run_thread(thread_id)

self.finished_ok = True


#=======================================================================================================================
# Test
Expand Down Expand Up @@ -2297,14 +2326,21 @@ def setup_fixtures(self, tmpdir):
def test_debug_zip_files(self):
self.check_case(WriterDebugZipFiles(self.tmpdir))

def test_case_suspension_policy(self):
self.check_case(WriterCaseBreakpointSuspensionPolicy)

@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
class TestPythonRemoteDebugger(unittest.TestCase, debugger_unittest.DebuggerRunner):

def get_command_line(self):
return [sys.executable, '-u']

def add_command_line_args(self, args):
return args + [self.writer_thread.TEST_FILE]
writer_thread = self.writer_thread

ret = args + [self.writer_thread.TEST_FILE]
ret = writer_thread.update_command_line_args(ret) # Provide a hook for the writer
return ret

def test_remote_debugger(self):
self.check_case(WriterThreadCaseRemoteDebugger)
Expand Down

0 comments on commit e08d53c

Please sign in to comment.