4242)
4343from docx_editor .workspace import _pid_alive # noqa: E402
4444
45- # Every wait in this module is bounded by the kernel start budget, so a loaded
46- # machine raises them all through DOCX_SESSION_START_TIMEOUT (CI sets 120).
45+ # Start waits and busy-kernel handshakes in this module are bounded by the
46+ # kernel start budget, so a loaded machine raises them all through
47+ # DOCX_EDITOR_SESSION_START_TIMEOUT (CI sets 60). A few fixed windows remain
48+ # where the tested behaviour is itself wall-clock (the 10 s silence probe in
49+ # TestKernelDeath, the stdin and silent-exec bounds).
4750START_BUDGET = _start_timeout ()
4851# For the one behaviour that is inherently wall-clock — "my own code overran":
4952# an idle kernel must dequeue a request within this long. 2 s locally, 12 s on CI.
@@ -395,7 +398,11 @@ def test_request_discarded_behind_a_raising_command(tmp_path):
395398 assert res .started is False # the tell: never dequeued
396399 assert res .stdout == ""
397400
398- _wait_for_reply (hog ) # the failing command is done; the kernel is idle
401+ # The reply lands just before ipykernel's post-error abort turn
402+ # (_abort_queues, one event-loop turn at the default
403+ # stop_on_error_timeout of 0); the eval below builds a fresh
404+ # client first, which comfortably outlasts it.
405+ _wait_for_reply (hog ) # the failing command is done
399406 assert eval_code ("globals().get('discarded')" , connection_file = conn ).value is None
400407 finally :
401408 stop_session (conn )
@@ -970,7 +977,9 @@ def test_stop_session_honours_the_shutdown_ack(tmp_path, monkeypatch):
970977 real_kill = os .kill
971978
972979 def spying_kill (target : int , sig : int ) -> None :
973- if sig != 0 : # _pid_alive probes with signal 0; only real signals count
980+ # _pid_alive probes with signal 0, and unrelated components may signal
981+ # other pids; only real signals aimed at this kernel count.
982+ if sig != 0 and target == pid :
974983 signalled .append ((target , sig ))
975984 real_kill (target , sig )
976985
@@ -1053,6 +1062,14 @@ def test_module_entrypoint_runs():
10531062"""
10541063
10551064
1065+ def _assert_reported_kernel_dead (report : str ) -> None :
1066+ """The stand-in kernel's argv has no ``ipykernel_launcher``, so conftest's
1067+ reaper cannot see it: every test must prove ``_abort`` killed it."""
1068+ pid_match = re .search (r"Kernel pid (\d+)" , report )
1069+ assert pid_match is not None , report
1070+ assert not _pid_alive (int (pid_match .group (1 )), reap = True )
1071+
1072+
10561073class TestStartFailureDiagnosis :
10571074 """A start that exhausts its budget must say why, and the budget must be
10581075 tunable for loaded machines (ROADMAP.md #72).
@@ -1083,21 +1100,21 @@ def test_start_timeout_explains_itself(self, tmp_path, fake_kernel):
10831100 # Nothing left behind: no state files, and the fake is dead.
10841101 assert not conn .exists ()
10851102 assert not conn .with_suffix (".pid" ).exists ()
1086- pid_match = re .search (r"Kernel pid (\d+)" , message )
1087- assert pid_match is not None , message
1088- assert not _pid_alive (int (pid_match .group (1 )), reap = True )
1103+ _assert_reported_kernel_dead (message )
10891104
10901105 def test_start_budget_comes_from_env (self , tmp_path , fake_kernel , monkeypatch ):
10911106 monkeypatch .setenv (START_TIMEOUT_ENV , "2" )
1092- with pytest .raises (SessionError , match = "did not become ready within 2.0s" ):
1107+ with pytest .raises (SessionError , match = "did not become ready within 2.0s" ) as excinfo :
10931108 start_session (tmp_path / "kernel.json" )
1109+ _assert_reported_kernel_dead (str (excinfo .value ))
10941110
10951111 def test_explicit_timeout_beats_env (self , tmp_path , fake_kernel , monkeypatch ):
10961112 monkeypatch .setenv (START_TIMEOUT_ENV , "2" )
1097- with pytest .raises (SessionError , match = "did not become ready within 1.0s" ):
1113+ with pytest .raises (SessionError , match = "did not become ready within 1.0s" ) as excinfo :
10981114 start_session (tmp_path / "kernel.json" , timeout = 1.0 )
1115+ _assert_reported_kernel_dead (str (excinfo .value ))
10991116
1100- @pytest .mark .parametrize ("value" , ["soon" , "0" , "-5" , "nan" ])
1117+ @pytest .mark .parametrize ("value" , ["soon" , "0" , "-5" , "nan" , "inf" ])
11011118 def test_invalid_start_budget_raises (self , tmp_path , monkeypatch , value ):
11021119 monkeypatch .setenv (START_TIMEOUT_ENV , value )
11031120 # Were a kernel spawned anyway, this exits at once with a distinct error.
@@ -1123,6 +1140,7 @@ def test_main_start_prints_the_diagnosis(self, tmp_path, fake_kernel, monkeypatc
11231140 assert "did not become ready within 2.0s" in err
11241141 assert "fake kernel: sockets bound, never served" in err
11251142 assert START_TIMEOUT_ENV in err
1143+ _assert_reported_kernel_dead (err )
11261144
11271145
11281146class TestKernelReaping :
0 commit comments