Skip to main content
Glama
NorthSeacoder

Frontend Test Generation & Code Review MCP Server

test-runner-worker.ts4.03 kB
/** * Test Runner Worker - 在 worker 线程中执行测试 */ import { parentPort, workerData } from 'worker_threads'; import { exec } from 'child_process'; import { promisify } from 'util'; import type { WorkerTask, WorkerResponse } from './worker-pool.js'; import { logger } from '../utils/logger.js'; const execAsync = promisify(exec); interface TestRunnerPayload { workDir: string; testFiles?: string[]; framework: 'vitest' | 'jest'; timeout?: number; } interface TestRunnerResult { summary: { total: number; passed: number; failed: number; skipped: number; duration: number; }; stdout: string; stderr: string; exitCode: number; } async function runTests(payload: TestRunnerPayload): Promise<TestRunnerResult> { const { workDir, testFiles, framework, timeout = 60000 } = payload; // 构建测试命令 let command: string; if (framework === 'jest') { command = 'npx jest --ci --no-coverage'; if (testFiles && testFiles.length > 0) { command += ` ${testFiles.join(' ')}`; } } else { // vitest command = 'npx vitest run --no-coverage'; if (testFiles && testFiles.length > 0) { command += ` ${testFiles.join(' ')}`; } } logger.info('[TestRunnerWorker] Executing tests', { command, workDir }); const startTime = Date.now(); let stdout = ''; let stderr = ''; let exitCode = 0; try { const result = await execAsync(command, { cwd: workDir, timeout, maxBuffer: 10 * 1024 * 1024, // 10MB env: { ...process.env, CI: '1', // 禁用交互式输出 }, }); stdout = result.stdout; stderr = result.stderr; } catch (error: any) { stdout = error.stdout || ''; stderr = error.stderr || ''; exitCode = error.code || 1; } const duration = Date.now() - startTime; // 解析测试结果 const summary = parseTestResults(stdout, stderr, framework); summary.duration = duration; return { summary, stdout, stderr, exitCode, }; } function parseTestResults( stdout: string, stderr: string, framework: string ): { total: number; passed: number; failed: number; skipped: number; duration: number; } { const combined = stdout + stderr; let total = 0; let passed = 0; let failed = 0; let skipped = 0; if (framework === 'vitest') { // 解析 Vitest 输出 const testMatch = combined.match(/Tests\s+(\d+)\s+passed[^(]*\((\d+)\)/i); if (testMatch) { passed = parseInt(testMatch[1], 10); total = parseInt(testMatch[2], 10); } const failMatch = combined.match(/Tests\s+(\d+)\s+failed/i); if (failMatch) { failed = parseInt(failMatch[1], 10); } const skipMatch = combined.match(/Tests\s+(\d+)\s+skipped/i); if (skipMatch) { skipped = parseInt(skipMatch[1], 10); } } else { // 解析 Jest 输出 const testMatch = combined.match(/Tests:\s+(?:(\d+)\s+failed,\s*)?(\d+)\s+passed,\s*(\d+)\s+total/i); if (testMatch) { failed = parseInt(testMatch[1] || '0', 10); passed = parseInt(testMatch[2], 10); total = parseInt(testMatch[3], 10); } const skipMatch = combined.match(/(\d+)\s+skipped/i); if (skipMatch) { skipped = parseInt(skipMatch[1], 10); } } return { total, passed, failed, skipped, duration: 0, // Will be set by caller }; } // Worker 主逻辑 if (parentPort) { const task: WorkerTask<TestRunnerPayload> = workerData; logger.info('[TestRunnerWorker] Starting test execution', { workspaceId: task.workspaceId }); runTests(task.payload) .then((result) => { const response: WorkerResponse<TestRunnerResult> = { success: true, data: result, }; parentPort!.postMessage(response); }) .catch((error) => { const response: WorkerResponse = { success: false, error: error instanceof Error ? error.message : String(error), }; parentPort!.postMessage(response); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/NorthSeacoder/fe-testgen-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server