mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
179 lines
6.5 KiB
Python
179 lines
6.5 KiB
Python
import socket
|
|
import threading
|
|
import time
|
|
|
|
from app.adapters.network import doh
|
|
from app.runtime.correlation import correlation_scope, get_correlation_id
|
|
from app.runtime.execution import OwnedThreadPoolExecutor
|
|
|
|
|
|
def test_doh_executor_is_lazy_and_shutdown_restores_socket(monkeypatch):
|
|
"""DoH 线程池按需创建,并在模块关闭时恢复系统 DNS"""
|
|
original_getaddrinfo = socket.getaddrinfo
|
|
helper = object.__new__(doh.DohHelper)
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_DOMAINS", "example.com")
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_RESOLVERS", "resolver.test")
|
|
monkeypatch.setattr(doh, "_doh_query", lambda resolver, host: "203.0.113.7")
|
|
monkeypatch.setattr(doh, "_orig_getaddrinfo", lambda host, *args, **kwargs: [])
|
|
|
|
try:
|
|
assert helper.shutdown() is True
|
|
assert doh._executor is None
|
|
|
|
assert doh.enable_doh(True) is True
|
|
socket.getaddrinfo("example.com", None)
|
|
executor = doh._executor
|
|
assert isinstance(executor, OwnedThreadPoolExecutor)
|
|
|
|
assert helper.shutdown() is True
|
|
|
|
assert doh._executor is None
|
|
assert socket.getaddrinfo is doh._orig_getaddrinfo
|
|
assert getattr(executor, "_shutdown", False)
|
|
finally:
|
|
helper.shutdown()
|
|
socket.getaddrinfo = original_getaddrinfo
|
|
|
|
|
|
def test_doh_shutdown_is_bounded_and_retryable(monkeypatch):
|
|
"""阻塞查询超时时保留同一 owner,释放后可重试并安全重新启用。"""
|
|
original_getaddrinfo = socket.getaddrinfo
|
|
helper = object.__new__(doh.DohHelper)
|
|
entered = threading.Event()
|
|
release = threading.Event()
|
|
future = None
|
|
monkeypatch.setattr(doh, "_orig_getaddrinfo", lambda host, *args, **kwargs: [])
|
|
|
|
def blocked_query() -> None:
|
|
"""模拟底层网络栈未按 DoH 请求超时返回的同步查询。"""
|
|
entered.set()
|
|
release.wait()
|
|
|
|
try:
|
|
assert helper.shutdown(timeout=1) is True
|
|
assert doh.enable_doh(True) is True
|
|
with doh._executor_lock:
|
|
executor = doh._get_executor_locked()
|
|
future = executor.submit(blocked_query)
|
|
assert entered.wait(timeout=1)
|
|
|
|
started_at = time.monotonic()
|
|
assert helper.shutdown(timeout=0.01) is False
|
|
assert time.monotonic() - started_at < 1
|
|
assert doh._executor is executor
|
|
assert socket.getaddrinfo is doh._orig_getaddrinfo
|
|
assert executor.accepting is False
|
|
|
|
# 未收敛 owner 不得被新 executor 覆盖,否则旧查询会脱离生命周期追踪。
|
|
assert doh.enable_doh(True) is False
|
|
assert doh._executor is executor
|
|
|
|
release.set()
|
|
future.result(timeout=1)
|
|
assert helper.shutdown(timeout=1) is True
|
|
assert doh._executor is None
|
|
assert doh.enable_doh(True) is True
|
|
finally:
|
|
release.set()
|
|
if future is not None:
|
|
future.result(timeout=1)
|
|
helper.shutdown(timeout=1)
|
|
socket.getaddrinfo = original_getaddrinfo
|
|
|
|
|
|
def test_doh_config_reload_disables_and_closes_executor(monkeypatch):
|
|
"""热更新关闭 DoH 时恢复系统 DNS 并释放已创建的线程池"""
|
|
original_getaddrinfo = socket.getaddrinfo
|
|
helper = object.__new__(doh.DohHelper)
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_DOMAINS", "example.com")
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_RESOLVERS", "resolver.test")
|
|
monkeypatch.setattr(doh, "_doh_query", lambda resolver, host: "203.0.113.7")
|
|
monkeypatch.setattr(doh, "_orig_getaddrinfo", lambda host, *args, **kwargs: [])
|
|
|
|
try:
|
|
assert helper.shutdown() is True
|
|
assert doh.enable_doh(True) is True
|
|
socket.getaddrinfo("example.com", None)
|
|
executor = doh._executor
|
|
assert executor is not None
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_ENABLE", False)
|
|
|
|
helper.on_config_changed()
|
|
|
|
assert doh._executor is None
|
|
assert getattr(executor, "_shutdown", False)
|
|
assert socket.getaddrinfo is doh._orig_getaddrinfo
|
|
finally:
|
|
helper.shutdown()
|
|
socket.getaddrinfo = original_getaddrinfo
|
|
|
|
|
|
def test_enable_doh_reuses_cached_host_resolution(monkeypatch):
|
|
"""
|
|
同一 DoH 域名第二次解析应命中缓存,避免重复请求远端解析器。
|
|
"""
|
|
query_calls = []
|
|
resolved_hosts = []
|
|
|
|
def fake_query(resolver: str, host: str) -> str:
|
|
query_calls.append((resolver, host))
|
|
return "203.0.113.7"
|
|
|
|
def fake_getaddrinfo(host: str, *args, **kwargs):
|
|
resolved_hosts.append(host)
|
|
return [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, 0))]
|
|
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_DOMAINS", "example.com")
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_RESOLVERS", "resolver.test")
|
|
monkeypatch.setattr(doh, "_doh_query", fake_query)
|
|
monkeypatch.setattr(doh, "_orig_getaddrinfo", fake_getaddrinfo)
|
|
|
|
original_getaddrinfo = socket.getaddrinfo
|
|
with doh._doh_lock:
|
|
doh._doh_cache.clear()
|
|
|
|
try:
|
|
assert doh.enable_doh(True) is True
|
|
|
|
socket.getaddrinfo("example.com", None)
|
|
socket.getaddrinfo("example.com", None)
|
|
finally:
|
|
object.__new__(doh.DohHelper).shutdown()
|
|
socket.getaddrinfo = original_getaddrinfo
|
|
with doh._doh_lock:
|
|
doh._doh_cache.clear()
|
|
|
|
assert query_calls == [("resolver.test", "example.com")]
|
|
assert resolved_hosts == ["203.0.113.7", "203.0.113.7"]
|
|
|
|
|
|
def test_doh_queries_use_each_request_context(monkeypatch):
|
|
"""复用的 DoH worker 应按查询恢复关联 ID,不能丢失或粘住首个请求。"""
|
|
original_getaddrinfo = socket.getaddrinfo
|
|
observed = []
|
|
helper = object.__new__(doh.DohHelper)
|
|
monkeypatch.setattr(
|
|
"app.runtime.config.settings.DOH_DOMAINS",
|
|
"first.example,second.example",
|
|
)
|
|
monkeypatch.setattr("app.runtime.config.settings.DOH_RESOLVERS", "resolver.test")
|
|
monkeypatch.setattr(
|
|
doh,
|
|
"_doh_query",
|
|
lambda _resolver, _host: observed.append(get_correlation_id()) or "203.0.113.7",
|
|
)
|
|
monkeypatch.setattr(doh, "_orig_getaddrinfo", lambda _host, *_args, **_kwargs: [])
|
|
|
|
try:
|
|
assert helper.shutdown() is True
|
|
assert doh.enable_doh(True) is True
|
|
with correlation_scope("doh-first"):
|
|
socket.getaddrinfo("first.example", None)
|
|
with correlation_scope("doh-second"):
|
|
socket.getaddrinfo("second.example", None)
|
|
finally:
|
|
helper.shutdown()
|
|
socket.getaddrinfo = original_getaddrinfo
|
|
|
|
assert observed == ["doh-first", "doh-second"]
|