11"""
22Tests for Jupyter kernel functionality.
33"""
4+
45import pytest
56import time
67from typing import Optional
@@ -15,12 +16,12 @@ def test_install_ipykernel(self, project_client):
1516 result = project_client .system .exec (
1617 command = "python3" ,
1718 args = ["-m" , "pip" , "install" , "ipykernel" ],
18- timeout = 120 # 2 minutes should be enough for pip install
19+ timeout = 120 , # 2 minutes should be enough for pip install
1920 )
2021
2122 # Check that installation succeeded
22- assert result [' exit_code' ] == 0
23- assert ' stderr' in result
23+ assert result [" exit_code" ] == 0
24+ assert " stderr" in result
2425
2526 def test_install_jupyter_kernel (self , project_client ):
2627 """Test installing the Python 3 Jupyter kernel."""
@@ -33,20 +34,21 @@ def test_install_jupyter_kernel(self, project_client):
3334 "install" ,
3435 "--user" , # Install to user location, not system
3536 "--name=python3" ,
36- "--display-name=Python 3"
37+ "--display-name=Python 3" ,
3738 ],
38- timeout = 30 )
39+ timeout = 30 ,
40+ )
3941
4042 # Check that kernel installation succeeded
41- assert result [' exit_code' ] == 0
43+ assert result [" exit_code" ] == 0
4244
4345
4446class TestJupyterKernels :
4547 """Tests for Jupyter kernel availability."""
4648
4749 def test_kernels_list_with_project (self , hub , temporary_project ):
4850 """Test getting kernel specs for a specific project."""
49- project_id = temporary_project [' project_id' ]
51+ project_id = temporary_project [" project_id" ]
5052 kernels = hub .jupyter .kernels (project_id = project_id )
5153
5254 # Should return a list of kernel specs
@@ -55,12 +57,12 @@ def test_kernels_list_with_project(self, hub, temporary_project):
5557
5658 def test_python3_kernel_available (self , hub , temporary_project ):
5759 """Test that the python3 kernel is available after installation."""
58- project_id = temporary_project [' project_id' ]
60+ project_id = temporary_project [" project_id" ]
5961 kernels = hub .jupyter .kernels (project_id = project_id )
6062
6163 # Extract kernel names from the list
62- kernel_names = [k .get (' name' ) for k in kernels if isinstance (k , dict )]
63- assert ' python3' in kernel_names
64+ kernel_names = [k .get (" name" ) for k in kernels if isinstance (k , dict )]
65+ assert " python3" in kernel_names
6466
6567
6668class TestJupyterExecuteViaHub :
@@ -69,64 +71,64 @@ class TestJupyterExecuteViaHub:
6971 @pytest .mark .skip (reason = "hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead" )
7072 def test_execute_simple_sum (self , hub , temporary_project ):
7173 """Test executing a simple sum using the python3 kernel."""
72- project_id = temporary_project [' project_id' ]
74+ project_id = temporary_project [" project_id" ]
7375
74- result = hub .jupyter .execute (input = ' sum(range(100))' , kernel = ' python3' , project_id = project_id )
76+ result = hub .jupyter .execute (input = " sum(range(100))" , kernel = " python3" , project_id = project_id )
7577
7678 # Check the result structure
7779 assert isinstance (result , dict )
78- assert ' output' in result
80+ assert " output" in result
7981
8082 # Check that we got the correct result (sum of 0..99 = 4950)
81- output = result [' output' ]
83+ output = result [" output" ]
8284 assert len (output ) > 0
8385
8486 # Extract the result from the output
8587 # Format: [{'data': {'text/plain': '4950'}}]
8688 first_output = output [0 ]
87- assert ' data' in first_output
88- assert ' text/plain' in first_output [' data' ]
89- assert first_output [' data' ][ ' text/plain' ] == ' 4950'
89+ assert " data" in first_output
90+ assert " text/plain" in first_output [" data" ]
91+ assert first_output [" data" ][ " text/plain" ] == " 4950"
9092
9193 @pytest .mark .skip (reason = "hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead" )
9294 def test_execute_with_history (self , hub , temporary_project ):
9395 """Test executing code with history context."""
94- project_id = temporary_project [' project_id' ]
96+ project_id = temporary_project [" project_id" ]
9597
96- result = hub .jupyter .execute (history = [' a = 100' ], input = ' sum(range(a + 1))' , kernel = ' python3' , project_id = project_id )
98+ result = hub .jupyter .execute (history = [" a = 100" ], input = " sum(range(a + 1))" , kernel = " python3" , project_id = project_id )
9799
98100 # Check the result (sum of 0..100 = 5050)
99101 assert isinstance (result , dict )
100- assert ' output' in result
102+ assert " output" in result
101103
102- output = result [' output' ]
104+ output = result [" output" ]
103105 assert len (output ) > 0
104106
105107 first_output = output [0 ]
106- assert ' data' in first_output
107- assert ' text/plain' in first_output [' data' ]
108- assert first_output [' data' ][ ' text/plain' ] == ' 5050'
108+ assert " data" in first_output
109+ assert " text/plain" in first_output [" data" ]
110+ assert first_output [" data" ][ " text/plain" ] == " 5050"
109111
110112 @pytest .mark .skip (reason = "hub.jupyter.execute() has timeout issues - use project.system.jupyter_execute() instead" )
111113 def test_execute_print_statement (self , hub , temporary_project ):
112114 """Test executing code that prints output."""
113- project_id = temporary_project [' project_id' ]
115+ project_id = temporary_project [" project_id" ]
114116
115- result = hub .jupyter .execute (input = 'print("Hello from Jupyter")' , kernel = ' python3' , project_id = project_id )
117+ result = hub .jupyter .execute (input = 'print("Hello from Jupyter")' , kernel = " python3" , project_id = project_id )
116118
117119 # Check that we got output
118120 assert isinstance (result , dict )
119- assert ' output' in result
121+ assert " output" in result
120122
121- output = result [' output' ]
123+ output = result [" output" ]
122124 assert len (output ) > 0
123125
124126 # Print statements produce stream output
125127 first_output = output [0 ]
126- assert ' name' in first_output
127- assert first_output [' name' ] == ' stdout'
128- assert ' text' in first_output
129- assert ' Hello from Jupyter' in first_output [' text' ]
128+ assert " name" in first_output
129+ assert first_output [" name" ] == " stdout"
130+ assert " text" in first_output
131+ assert " Hello from Jupyter" in first_output [" text" ]
130132
131133
132134class TestJupyterExecuteViaProject :
@@ -147,10 +149,10 @@ def test_jupyter_execute_simple_sum(self, project_client):
147149
148150 for attempt in range (max_retries ):
149151 try :
150- result = project_client .system .jupyter_execute (input = ' sum(range(100))' , kernel = ' python3' )
152+ result = project_client .system .jupyter_execute (input = " sum(range(100))" , kernel = " python3" )
151153 break
152154 except RuntimeError as e :
153- if ' timeout' in str (e ).lower () and attempt < max_retries - 1 :
155+ if " timeout" in str (e ).lower () and attempt < max_retries - 1 :
154156 print (f"Attempt { attempt + 1 } timed out, retrying in { retry_delay } s..." )
155157 time .sleep (retry_delay )
156158 else :
@@ -162,45 +164,59 @@ def test_jupyter_execute_simple_sum(self, project_client):
162164
163165 # Check that we got the correct result (sum of 0..99 = 4950)
164166 first_output = result [0 ]
165- assert ' data' in first_output
166- assert ' text/plain' in first_output [' data' ]
167- assert first_output [' data' ][ ' text/plain' ] == ' 4950'
167+ assert " data" in first_output
168+ assert " text/plain" in first_output [" data" ]
169+ assert first_output [" data" ][ " text/plain" ] == " 4950"
168170
169171 def test_jupyter_execute_with_history (self , project_client ):
170172 """
171173 Test executing code with history via project API.
172174
173175 The result is a list of output items directly.
174176 """
175- result = project_client .system .jupyter_execute (history = [' b = 50' ], input = ' b * 2' , kernel = ' python3' )
177+ result = project_client .system .jupyter_execute (history = [" b = 50" ], input = " b * 2" , kernel = " python3" )
176178
177179 # Result is a list
178180 assert isinstance (result , list )
179181 assert len (result ) > 0
180182
181183 # Check the result (50 * 2 = 100)
182184 first_output = result [0 ]
183- assert ' data' in first_output
184- assert ' text/plain' in first_output [' data' ]
185- assert first_output [' data' ][ ' text/plain' ] == ' 100'
185+ assert " data" in first_output
186+ assert " text/plain" in first_output [" data" ]
187+ assert first_output [" data" ][ " text/plain" ] == " 100"
186188
187189 def test_jupyter_execute_list_operation (self , project_client ):
188190 """
189191 Test executing code that works with lists.
190192
191193 The result is a list of output items directly.
192194 """
193- result = project_client .system .jupyter_execute (input = '[x**2 for x in range(5)]' , kernel = 'python3' )
195+ # Retry logic for kernel startup
196+ max_retries = 3
197+ retry_delay = 15
198+ result : Optional [list ] = None
199+
200+ for attempt in range (max_retries ):
201+ try :
202+ result = project_client .system .jupyter_execute (input = "[x**2 for x in range(5)]" , kernel = "python3" )
203+ break
204+ except RuntimeError as e :
205+ if "timeout" in str (e ).lower () and attempt < max_retries - 1 :
206+ print (f"Attempt { attempt + 1 } timed out, retrying in { retry_delay } s..." )
207+ time .sleep (retry_delay )
208+ else :
209+ raise
194210
195211 # Result is a list
196212 assert isinstance (result , list )
197213 assert len (result ) > 0
198214
199215 # Check the result ([0, 1, 4, 9, 16])
200216 first_output = result [0 ]
201- assert ' data' in first_output
202- assert ' text/plain' in first_output [' data' ]
203- assert first_output [' data' ][ ' text/plain' ] == ' [0, 1, 4, 9, 16]'
217+ assert " data" in first_output
218+ assert " text/plain" in first_output [" data" ]
219+ assert first_output [" data" ][ " text/plain" ] == " [0, 1, 4, 9, 16]"
204220
205221
206222class TestJupyterKernelManagement :
@@ -209,7 +225,20 @@ class TestJupyterKernelManagement:
209225 def test_list_jupyter_kernels (self , project_client ):
210226 """Test listing running Jupyter kernels."""
211227 # First execute some code to ensure a kernel is running
212- project_client .system .jupyter_execute (input = '1+1' , kernel = 'python3' )
228+ # Retry logic for first kernel startup (may take longer in CI)
229+ max_retries = 3
230+ retry_delay = 15
231+
232+ for attempt in range (max_retries ):
233+ try :
234+ project_client .system .jupyter_execute (input = "1+1" , kernel = "python3" )
235+ break
236+ except RuntimeError as e :
237+ if "timeout" in str (e ).lower () and attempt < max_retries - 1 :
238+ print (f"Attempt { attempt + 1 } timed out, retrying in { retry_delay } s..." )
239+ time .sleep (retry_delay )
240+ else :
241+ raise
213242
214243 # List kernels
215244 kernels = project_client .system .list_jupyter_kernels ()
@@ -222,30 +251,30 @@ def test_list_jupyter_kernels(self, project_client):
222251
223252 # Each kernel should have required fields
224253 for kernel in kernels :
225- assert ' pid' in kernel
226- assert ' connectionFile' in kernel
227- assert isinstance (kernel [' pid' ], int )
228- assert isinstance (kernel [' connectionFile' ], str )
254+ assert " pid" in kernel
255+ assert " connectionFile" in kernel
256+ assert isinstance (kernel [" pid" ], int )
257+ assert isinstance (kernel [" connectionFile" ], str )
229258
230259 def test_stop_jupyter_kernel (self , project_client ):
231260 """Test stopping a specific Jupyter kernel."""
232261 # Execute code to start a kernel
233- project_client .system .jupyter_execute (input = ' 1+1' , kernel = ' python3' )
262+ project_client .system .jupyter_execute (input = " 1+1" , kernel = " python3" )
234263
235264 # List kernels
236265 kernels = project_client .system .list_jupyter_kernels ()
237266 assert len (kernels ) > 0
238267
239268 # Stop the first kernel
240269 kernel_to_stop = kernels [0 ]
241- result = project_client .system .stop_jupyter_kernel (pid = kernel_to_stop [' pid' ])
270+ result = project_client .system .stop_jupyter_kernel (pid = kernel_to_stop [" pid" ])
242271
243272 # Should return success
244273 assert isinstance (result , dict )
245- assert ' success' in result
246- assert result [' success' ] is True
274+ assert " success" in result
275+ assert result [" success" ] is True
247276
248277 # Verify kernel is no longer in the list
249278 kernels_after = project_client .system .list_jupyter_kernels ()
250- remaining_pids = [k [' pid' ] for k in kernels_after ]
251- assert kernel_to_stop [' pid' ] not in remaining_pids
279+ remaining_pids = [k [" pid" ] for k in kernels_after ]
280+ assert kernel_to_stop [" pid" ] not in remaining_pids
0 commit comments