137 lines
4.9 KiB
JavaScript
137 lines
4.9 KiB
JavaScript
|
|
import { exec } from "child_process";
|
|||
|
|
import { promisify } from "util";
|
|||
|
|
import { existsSync } from "fs";
|
|||
|
|
import { join } from "path";
|
|||
|
|
const execAsync = promisify(exec);
|
|||
|
|
export const runTestsTool = {
|
|||
|
|
name: "run_tests",
|
|||
|
|
description: "运行项目测试。自动检测测试框架(Jest、Mocha、Pytest 等),执行测试并返回结构化的结果(通过/失败/跳过数量及失败详情)。",
|
|||
|
|
inputSchema: {
|
|||
|
|
type: "object",
|
|||
|
|
properties: {
|
|||
|
|
project_path: {
|
|||
|
|
type: "string",
|
|||
|
|
description: "项目根目录(绝对路径)",
|
|||
|
|
},
|
|||
|
|
test_file: {
|
|||
|
|
type: "string",
|
|||
|
|
description: "指定测试文件(可选,默认运行全部测试)",
|
|||
|
|
},
|
|||
|
|
test_name: {
|
|||
|
|
type: "string",
|
|||
|
|
description: "指定测试名称或模式(可选)",
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
required: ["project_path"],
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
async function runCommand(cmd, cwd) {
|
|||
|
|
try {
|
|||
|
|
const { stdout, stderr } = await execAsync(cmd, {
|
|||
|
|
cwd,
|
|||
|
|
timeout: 120000,
|
|||
|
|
maxBuffer: 1024 * 1024 * 10,
|
|||
|
|
shell: process.platform === "win32" ? "powershell.exe" : "/bin/bash",
|
|||
|
|
});
|
|||
|
|
return { stdout, stderr, code: 0 };
|
|||
|
|
}
|
|||
|
|
catch (error) {
|
|||
|
|
return {
|
|||
|
|
stdout: error.stdout || "",
|
|||
|
|
stderr: error.stderr || "",
|
|||
|
|
code: error.code ?? 1,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
export async function executeRunTests(args) {
|
|||
|
|
const { project_path, test_file, test_name } = args;
|
|||
|
|
const hasFile = (name) => existsSync(join(project_path, name));
|
|||
|
|
let testCmd = "";
|
|||
|
|
let framework = "";
|
|||
|
|
// 检测测试框架
|
|||
|
|
if (hasFile("jest.config.js") || hasFile("jest.config.ts") || hasFile("jest.config.mjs")) {
|
|||
|
|
framework = "Jest";
|
|||
|
|
testCmd = "npx jest --verbose --no-coverage";
|
|||
|
|
if (test_file)
|
|||
|
|
testCmd += ` "${test_file}"`;
|
|||
|
|
if (test_name)
|
|||
|
|
testCmd += ` -t "${test_name}"`;
|
|||
|
|
}
|
|||
|
|
else if (hasFile("vitest.config.ts") || hasFile("vitest.config.js")) {
|
|||
|
|
framework = "Vitest";
|
|||
|
|
testCmd = "npx vitest run --reporter verbose";
|
|||
|
|
if (test_file)
|
|||
|
|
testCmd += ` "${test_file}"`;
|
|||
|
|
}
|
|||
|
|
else if (hasFile("pytest.ini") || hasFile("pyproject.toml") || hasFile("setup.cfg")) {
|
|||
|
|
framework = "Pytest";
|
|||
|
|
testCmd = "python -m pytest -v";
|
|||
|
|
if (test_file)
|
|||
|
|
testCmd += ` "${test_file}"`;
|
|||
|
|
if (test_name)
|
|||
|
|
testCmd += ` -k "${test_name}"`;
|
|||
|
|
}
|
|||
|
|
else if (hasFile("package.json")) {
|
|||
|
|
// 尝试 package.json 中的 test 脚本
|
|||
|
|
framework = "npm test";
|
|||
|
|
testCmd = "npm test 2>&1";
|
|||
|
|
}
|
|||
|
|
else if (hasFile("requirements.txt")) {
|
|||
|
|
framework = "Pytest (default)";
|
|||
|
|
testCmd = "python -m pytest -v";
|
|||
|
|
if (test_file)
|
|||
|
|
testCmd += ` "${test_file}"`;
|
|||
|
|
}
|
|||
|
|
if (!testCmd) {
|
|||
|
|
return `# 测试结果\n\n⚠️ 未检测到测试框架\n项目路径: ${project_path}\n\n建议:\n- JS/TS: npm install -D jest 或 vitest\n- Python: pip install pytest`;
|
|||
|
|
}
|
|||
|
|
const result = await runCommand(testCmd, project_path);
|
|||
|
|
const fullOutput = (result.stdout + "\n" + result.stderr).trim();
|
|||
|
|
// 解析测试结果
|
|||
|
|
let passed = 0, failed = 0, skipped = 0;
|
|||
|
|
// Jest / Vitest 格式
|
|||
|
|
const jestMatch = fullOutput.match(/Tests:\s+(?:(\d+) failed,?\s*)?(?:(\d+) skipped,?\s*)?(?:(\d+) passed)?/);
|
|||
|
|
if (jestMatch) {
|
|||
|
|
failed = parseInt(jestMatch[1] || "0");
|
|||
|
|
skipped = parseInt(jestMatch[2] || "0");
|
|||
|
|
passed = parseInt(jestMatch[3] || "0");
|
|||
|
|
}
|
|||
|
|
// Pytest 格式
|
|||
|
|
const pytestMatch = fullOutput.match(/(\d+) passed(?:.*?(\d+) failed)?(?:.*?(\d+) skipped)?/);
|
|||
|
|
if (pytestMatch) {
|
|||
|
|
passed = parseInt(pytestMatch[1] || "0");
|
|||
|
|
failed = parseInt(pytestMatch[2] || "0");
|
|||
|
|
skipped = parseInt(pytestMatch[3] || "0");
|
|||
|
|
}
|
|||
|
|
const total = passed + failed + skipped;
|
|||
|
|
const allPassed = failed === 0 && result.code === 0;
|
|||
|
|
const output = [
|
|||
|
|
`# 测试报告`,
|
|||
|
|
``,
|
|||
|
|
`📂 项目: ${project_path}`,
|
|||
|
|
`🔧 框架: ${framework}`,
|
|||
|
|
``,
|
|||
|
|
`## 结果`,
|
|||
|
|
allPassed ? "✅ **全部通过**" : "❌ **存在失败**",
|
|||
|
|
``,
|
|||
|
|
`| 状态 | 数量 |`,
|
|||
|
|
`|------|------|`,
|
|||
|
|
`| ✅ 通过 | ${passed} |`,
|
|||
|
|
`| ❌ 失败 | ${failed} |`,
|
|||
|
|
`| ⏭️ 跳过 | ${skipped} |`,
|
|||
|
|
`| 📊 总计 | ${total} |`,
|
|||
|
|
``,
|
|||
|
|
`## 命令`,
|
|||
|
|
`\`${testCmd}\``,
|
|||
|
|
``,
|
|||
|
|
`## 完整输出`,
|
|||
|
|
"```",
|
|||
|
|
fullOutput.slice(0, 5000),
|
|||
|
|
"```",
|
|||
|
|
];
|
|||
|
|
if (failed > 0) {
|
|||
|
|
output.push(``, `## 建议`, `1. 查看上方失败的测试用例详情`, `2. 使用 code_debug 工具分析失败原因`, `3. 修复后重新运行 run_tests 验证`);
|
|||
|
|
}
|
|||
|
|
return output.join("\n");
|
|||
|
|
}
|
|||
|
|
//# sourceMappingURL=runTests.js.map
|