66
77import platform
88import time
9+ from queue import Empty
910
1011import pytest
1112from jupyter_client .blocking .client import BlockingKernelClient
1213
13- from .utils import TIMEOUT , assemble_output , get_replies , get_reply , new_kernel
14+ from .utils import TIMEOUT , assemble_output , get_replies , get_reply , new_kernel , flush_channels
1415
1516# Helpers
1617
@@ -39,8 +40,8 @@ def list_subshell_helper(kc: BlockingKernelClient):
3940 return reply ["content" ]
4041
4142
42- def execute_request (kc : BlockingKernelClient , code : str , subshell_id : str | None ):
43- 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 })
4445 msg ["header" ]["subshell_id" ] = subshell_id
4546 kc .shell_channel .send (msg )
4647 return msg
@@ -51,17 +52,17 @@ def execute_request_subshell_id(
5152):
5253 msg = execute_request (kc , code , subshell_id )
5354 msg_id = msg ["header" ]["msg_id" ]
54- stdout , _ = assemble_output (kc .get_iopub_msg , None , msg_id )
55+ stdout , _ = assemble_output (kc .get_iopub_msg , parent_msg_id = msg_id )
5556 return stdout .strip ()
5657
5758
5859def execute_thread_count (kc : BlockingKernelClient ) -> int :
59- code = "print(threading.active_count())"
60+ code = "import threading; print(threading.active_count())"
6061 return int (execute_request_subshell_id (kc , code , None ))
6162
6263
6364def execute_thread_ids (kc : BlockingKernelClient , subshell_id : str | None = None ) -> tuple [str , str ]:
64- code = "print(threading.get_ident(), threading.main_thread().ident)"
65+ code = "import threading; print(threading.get_ident(), threading.main_thread().ident)"
6566 return execute_request_subshell_id (kc , code , subshell_id ).split ()
6667
6768
@@ -223,16 +224,16 @@ def test_execute_stop_on_error(are_subshells):
223224 msg = execute_request (
224225 kc , "import asyncio; await asyncio.sleep(1); raise ValueError()" , subshell_ids [0 ]
225226 )
226- msg_ids .append (msg ["msg_id" ])
227+ msg_ids .append (msg ["header" ][ " msg_id" ])
227228 msg = execute_request (kc , "print('hello')" , subshell_ids [0 ])
228- msg_ids .append (msg ["msg_id" ])
229+ msg_ids .append (msg ["header" ][ " msg_id" ])
229230 msg = execute_request (kc , "print('goodbye')" , subshell_ids [0 ])
230- msg_ids .append (msg ["msg_id" ])
231+ msg_ids .append (msg ["header" ][ " msg_id" ])
231232
232233 msg = execute_request (kc , "import time; time.sleep(1.5)" , subshell_ids [1 ])
233- msg_ids .append (msg ["msg_id" ])
234+ msg_ids .append (msg ["header" ][ " msg_id" ])
234235 msg = execute_request (kc , "print('other')" , subshell_ids [1 ])
235- msg_ids .append (msg ["msg_id" ])
236+ msg_ids .append (msg ["header" ][ " msg_id" ])
236237
237238 replies = get_replies (kc , msg_ids )
238239
@@ -258,3 +259,57 @@ def test_execute_stop_on_error(are_subshells):
258259 for subshell_id in subshell_ids :
259260 if subshell_id :
260261 delete_subshell_helper (kc , subshell_id )
262+
263+ def test_silent_flag_in_subshells ():
264+ """Verifies that the 'silent' flag suppresses output in main and subshell contexts."""
265+ with new_kernel () as kc :
266+ flush_channels (kc )
267+ # Test silent execution in main shell
268+ msg_main_silent = execute_request (kc , "a=1" , None , silent = True )
269+ reply_main_silent = get_reply (kc , msg_main_silent ["header" ]["msg_id" ])
270+ assert reply_main_silent ['content' ]['status' ] == 'ok'
271+
272+ # Test silent execution in subshell
273+ subshell_id = create_subshell_helper (kc )["subshell_id" ]
274+ msg_sub_silent = execute_request (kc , "b=2" , subshell_id , silent = True )
275+ reply_sub_silent = get_reply (kc , msg_sub_silent ["header" ]["msg_id" ])
276+ assert reply_sub_silent ['content' ]['status' ] == 'ok'
277+
278+ # Check for no output from silent requests. We should only see status messages,
279+ # so we expect a timeout here when looking for other messages.
280+ while True :
281+ try :
282+ msg = kc .get_iopub_msg (timeout = 0.2 )
283+ # We should only receive status messages
284+ if msg ['header' ]['msg_type' ] == 'status' :
285+ continue
286+ # If we get anything else, it's a failure
287+ pytest .fail (f"Silent execution produced an unexpected IOPub message: { msg ['header' ]['msg_type' ]} " )
288+ except Empty :
289+ # No more messages, which is the expected behavior for silent execution
290+ break
291+
292+ # Test concurrent silent and non-silent execution
293+ msg_silent = execute_request (kc , "import time; time.sleep(0.5); c=3" , subshell_id , silent = True )
294+ msg_noisy = execute_request (kc , "print('noisy')" , None , silent = False )
295+
296+ # Get replies for both messages
297+ replies = get_replies (kc , [msg_silent ['header' ]['msg_id' ], msg_noisy ['header' ]['msg_id' ]])
298+ assert len (replies ) == 2
299+
300+ # Verify that we only receive stream output from the noisy message
301+ stdout , stderr = assemble_output (kc .get_iopub_msg , parent_msg_id = msg_noisy ['header' ]['msg_id' ])
302+ assert "noisy" in stdout
303+ assert not stderr
304+
305+ # Verify there is no output from the silent message.
306+ try :
307+ stdout , stderr = assemble_output (kc .get_iopub_msg , parent_msg_id = msg_silent ['header' ]['msg_id' ])
308+ assert not stdout
309+ assert not stderr
310+ except Empty :
311+ # This is expected since there should be no output
312+ pass
313+
314+ # Cleanup
315+ delete_subshell_helper (kc , subshell_id )
0 commit comments