UNPKG

oimp

Version:

A CLI tool for generating OI problem and packages

246 lines (227 loc) 11.3 kB
const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); const { validateSolution,showTestCase } = require('./check.js'); const eventEmitter = require("events"); const { execSync } = require('child_process'); module.exports = async function testsampleCommand(problemName) { const problemDir = path.join(process.cwd(), problemName); const sampleDir = path.join(problemDir, "sample"); const srcDir = path.join(problemDir, "src"); const outputsDir = path.join(problemDir, "outputs"); const event = new eventEmitter(); const stdSolutions = fs .readdirSync(srcDir) // 修改过滤条件:支持 .cpp 和 .cc 扩展名 .filter( (f) => f.startsWith("std") && (f.endsWith(".cpp") || f.endsWith(".cc")) ); // 修改映射逻辑:正确移除扩展名 // .map((f) => { // const ext = path.extname(f); // 获取扩展名 (.cpp 或 .cc) // return path.basename(f, ext); // 移除扩展名 // }); console.log(chalk.blueBright(`[1/4] 查找解决方案...`)); stdSolutions.sort(); if (stdSolutions.length === 0) { console.error(chalk.red("× 未找到任何std开头的解决方案")); process.exit(1); } // 自动编译所有 std*.cpp/.cc for (const solut of stdSolutions) { const srcPath = path.join(srcDir, solut); const exname = path.extname(solut); const exePath = path.join(srcDir, path.basename(solut, exname)); try { execSync(`g++ -O2 -std=c++14 -o "${exePath}" "${srcPath}"`); console.log(chalk.green(`✓ ${solut} 编译成功`)); } catch (err) { console.error(chalk.red(${solut} 编译失败:`), err.message); if(solut !== 'std') continue; // 新增:编译失败时立即写 status.json const statusPath = path.join(problemDir, 'status.json'); if (fs.existsSync(statusPath)) { let status = JSON.parse(fs.readFileSync(statusPath, 'utf-8')); status.std = { desc: '标准程序', status: 'pending' }; status.testsample = { desc: '样例检测', status: 'need-redo' }; // 修改点:将 testsample 状态设为 need-redo if (!status.data || status.data.status === 'done' || status.data.status === 'need-redo') { status.data = { desc: '测评数据', status: 'need-redo' }; } if (!status.check || status.check.status === 'done' || status.check.status === 'need-redo') { status.check = { desc: '完整性检查', status: 'need-redo' }; } if (!status.package || status.package.status === 'done' || status.package.status === 'need-redo') { status.package = { desc: '打包', status: 'need-redo' }; } fs.writeFileSync(statusPath, JSON.stringify(status, null, 2)); console.log(chalk.yellow('× std 编译失败,std/testsample 状态已重置为 pending/need-redo,data/check/package 状态已重置为 need-redo')); } process.exit(1); } } console.log(chalk.green(`✓ 找到 ${stdSolutions.length} 个解决方案`)); console.log(chalk.blueBright(`\n[2/4] 查找样例文件...`)); // 定义 testCases 和样例检查 const testCases = fs .readdirSync(sampleDir) .filter((f) => f.endsWith(".in")) .map((f) => ({ inputFile: path.join(sampleDir, f), answerFile: path.join(sampleDir, f.replace(".in", ".ans")), })); if (testCases.length === 0) { console.error( chalk.red( "× 未找到样例,可以将样例放入题目ID目录下的sample文件夹下,.in表示输入,.ans表示输出" ) ); process.exit(1); } testCases.sort(); console.log(chalk.green(`✓ 找到 ${testCases.length} 个测试样例`)); // 检查样例答案文件是否为空 const sampleAns = path.join(sampleDir, 'sample01.ans'); if (fs.existsSync(sampleAns)) { const ansContent = fs.readFileSync(sampleAns, 'utf8').trim(); if (ansContent === '') { console.error(chalk.red('× 样例 sample01.ans 为空,请先填写样例答案后再进行样例检测!')); process.exit(1); } } console.log(chalk.blueBright(`\n[3/4] 测试数据验证器...`)); // ===== 新增:用 sample*.in 测试 validator ===== const validatorPath = path.join(srcDir, 'validator'); let validatorOk = true; if (fs.existsSync(path.join(srcDir, 'validator.cpp'))) { // 编译 validator.cpp try { execSync(`g++ -O2 -std=c++14 -o "${validatorPath}" "${path.join(srcDir, 'validator.cpp')}"`); console.log(chalk.green('✓ validator.cpp 编译成功')); } catch (err) { console.error(chalk.red('× validator.cpp 编译失败:'), err.message); validatorOk = false; } if (validatorOk) { // 用所有 sample*.in 测试 validator const sampleInFiles = fs.readdirSync(sampleDir).filter(f => f.endsWith('.in')); for (const inFile of sampleInFiles) { try { const res = execSync(`"${validatorPath}" < "${path.join(sampleDir, inFile)}"`, { stdio: 'pipe' }); if (res.toString().trim() !== '') { throw new Error(`validator 输出不为空: ${res}`); } console.log(chalk.green(`✓ validator.cpp 验证 ${inFile} 成功。`)); } catch (e) { console.error(chalk.red(`× validator 检查 ${inFile} 失败: ${e.message}`)); validatorOk = false; break; } } } } // ===== validator 检查结果推进状态 ===== const statusPath = path.join(problemDir, 'status.json'); if (fs.existsSync(statusPath)) { const status = JSON.parse(fs.readFileSync(statusPath, 'utf-8')); if (!validatorOk) { status.validator = { desc: '数据验证器', status: 'pending' }; status.testsample = { desc: '样例检测', status: 'pending' }; // 只处理 done/need-redo if (status.data && (status.data.status === 'done' || status.data.status === 'need-redo')) { status.data = { desc: '测评数据', status: 'need-redo' }; } if (status.check && (status.check.status === 'done' || status.check.status === 'need-redo')) { status.check = { desc: '完整性检查', status: 'need-redo' }; } if (status.package && (status.package.status === 'done' || status.package.status === 'need-redo')) { status.package = { desc: '打包', status: 'need-redo' }; } fs.writeFileSync(statusPath, JSON.stringify(status, null, 2)); console.log(chalk.red('× validator 检查未通过,validator/testsample 状态已重置为 pending,data/check/package 状态已重置为 need-redo(如原为 pending 则不变)')); return; }else{status.validator = { desc: '数据验证器', status: 'done' };fs.writeFileSync(statusPath, JSON.stringify(status, null, 2));} } // 强制编译 checker.cpp 以确保使用最新版本 console.log(chalk.blueBright(`\n[4/4] 编译数据检查器...`)); const checkerPath = path.join(srcDir, 'checker'); if (fs.existsSync(path.join(srcDir, 'checker.cpp'))) { try { execSync(`g++ -O2 -std=c++14 -o "${checkerPath}" "${path.join(srcDir, 'checker.cpp')}"`); console.log(chalk.green('✓ checker.cpp 编译成功')); } catch (err) { console.error(chalk.red('× checker.cpp 编译失败:'), err.message); process.exit(1); } } else { console.error(chalk.red('× 未找到 checker.cpp 文件')); process.exit(1); } // 5. 运行测试 console.log(chalk.blueBright(`\n[5/5] 运行样例测试...`)); event.on("testcase_end", showTestCase); let anyStdPassed = false; for (const solution of stdSolutions) { console.log(chalk.cyan(`\n测试 ${solution}:`)); const exname = path.extname(solution); const sol = path.basename(solution, exname); const timeLimit = 1000; const { passed, results } = await validateSolution( path.join(srcDir, sol), testCases, timeLimit, checkerPath,event ); console.log("---------------------------------"); if (passed) { anyStdPassed = true; console.log( chalk.green.bold(`✓ ${solution} 通过${results.length} 项测试`) ); } else { console.error(chalk.red.bold(${solution} 未通过测试`)); } } // 检查是否有 std 通过后,写回 status.json if (fs.existsSync(statusPath)) { const status = JSON.parse(fs.readFileSync(statusPath, 'utf-8')); if (anyStdPassed) { status.testsample = { desc: '样例检测', status: 'done' }; status.std = { desc: '标准程序', status: 'done' }; fs.writeFileSync(statusPath, JSON.stringify(status, null, 2)); console.log(chalk.green('✓ 样例检测已通过,状态已更新')); updateMtimeRecord(problemDir); } else { status.testsample = { desc: '样例检测', status: 'pending' }; status.std = { desc: '标准程序', status: 'pending' }; if (!status.data || status.data.status === 'done' || status.data.status === 'need-redo') { status.data = { desc: '测评数据', status: 'need-redo' }; } if (!status.check || status.check.status === 'done' || status.check.status === 'need-redo') { status.check = { desc: '完整性检查', status: 'need-redo' }; } if (!status.package || status.package.status === 'done' || status.package.status === 'need-redo') { status.package = { desc: '打包', status: 'need-redo' }; } fs.writeFileSync(statusPath, JSON.stringify(status, null, 2)); console.log(chalk.yellow('× 样例检测未通过,std 状态已重置为 pending,data/check/package 状态已重置为 need-redo')); } } }; // 新增函数:更新文件mtime记录 function updateMtimeRecord(problemDir) { const srcDir = path.join(problemDir, 'src'); const mtimePath = path.join(problemDir, '.snapshots', 'file-mtime.json'); // 读取现有的mtime记录 let mtimeInfo = {}; if (fs.existsSync(mtimePath)) { mtimeInfo = JSON.parse(fs.readFileSync(mtimePath, 'utf-8')); } // 更新std.cpp的mtime const stdFile = path.join(srcDir, 'std.cpp'); if (fs.existsSync(stdFile)) { const stat = fs.statSync(stdFile); mtimeInfo['std.cpp'] = stat.mtimeMs; // 写回更新后的mtime记录 fs.writeFileSync(mtimePath, JSON.stringify(mtimeInfo, null, 2)); } }