Skip to content

Commit

Permalink
fix(docker_test): 修复 Docker Test 报错未捕获的问题 (#296)
Browse files Browse the repository at this point in the history
顺便优化了 plugin_test 的输入和输出。
  • Loading branch information
he0119 authored Nov 27, 2024
1 parent fed3f0f commit e1a7756
Show file tree
Hide file tree
Showing 15 changed files with 135 additions and 145 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/
### Fixed

- 超时后仍需读取 stdout 与 stderr 的内容
- 修复 Docker Test 报错未捕获的问题

## [4.0.11] - 2024-11-23

Expand Down
5 changes: 2 additions & 3 deletions src/plugins/github/plugins/config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async def validate_info_from_issue(handler: IssueHandler) -> ValidationDict:
test_result = await test.run("3.12")

# 去除颜色字符
test_output = strip_ansi("\n".join(test_result.outputs))
test_output = strip_ansi(test_result.output)
metadata = test_result.metadata
if metadata:
# 从插件测试结果中获得元数据
Expand All @@ -78,8 +78,7 @@ async def validate_info_from_issue(handler: IssueHandler) -> ValidationDict:
)
logger.info(f"插件元数据:{metadata}")
logger.info("插件测试输出:")
for output in test_result.outputs:
logger.info(output)
logger.info(test_output)

# 验证插件相关信息
result = validate_info(PublishType.PLUGIN, raw_data, previous_data)
Expand Down
5 changes: 2 additions & 3 deletions src/plugins/github/plugins/publish/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ async def validate_plugin_info_from_issue(
project_link, module_name, test_config
).run("3.12")
# 去除颜色字符
test_output = strip_ansi("\n".join(test_result.outputs))
test_output = strip_ansi(test_result.output)
metadata = test_result.metadata
if metadata:
# 从插件测试结果中获得元数据
Expand All @@ -130,8 +130,7 @@ async def validate_plugin_info_from_issue(
)
logger.info(f"插件元数据:{metadata}")
logger.info("插件测试输出:")
for output in test_result.outputs:
logger.info(output)
logger.info(test_result.output)

# 验证插件相关信息
result = validate_info(PublishType.PLUGIN, raw_data, previous_data)
Expand Down
46 changes: 22 additions & 24 deletions src/providers/docker_test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class DockerTestResult(BaseModel):
"""
metadata: SkipValidation[Metadata] | None
""" 插件元数据 """
outputs: list[str]
output: str
""" 测试输出 """

@field_validator("config", mode="before")
Expand All @@ -50,15 +50,6 @@ def __init__(self, project_link: str, module_name: str, config: str = ""):
self.module_name = module_name
self.config = config

@property
def key(self) -> str:
"""插件的标识符
project_link:module_name
例:nonebot-plugin-test:nonebot_plugin_test
"""
return f"{self.project_link}:{self.module_name}"

async def run(self, version: str) -> DockerTestResult:
"""运行 Docker 容器测试插件
Expand All @@ -72,18 +63,25 @@ async def run(self, version: str) -> DockerTestResult:
# 连接 Docker 环境
client = docker.DockerClient(base_url="unix://var/run/docker.sock")

# 运行 Docker 容器,捕获输出。 容器内运行的代码拥有超时设限,此处无需设置超时
output = client.containers.run(
image_name,
environment={
"PLUGIN_INFO": self.key,
"PLUGIN_CONFIG": self.config,
# 插件测试需要用到的插件列表来验证插件依赖是否正确加载
"PLUGINS_URL": REGISTRY_PLUGINS_URL,
},
detach=False,
remove=True,
).decode()

data = json.loads(output)
try:
# 运行 Docker 容器,捕获输出。 容器内运行的代码拥有超时设限,此处无需设置超时
output = client.containers.run(
image_name,
environment={
"PROJECT_LINK": self.project_link,
"MODULE_NAME": self.module_name,
"PLUGIN_CONFIG": self.config,
# 插件测试需要用到的插件列表来验证插件依赖是否正确加载
"PLUGINS_URL": REGISTRY_PLUGINS_URL,
},
detach=False,
remove=True,
).decode()
data = json.loads(output)
except Exception as e:
data = {
"run": False,
"load": False,
"outputs": str(e),
}
return DockerTestResult(**data)
71 changes: 32 additions & 39 deletions src/providers/docker_test/plugin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,27 +219,30 @@ def parse_requirements(requirements: str) -> dict[str, str]:


