Ratan1870/code-execution
0
1// ─── Judge Service ───────────────────────────2// Runs code against test cases, compares output, returns verdicts3// NOW USES BATCH MODE: 1 container for ALL test cases4 5const executorService = require("./executor.service");6const logger = require("../utils/logger.util");7 8class JudgeService {9 /**10 * Judge code against multiple test cases in ONE container.11 *12 * Flow:13 * 1. Extract all inputs from test cases14 * 2. Send to batchExecute (ONE Docker container)15 * 3. Compare each output with expected16 * 4. Return detailed results17 */18 async judge({ language, code, testCases, timeLimit, memoryLimit }) {19 logger.info(20 ` ⚖️ Judging ${testCases.length} test cases in ONE container...`,21 );22 23 // Extract all inputs24 const inputs = testCases.map((tc) => tc.input);25 26 // Execute ALL test cases in a single container27 const execResults = await executorService.batchExecute({28 language,29 code,30 inputs,31 timeLimit,32 memoryLimit,33 });34 35 // Build results36 let overallVerdict = "AC";37 let totalTime = 0;38 let maxMemory = 0;39 let firstFailedTestCase = null;40 const results = [];41 42 for (let i = 0; i < testCases.length; i++) {43 const tc = testCases[i];44 const result = execResults[i];45 const verdict = this._getVerdict(result, tc.expectedOutput);46 47 results.push({48 testCase: i + 1,49 verdict,50 executionTime: result.executionTime,51 memoryUsed: result.memoryUsed,52 input: this._truncate(tc.input, 1000),53 expectedOutput: this._truncate(tc.expectedOutput, 1000),54 actualOutput: this._truncate(result.output, 1000),55 error: result.error,56 exitCode: result.exitCode,57 });58 59 totalTime += result.executionTime;60 maxMemory = Math.max(maxMemory, result.memoryUsed);61 62 if (verdict !== "AC" && overallVerdict === "AC") {63 overallVerdict = verdict;64 firstFailedTestCase = i + 1;65 }66 }67 68 return {69 overallVerdict,70 totalTime,71 maxMemory,72 totalTestCases: testCases.length,73 passed: results.filter((r) => r.verdict === "AC").length,74 failed: results.filter((r) => r.verdict !== "AC").length,75 skipped: 0,76 firstFailedTestCase,77 results,78 };79 }80 81 /**82 * Determine verdict by comparing actual output to expected.83 *84 * Priority:85 * 1. If executor verdict is not OK → return that (CE, TLE, MLE, RE)86 * 2. Normalize both outputs and compare87 * 3. AC if match, WA if mismatch88 */89 _getVerdict(result, expectedOutput) {90 // Non-OK verdicts take priority91 if (result.verdict !== "OK") {92 return result.verdict; // CE, TLE, MLE, RE, IE93 }94 95 // Compare normalized outputs96 const actual = this._normalize(result.output);97 const expected = this._normalize(expectedOutput);98 99 if (actual === expected) {100 return "AC";101 }102 103 return "WA";104 }105 106 /**107 * Normalize output for comparison:108 * - Trim trailing whitespace from each line109 * - Remove trailing empty lines110 * - Consistent line endings111 */112 _normalize(str) {113 if (!str) return "";114 return str115 .replace(/\r\n/g, "\n") // Normalize CRLF → LF116 .split("\n")117 .map((line) => line.trimEnd()) // Trim trailing whitespace per line118 .join("\n")119 .trimEnd(); // Remove trailing empty lines120 }121 122 /**123 * Truncate for response size control124 */125 _truncate(str, maxLen) {126 if (!str) return "";127 if (str.length <= maxLen) return str;128 return str.substring(0, maxLen) + "...";129 }130}131 132module.exports = new JudgeService();133 