Path: blob/master/src/python/cocalc-api/tests/test_jupyter.py
5578 views
"""1Tests for Jupyter kernel functionality.23Note: These tests are skipped in CI environments due to unreliable Jupyter setup4in containerized environments. They are thoroughly tested locally.5"""67import os8import pytest910# Import helper from conftest11from tests.conftest import retry_with_backoff1213# Skip all Jupyter tests in CI (GitHub Actions)14# Jupyter kernel startup and execution is unreliable in containerized CI environments15pytestmark = pytest.mark.skipif(os.environ.get("CI") == "true", reason="Jupyter tests skipped in CI due to environment constraints")161718class TestJupyterKernelSetup:19"""Tests for Jupyter kernel installation and availability."""2021def test_install_ipykernel(self, project_client):22"""Test installing ipykernel in the project."""23# Install ipykernel package24result = project_client.system.exec(25command="python3",26args=["-m", "pip", "install", "ipykernel"],27timeout=120, # 2 minutes should be enough for pip install28)2930# Check that installation succeeded31assert result["exit_code"] == 032assert "stderr" in result3334def test_install_jupyter_kernel(self, project_client):35"""Test installing the Python 3 Jupyter kernel."""36# Install the kernel spec37result = project_client.system.exec(38command="python3",39args=[40"-m",41"ipykernel",42"install",43"--user", # Install to user location, not system44"--name=python3",45"--display-name=Python 3",46],47timeout=30,48)4950# Check that kernel installation succeeded51assert result["exit_code"] == 0525354class TestJupyterKernels:55"""Tests for Jupyter kernel availability."""5657def test_kernels_list_with_project(self, hub, temporary_project):58"""Test getting kernel specs for a specific project."""59project_id = temporary_project["project_id"]60kernels = hub.jupyter.kernels(project_id=project_id)6162# Should return a list of kernel specs63assert isinstance(kernels, list)64assert len(kernels) > 06566def test_python3_kernel_available(self, hub, temporary_project):67"""Test that the python3 kernel is available after installation."""68project_id = temporary_project["project_id"]69kernels = hub.jupyter.kernels(project_id=project_id)7071# Extract kernel names from the list72kernel_names = [k.get("name") for k in kernels if isinstance(k, dict)]73assert "python3" in kernel_names747576class TestJupyterExecuteViaHub:77"""Tests for executing code via hub.jupyter.execute()."""7879def test_execute_simple_sum(self, hub, temporary_project):80"""Test executing a simple sum using the python3 kernel.8182Note: First execution may take longer as kernel needs to start up (30+ seconds).83In CI environments, this can take even longer, so we use more retries.84"""85project_id = temporary_project["project_id"]8687result = retry_with_backoff(lambda: hub.jupyter.execute(input="sum(range(100))", kernel="python3", project_id=project_id),88max_retries=5,89retry_delay=10)9091# Check the result structure92assert isinstance(result, dict)93assert "output" in result9495# Check that we got the correct result (sum of 0..99 = 4950)96output = result["output"]97assert len(output) > 09899# Extract the result from the output100# Format: [{'data': {'text/plain': '4950'}}]101first_output = output[0]102assert "data" in first_output103assert "text/plain" in first_output["data"]104assert first_output["data"]["text/plain"] == "4950"105106def test_execute_with_history(self, hub, temporary_project):107"""Test executing code with history context."""108project_id = temporary_project["project_id"]109110result = retry_with_backoff(111lambda: hub.jupyter.execute(history=["a = 100"], input="sum(range(a + 1))", kernel="python3", project_id=project_id))112113# Check the result (sum of 0..100 = 5050)114assert isinstance(result, dict)115assert "output" in result116117output = result["output"]118assert len(output) > 0119120first_output = output[0]121assert "data" in first_output122assert "text/plain" in first_output["data"]123assert first_output["data"]["text/plain"] == "5050"124125def test_execute_print_statement(self, hub, temporary_project):126"""Test executing code that prints output.127128Note: First execution may take longer as kernel needs to start up (30+ seconds).129"""130project_id = temporary_project["project_id"]131132result = retry_with_backoff(lambda: hub.jupyter.execute(input='print("Hello from Jupyter")', kernel="python3", project_id=project_id))133134# Check that we got output135assert isinstance(result, dict)136assert "output" in result137138output = result["output"]139assert len(output) > 0140141# Print statements produce stream output142first_output = output[0]143assert "name" in first_output144assert first_output["name"] == "stdout"145assert "text" in first_output146assert "Hello from Jupyter" in first_output["text"]147148149class TestJupyterExecuteViaProject:150"""Tests for executing code via project.system.jupyter_execute()."""151152def test_jupyter_execute_simple_sum(self, project_client):153"""154Test executing a simple sum via project API.155156The result is a list of output items directly (not wrapped in a dict).157158Note: First execution may take longer as kernel needs to start up (30+ seconds).159"""160result = retry_with_backoff(lambda: project_client.system.jupyter_execute(input="sum(range(100))", kernel="python3"))161162# Result is a list, not a dict with 'output' key163assert isinstance(result, list)164assert len(result) > 0165166# Check that we got the correct result (sum of 0..99 = 4950)167first_output = result[0]168assert "data" in first_output169assert "text/plain" in first_output["data"]170assert first_output["data"]["text/plain"] == "4950"171172def test_jupyter_execute_with_history(self, project_client):173"""174Test executing code with history via project API.175176The result is a list of output items directly.177178Note: First execution may take longer as kernel needs to start up (30+ seconds).179"""180result = retry_with_backoff(lambda: project_client.system.jupyter_execute(history=["b = 50"], input="b * 2", kernel="python3"))181182# Result is a list183assert isinstance(result, list)184assert len(result) > 0185186# Check the result (50 * 2 = 100)187first_output = result[0]188assert "data" in first_output189assert "text/plain" in first_output["data"]190assert first_output["data"]["text/plain"] == "100"191192def test_jupyter_execute_list_operation(self, project_client):193"""194Test executing code that works with lists.195196The result is a list of output items directly.197"""198result = retry_with_backoff(lambda: project_client.system.jupyter_execute(input="[x**2 for x in range(5)]", kernel="python3"))199200# Result is a list201assert isinstance(result, list)202assert len(result) > 0203204# Check the result ([0, 1, 4, 9, 16])205first_output = result[0]206assert "data" in first_output207assert "text/plain" in first_output["data"]208assert first_output["data"]["text/plain"] == "[0, 1, 4, 9, 16]"209210211class TestJupyterKernelManagement:212"""Tests for Jupyter kernel management (list and stop kernels)."""213214def test_list_jupyter_kernels(self, project_client):215"""Test listing running Jupyter kernels."""216# First execute some code to ensure a kernel is running217retry_with_backoff(lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3"))218219# List kernels220kernels = project_client.system.list_jupyter_kernels()221222# Should return a list223assert isinstance(kernels, list)224225# Should have at least one kernel running (from previous tests)226assert len(kernels) > 0227228# Each kernel should have required fields229for kernel in kernels:230assert "pid" in kernel231assert "connectionFile" in kernel232assert isinstance(kernel["pid"], int)233assert isinstance(kernel["connectionFile"], str)234235def test_stop_jupyter_kernel(self, project_client):236"""Test stopping a specific Jupyter kernel."""237# Execute code to start a kernel238retry_with_backoff(lambda: project_client.system.jupyter_execute(input="1+1", kernel="python3"))239240# List kernels241kernels = project_client.system.list_jupyter_kernels()242assert len(kernels) > 0243244# Stop the first kernel245kernel_to_stop = kernels[0]246result = project_client.system.stop_jupyter_kernel(pid=kernel_to_stop["pid"])247248# Should return success249assert isinstance(result, dict)250assert "success" in result251assert result["success"] is True252253# Verify kernel is no longer in the list254kernels_after = project_client.system.list_jupyter_kernels()255remaining_pids = [k["pid"] for k in kernels_after]256assert kernel_to_stop["pid"] not in remaining_pids257258259