Skip to content

Commit ef90d60

Browse files
committed
feat: 全面修复 AutoCode 核心 Bug 并增强功能
修复 6 个已知 Bug,参数化硬编码配置,完善 Interactor 通信,补充集成测试和错误处理。 ## Bug 修复 (P0) - stress_test: 修复对拍时未传 seed 参数导致每轮生成相同数据的问题 改用 run_binary_with_args 传递 seed 作为命令行参数 - generator: 修复 GeneratorRunTool.execute() 参数与 input_schema 不匹配 添加 n_min/n_max/t_min/t_max 到 schema,MCP Client 现在可正确传递参数 - problem: 修复 ProblemGenerateTestsTool 中 test_configs 参数被忽略的问题 现在使用配置中的 seed_offset/type/n_min/n_max/t_min/t_max 调用 generator ## 设计改进 (P1) - problem: test_configs 参数化,支持通过 API 传入自定义测试配置 提取 _get_default_configs() 方法,保持向后兼容 - interactor: 实现双向管道通信(interactor.stdout <-> solution.stdin) 添加 pipe_data helper,处理 Windows 兼容性降级 ## 小修复 (P2) - problem: problem.xml 中 test-count 从硬编码 20 改为动态计算实际 .in 文件数 - docs: 修复 CONTRIBUTING.md 中项目名称 ACMGO -> AutoCode ## 增强 - 新增 tests/test_integration/ 集成测试模块(3 个端到端测试) - 改进 compiler.py 超时错误提示,提供具体排查建议 - 改进 stress_test generator 失败错误提示 ## 文件变更 - src/autocode_mcp/tools/stress_test.py: seed 传递 + 错误提示 - src/autocode_mcp/tools/generator.py: schema 补全 - src/autocode_mcp/tools/problem.py: test_configs 参数化 + 动态 test-count - src/autocode_mcp/tools/interactor.py: 双向管道通信 - src/autocode_mcp/utils/compiler.py: 超时错误提示增强 - CONTRIBUTING.md: 项目名称修正 - tests/: 新增 8 个单元测试 + 3 个集成测试
1 parent a7ef7fc commit ef90d60

File tree

15 files changed

+680
-94
lines changed

15 files changed

+680
-94
lines changed

.claude/settings.local.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# 贡献指南 (Contributing to ACMGO)
1+
# 贡献指南 (Contributing to AutoCode)
22

3-
由于 ACMGO 是一个旨在产出自动化高质量竞赛题目的辅助工具和 SOP 集合,我们非常珍惜所有形式的建议和改进。
3+
由于 AutoCode 是一个旨在产出自动化高质量竞赛题目的辅助工具和 SOP 集合,我们非常珍惜所有形式的建议和改进。
44

55
## 提交 Issue
66

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ packages = ["src/autocode_mcp"]
3131
[tool.pytest.ini_options]
3232
asyncio_mode = "auto"
3333
testpaths = ["tests"]
34+
markers = ["integration: marks tests as integration tests (deselect with '-m \"not integration\"')"]
3435

3536
[tool.ruff]
3637
line-length = 100

src/autocode_mcp/tools/generator.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
基于论文 Algorithm 2: BUILDGENERATORSUITE 实现。
55
"""
6+
67
import hashlib
78
import os
89

@@ -137,6 +138,26 @@ def input_schema(self) -> dict:
137138
"description": "随机种子起始值",
138139
"default": 1,
139140
},
141+
"n_min": {
142+
"type": "integer",
143+
"description": "N 最小值",
144+
"default": 1,
145+
},
146+
"n_max": {
147+
"type": "integer",
148+
"description": "N 最大值",
149+
"default": 100000,
150+
},
151+
"t_min": {
152+
"type": "integer",
153+
"description": "T 最小值",
154+
"default": 1,
155+
},
156+
"t_max": {
157+
"type": "integer",
158+
"description": "T 最大值",
159+
"default": 1,
160+
},
140161
},
141162
"required": ["problem_dir", "strategies"],
142163
}
@@ -215,11 +236,13 @@ async def execute(
215236
if val_result.return_code != 0:
216237
continue
217238

218-
generated_inputs.append({
219-
"input": input_data,
220-
"strategy": strategy,
221-
"seed": seed,
222-
})
239+
generated_inputs.append(
240+
{
241+
"input": input_data,
242+
"strategy": strategy,
243+
"seed": seed,
244+
}
245+
)
223246
seed += 1
224247

225248
return ToolResult.ok(

src/autocode_mcp/tools/interactor.py

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
基于论文 Algorithm 4: BUILDINTERACTOR 实现。
55
"""
6+
67
import asyncio
78
import os
89

