收到合法 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 项)
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""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()
|