/** * YouTube Crawler Benchmark Test * 基于 benchmark.md 的评分标准(5维度:30/25/20/15/10) * * 重要:本测试在无 yt-dlp / 无 API Key 环境下使用 Mock 模式运行 */ const { YouTubeTrendingCrawler, getMockTrendingVideos } = require('./youtube_crawler'); const fs = require('fs'); const path = require('path'); // 强制使用 Mock 模式(无外部依赖) const FORCE_MOCK = true; // 测试结果 const SCORES = { functionality: { score: 0, max: 30, details: [] }, code_quality: { score: 0, max: 25, details: [] }, api_usage: { score: 0, max: 20, details: [] }, data_completeness: { score: 0, max: 15, details: [] }, extensibility: { score: 0, max: 10, details: [] } }; const LOGS = []; function log(message, level = 'info') { const entry = { time: new Date().toISOString(), level, message }; LOGS.push(entry); console.log(`[${level.toUpperCase()}] ${message}`); } // ============ 维度1: 功能完整性 (30分) ============ async function testGetTrending() { log('\n--- 测试: get_trending() 方法 (8分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler({ useMock: true }); const videos = await crawler.get_trending('US', 5); if (Array.isArray(videos) && videos.length > 0) { score += 4; log('✅ 成功返回视频数组'); } else { log('❌ 未返回视频数组'); } if (videos.length <= 5) { score += 2; log('✅ maxResults 参数生效'); } if (videos[0]?.videoId) { score += 2; log('✅ 视频包含 videoId'); } } catch (error) { log(`❌ get_trending 测试失败: ${error.message}`, 'error'); } SCORES.functionality.details.push({ test: 'get_trending', score, max: 8 }); SCORES.functionality.score += score; log(`得分: ${score}/8`); return score; } async function testGetMultiRegionTrending() { log('\n--- 测试: get_multi_region_trending() 方法 (7分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler({ useMock: true }); const results = await crawler.get_multi_region_trending(['US', 'JP', 'KR'], 3); if (results && typeof results === 'object') { score += 2; log('✅ 返回对象结构'); } if (results.US && Array.isArray(results.US)) { score += 2; log('✅ US 地区数据正确'); } if (results.JP && results.KR) { score += 2; log('✅ 多地区支持正常'); } if (Object.keys(results).length === 3) { score += 1; log('✅ 所有请求的地区都有结果'); } } catch (error) { log(`❌ get_multi_region_trending 测试失败: ${error.message}`, 'error'); } SCORES.functionality.details.push({ test: 'get_multi_region_trending', score, max: 7 }); SCORES.functionality.score += score; log(`得分: ${score}/7`); return score; } function testExtractVideoInfo() { log('\n--- 测试: extract_video_info() 方法 (5分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler(); const mockApiItem = { id: 'test123', snippet: { title: 'Test Video', channelTitle: 'Test Channel', publishedAt: '2024-01-01T00:00:00Z', thumbnails: { default: { url: 'http://example.com/default.jpg' }, medium: { url: 'http://example.com/medium.jpg' }, high: { url: 'http://example.com/high.jpg' } }, categoryId: '10' }, statistics: { viewCount: '1000000' } }; const result = crawler.extract_video_info(mockApiItem); // 检查所有必需字段 const requiredFields = ['videoId', 'url', 'title', 'channelTitle', 'viewCount', 'publishedAt', 'thumbnails']; const hasAllFields = requiredFields.every(f => result[f] !== undefined); if (hasAllFields) { score += 2; log('✅ 所有必需字段存在'); } if (result.videoId === 'test123') { score += 1; log('✅ videoId 正确提取'); } if (result.url === 'https://youtube.com/watch?v=test123') { score += 1; log('✅ URL 正确构建'); } if (result.viewCount === 1000000) { score += 1; log('✅ viewCount 正确解析为数字'); } } catch (error) { log(`❌ extract_video_info 测试失败: ${error.message}`, 'error'); } SCORES.functionality.details.push({ test: 'extract_video_info', score, max: 5 }); SCORES.functionality.score += score; log(`得分: ${score}/5`); return score; } function testSaveToJson() { log('\n--- 测试: save_to_json() 方法 (5分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler(); const testData = [{ videoId: 'test1', title: 'Test' }]; const testPath = '/tmp/test_youtube_crawler.json'; crawler.save_to_json(testData, testPath); if (fs.existsSync(testPath)) { score += 2; log('✅ 文件成功创建'); const content = fs.readFileSync(testPath, 'utf-8'); const parsed = JSON.parse(content); if (Array.isArray(parsed) && parsed[0].videoId === 'test1') { score += 2; log('✅ JSON 格式正确'); } // 清理 fs.unlinkSync(testPath); score += 1; log('✅ 文件写入完整'); } } catch (error) { log(`❌ save_to_json 测试失败: ${error.message}`, 'error'); } SCORES.functionality.details.push({ test: 'save_to_json', score, max: 5 }); SCORES.functionality.score += score; log(`得分: ${score}/5`); return score; } function testSaveToCsv() { log('\n--- 测试: save_to_csv() 方法 (5分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler(); const testData = [ { videoId: 'v1', title: 'Video 1', channelTitle: 'Channel A', viewCount: 1000 }, { videoId: 'v2', title: 'Video 2', channelTitle: 'Channel B', viewCount: 2000 } ]; const testPath = '/tmp/test_youtube_crawler.csv'; crawler.save_to_csv(testData, testPath); if (fs.existsSync(testPath)) { score += 2; log('✅ CSV 文件成功创建'); const content = fs.readFileSync(testPath, 'utf-8'); const lines = content.split('\n').filter(l => l.trim()); if (lines.length >= 2) { score += 2; log('✅ CSV 包含表头和数据行'); } if (lines[0].includes('videoId') && lines[0].includes('title')) { score += 1; log('✅ CSV 表头正确'); } // 清理 fs.unlinkSync(testPath); } } catch (error) { log(`❌ save_to_csv 测试失败: ${error.message}`, 'error'); } SCORES.functionality.details.push({ test: 'save_to_csv', score, max: 5 }); SCORES.functionality.score += score; log(`得分: ${score}/5`); return score; } // ============ 维度2: 代码质量 (25分) ============ function testClassStructure() { log('\n--- 测试: 类结构清晰 (5分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // 类定义 if (code.includes('class YouTubeTrendingCrawler')) { score += 2; log('✅ YouTubeTrendingCrawler 类已定义'); } // 构造函数 if (code.includes('constructor(')) { score += 1; log('✅ 构造函数存在'); } // 方法定义 const methods = ['get_trending', 'get_multi_region_trending', 'extract_video_info', 'save_to_json', 'save_to_csv']; const hasAllMethods = methods.every(m => code.includes(m)); if (hasAllMethods) { score += 2; log('✅ 所有必需方法存在'); } } catch (error) { log(`❌ 类结构测试失败: ${error.message}`, 'error'); } SCORES.code_quality.details.push({ test: 'class_structure', score, max: 5 }); SCORES.code_quality.score += score; log(`得分: ${score}/5`); return score; } function testExceptionHandling() { log('\n--- 测试: 异常处理 (8分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // try-catch 块 const tryCatchCount = (code.match(/try\s*\{/g) || []).length; if (tryCatchCount >= 3) { score += 2; log(`✅ 包含 ${tryCatchCount} 个 try-catch 块`); } // 输入验证 if (code.includes('_validateRegionCode') && code.includes('_validateMaxResults')) { score += 2; log('✅ 输入验证方法存在'); } // 网络异常处理 if (code.includes('fetch') && code.includes('response.ok')) { score += 2; log('✅ 网络响应检查'); } // API 错误处理 if (code.includes('API error') || code.includes('403') || code.includes('429')) { score += 2; log('✅ API 错误码处理'); } } catch (error) { log(`❌ 异常处理测试失败: ${error.message}`, 'error'); } SCORES.code_quality.details.push({ test: 'exception_handling', score, max: 8 }); SCORES.code_quality.score += score; log(`得分: ${score}/8`); return score; } function testLogging() { log('\n--- 测试: 日志记录 (5分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler({ useMock: true }); // 检查日志方法 if (typeof crawler._log === 'function' && typeof crawler.getLogs === 'function') { score += 2; log('✅ 日志方法存在'); } // 执行操作产生日志 crawler.getLogs(); // 触发可能的日志 const logs = crawler.getLogs(); if (logs.length > 0) { score += 2; log('✅ 日志记录工作正常'); } // 日志结构 if (logs[0]?.timestamp && logs[0]?.level) { score += 1; log('✅ 日志包含必要字段'); } } catch (error) { log(`❌ 日志测试失败: ${error.message}`, 'error'); } SCORES.code_quality.details.push({ test: 'logging', score, max: 5 }); SCORES.code_quality.score += score; log(`得分: ${score}/5`); return score; } function testComments() { log('\n--- 测试: 代码注释 (4分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // JSDoc 注释 const jsdocCount = (code.match(/\/\*\*/g) || []).length; if (jsdocCount >= 5) { score += 2; log(`✅ 包含 ${jsdocCount} 个 JSDoc 注释`); } // 方法注释 const methods = ['get_trending', 'get_multi_region_trending', 'extract_video_info']; const commentedMethods = methods.filter(m => { const idx = code.indexOf(m); const before = code.slice(Math.max(0, idx - 200), idx); return before.includes('/**') || before.includes('//'); }); if (commentedMethods.length >= 2) { score += 2; log('✅ 关键方法有注释'); } } catch (error) { log(`❌ 注释测试失败: ${error.message}`, 'error'); } SCORES.code_quality.details.push({ test: 'comments', score, max: 4 }); SCORES.code_quality.score += score; log(`得分: ${score}/4`); return score; } function testTypeAnnotations() { log('\n--- 测试: 类型提示 (3分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // JSDoc 类型注解 if (code.includes('@param') && code.includes('@returns')) { score += 2; log('✅ 包含 JSDoc 类型注解'); } // 参数类型说明 const paramTypes = ['string', 'number', 'Object', 'Array', 'boolean']; const hasTypeDocs = paramTypes.some(t => code.includes(`{${t}}`) || code.includes(`{${t}[]}`)); if (hasTypeDocs) { score += 1; log('✅ 参数类型有说明'); } } catch (error) { log(`❌ 类型提示测试失败: ${error.message}`, 'error'); } SCORES.code_quality.details.push({ test: 'type_annotations', score, max: 3 }); SCORES.code_quality.score += score; log(`得分: ${score}/3`); return score; } // ============ 维度3: API 使用规范 (20分) ============ function testEnvironmentVariables() { log('\n--- 测试: 环境变量读取 (5分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // 检查环境变量读取 if (code.includes('process.env.YOUTUBE_API_KEY')) { score += 3; log('✅ 从环境变量读取 API Key'); } if (code.includes('YOUTUBE_CRAWLER_MOCK')) { score += 2; log('✅ 支持 Mock 环境变量'); } } catch (error) { log(`❌ 环境变量测试失败: ${error.message}`, 'error'); } SCORES.api_usage.details.push({ test: 'environment_variables', score, max: 5 }); SCORES.api_usage.score += score; log(`得分: ${score}/5`); return score; } function testQuotaAwareness() { log('\n--- 测试: 配额意识 (5分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler(); // 配额追踪属性 if (crawler.quotaUsed !== undefined && crawler.quotaLimit !== undefined) { score += 2; log('✅ 配额追踪属性存在'); } // 配额检查方法 if (typeof crawler.getRemainingQuota === 'function') { score += 2; log('✅ 配额检查方法存在'); } // 配额使用说明 const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); if (code.includes('quota') && code.includes('10000')) { score += 1; log('✅ 配额限制说明'); } } catch (error) { log(`❌ 配额意识测试失败: ${error.message}`, 'error'); } SCORES.api_usage.details.push({ test: 'quota_awareness', score, max: 5 }); SCORES.api_usage.score += score; log(`得分: ${score}/5`); return score; } function testPagination() { log('\n--- 测试: 分页处理 (5分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // 检查 pageToken 或类似的分页支持 if (code.includes('pageToken') || code.includes('nextPageToken')) { score += 3; log('✅ 分页参数支持'); } else { // 或者检查是否有 maxResults 限制处理 if (code.includes('maxResults') && code.includes('50')) { score += 2; log('✅ maxResults 范围限制(分页相关)'); } } // 支持多次调用获取更多结果 if (code.includes('maxResults') && code.includes('Math.min')) { score += 2; log('✅ 结果数量控制'); } } catch (error) { log(`❌ 分页测试失败: ${error.message}`, 'error'); } SCORES.api_usage.details.push({ test: 'pagination', score, max: 5 }); SCORES.api_usage.score += score; log(`得分: ${score}/5`); return score; } function testErrorCodeHandling() { log('\n--- 测试: 错误码处理 (5分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // 403 错误 if (code.includes('403')) { score += 2; log('✅ 403 错误处理'); } // 429 错误 if (code.includes('429')) { score += 2; log('✅ 429 错误处理'); } // 400 错误 if (code.includes('400')) { score += 1; log('✅ 400 错误处理'); } } catch (error) { log(`❌ 错误码处理测试失败: ${error.message}`, 'error'); } SCORES.api_usage.details.push({ test: 'error_code_handling', score, max: 5 }); SCORES.api_usage.score += score; log(`得分: ${score}/5`); return score; } // ============ 维度4: 数据完整性 (15分) ============ async function testRequiredFields() { log('\n--- 测试: 必需字段 (8分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler({ useMock: true }); const videos = await crawler.get_trending('US', 3); if (!videos || videos.length === 0) { throw new Error('No videos returned'); } const requiredFields = ['videoId', 'title', 'viewCount', 'channelTitle', 'thumbnails']; for (const video of videos) { const hasAllFields = requiredFields.every(f => video[f] !== undefined); if (hasAllFields) { score += 2; log('✅ 视频包含所有必需字段'); break; } } // videoId 格式 const validVideoId = videos[0].videoId && typeof videos[0].videoId === 'string'; if (validVideoId) { score += 2; log('✅ videoId 格式正确'); } // viewCount 数字 if (typeof videos[0].viewCount === 'number') { score += 2; log('✅ viewCount 是数字类型'); } // thumbnails 结构 if (videos[0].thumbnails && typeof videos[0].thumbnails === 'object') { score += 2; log('✅ thumbnails 结构正确'); } } catch (error) { log(`❌ 必需字段测试失败: ${error.message}`, 'error'); } SCORES.data_completeness.details.push({ test: 'required_fields', score, max: 8 }); SCORES.data_completeness.score += score; log(`得分: ${score}/8`); return score; } async function testDataFormat() { log('\n--- 测试: 数据格式 (4分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler({ useMock: true }); const videos = await crawler.get_trending('JP', 2); // JSON 结构验证 const jsonStr = JSON.stringify(videos); const parsed = JSON.parse(jsonStr); if (Array.isArray(parsed) && parsed.length > 0) { score += 2; log('✅ JSON 结构可序列化'); } // URL 格式 const validUrl = videos[0]?.url?.includes('youtube.com/watch'); if (validUrl) { score += 2; log('✅ URL 格式正确'); } } catch (error) { log(`❌ 数据格式测试失败: ${error.message}`, 'error'); } SCORES.data_completeness.details.push({ test: 'data_format', score, max: 4 }); SCORES.data_completeness.score += score; log(`得分: ${score}/4`); return score; } async function testDataCleaning() { log('\n--- 测试: 数据清洗 (3分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // 空值处理 if (code.includes('_cleanString') || code.includes('null') || code.includes('undefined')) { score += 1; log('✅ 空值处理逻辑'); } // 字符串清理 if (code.includes('_cleanString') || code.includes('trim()')) { score += 1; log('✅ 字符串清理'); } // 数字解析容错 if (code.includes('_parseViewCount') || code.includes('parseInt')) { score += 1; log('✅ 数字解析容错'); } } catch (error) { log(`❌ 数据清洗测试失败: ${error.message}`, 'error'); } SCORES.data_completeness.details.push({ test: 'data_cleaning', score, max: 3 }); SCORES.data_completeness.score += score; log(`得分: ${score}/3`); return score; } // ============ 维度5: 扩展性 (10分) ============ function testMultiRegionSupport() { log('\n--- 测试: 多地区支持 (4分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler({ useMock: true }); // 测试多个地区代码 const regions = ['US', 'JP', 'KR', 'GB', 'DE']; for (const region of regions.slice(0, 3)) { try { const videos = crawler._getMockData(region, 2); if (videos.length > 0) { score += 1; log(`✅ 支持地区: ${region}`); } if (score >= 4) break; } catch (e) { // 继续测试其他地区 } } } catch (error) { log(`❌ 多地区支持测试失败: ${error.message}`, 'error'); } SCORES.extensibility.details.push({ test: 'multi_region_support', score, max: 4 }); SCORES.extensibility.score += score; log(`得分: ${score}/4`); return score; } async function testCategoryFilter() { log('\n--- 测试: 分类过滤 (3分) ---'); let score = 0; try { const crawler = new YouTubeTrendingCrawler({ useMock: true }); // 检查 videoCategoryId 参数支持 const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); if (code.includes('videoCategoryId') && code.includes('categoryId')) { score += 2; log('✅ videoCategoryId 参数支持'); } // 测试分类过滤 try { const videos = await crawler.get_trending('US', 5, '10'); // Music category if (videos && videos.length > 0) { score += 1; log('✅ 分类过滤功能可用'); } } catch (e) { // 即使失败也给部分分,因为参数已支持 } } catch (error) { log(`❌ 分类过滤测试失败: ${error.message}`, 'error'); } SCORES.extensibility.details.push({ test: 'category_filter', score, max: 3 }); SCORES.extensibility.score += score; log(`得分: ${score}/3`); return score; } function testConfigurability() { log('\n--- 测试: 可配置性 (3分) ---'); let score = 0; try { const code = fs.readFileSync(path.join(__dirname, 'youtube_crawler.js'), 'utf-8'); // 构造函数配置 if (code.includes('constructor(options') || code.includes('constructor({')) { score += 1; log('✅ 构造函数接受配置选项'); } // 可配置参数 const configParams = ['apiKey', 'useMock', 'timeout', 'maxRetries', 'enableFallback']; const hasConfigParams = configParams.filter(p => code.includes(p)).length; if (hasConfigParams >= 3) { score += 1; log('✅ 丰富的配置参数'); } // 方法参数可配置 if (code.includes('maxResults') && code.includes('regionCode')) { score += 1; log('✅ 方法参数可配置'); } } catch (error) { log(`❌ 可配置性测试失败: ${error.message}`, 'error'); } SCORES.extensibility.details.push({ test: 'configurability', score, max: 3 }); SCORES.extensibility.score += score; log(`得分: ${score}/3`); return score; } // ============ 负例测试 ============ async function testNegativeCases() { log('\n========================================'); log('负例测试 (错误处理)'); log('========================================'); const crawler = new YouTubeTrendingCrawler({ useMock: false }); // 无效地区代码 try { await crawler.get_trending('INVALID'); log('❌ 未拒绝无效地区代码'); } catch (error) { log('✅ 正确处理无效地区代码'); } // 无效 maxResults try { await crawler.get_trending('US', 100); log('❌ 未拒绝无效 maxResults'); } catch (error) { log('✅ 正确处理无效 maxResults'); } // 无效 videoId 提取 try { crawler.extract_video_info({}); log('❌ 未拒绝无效视频数据'); } catch (error) { log('✅ 正确处理无效视频数据'); } } // ============ 主测试流程 ============ async function runBenchmark() { log('========================================'); log('YouTube Crawler Benchmark Test'); log('基于 benchmark.md 评分标准'); log('========================================'); log(`开始时间: ${new Date().toISOString()}`); log(`强制 Mock 模式: ${FORCE_MOCK}`); log(''); // 维度1: 功能完整性 (30分) log('╔══════════════════════════════════════╗'); log('║ 维度1: 功能完整性 (30分) ║'); log('╚══════════════════════════════════════╝'); await testGetTrending(); await testGetMultiRegionTrending(); testExtractVideoInfo(); testSaveToJson(); testSaveToCsv(); // 维度2: 代码质量 (25分) log('\n╔══════════════════════════════════════╗'); log('║ 维度2: 代码质量 (25分) ║'); log('╚══════════════════════════════════════╝'); testClassStructure(); testExceptionHandling(); testLogging(); testComments(); testTypeAnnotations(); // 维度3: API 使用规范 (20分) log('\n╔══════════════════════════════════════╗'); log('║ 维度3: API 使用规范 (20分) ║'); log('╚══════════════════════════════════════╝'); testEnvironmentVariables(); testQuotaAwareness(); testPagination(); testErrorCodeHandling(); // 维度4: 数据完整性 (15分) log('\n╔══════════════════════════════════════╗'); log('║ 维度4: 数据完整性 (15分) ║'); log('╚══════════════════════════════════════╝'); await testRequiredFields(); await testDataFormat(); await testDataCleaning(); // 维度5: 扩展性 (10分) log('\n╔══════════════════════════════════════╗'); log('║ 维度5: 扩展性 (10分) ║'); log('╚══════════════════════════════════════╝'); testMultiRegionSupport(); await testCategoryFilter(); testConfigurability(); // 负例测试 await testNegativeCases(); // 总分计算 const total = SCORES.functionality.score + SCORES.code_quality.score + SCORES.api_usage.score + SCORES.data_completeness.score + SCORES.extensibility.score; const maxTotal = 100; log('\n========================================'); log('评分汇总'); log('========================================'); log(`功能完整性: ${SCORES.functionality.score.toString().padStart(2)}/${SCORES.functionality.max}`); log(`代码质量: ${SCORES.code_quality.score.toString().padStart(2)}/${SCORES.code_quality.max}`); log(`API 使用规范: ${SCORES.api_usage.score.toString().padStart(2)}/${SCORES.api_usage.max}`); log(`数据完整性: ${SCORES.data_completeness.score.toString().padStart(2)}/${SCORES.data_completeness.max}`); log(`扩展性: ${SCORES.extensibility.score.toString().padStart(2)}/${SCORES.extensibility.max}`); log('----------------------------------------'); log(`总分: ${total}/${maxTotal}`); log(`目标: ≥ 95`); log(`结果: ${total >= 95 ? '✅ 通过' : total >= 75 ? '⚠️ 需改进' : '❌ 未通过'}`); log('========================================'); // 输出 JSON 结果 const result = { total_score: total, max_score: maxTotal, passed: total >= 95, target: 95, dimensions: { functionality: { score: SCORES.functionality.score, max: SCORES.functionality.max, details: SCORES.functionality.details }, code_quality: { score: SCORES.code_quality.score, max: SCORES.code_quality.max, details: SCORES.code_quality.details }, api_usage: { score: SCORES.api_usage.score, max: SCORES.api_usage.max, details: SCORES.api_usage.details }, data_completeness: { score: SCORES.data_completeness.score, max: SCORES.data_completeness.max, details: SCORES.data_completeness.details }, extensibility: { score: SCORES.extensibility.score, max: SCORES.extensibility.max, details: SCORES.extensibility.details } }, timestamp: new Date().toISOString(), logs: LOGS, weak_dimensions: total >= 95 ? [] : Object.entries(SCORES) .filter(([_, v]) => v.score < v.max * 0.8) .map(([k, _]) => k) }; fs.writeFileSync( path.join(__dirname, 'benchmark_result.json'), JSON.stringify(result, null, 2) ); return result; } // 运行测试 runBenchmark().then(result => { process.exit(result.passed ? 0 : 1); }).catch(error => { console.error('Benchmark failed:', error); process.exit(1); });