Files
MoviePilot/tests/test_plugin_platform_browser_regressions.py
Aqr-K 6610a8c008 fix: retain plugin lifecycle ownership and recover installation state (#7146)
* fix: retain plugin lifecycle ownership and recover installation state

* test: cover plugin rollback and uninstall lifecycle boundaries

---------

Co-authored-by: jxxghp <jxxghp@gmail.com>
2026-10-23 07:44:07 +08:00

76 lines
2.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
用隔离子进程验证浏览器并发容量淘汰不会互相等待。
"""
import json
import os
import subprocess
import sys
import threading
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from unittest.mock import patch
from app.adapters.network.browser import BrowserSessionHelper
from tests.test_browser_helper import _FakeContext
def _concurrent_capacity_case():
"""
两个会话同时达到淘汰点,保留真实会话线程和关闭线程归属。
"""
BrowserSessionHelper.MAX_SESSIONS = 1
helper = BrowserSessionHelper()
original = BrowserSessionHelper._enforce_session_limit.__func__
capacity_barrier = threading.Barrier(2)
contexts = []
def launch(**_kwargs):
"""
仅替换真实浏览器资源边界,并记录创建线程。
"""
context = _FakeContext()
contexts.append((context, threading.get_ident()))
return context
def enforce(cls, protect_session_key=None):
"""
固定两个新增会话互相进入容量淘汰的竞争窗口。
"""
capacity_barrier.wait(5)
return original(cls, protect_session_key)
try:
with patch.object(helper, "_launch_context", side_effect=launch), patch.object(
BrowserSessionHelper, "_enforce_session_limit", classmethod(enforce),
), ThreadPoolExecutor(max_workers=2) as callers:
futures = [callers.submit(helper.with_session, key, lambda session: session.session_key)
for key in ("one", "two")]
assert {future.result(6) for future in futures} == {"one", "two"}
assert len(BrowserSessionHelper._sessions) <= 1
finally:
BrowserSessionHelper.close_all_sessions()
assert not BrowserSessionHelper._session_executors
assert len(contexts) == 2
assert all(context.closed and context.close_thread_id == thread_id
for context, thread_id in contexts)
print(json.dumps({"completed": True, "contexts_closed_on_owner_thread": 2}))
def test_parallel_session_capacity_eviction_converges_in_subprocess(tmp_path):
"""
真正死锁由父进程超时终止,红测不会把不可回收线程留在pytest进程中。
"""
env = dict(os.environ, CONFIG_DIR=str(tmp_path / "config"), DEBUG="false")
script = (
"from app.testing.bootstrap import prepare_backend; prepare_backend(); "
"from tests.test_plugin_platform_browser_regressions import _concurrent_capacity_case; "
"_concurrent_capacity_case()"
)
result = subprocess.run(
[sys.executable, "-c", script], cwd=Path(__file__).resolve().parents[1],
env=env, capture_output=True, text=True, timeout=12,
)
assert result.returncode == 0, result.stdout + result.stderr
assert '"completed": true' in result.stdout