66
77import platform
88import time
9- from collections import Counter
9+ from queue import Empty
1010
1111import pytest
1212from jupyter_client .blocking .client import BlockingKernelClient
1313
14- from .utils import TIMEOUT , assemble_output , get_replies , get_reply , new_kernel , wait_for_idle
14+ from .utils import TIMEOUT , assemble_output , get_replies , get_reply , new_kernel , flush_channels
1515
1616# Helpers
1717
@@ -40,8 +40,8 @@ def list_subshell_helper(kc: BlockingKernelClient):
4040 return reply ["content" ]
4141
4242
43- def execute_request (kc : BlockingKernelClient , code : str , subshell_id : str | None ):
44- msg = kc .session .msg ("execute_request" , {"code" : code })
43+ def execute_request (kc : BlockingKernelClient , code : str , subshell_id : str | None , silent : bool = False ):
44+ msg = kc .session .msg ("execute_request" , {"code" : code , "silent" : silent })
4545 msg ["header" ]["subshell_id" ] = subshell_id
4646 kc .shell_channel .send (msg )
4747 return msg
@@ -52,17 +52,17 @@ def execute_request_subshell_id(
5252):
5353 msg = execute_request (kc , code , subshell_id )
5454 msg_id = msg ["header" ]["msg_id" ]
55- stdout , _ = assemble_output (kc .get_iopub_msg , None , msg_id )
55+ stdout , _ = assemble_output (kc .get_iopub_msg , parent_msg_id = msg_id )
5656 return stdout .strip ()
5757
5858
5959def execute_thread_count (kc : BlockingKernelClient ) -> int :
60- code = "print(threading.active_count())"
60+ code = "import threading; print(threading.active_count())"
6161 return int (execute_request_subshell_id (kc , code , None ))
6262
6363
6464def execute_thread_ids (kc : BlockingKernelClient , subshell_id : str | None = None ) -> tuple [str , str ]:
65- code = "print(threading.get_ident(), threading.main_thread().ident)"
65+ code = "import threading; print(threading.get_ident(), threading.main_thread().ident)"
6666 return execute_request_subshell_id (kc , code , subshell_id ).split ()
6767
6868
@@ -224,16 +224,16 @@ def test_execute_stop_on_error(are_subshells):
224224 msg = execute_request (
225225 kc , "import asyncio; await asyncio.sleep(1); raise ValueError()" , subshell_ids [0 ]
226226 )
227- msg_ids .append (msg ["msg_id" ])
227+ msg_ids .append (msg ["header" ][ " msg_id" ])
228228 msg = execute_request (kc , "print('hello')" , subshell_ids [0 ])
229- msg_ids .append (msg ["msg_id" ])
229+ msg_ids .append (msg ["header" ][ " msg_id" ])
230230 msg = execute_request (kc , "print('goodbye')" , subshell_ids [0 ])
231- msg_ids .append (msg ["msg_id" ])
231+ msg_ids .append (msg ["header" ][ " msg_id" ])
232232
233233 msg = execute_request (kc , "import time; time.sleep(1.5)" , subshell_ids [1 ])
234- msg_ids .append (msg ["msg_id" ])
234+ msg_ids .append (msg ["header" ][ " msg_id" ])
235235 msg = execute_request (kc , "print('other')" , subshell_ids [1 ])
236- msg_ids .append (msg ["msg_id" ])
236+ msg_ids .append (msg ["header" ][ " msg_id" ])
237237
238238 replies = get_replies (kc , msg_ids )
239239
@@ -260,56 +260,63 @@ def test_execute_stop_on_error(are_subshells):
260260 if subshell_id :
261261 delete_subshell_helper (kc , subshell_id )
262262
263-
264- @pytest .mark .parametrize ("are_subshells" , [(False , True ), (True , False ), (True , True )])
265- def test_idle_message_parent_headers (are_subshells ):
263+ def test_silent_flag_in_subshells ():
264+ """Verifies that the 'silent' flag suppresses output in main and subshell contexts."""
266265 with new_kernel () as kc :
267- # import time module on main shell.
268- msg = kc .session .msg ("execute_request" , {"code" : "import time" })
269- kc .shell_channel .send (msg )
270-
271- subshell_ids = [
272- create_subshell_helper (kc )["subshell_id" ] if is_subshell else None
273- for is_subshell in are_subshells
274- ]
275-
276- # Wait for all idle status messages to be received.
277- for _ in range (1 + sum (are_subshells )):
278- wait_for_idle (kc )
279-
280- msg_ids = []
281- for subshell_id in subshell_ids :
282- msg = execute_request (kc , "time.sleep(0.5)" , subshell_id )
283- msg_ids .append (msg ["msg_id" ])
284-
285- # Expect 4 status messages (2 busy, 2 idle) on iopub channel for the two execute_requests
286- statuses = []
287- timeout = TIMEOUT # Combined timeout to receive all the status messages
288- t0 = time .time ()
289- while True :
290- status = kc .get_iopub_msg (timeout = timeout )
291- if status ["msg_type" ] != "status" or status ["parent_header" ]["msg_id" ] not in msg_ids :
292- continue
293- statuses .append (status )
294- if len (statuses ) == 4 :
295- break
296- t1 = time .time ()
297- timeout -= t1 - t0
298- t0 = t1
299-
300- execution_states = Counter (msg ["content" ]["execution_state" ] for msg in statuses )
301- assert execution_states ["busy" ] == 2
302- assert execution_states ["idle" ] == 2
303-
304- parent_msg_ids = Counter (msg ["parent_header" ]["msg_id" ] for msg in statuses )
305- assert parent_msg_ids [msg_ids [0 ]] == 2
306- assert parent_msg_ids [msg_ids [1 ]] == 2
307-
308- parent_subshell_ids = Counter (msg ["parent_header" ].get ("subshell_id" ) for msg in statuses )
309- assert parent_subshell_ids [subshell_ids [0 ]] == 2
310- assert parent_subshell_ids [subshell_ids [1 ]] == 2
311-
312- # Cleanup
313- for subshell_id in subshell_ids :
266+ subshell_id = None
267+ try :
268+ flush_channels (kc )
269+ # Test silent execution in main shell
270+ msg_main_silent = execute_request (kc , "a=1" , None , silent = True )
271+ reply_main_silent = get_reply (kc , msg_main_silent ["header" ]["msg_id" ])
272+ assert reply_main_silent ['content' ]['status' ] == 'ok'
273+
274+ # Test silent execution in subshell
275+ subshell_id = create_subshell_helper (kc )["subshell_id" ]
276+ msg_sub_silent = execute_request (kc , "b=2" , subshell_id , silent = True )
277+ reply_sub_silent = get_reply (kc , msg_sub_silent ["header" ]["msg_id" ])
278+ assert reply_sub_silent ['content' ]['status' ] == 'ok'
279+
280+ # Check for no iopub messages (other than status) from the silent requests
281+ for msg_id in [msg_main_silent ["header" ]["msg_id" ], msg_sub_silent ["header" ]["msg_id" ]]:
282+ while True :
283+ try :
284+ msg = kc .get_iopub_msg (timeout = 0.2 )
285+ if msg ['header' ]['msg_type' ] == 'status' :
286+ continue
287+ pytest .fail (f"Silent execution produced unexpected IOPub message: { msg ['header' ]['msg_type' ]} " )
288+ except Empty :
289+ break
290+
291+ # Test concurrent silent and non-silent execution
292+ msg_silent = execute_request (
293+ kc , "import time; time.sleep(0.5); c=3" , subshell_id , silent = True
294+ )
295+ msg_noisy = execute_request (kc , "print('noisy')" , None , silent = False )
296+
297+ # Wait for both replies
298+ get_replies (kc , [msg_silent ["header" ]["msg_id" ], msg_noisy ["header" ]["msg_id" ]])
299+
300+ # Verify that we only receive stream output from the noisy message
301+ stdout , stderr = assemble_output (
302+ kc .get_iopub_msg , parent_msg_id = msg_noisy ["header" ]["msg_id" ]
303+ )
304+ assert "noisy" in stdout
305+ assert not stderr
306+
307+ # Verify there is no output from the concurrent silent message
308+ while True :
309+ try :
310+ msg = kc .get_iopub_msg (timeout = 0.2 )
311+ if msg ['header' ]['msg_type' ] == 'status' :
312+ if msg ['parent_header' ].get ('msg_id' ) == msg_silent ['header' ]['msg_id' ]:
313+ continue
314+ # Any other message related to the silent request is a failure
315+ if msg ['parent_header' ].get ('msg_id' ) == msg_silent ['header' ]['msg_id' ]:
316+ pytest .fail ("Silent execution in concurrent setting produced unexpected IOPub message" )
317+ except Empty :
318+ break
319+ finally :
320+ # Ensure subshell is always deleted
314321 if subshell_id :
315322 delete_subshell_helper (kc , subshell_id )
0 commit comments