Skip to content

Commit dccf2f0

Browse files
authored
fix: add check_worker_health test
1 parent df6c877 commit dccf2f0

1 file changed

Lines changed: 69 additions & 7 deletions

File tree

tests/test_parallel_processor.py

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -559,13 +559,13 @@ def test_process_stream_error_signal():
559559
pool.input_queue = MagicMock()
560560
pool.output_queue = MagicMock()
561561
pool.output_queue.get_nowait.return_value = QueueSignals.error
562-
pool.join_or_terminate = MagicMock()
563-
pool.check_worker_health = MagicMock()
562+
pool.join_or_terminate = MagicMock() # type: ignore[assignment]
563+
pool.check_worker_health = MagicMock() # type: ignore[assignment]
564564

565565
with pytest.raises(RuntimeError, match="Thread unexpectedly terminated"):
566566
list(pool._process_stream([1]))
567567

568-
pool.join_or_terminate.assert_called_once()
568+
pool.join_or_terminate.assert_called_once() # type: ignore[attr-defined]
569569

570570

571571
def test_join_or_terminate_mixed_states():
@@ -586,16 +586,16 @@ def test_join_or_terminate_mixed_states():
586586

587587
pool.processes = [p1, p2, p3]
588588

589-
pool.join_or_terminate(timeout=0.1)
589+
pool.join_or_terminate(timeout=1)
590590

591591
# P1 should have been joined but not terminated
592-
p1.join.assert_called_once_with(timeout=0.1)
592+
p1.join.assert_called_once_with(timeout=1)
593593
p1.terminate.assert_not_called()
594594

595595
# P2 and P3 should have been joined AND terminated
596-
p2.join.assert_called_once_with(timeout=0.1)
596+
p2.join.assert_called_once_with(timeout=1)
597597
p2.terminate.assert_called_once()
598-
p3.join.assert_called_once_with(timeout=0.1)
598+
p3.join.assert_called_once_with(timeout=1)
599599
p3.terminate.assert_called_once()
600600

601601
# Processes list should be cleared
@@ -699,3 +699,65 @@ def test_semi_ordered_map_start_failure():
699699
# queues should be None or handled safely
700700
assert pool.input_queue is None
701701
assert pool.output_queue is None
702+
def test_check_worker_health_healthy():
703+
"""Test check_worker_health does not raise when all processes are alive or exited cleanly."""
704+
pool = ParallelWorkerPool(worker=SquareWorker, config=PoolConfig(num_workers=2))
705+
706+
# Process 1: Alive
707+
p1 = MagicMock()
708+
p1.is_alive.return_value = True
709+
p1.exitcode = None
710+
711+
# Process 2: Exited cleanly
712+
p2 = MagicMock()
713+
p2.is_alive.return_value = False
714+
p2.exitcode = 0
715+
716+
pool.processes = [p1, p2]
717+
718+
# Should not raise
719+
pool.check_worker_health()
720+
assert pool.emergency_shutdown is False
721+
722+
723+
def test_check_worker_health_unhealthy():
724+
"""Test check_worker_health raises RuntimeError when a process terminates unexpectedly."""
725+
pool = ParallelWorkerPool(worker=SquareWorker, config=PoolConfig(num_workers=1))
726+
727+
p1 = MagicMock()
728+
p1.is_alive.return_value = False
729+
p1.exitcode = 1
730+
p1.pid = 1234
731+
732+
pool.processes = [p1]
733+
pool.join_or_terminate = MagicMock() # type: ignore[assignment]
734+
735+
with pytest.raises(RuntimeError, match="Worker PID: 1234 terminated unexpectedly with code 1"):
736+
pool.check_worker_health()
737+
738+
assert pool.emergency_shutdown is True
739+
pool.join_or_terminate.assert_called_once() # type: ignore[attr-defined]
740+
741+
742+
def test_check_worker_health_called_during_processing():
743+
"""Test that check_worker_health is called during the processing loop."""
744+
pool = ParallelWorkerPool(worker=SquareWorker, config=PoolConfig(num_workers=1))
745+
746+
# Mock necessary parts to avoid real multiprocessing
747+
pool.start = MagicMock() # type: ignore[assignment]
748+
pool.join = MagicMock() # type: ignore[assignment]
749+
pool.input_queue = MagicMock() # type: ignore[assignment]
750+
pool.output_queue = MagicMock() # type: ignore[assignment]
751+
752+
# Simulate one item being processed
753+
# First call to get_nowait returns None, second call returns item
754+
pool.output_queue.get_nowait.side_effect = [None, (0, 100)] # type: ignore[attr-defined]
755+
# Final get call for the remaining items
756+
pool.output_queue.get.return_value = (0, 100) # type: ignore[attr-defined]
757+
758+
with patch.object(ParallelWorkerPool, "check_worker_health") as mock_check_health:
759+
# We need to exhaust the generator
760+
list(pool.semi_ordered_map([10]))
761+
762+
# It should be called at least once
763+
assert mock_check_health.called

0 commit comments

Comments
 (0)