mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
parent
df9c8fd9e0
commit
6668ceec49
8 changed files with 176 additions and 39 deletions
|
|
@ -13,7 +13,7 @@ matrix:
|
|||
env:
|
||||
- PYDEVD_USE_CYTHON=NO
|
||||
- PYDEVD_TEST_JYTHON=YES
|
||||
- JYTHON_URL=http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7.0/jython-installer-2.7.0.jar
|
||||
- JYTHON_URL=http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar
|
||||
|
||||
# Python 2.6 (with and without cython)
|
||||
# - python: 2.7
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/bash
|
||||
set -ev
|
||||
|
||||
pip install pytest==3.6
|
||||
pip install pytest
|
||||
pip install untangle
|
||||
|
|
@ -25,6 +25,7 @@ from _pydevd_bundle.pydevd_comm import CMD_RUN, CMD_VERSION, CMD_LIST_THREADS, C
|
|||
from _pydevd_bundle.pydevd_constants import get_thread_id, IS_PY3K, DebugInfoHolder, dict_keys, STATE_RUN, \
|
||||
NEXT_VALUE_SEPARATOR, IS_WINDOWS
|
||||
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
|
||||
def process_net_command(py_db, cmd_id, seq, text):
|
||||
'''Processes a command received from the Java side
|
||||
|
|
@ -115,31 +116,46 @@ def process_net_command(py_db, cmd_id, seq, text):
|
|||
py_db.post_internal_command(int_cmd, text)
|
||||
|
||||
elif cmd_id == CMD_THREAD_SUSPEND:
|
||||
# Yes, thread suspend is still done at this point, not through an internal command!
|
||||
t = pydevd_find_thread_by_id(text)
|
||||
if t and not getattr(t, 'pydev_do_not_trace', None):
|
||||
additional_info = set_additional_thread_info(t)
|
||||
frame = additional_info.get_topmost_frame(t)
|
||||
if frame is not None:
|
||||
try:
|
||||
py_db.set_trace_for_frame_and_parents(frame, overwrite_prev_trace=True)
|
||||
finally:
|
||||
frame = None
|
||||
|
||||
py_db.set_suspend(t, CMD_THREAD_SUSPEND)
|
||||
# Yes, thread suspend is done at this point, not through an internal command.
|
||||
threads = []
|
||||
if text.strip() == '*':
|
||||
threads = threading.enumerate()
|
||||
|
||||
elif text.startswith('__frame__:'):
|
||||
sys.stderr.write("Can't suspend tasklet: %s\n" % (text,))
|
||||
|
||||
else:
|
||||
threads = [pydevd_find_thread_by_id(text)]
|
||||
|
||||
for t in threads:
|
||||
if t and not getattr(t, 'pydev_do_not_trace', None):
|
||||
additional_info = set_additional_thread_info(t)
|
||||
frame = additional_info.get_topmost_frame(t)
|
||||
if frame is not None:
|
||||
try:
|
||||
py_db.set_trace_for_frame_and_parents(frame, overwrite_prev_trace=True)
|
||||
finally:
|
||||
frame = None
|
||||
|
||||
py_db.set_suspend(t, CMD_THREAD_SUSPEND)
|
||||
|
||||
elif cmd_id == CMD_THREAD_RUN:
|
||||
t = pydevd_find_thread_by_id(text)
|
||||
if t:
|
||||
t.additional_info.pydev_step_cmd = -1
|
||||
t.additional_info.pydev_step_stop = None
|
||||
t.additional_info.pydev_state = STATE_RUN
|
||||
|
||||
threads = []
|
||||
if text.strip() == '*':
|
||||
threads = threading.enumerate()
|
||||
|
||||
elif text.startswith('__frame__:'):
|
||||
sys.stderr.write("Can't make tasklet run: %s\n" % (text,))
|
||||
|
||||
else:
|
||||
threads = [pydevd_find_thread_by_id(text)]
|
||||
|
||||
for t in threads:
|
||||
if t and not getattr(t, 'pydev_do_not_trace', None):
|
||||
additional_info = set_additional_thread_info(t)
|
||||
additional_info.pydev_step_cmd = -1
|
||||
additional_info.pydev_step_stop = None
|
||||
additional_info.pydev_state = STATE_RUN
|
||||
|
||||
elif cmd_id == CMD_STEP_INTO or cmd_id == CMD_STEP_OVER or cmd_id == CMD_STEP_RETURN or \
|
||||
cmd_id == CMD_STEP_INTO_MY_CODE:
|
||||
|
|
|
|||
|
|
@ -129,11 +129,11 @@ class TestMod(unittest.TestCase):
|
|||
assert 'byte[]' in tup[1]
|
||||
|
||||
f, tip = _pydev_jy_imports_tipper.generate_tip('__builtin__.str')
|
||||
assert f.endswith('jython.jar')
|
||||
assert f is None or f.endswith('jython.jar') # Depends on jython version
|
||||
self.assert_in('find' , tip)
|
||||
|
||||
f, tip = _pydev_jy_imports_tipper.generate_tip('__builtin__.dict')
|
||||
assert f.endswith('jython.jar')
|
||||
assert f is None or f.endswith('jython.jar') # Depends on jython version
|
||||
self.assert_in('get' , tip)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -136,12 +136,12 @@ def overrides(method):
|
|||
TIMEOUT = 20
|
||||
|
||||
|
||||
def wait_for_condition(condition, msg=None):
|
||||
def wait_for_condition(condition, msg=None, timeout=TIMEOUT, sleep=.05):
|
||||
curtime = time.time()
|
||||
while True:
|
||||
if condition():
|
||||
break
|
||||
if time.time() - curtime > TIMEOUT:
|
||||
if time.time() - curtime > timeout:
|
||||
error_msg = 'Condition not reached in %s seconds'
|
||||
if msg is not None:
|
||||
error_msg += '\n'
|
||||
|
|
@ -151,7 +151,7 @@ def wait_for_condition(condition, msg=None):
|
|||
error_msg += str(msg)
|
||||
|
||||
raise AssertionError(error_msg)
|
||||
time.sleep(.05)
|
||||
time.sleep(sleep)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
|
|
@ -477,6 +477,7 @@ class AbstractWriterThread(threading.Thread):
|
|||
'An event executor terminated with non-empty task',
|
||||
'java.lang.UnsupportedOperationException',
|
||||
"RuntimeWarning: Parent module '_pydevd_bundle' not found while handling absolute import",
|
||||
'from _pydevd_bundle.pydevd_additional_thread_info_regular import _current_frames',
|
||||
):
|
||||
if expected in line:
|
||||
return True
|
||||
|
|
@ -697,7 +698,7 @@ class AbstractWriterThread(threading.Thread):
|
|||
if v not in all_found:
|
||||
missing.append(v)
|
||||
raise ValueError('Not Found:\n%s\nNot found messages: %s\nFound messages: %s\nExpected messages: %s\nIgnored messages:\n%s' % (
|
||||
'\n'.join(missing), len(missing), len(all_found), len(expected_vars), '\n'.join(ignored)))
|
||||
'\n'.join(str(x) for x in missing), len(missing), len(all_found), len(expected_vars), '\n'.join(str(x) for x in ignored)))
|
||||
|
||||
was_message_used = False
|
||||
new_expected = []
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
import time
|
||||
|
||||
|
||||
class ProceedContainer:
|
||||
proceed = False
|
||||
|
||||
|
||||
def exit_while_loop():
|
||||
ProceedContainer.proceed = True
|
||||
return 'ok'
|
||||
|
||||
|
||||
def sleep():
|
||||
while not ProceedContainer.proceed: # The debugger should change the proceed to True to exit the loop.
|
||||
time.sleep(.1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for i in range(6):
|
||||
print('here %s' % i)
|
||||
time.sleep(1)
|
||||
|
||||
sleep()
|
||||
|
||||
print('TEST SUCEEDED')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import time
|
||||
import threading
|
||||
|
||||
|
||||
class ProceedContainer:
|
||||
tid_to_proceed = {
|
||||
1: False,
|
||||
2: False,
|
||||
}
|
||||
|
||||
|
||||
def exit_while_loop(tid):
|
||||
ProceedContainer.tid_to_proceed[tid] = True
|
||||
return 'ok'
|
||||
|
||||
|
||||
def thread_func(tid):
|
||||
while not ProceedContainer.tid_to_proceed[tid]: # The debugger should change the proceed to True to exit the loop.
|
||||
time.sleep(.1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
threads = [
|
||||
threading.Thread(target=thread_func, args=(1,)),
|
||||
threading.Thread(target=thread_func, args=(2,)),
|
||||
]
|
||||
for t in threads:
|
||||
t.start()
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
print('TEST SUCEEDED')
|
||||
|
|
@ -16,7 +16,7 @@ from tests_python import debugger_unittest
|
|||
from tests_python.debugger_unittest import (CMD_SET_PROPERTY_TRACE, REASON_CAUGHT_EXCEPTION,
|
||||
REASON_UNCAUGHT_EXCEPTION, REASON_STOP_ON_BREAKPOINT, REASON_THREAD_SUSPEND, overrides, CMD_THREAD_CREATE,
|
||||
CMD_GET_THREAD_STACK, REASON_STEP_INTO_MY_CODE, CMD_GET_EXCEPTION_DETAILS, IS_IRONPYTHON, IS_JYTHON, IS_CPYTHON,
|
||||
IS_APPVEYOR)
|
||||
IS_APPVEYOR, wait_for_condition)
|
||||
from _pydevd_bundle.pydevd_constants import IS_WINDOWS
|
||||
try:
|
||||
from urllib import unquote
|
||||
|
|
@ -157,24 +157,93 @@ def test_case_3(case_setup):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_JYTHON, reason='This test is flaky on Jython, so, skipping it.')
|
||||
def test_case_4(case_setup):
|
||||
def test_case_suspend_thread(case_setup):
|
||||
with case_setup.test_file('_debugger_case4.py') as writer:
|
||||
writer.FORCE_KILL_PROCESS_WHEN_FINISHED_OK = True
|
||||
writer.write_make_initial_run()
|
||||
|
||||
thread_id = writer.wait_for_new_thread()
|
||||
|
||||
writer.write_suspend_thread(thread_id)
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit(REASON_THREAD_SUSPEND)
|
||||
while True:
|
||||
hit = writer.wait_for_breakpoint_hit(REASON_THREAD_SUSPEND)
|
||||
if hit.name == 'sleep':
|
||||
break # Ok, broke on 'sleep'.
|
||||
else:
|
||||
# i.e.: if it doesn't hit on 'sleep', release and pause again.
|
||||
writer.write_run_thread(thread_id)
|
||||
time.sleep(.1)
|
||||
writer.write_suspend_thread(thread_id)
|
||||
|
||||
assert hit.thread_id == thread_id
|
||||
|
||||
writer.write_evaluate_expression('%s\t%s\t%s' % (hit.thread_id, hit.frame_id, 'LOCAL'), 'exit_while_loop()')
|
||||
writer.wait_for_evaluation([
|
||||
[
|
||||
'<var name="exit_while_loop()" type="str" qualifier="{0}" value="str: ok'.format(builtin_qualifier),
|
||||
'<var name="exit_while_loop()" type="str" value="str: ok"', # jython
|
||||
]
|
||||
])
|
||||
|
||||
writer.write_run_thread(thread_id)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
# Jython has a weird behavior: it seems it has fine-grained locking so that when
|
||||
# we're inside the tracing other threads don't run (so, we can have only one
|
||||
# thread paused in the debugger).
|
||||
@pytest.mark.skipif(IS_JYTHON, reason='Jython can only have one thread stopped at each time.')
|
||||
def test_case_suspend_all_thread(case_setup):
|
||||
with case_setup.test_file('_debugger_case_suspend_all.py') as writer:
|
||||
writer.write_make_initial_run()
|
||||
|
||||
main_thread_id = writer.wait_for_new_thread() # Main thread
|
||||
thread_id1 = writer.wait_for_new_thread() # Thread 1
|
||||
thread_id2 = writer.wait_for_new_thread() # Thread 2
|
||||
|
||||
# Ok, all threads created, let's wait for the main thread to get to the join.
|
||||
def get_frame_names():
|
||||
writer.write_get_thread_stack(main_thread_id)
|
||||
msg = writer.wait_for_message(lambda msg:msg.startswith('%s\t' % (CMD_GET_THREAD_STACK,)))
|
||||
if msg.thread.frame:
|
||||
frame_names = [frame['name'] for frame in msg.thread.frame]
|
||||
return frame_names
|
||||
return [msg.thread.frame['name']]
|
||||
|
||||
def condition():
|
||||
return get_frame_names() in (
|
||||
['wait', 'join', '<module>'],
|
||||
['_wait_for_tstate_lock', 'join', '<module>']
|
||||
)
|
||||
|
||||
def msg():
|
||||
return 'Found stack: %s' % (get_frame_names(),)
|
||||
|
||||
wait_for_condition(condition, msg, timeout=5, sleep=.5)
|
||||
|
||||
writer.write_suspend_thread('*')
|
||||
|
||||
# Wait for 2 threads to be suspended (the main thread is already in a join, so, it can't actually
|
||||
# break out of it while others don't proceed).
|
||||
hit0 = writer.wait_for_breakpoint_hit(REASON_THREAD_SUSPEND)
|
||||
hit1 = writer.wait_for_breakpoint_hit(REASON_THREAD_SUSPEND)
|
||||
|
||||
writer.write_evaluate_expression('%s\t%s\t%s' % (hit0.thread_id, hit0.frame_id, 'LOCAL'), 'exit_while_loop(1)')
|
||||
writer.wait_for_evaluation([
|
||||
[
|
||||
'<var name="exit_while_loop(1)" type="str" qualifier="{0}" value="str: ok'.format(builtin_qualifier)
|
||||
]
|
||||
])
|
||||
|
||||
writer.write_evaluate_expression('%s\t%s\t%s' % (hit1.thread_id, hit1.frame_id, 'LOCAL'), 'exit_while_loop(2)')
|
||||
writer.wait_for_evaluation('<var name="exit_while_loop(2)" type="str" qualifier="{0}" value="str: ok'.format(builtin_qualifier))
|
||||
|
||||
writer.write_run_thread('*')
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_case_5(case_setup):
|
||||
with case_setup.test_file('_debugger_case56.py') as writer:
|
||||
breakpoint_id = writer.write_add_breakpoint(2, 'Call2')
|
||||
|
|
@ -1777,6 +1846,7 @@ def _get_breakpoint_cases():
|
|||
# Check breakpoint() and sys.__breakpointhook__ replacement.
|
||||
return ('_debugger_case_breakpoint.py', '_debugger_case_breakpoint2.py')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename", _get_breakpoint_cases())
|
||||
def test_py_37_breakpoint(case_setup, filename):
|
||||
with case_setup.test_file(filename) as writer:
|
||||
|
|
@ -1887,7 +1957,7 @@ def test_remote_debugger_basic(case_setup_remote):
|
|||
writer.log.append('asserted')
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
|
||||
def test_py_37_breakpoint_remote(case_setup_remote):
|
||||
|
|
@ -1895,7 +1965,7 @@ def test_py_37_breakpoint_remote(case_setup_remote):
|
|||
writer.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit(
|
||||
REASON_THREAD_SUSPEND,
|
||||
REASON_THREAD_SUSPEND,
|
||||
filename='_debugger_case_breakpoint_remote.py',
|
||||
line=13,
|
||||
)
|
||||
|
|
@ -1911,8 +1981,10 @@ def test_py_37_breakpoint_remote(case_setup_remote):
|
|||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
|
||||
def test_py_37_breakpoint_remote_no_import(case_setup_remote):
|
||||
|
||||
def get_environ(writer):
|
||||
env = os.environ.copy()
|
||||
curr_pythonpath = env.get('PYTHONPATH', '')
|
||||
|
|
@ -1931,7 +2003,7 @@ def test_py_37_breakpoint_remote_no_import(case_setup_remote):
|
|||
writer.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit(
|
||||
"108",
|
||||
"108",
|
||||
filename='_debugger_case_breakpoint_remote_no_import.py',
|
||||
line=12,
|
||||
)
|
||||
|
|
@ -1947,6 +2019,7 @@ def test_py_37_breakpoint_remote_no_import(case_setup_remote):
|
|||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
|
||||
def test_remote_debugger_multi_proc(case_setup_remote):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue