OPEN SOURCE
software/tests/test_signals.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
1"""Tests for graceful shutdown signal handling."""23from __future__ import annotations45import signal6import threading7import time89import pytest1011from src.orchestration.signals import (12GracefulShutdown,13ShutdownCallback,14ignore_signals_in_child,15)161718class TestGracefulShutdown:19"""Tests for GracefulShutdown context manager."""2021def test_initial_state(self) -> None:22"""Test initial state is not stopped."""23shutdown = GracefulShutdown()24assert shutdown.should_stop is False25assert shutdown.signal_received is None2627def test_context_manager_entry(self) -> None:28"""Test context manager returns self."""29shutdown = GracefulShutdown()30with shutdown as s:31assert s is shutdown32assert shutdown.should_stop is False3334def test_request_stop(self) -> None:35"""Test programmatic stop request."""36shutdown = GracefulShutdown()37assert shutdown.should_stop is False3839shutdown.request_stop()4041assert shutdown.should_stop is True4243def test_wait_for_stop_timeout(self) -> None:44"""Test wait_for_stop with timeout."""45shutdown = GracefulShutdown()4647start = time.time()48result = shutdown.wait_for_stop(timeout=0.1)49elapsed = time.time() - start5051assert result is False52assert elapsed >= 0.153assert elapsed < 0.5 # Should not take too long5455def test_wait_for_stop_signaled(self) -> None:56"""Test wait_for_stop when stop is signaled."""57shutdown = GracefulShutdown()5859# Signal stop in a separate thread60def signal_stop() -> None:61time.sleep(0.05)62shutdown.request_stop()6364thread = threading.Thread(target=signal_stop)65thread.start()6667result = shutdown.wait_for_stop(timeout=1.0)6869thread.join()70assert result is True7172def test_context_manager_restores_handlers(self) -> None:73"""Test that context manager restores original handlers."""74original_sigint = signal.getsignal(signal.SIGINT)75original_sigterm = signal.getsignal(signal.SIGTERM)7677with GracefulShutdown():78pass7980assert signal.getsignal(signal.SIGINT) == original_sigint81assert signal.getsignal(signal.SIGTERM) == original_sigterm8283def test_nested_context_managers(self) -> None:84"""Test that nested context managers work correctly."""85with GracefulShutdown() as outer:86with GracefulShutdown() as inner:87inner.request_stop()88assert inner.should_stop is True89assert outer.should_stop is False9091def test_multiple_stop_requests(self) -> None:92"""Test that multiple stop requests are idempotent."""93shutdown = GracefulShutdown()9495shutdown.request_stop()96shutdown.request_stop()97shutdown.request_stop()9899assert shutdown.should_stop is True100101102class TestShutdownCallback:103"""Tests for ShutdownCallback utility."""104105def test_register_callback(self) -> None:106"""Test registering a callback."""107shutdown = GracefulShutdown()108callbacks = ShutdownCallback(shutdown)109110called = []111callbacks.register(lambda: called.append(1))112113callbacks.run_all()114115assert called == [1]116117def test_multiple_callbacks(self) -> None:118"""Test registering and running multiple callbacks."""119shutdown = GracefulShutdown()120callbacks = ShutdownCallback(shutdown)121122results = []123callbacks.register(lambda: results.append("first"))124callbacks.register(lambda: results.append("second"))125callbacks.register(lambda: results.append("third"))126127callbacks.run_all()128129assert results == ["first", "second", "third"]130131def test_callback_exception_handling(self) -> None:132"""Test that callback exceptions are collected, not raised."""133shutdown = GracefulShutdown()134callbacks = ShutdownCallback(shutdown)135136results = []137callbacks.register(lambda: results.append("first"))138callbacks.register(lambda: (_ for _ in ()).throw(ValueError("error")))139callbacks.register(lambda: results.append("third"))140141exceptions = callbacks.run_all()142143# First and third should still run144assert results == ["first", "third"]145# Exception should be collected146assert len(exceptions) == 1147assert isinstance(exceptions[0], ValueError)148149def test_run_all_empty(self) -> None:150"""Test run_all with no callbacks."""151shutdown = GracefulShutdown()152callbacks = ShutdownCallback(shutdown)153154exceptions = callbacks.run_all()155156assert exceptions == []157158159class TestIgnoreSignalsInChild:160"""Tests for ignore_signals_in_child function."""161162def test_sets_signal_handlers(self) -> None:163"""Test that function sets SIG_IGN handlers."""164original_sigint = signal.getsignal(signal.SIGINT)165original_sigterm = signal.getsignal(signal.SIGTERM)166167try:168ignore_signals_in_child()169170assert signal.getsignal(signal.SIGINT) == signal.SIG_IGN171assert signal.getsignal(signal.SIGTERM) == signal.SIG_IGN172finally:173# Restore original handlers174signal.signal(signal.SIGINT, original_sigint)175signal.signal(signal.SIGTERM, original_sigterm)176177178class TestThreadSafety:179"""Tests for thread safety of shutdown handling."""180181def test_concurrent_stop_requests(self) -> None:182"""Test concurrent stop requests from multiple threads."""183shutdown = GracefulShutdown()184threads = []185186for _ in range(10):187t = threading.Thread(target=shutdown.request_stop)188threads.append(t)189t.start()190191for t in threads:192t.join()193194assert shutdown.should_stop is True195196def test_concurrent_should_stop_reads(self) -> None:197"""Test concurrent reads of should_stop."""198shutdown = GracefulShutdown()199results = []200201def reader() -> None:202for _ in range(100):203results.append(shutdown.should_stop)204time.sleep(0.001)205206threads = [threading.Thread(target=reader) for _ in range(5)]207208for t in threads:209t.start()210211time.sleep(0.05)212shutdown.request_stop()213214for t in threads:215t.join()216217# Should have some False and some True218assert False in results219assert True in results220