Skip to content

Commit be6c693

Browse files
authored
Merge branch 'main' into skip-pypy
2 parents 2228448 + 5a00327 commit be6c693

File tree

5 files changed

+89
-15
lines changed

5 files changed

+89
-15
lines changed

ipykernel/debugger.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from tornado.locks import Event
99

1010
from IPython.core.getipython import get_ipython
11+
from IPython.core.inputtransformer2 import leading_empty_lines
1112

1213
try:
1314
from jupyter_client.jsonutil import json_default
@@ -286,6 +287,7 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session):
286287
self.stopped_threads = []
287288

288289
self.debugpy_initialized = False
290+
self._removed_cleanup = {}
289291

290292
self.debugpy_host = '127.0.0.1'
291293
self.debugpy_port = 0
@@ -341,12 +343,25 @@ def start(self):
341343

342344
ident, msg = self.session.recv(self.shell_socket, mode=0)
343345
self.debugpy_initialized = msg['content']['status'] == 'ok'
346+
347+
# Don't remove leading empty lines when debugging so the breakpoints are correctly positioned
348+
cleanup_transforms = get_ipython().input_transformer_manager.cleanup_transforms
349+
if leading_empty_lines in cleanup_transforms:
350+
index = cleanup_transforms.index(leading_empty_lines)
351+
self._removed_cleanup[index] = cleanup_transforms.pop(index)
352+
344353
self.debugpy_client.connect_tcp_socket()
345354
return self.debugpy_initialized
346355

347356
def stop(self):
348357
self.debugpy_client.disconnect_tcp_socket()
349358

359+
# Restore remove cleanup transformers
360+
cleanup_transforms = get_ipython().input_transformer_manager.cleanup_transforms
361+
for index in sorted(self._removed_cleanup):
362+
func = self._removed_cleanup.pop(index)
363+
cleanup_transforms.insert(index, func)
364+
350365
async def dumpCell(self, message):
351366
code = message['arguments']['code']
352367
file_name = get_file_name(code)

ipykernel/tests/test_async.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,14 @@ def teardown_function():
2626
KM.shutdown_kernel(now=True)
2727

2828

29-
skip_without_async = pytest.mark.skipif(
30-
sys.version_info < (3, 5) or V(IPython.__version__) < V("7.0"),
31-
reason="IPython >=7 with async/await required",
32-
)
3329

34-
35-
@skip_without_async
3630
def test_async_await():
3731
flush_channels(KC)
3832
msg_id, content = execute("import asyncio; await asyncio.sleep(0.1)", KC)
3933
assert content["status"] == "ok", content
4034

4135

4236
@pytest.mark.parametrize("asynclib", ["asyncio", "trio", "curio"])
43-
@skip_without_async
4437
def test_async_interrupt(asynclib, request):
4538
try:
4639
__import__(asynclib)

ipykernel/tests/test_debugger.py

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
import sys
12
import pytest
23

3-
from .utils import new_kernel, get_reply
4+
from .utils import TIMEOUT, new_kernel, get_reply
45

56
seq = 0
67

78
# Skip if debugpy is not available
89
pytest.importorskip("debugpy")
910

1011

11-
def wait_for_debug_request(kernel, command, arguments=None):
12+
def wait_for_debug_request(kernel, command, arguments=None, full_reply=False):
1213
"""Carry out a debug request and return the reply content.
1314
1415
It does not check if the request was successful.
@@ -27,7 +28,7 @@ def wait_for_debug_request(kernel, command, arguments=None):
2728
)
2829
kernel.control_channel.send(msg)
2930
reply = get_reply(kernel, msg["header"]["msg_id"], channel="control")
30-
return reply["content"]
31+
return reply if full_reply else reply["content"]
3132

3233

3334
@pytest.fixture
@@ -127,6 +128,76 @@ def test_set_breakpoints(kernel_with_debug):
127128
assert r["success"]
128129

129130

131+
def test_stop_on_breakpoint(kernel_with_debug):
132+
code = """def f(a, b):
133+
c = a + b
134+
return c
135+
136+
f(2, 3)"""
137+
138+
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
139+
source = r["body"]["sourcePath"]
140+
141+
wait_for_debug_request(kernel_with_debug, "debugInfo")
142+
143+
wait_for_debug_request(
144+
kernel_with_debug,
145+
"setBreakpoints",
146+
{
147+
"breakpoints": [{"line": 2}],
148+
"source": {"path": source},
149+
"sourceModified": False,
150+
},
151+
)
152+
153+
wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True)
154+
155+
kernel_with_debug.execute(code)
156+
157+
# Wait for stop on breakpoint
158+
msg = {"msg_type": "", "content": {}}
159+
while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped":
160+
msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT)
161+
162+
assert msg["content"]["body"]["reason"] == "breakpoint"
163+
164+
165+
@pytest.mark.skipif(sys.version_info >= (3, 10), reason="TODO Does not work on Python 3.10")
166+
def test_breakpoint_in_cell_with_leading_empty_lines(kernel_with_debug):
167+
code = """
168+
def f(a, b):
169+
c = a + b
170+
return c
171+
172+
f(2, 3)"""
173+
174+
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
175+
source = r["body"]["sourcePath"]
176+
177+
wait_for_debug_request(kernel_with_debug, "debugInfo")
178+
179+
wait_for_debug_request(
180+
kernel_with_debug,
181+
"setBreakpoints",
182+
{
183+
"breakpoints": [{"line": 6}],
184+
"source": {"path": source},
185+
"sourceModified": False,
186+
},
187+
)
188+
189+
wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True)
190+
191+
kernel_with_debug.execute(code)
192+
193+
# Wait for stop on breakpoint
194+
msg = {"msg_type": "", "content": {}}
195+
while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped":
196+
msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT)
197+
198+
assert msg["content"]["body"]["reason"] == "breakpoint"
199+
200+
130201
def test_rich_inspect_not_at_breakpoint(kernel_with_debug):
131202
var_name = "text"
132203
value = "Hello the world"

ipykernel/tests/test_eventloop.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ def teardown():
2828
"""
2929

3030

31-
@pytest.mark.skipif(sys.version_info < (3, 5), reason="async/await syntax required")
3231
@pytest.mark.skipif(tornado.version_info < (5,), reason="only relevant on tornado 5")
3332
def test_asyncio_interrupt():
3433
flush_channels(KC)

ipykernel/tests/test_kernel.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,6 @@ def test_interrupt_with_message():
429429
"__pypy__" in sys.builtin_module_names,
430430
reason="fails on pypy",
431431
)
432-
@pytest.mark.skipif(
433-
version.parse(IPython.__version__) < version.parse("7.14.0"),
434-
reason="Need new IPython"
435-
)
436432
def test_interrupt_during_pdb_set_trace():
437433
"""
438434
The kernel exits after being interrupted while waiting in pdb.set_trace().

0 commit comments

Comments
 (0)