"""host.py 覆盖率 bugfix 轨(host 分工)回归测试。 只覆盖本分工的三项:_read_loop 非 dict 消息、配置/init 的 open() 关闭、daemon 循环的 --state 透传。不碰其它轨。 """ import contextlib import io import json import queue import sys import threading import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT)) import host def _make_read_loop_server(lines, pending_id=7): """不拉起真实 app-server 的 Server:只给 _read_loop 供料。 proc.stdout 直接用字节行列表;_read_loop 对它做 `for raw in self.proc.stdout`,与真实 PIPE 的逐行迭代 语义一致。脏消息后仍能送达正常响应,即证明读循环 没有在脏消息处提前退出(旧代码会在第一条非 dict 处 抛 AttributeError/TypeError,被外层 except 吞掉后退出)。 """ srv = host.Server.__new__(host.Server) srv.approval = "deny" srv.pending_lock = threading.Lock() srv.pending = {pending_id: queue.Queue()} srv.events_lock = threading.Lock() srv.events = [] srv.turn_queues = {} srv.trace_path = None srv._trace_lines = 0 class FakeProc: def __init__(self, stdout_lines): self.stdout = stdout_lines srv.proc = FakeProc(lines) return srv class ReadLoopNonDictTest(unittest.TestCase): def test_non_dict_json_then_normal_response(self): lines = [ b'""\n', # 合法 JSON,值是 str(旧代码致死) b'"hello"\n', # 合法 JSON,值是 str b'123\n', # 合法 JSON,值是 int b'[1,2]\n', # 合法 JSON,值是 list b'null\n', # 合法 JSON,值是 None b'true\n', # 合法 JSON,值是 bool b'not-json{\n', # 畸形 JSON:旧代码本已 continue b'{"jsonrpc": "2.0", "id": 7, ' b'"result": {"ok": true}}\n', ] srv = _make_read_loop_server(lines) err = io.StringIO() with contextlib.redirect_stderr(err): srv._read_loop() resp = srv.pending[7].get(timeout=1) self.assertEqual(resp["result"], {"ok": True}) # 脏消息没有被当成通知塞进 events,也没有被当成请求处理。 self.assertEqual(srv.events, []) self.assertIn("skip non-dict message", err.getvalue()) def test_non_dict_json_then_normal_notification(self): lines = [ b'""\n', b'{"jsonrpc": "2.0", "method": "thread/started", ' b'"params": {"threadId": "t1"}}\n', ] srv = _make_read_loop_server(lines) srv._read_loop() self.assertEqual(len(srv.events), 1) self.assertEqual(srv.events[0]["method"], "thread/started") if __name__ == "__main__": unittest.main()