Skip to content

Commit caa06ef

Browse files
BigOrangeQWQhe0119
andauthored
refactor: 将容器内测试数据的传输方式由标准输入输出流改为优先通过文件传输 (#447)
* refactor: 将容器内测试数据的传输方式由标准输入输出流改为其他方式 * fix: 修改测试结果文件的默认内容 * fix: 将 ./plugin_test 提升为常量,进一步优化错误信息 * test: 为 docker 相关测试适配新的测试逻辑 * refactor: 将 PLUGIN_TEST_DIR 由字符串改为 Path 对象 * style: 减少不必要的 Path 调用 * refactor: 不需要用环境变量传递 TEST_RESULT_PATH * fix: 应该使用完整的 PYPI_KEY * test: 使用 mocker.patch 简化写法 * refactor: 调整容器输出解码位置 * refactor: 移除不必要 errors="ignore" closed #446 --------- Co-authored-by: uy/sun <[email protected]>
1 parent 1c4221c commit caa06ef

File tree

5 files changed

+299
-64
lines changed

5 files changed

+299
-64
lines changed

src/providers/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from pathlib import Path
23
from zoneinfo import ZoneInfo
34

45
TIME_ZONE = ZoneInfo("Asia/Shanghai")
@@ -38,6 +39,9 @@
3839
# https://github.com/orgs/nonebot/packages/container/package/nonetest
3940
DOCKER_IMAGES_VERSION = os.environ.get("DOCKER_IMAGES_VERSION") or "latest"
4041
DOCKER_IMAGES = f"ghcr.io/nonebot/nonetest:{DOCKER_IMAGES_VERSION}"
42+
DOCKER_BIND_RESULT_PATH = "/app/plugin_test/test_result.json"
43+
44+
PLUGIN_TEST_DIR = Path("plugin_test")
4145

4246
# Artifact 相关常量
4347
REGISTRY_DATA_NAME = "registry_data.json"

src/providers/docker_test/__init__.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import json
2+
import traceback
23
from typing import TypedDict
34

45
import docker
56
from pydantic import BaseModel, SkipValidation, field_validator
67

7-
from src.providers.constants import DOCKER_IMAGES
8+
from src.providers.constants import (
9+
DOCKER_BIND_RESULT_PATH,
10+
DOCKER_IMAGES,
11+
PLUGIN_TEST_DIR,
12+
PYPI_KEY_TEMPLATE,
13+
)
14+
from src.providers.utils import pypi_key_to_path
815

916

1017
class Metadata(TypedDict):
@@ -50,6 +57,9 @@ def __init__(self, project_link: str, module_name: str, config: str = ""):
5057
self.module_name = module_name
5158
self.config = config
5259

60+
if not PLUGIN_TEST_DIR.exists():
61+
PLUGIN_TEST_DIR.mkdir(parents=True, exist_ok=True)
62+
5363
async def run(self, version: str) -> DockerTestResult:
5464
"""运行 Docker 容器测试插件
5565
@@ -61,6 +71,13 @@ async def run(self, version: str) -> DockerTestResult:
6171
"""
6272
# 连接 Docker 环境
6373
client = docker.DockerClient(base_url="unix://var/run/docker.sock")
74+
key = PYPI_KEY_TEMPLATE.format(
75+
project_link=self.project_link, module_name=self.module_name
76+
)
77+
plugin_test_result = PLUGIN_TEST_DIR / f"{pypi_key_to_path(key)}.json"
78+
79+
# 创建文件,以确保 Docker 容器内可以写入
80+
plugin_test_result.touch(exist_ok=True)
6481

6582
try:
6683
# 运行 Docker 容器,捕获输出。 容器内运行的代码拥有超时设限,此处无需设置超时
@@ -76,20 +93,42 @@ async def run(self, version: str) -> DockerTestResult:
7693
},
7794
detach=False,
7895
remove=True,
79-
).decode()
96+
volumes={
97+
plugin_test_result.resolve(strict=False).as_posix(): {
98+
"bind": DOCKER_BIND_RESULT_PATH,
99+
"mode": "rw",
100+
}
101+
},
102+
)
80103

81104
try:
82-
data = json.loads(output)
83-
except json.JSONDecodeError:
84-
data = {
85-
"run": True,
86-
"load": False,
87-
"output": f"插件测试结果解析失败,输出内容非 JSON 格式。\n输出内容:{output}",
88-
}
105+
# 若测试结果文件存在且可解析,则优先使用测试结果文件
106+
data = json.loads(plugin_test_result.read_text(encoding="utf-8"))
107+
except Exception as e:
108+
# 如果测试结果文件不存在或不可解析,则尝试使用容器输出内容
109+
# 这个时候才需要解码容器输出内容,避免不必要的解码开销
110+
try:
111+
data = json.loads(output.decode(encoding="utf-8"))
112+
except json.JSONDecodeError:
113+
data = {
114+
"run": True,
115+
"load": False,
116+
"output": f"""
117+
测试结果文件解析失败,输出内容写入失败。
118+
{e}
119+
输出内容:{output}
120+
""",
121+
}
89122
except Exception as e:
123+
# 格式化异常堆栈信息
124+
trackback = "".join(traceback.format_exception(type(e), e, e.__traceback__))
125+
MAX_OUTPUT = 2000
126+
if len(trackback) > MAX_OUTPUT:
127+
trackback = trackback[-MAX_OUTPUT:]
128+
90129
data = {
91130
"run": False,
92131
"load": False,
93-
"output": str(e),
132+
"output": trackback,
94133
}
95134
return DockerTestResult(**data)

src/providers/docker_test/plugin_test.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313

1414
import httpx
1515

16-
from src.providers.constants import REGISTRY_PLUGINS_URL
16+
from src.providers.constants import (
17+
DOCKER_BIND_RESULT_PATH,
18+
PLUGIN_TEST_DIR,
19+
REGISTRY_PLUGINS_URL,
20+
)
1721

1822
from .render import render_fake, render_runner
1923

@@ -115,7 +119,7 @@ def __init__(
115119
self.config = config
116120

117121
self._plugin_list = None
118-
self._test_dir = Path("plugin_test")
122+
self._test_dir = PLUGIN_TEST_DIR
119123
# 插件信息
120124
self._version = None
121125
# 插件测试结果
@@ -190,6 +194,13 @@ async def run(self):
190194
"config": self.config,
191195
"test_env": " ".join(self._test_env),
192196
}
197+
# 写入测试结果文件
198+
try:
199+
result_path = Path(DOCKER_BIND_RESULT_PATH)
200+
with open(result_path, "w", encoding="utf-8") as f:
201+
json.dump(result, f, ensure_ascii=False)
202+
except Exception as e:
203+
self._log_output(f"写入测试结果文件失败,错误信息:{e}")
193204
# 输出测试结果
194205
print(json.dumps(result, ensure_ascii=False))
195206
return result

src/providers/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ def add_step_summary(summary: str):
125125
logger.debug(f"已添加作业摘要:{summary}")
126126

127127

128+
def pypi_key_to_path(key: str) -> str:
129+
"""将 PyPI 键名转换为路径字符"""
130+
return key.replace(":", "-").replace(".", "-").replace("_", "-")
131+
132+
128133
@cache
129134
def get_author_name(author_id: int) -> str:
130135
"""通过作者的ID获取作者名字"""

0 commit comments

Comments
 (0)