项目文件夹

文件
2026-07-13 13:22:34 +08:00

107 行
3.6 KiB
Python

import logging
import threading
import time
from unittest.mock import MagicMock, patch
import pytest
import mlflow.spark.autologging as autologging_module
from mlflow.spark.autologging import _stop_listen_for_spark_activity
@pytest.fixture
def autolog_caplog(caplog):
# "mlflow" sets propagate=False, so its records never reach caplog's root handler.
# Enable propagation so caplog captures them. Don't attach caplog.handler directly:
# pytest >= 9.1 attaches it to non-propagating loggers, which double-counts records.
mlflow_logger = logging.getLogger("mlflow")
original_propagate = mlflow_logger.propagate
mlflow_logger.propagate = True
try:
with caplog.at_level(logging.WARNING, logger="mlflow.spark.autologging"):
yield caplog
finally:
mlflow_logger.propagate = original_propagate
def _make_spark_context(shutdown_side_effect=None):
gw = MagicMock()
if shutdown_side_effect is not None:
gw.shutdown_callback_server.side_effect = shutdown_side_effect
sc = MagicMock()
sc._gateway = gw
return sc
def test_fast_shutdown_returns_without_warning(autolog_caplog):
sc = _make_spark_context()
_stop_listen_for_spark_activity(sc)
sc._gateway.shutdown_callback_server.assert_called_once()
assert not any("did not complete" in r.message for r in autolog_caplog.records)
def test_hanging_shutdown_times_out_and_logs_warning(autolog_caplog):
hang = threading.Event()
def _hang():
hang.wait()
sc = _make_spark_context(shutdown_side_effect=_hang)
try:
with patch.object(autologging_module, "_CALLBACK_SERVER_SHUTDOWN_TIMEOUT_SECONDS", 0.2):
_stop_listen_for_spark_activity(sc)
warnings = [r for r in autolog_caplog.records if "did not complete" in r.message]
assert len(warnings) == 1
assert "CLOSE_WAIT" in warnings[0].message
finally:
hang.set()
def test_hanging_shutdown_does_not_block_caller():
hang = threading.Event()
def _hang():
hang.wait()
sc = _make_spark_context(shutdown_side_effect=_hang)
try:
with patch.object(autologging_module, "_CALLBACK_SERVER_SHUTDOWN_TIMEOUT_SECONDS", 0.2):
start = time.monotonic()
_stop_listen_for_spark_activity(sc)
elapsed = time.monotonic() - start
assert elapsed < 2.0
finally:
hang.set()
def test_exception_during_shutdown_logs_error_not_timeout(autolog_caplog):
sc = _make_spark_context(shutdown_side_effect=Exception("connection reset"))
with patch.object(autologging_module, "_CALLBACK_SERVER_SHUTDOWN_TIMEOUT_SECONDS", 0.2):
_stop_listen_for_spark_activity(sc)
error_warnings = [r for r in autolog_caplog.records if "Failed to shut down" in r.message]
assert len(error_warnings) == 1
assert "connection reset" in error_warnings[0].message
assert not any("did not complete" in r.message for r in autolog_caplog.records)
def test_shutdown_thread_is_daemon():
created_threads = []
original_init = threading.Thread.__init__
def capturing_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
created_threads.append(self)
hang = threading.Event()
sc = _make_spark_context(shutdown_side_effect=lambda: hang.wait())
try:
with (
patch.object(threading.Thread, "__init__", capturing_init),
patch.object(autologging_module, "_CALLBACK_SERVER_SHUTDOWN_TIMEOUT_SECONDS", 0.1),
):
_stop_listen_for_spark_activity(sc)
assert created_threads
assert all(t.daemon for t in created_threads)
finally:
hang.set()