class PluginTest:
def __init__(self, project_info: str, config: str | None = None) -> None:
def __init__(
self, project_link: str, module_name: str, config: str | None = None
) -> None:
"""插件测试构造函数
Args:
project_info (str): 项目信息,格式为 project_link:module_name
config (str | None, optional): 插件配置. 默认为 None.
"""
self.project_link = project_info.split(":")[0]
self.module_name = project_info.split(":")[1]
self.project_link = project_link
self.module_name = module_name
self.config = config
self._version = None
self._plugin_list = None

self._plugin_list = None
self._test_dir = Path("plugin_test")
# 插件信息
self._version = None
# 插件测试结果
self._create = False
self._run = False
self._deps = []

# 插件输出
self._lines_output = []

# 插件测试目录
self._test_dir = Path("plugin_test")
# 插件测试环境
self._deps = []
self._test_env = []

@property
Expand All @@ -251,13 +254,6 @@ def key(self) -> str:
"""
return f"{self.project_link}:{self.module_name}"

@property
def path(self) -> Path:
"""插件测试目录"""
# 替换 : 为 -,防止文件名不合法
key = self.key.replace(":", "-")
return self._test_dir / f"{key}"

@property
def env(self) -> dict[str, str]:
"""获取环境变量"""
Expand All @@ -279,11 +275,6 @@ def _log_output(self, msg: str):

async def run(self):
"""插件测试入口"""

# 创建测试目录
if not self._test_dir.exists():
self._test_dir.mkdir()

# 创建插件测试项目
await self.create_poetry_project()
if self._create:
Expand All @@ -294,14 +285,14 @@ async def run(self):
await self.run_poetry_project()

metadata = None
metadata_path = self.path / "metadata.json"
metadata_path = self._test_dir / "metadata.json"
if metadata_path.exists():
with open(self.path / "metadata.json", encoding="utf-8") as f:
with open(self._test_dir / "metadata.json", encoding="utf-8") as f:
metadata = json.load(f)

result = {
"metadata": metadata,
"outputs": self._lines_output,
"output": "\n".join(self._lines_output),
"load": self._run,
"run": self._create,
"version": self._version,
Expand All @@ -326,7 +317,7 @@ async def command(self, cmd: str, timeout: int = 300) -> tuple[bool, str, str]:
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.path,
cwd=self._test_dir,
env=self.env,
)
try:
Expand All @@ -347,8 +338,8 @@ async def command(self, cmd: str, timeout: int = 300) -> tuple[bool, str, str]:

async def create_poetry_project(self):
"""创建 poetry 项目用来测试插件"""
if not self.path.exists():
self.path.mkdir()
if not self._test_dir.exists():
self._test_dir.mkdir()

code, stdout, stderr = await self.command(
f"""poetry init -n && sed -i "s/\\^/~/g" pyproject.toml && poetry env info --ansi && poetry add {self.project_link}"""
Expand All @@ -372,7 +363,7 @@ async def create_poetry_project(self):

async def show_package_info(self) -> None:
"""获取插件的版本与插件信息"""
if self.path.exists():
if self._test_dir.exists():
code, stdout, stderr = await self.command(
f"poetry show {self.project_link}"
)
Expand All @@ -389,19 +380,19 @@ async def show_package_info(self) -> None:

async def run_poetry_project(self) -> None:
"""运行插件"""
if self.path.exists():
if self._test_dir.exists():
# 默认使用 fake 驱动
with open(self.path / ".env", "w", encoding="utf-8") as f:
with open(self._test_dir / ".env", "w", encoding="utf-8") as f:
f.write("DRIVER=fake")
# 如果提供了插件配置项,则写入配置文件
if self.config is not None:
with open(self.path / ".env.prod", "w", encoding="utf-8") as f:
with open(self._test_dir / ".env.prod", "w", encoding="utf-8") as f:
f.write(self.config)

with open(self.path / "fake.py", "w", encoding="utf-8") as f:
with open(self._test_dir / "fake.py", "w", encoding="utf-8") as f:
f.write(FAKE_SCRIPT)

with open(self.path / "runner.py", "w", encoding="utf-8") as f:
with open(self._test_dir / "runner.py", "w", encoding="utf-8") as f:
f.write(
RUNNER_SCRIPT.format(
self.module_name,
Expand All @@ -424,7 +415,7 @@ async def run_poetry_project(self) -> None:

async def show_plugin_dependencies(self) -> None:
"""获取插件的依赖"""
if self.path.exists():
if self._test_dir.exists():
code, stdout, stderr = await self.command("poetry export --without-hashes")

if code:
Expand Down Expand Up @@ -485,13 +476,15 @@ def _get_test_env(self, requirements: dict[str, str]) -> list[str]:
def main():
"""根据传入的环境变量进行测试
PLUGIN_INFO 即为该插件的 KEY
PROJECT_LINK 为插件的项目名
MODULE_NAME 为插件的模块名
PLUGIN_CONFIG 即为该插件的配置
"""

plugin_info = os.environ.get("PLUGIN_INFO", "")
project_link = os.environ.get("PROJECT_LINK", "")
module_name = os.environ.get("MODULE_NAME", "")
plugin_config = os.environ.get("PLUGIN_CONFIG", None)
plugin = PluginTest(plugin_info, plugin_config)

plugin = PluginTest(project_link, module_name, plugin_config)

asyncio.run(plugin.run())

Expand Down
5 changes: 2 additions & 3 deletions src/providers/store_test/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def validate_plugin(
)

plugin_test_load = plugin_test_result.load
plugin_test_output = "\n".join(plugin_test_result.outputs)
plugin_test_output = plugin_test_result.output
plugin_test_version = plugin_test_result.version
plugin_test_env = plugin_test_result.test_env
plugin_metadata = plugin_test_result.metadata
Expand All @@ -52,8 +52,7 @@ async def validate_plugin(
)
click.echo(f"插件元数据:{plugin_metadata}")
click.echo("插件测试输出:")
for output in plugin_test_result.outputs:
click.echo(output)
click.echo(plugin_test_output)

if previous_plugin is None:
# 使用商店插件数据作为新的插件数据
Expand Down
1 change: 1 addition & 0 deletions tests/github/config/process/test_config_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ async def test_process_config_check(
)
mock_test_result.load = True
mock_test_result.version = "1.0.0"
mock_test_result.output = ""
mock_docker = mocker.patch("src.providers.docker_test.DockerPluginTest.run")
mock_docker.return_value = mock_test_result

Expand Down
1 change: 1 addition & 0 deletions tests/github/publish/process/test_publish_pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ async def test_process_pull_request(
)
mock_test_result.load = True
mock_test_result.version = "1.0.0"
mock_test_result.output = ""
mock_docker = mocker.patch("src.providers.docker_test.DockerPluginTest.run")
mock_docker.return_value = mock_test_result

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ async def test_resolve_conflict_pull_requests_plugin(
supported_adapters=["~onebot.v11"],
)
mock_test_result.version = "1.0.0"
mock_test_result.output = ""
mock_docker = mocker.patch("src.providers.docker_test.DockerPluginTest.run")
mock_docker.return_value = mock_test_result

Expand Down Expand Up @@ -461,6 +462,7 @@ async def test_resolve_conflict_pull_requests_plugin_not_valid(
mock_test_result = mocker.MagicMock()
mock_test_result.load = False
mock_test_result.metadata = None
mock_test_result.output = ""
mock_docker = mocker.patch("src.providers.docker_test.DockerPluginTest.run")
mock_docker.return_value = mock_test_result

Expand Down
2 changes: 2 additions & 0 deletions tests/github/publish/utils/test_trigger_registry_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ async def test_trigger_registry_update(
)
mock_test_result.load = True
mock_test_result.version = "1.0.0"
mock_test_result.output = ""
mock_docker = mocker.patch("src.providers.docker_test.DockerPluginTest.run")
mock_docker.return_value = mock_test_result

Expand Down Expand Up @@ -307,6 +308,7 @@ async def test_trigger_registry_update_plugins_issue_body_info_missing(
supported_adapters=["~onebot.v11"],
)
mock_test_result.load = True
mock_test_result.output = ""
mock_docker = mocker.patch("src.providers.docker_test.DockerPluginTest.run")
mock_docker.return_value = mock_test_result

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async def test_validate_info_from_issue_plugin(
)
mock_test_result.version = "1.0.0"
mock_test_result.load = True
mock_test_result.outputs = ['require("nonebot_plugin_alconna")', "test"]
mock_test_result.output = 'require("nonebot_plugin_alconna")\ntest'
mock_docker = mocker.patch("src.providers.docker_test.DockerPluginTest.run")
mock_docker.return_value = mock_test_result

Expand Down
Loading

0 comments on commit e1a7756

Please sign in to comment.