@@ -111,9 +112,7 @@ async def execute(
111112
pass_total = 1
112113
# 运行交互测试:参考解应该被接受
113114
test_result = await self._run_interactor_test(
114-
interactor_exe=binary_path,
115-
solution_exe=reference_solution_path,
116-
timeout=10
115+
interactor_exe=binary_path, solution_exe=reference_solution_path, timeout=10
117116
)
118117
if test_result["verdict"] == "AC":
119118
pass_count = 1
@@ -127,9 +126,7 @@ async def execute(
127126
if os.path.exists(mutant_path):
128127
# 运行交互测试:变异解应该被拒绝
129128
test_result = await self._run_interactor_test(
130-
interactor_exe=binary_path,
131-
solution_exe=mutant_path,
132-
timeout=10
129+
interactor_exe=binary_path, solution_exe=mutant_path, timeout=10
133130
)
134131
# 交互器返回 WA 或其他非 AC 结果表示拒绝
135132
if test_result.get("verdict") != "AC":
@@ -201,9 +198,9 @@ async def _run_interactor_test(
201198
done, pending = await asyncio.wait(
202199
[
203200
asyncio.create_task(self._communicate(interactor, solution)),
204-
asyncio.create_task(asyncio.sleep(timeout))
201+
asyncio.create_task(asyncio.sleep(timeout)),
205202
],
206-
return_when=asyncio.FIRST_COMPLETED
203+
return_when=asyncio.FIRST_COMPLETED,
207204
)
208205

209206
# 检查是否超时
@@ -249,25 +246,72 @@ async def _communicate(
249246
solution: asyncio.subprocess.Process,
250247
) -> dict:
251248
"""
252-
在交互器和解法之间建立通信
249+
在交互器和解法之间建立双向通信管道
253250
254-
简化版本:假设交互器通过 exit code 返回判断结果
255-
0 = AC, 1 = WA, 其他 = RE
251+
interactor.stdout -> solution.stdin
252+
solution.stdout -> interactor.stdin
256253
"""
254+
255+
async def pipe_data(reader, writer, name: str):
256+
"""从 reader 读取数据并写入 writer"""
257+
try:
258+
while True:
259+
data = await reader.read(4096)
260+
if not data:
261+
break
262+
writer.write(data)
263+
await writer.drain()
264+
except asyncio.CancelledError, ConnectionResetError, BrokenPipeError, OSError:
265+
pass
266+
267+
pipe_tasks = []
257268
try:
258-
# 等待交互器完成
259-
await interactor.wait()
260-
261-
# 根据交互器的返回码判断结果
262-
if interactor.returncode == 0:
263-
return {"verdict": "AC", "reason": "Accepted"}
264-
elif interactor.returncode == 1:
265-
return {"verdict": "WA", "reason": "Wrong answer"}
266-
else:
267-
return {
268-
"verdict": "RE",
269-
"reason": f"Runtime error (exit code: {interactor.returncode})"
270-
}
269+
# 启动双向管道
270+
pipe_tasks = [
271+
asyncio.create_task(
272+
pipe_data(interactor.stdout, solution.stdin, "interactor->solution")
273+
),
274+
asyncio.create_task(
275+
pipe_data(solution.stdout, interactor.stdin, "solution->interactor")
276+
),
277+
]
278+
279+
# 等待任一进程完成
280+
await asyncio.wait(
281+
[interactor.wait(), solution.wait()],
282+
return_when=asyncio.FIRST_COMPLETED,
283+
)
284+
except NotImplementedError:
285+
# Windows 上可能不支持某些 asyncio 功能
286+
pass
287+
finally:
288+
# 取消管道任务
289+
for task in pipe_tasks:
290+
task.cancel()
291+
try:
292+
await task
293+
except asyncio.CancelledError:
294+
pass
271295

272-
except Exception as e:
273-
return {"verdict": "RE", "reason": str(e)}
296+
# 清理进程
297+
for proc in [interactor, solution]:
298+
if proc.returncode is None:
299+
try:
300+
proc.kill()
301+
except ProcessLookupError, OSError:
302+
pass
303+
try:
304+
await asyncio.wait_for(proc.wait(), timeout=2)
305+
except TimeoutError, OSError:
306+
pass
307+
308+
# 根据交互器退出码判断结果
309+
if interactor.returncode == 0:
310+
return {"verdict": "AC", "reason": "Accepted"}
311+
elif interactor.returncode == 1:
312+
return {"verdict": "WA", "reason": "Wrong answer"}
313+
else:
314+
return {
315+
"verdict": "RE",
316+
"reason": f"Runtime error (exit code: {interactor.returncode})",
317+
}

0 commit comments

Comments
 (0)