Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(docker_test): 修复 Docker Test 报错未捕获的问题 #296

Merged
merged 5 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"""
metadata: SkipValidation[Metadata] | None
""" 插件元数据 """
outputs: list[str]
output: str
""" 测试输出 """

@field_validator("config", mode="before")
Expand All @@ -50,15 +50,6 @@
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 @@
# 连接 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 = {

Check warning on line 82 in src/providers/docker_test/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/providers/docker_test/__init__.py#L81-L82

Added lines #L81 - L82 were not covered by tests
"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 @@


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 @@
"""
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 @@

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 @@
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 @@
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 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 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 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 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 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", "")

Check warning on line 484 in src/providers/docker_test/plugin_test.py

View check run for this annotation

Codecov / codecov/patch

src/providers/docker_test/plugin_test.py#L483-L484

Added lines #L483 - L484 were not covered by tests
plugin_config = os.environ.get("PLUGIN_CONFIG", None)
plugin = PluginTest(plugin_info, plugin_config)

plugin = PluginTest(project_link, module_name, plugin_config)

Check warning on line 487 in src/providers/docker_test/plugin_test.py

View check run for this annotation

Codecov / codecov/patch

src/providers/docker_test/plugin_test.py#L487

Added line #L487 was not covered by tests

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
Loading