22
33import pytest
44
5- from .utils import TIMEOUT , get_reply , new_kernel
5+ from .utils import TIMEOUT , get_replies , get_reply , new_kernel
66
77seq = 0
88
1515 debugpy = None
1616
1717
18- def wait_for_debug_request (kernel , command , arguments = None , full_reply = False ):
19- """Carry out a debug request and return the reply content.
20-
21- It does not check if the request was successful.
22- """
18+ def prepare_debug_request (kernel , command , arguments = None ):
19+ """Prepare a debug request but do not send it."""
2320 global seq
2421 seq += 1
2522
@@ -32,6 +29,15 @@ def wait_for_debug_request(kernel, command, arguments=None, full_reply=False):
3229 "arguments" : arguments or {},
3330 },
3431 )
32+ return msg
33+
34+
35+ def wait_for_debug_request (kernel , command , arguments = None , full_reply = False ):
36+ """Carry out a debug request and return the reply content.
37+
38+ It does not check if the request was successful.
39+ """
40+ msg = prepare_debug_request (kernel , command , arguments )
3541 kernel .control_channel .send (msg )
3642 reply = get_reply (kernel , msg ["header" ]["msg_id" ], channel = "control" )
3743 return reply if full_reply else reply ["content" ]
@@ -448,3 +454,96 @@ def my_test():
448454
449455 # Compare local and global variable
450456 assert global_var ["value" ] == local_var ["value" ] and global_var ["type" ] == local_var ["type" ] # noqa: PT018
457+
458+
459+ def test_debug_requests_sequential (kernel_with_debug ):
460+ # Issue https://github.com/ipython/ipykernel/issues/1412
461+ # Control channel requests should be executed sequentially not concurrently.
462+ code = """def f(a, b):
463+ c = a + b
464+ return c
465+
466+ f(2, 3)"""
467+
468+ r = wait_for_debug_request (kernel_with_debug , "dumpCell" , {"code" : code })
469+ if debugpy :
470+ source = r ["body" ]["sourcePath" ]
471+ else :
472+ assert r == {}
473+ source = "some path"
474+
475+ wait_for_debug_request (
476+ kernel_with_debug ,
477+ "setBreakpoints" ,
478+ {
479+ "breakpoints" : [{"line" : 2 }],
480+ "source" : {"path" : source },
481+ "sourceModified" : False ,
482+ },
483+ )
484+
485+ wait_for_debug_request (kernel_with_debug , "debugInfo" )
486+ wait_for_debug_request (kernel_with_debug , "configurationDone" )
487+ kernel_with_debug .execute (code )
488+
489+ if not debugpy :
490+ # Cannot stop on breakpoint if debugpy not installed
491+ return
492+
493+ # Wait for stop on breakpoint
494+ msg : dict = {"msg_type" : "" , "content" : {}}
495+ while msg .get ("msg_type" ) != "debug_event" or msg ["content" ].get ("event" ) != "stopped" :
496+ msg = kernel_with_debug .get_iopub_msg (timeout = TIMEOUT )
497+
498+ stacks = wait_for_debug_request (kernel_with_debug , "stackTrace" , {"threadId" : 1 })["body" ][
499+ "stackFrames"
500+ ]
501+
502+ scopes = wait_for_debug_request (kernel_with_debug , "scopes" , {"frameId" : stacks [0 ]["id" ]})[
503+ "body"
504+ ]["scopes" ]
505+
506+ # Get variablesReference for both Locals and Globals.
507+ locals_ref = next (filter (lambda s : s ["name" ] == "Locals" , scopes ))["variablesReference" ]
508+ globals_ref = next (filter (lambda s : s ["name" ] == "Globals" , scopes ))["variablesReference" ]
509+
510+ msgs = []
511+ for ref in [locals_ref , globals_ref ]:
512+ msgs .append (
513+ prepare_debug_request (kernel_with_debug , "variables" , {"variablesReference" : ref })
514+ )
515+
516+ # Send messages in quick succession.
517+ for msg in msgs :
518+ kernel_with_debug .control_channel .send (msg )
519+
520+ replies = get_replies (kernel_with_debug , [msg ["msg_id" ] for msg in msgs ], channel = "control" )
521+
522+ # Check debug variable returns are correct.
523+ locals = replies [0 ]["content" ]
524+ assert locals ["success" ]
525+ variables = locals ["body" ]["variables" ]
526+ var = next (filter (lambda v : v ["name" ] == "a" , variables ))
527+ assert var ["type" ] == "int"
528+ assert var ["value" ] == "2"
529+ var = next (filter (lambda v : v ["name" ] == "b" , variables ))
530+ assert var ["type" ] == "int"
531+ assert var ["value" ] == "3"
532+
533+ globals = replies [1 ]["content" ]
534+ assert globals ["success" ]
535+ variables = globals ["body" ]["variables" ]
536+
537+ names = [v ["name" ] for v in variables ]
538+ assert "function variables" in names
539+ assert "special variables" in names
540+
541+ # Check status iopub messages alternate between busy and idle.
542+ execution_states = []
543+ while len (execution_states ) < 8 :
544+ msg = kernel_with_debug .get_iopub_msg (timeout = TIMEOUT )
545+ if msg ["msg_type" ] == "status" :
546+ execution_states .append (msg ["content" ]["execution_state" ])
547+ assert execution_states .count ("busy" ) == 4
548+ assert execution_states .count ("idle" ) == 4
549+ assert execution_states == ["busy" , "idle" ] * 4
0 commit comments