Files
bigwo/dev-assistant-mcp/dist/tools/runTests.js
2026-03-12 12:47:56 +08:00

137 lines
4.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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