fix(host): _read_loop 跳过非 dict JSON 消息,不再静默死亡
收到合法 JSON 但值不是 dict(如 ""、123、[]、null)时,
旧代码走到 msg.get('params') 抛 AttributeError/TypeError,
被外层 except 吞掉后读线程退出,此后所有 request() 等到超时。
现校验后记 stderr 日志并跳过,读循环继续;脏消息不进
events、不当正常请求处理。畸形 JSON 本已由 json.loads 的
except continue 处理,本次未改动该路径。
回归:tests/test_host_bugfix_host.py ReadLoopNonDictTest(2 项)
This commit is contained in:
1 parent
b9466e556b
commit
e768959c93
2 files changed
+98
No files matched your search
@@ -0,0 +1,84 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user