sanket3280/code-execution
0
1const axios = require('axios');2const { applyWrappers } = require('./execution');3const heClient = require('./execution/hackerEarthClient');4const { LanguageExecutorFactory } = require('./execution/languageExecutors');5const AnswerVerifier = require('../utils/answerVerifiers');6 7// NEW: Import modular test harness and validators8const { createTestHarness, supportsOptimizedExecution } = require('./testHarness');9const { validateMultipleOutputs, compareOutputs } = require('./validators/outputValidator');10 11// DOCKER: Import Docker executor12const dockerExecutor = require('./execution/dockerExecutor');13const USE_DOCKER = process.env.USE_DOCKER_EXECUTOR === 'true';14 15// Silent initialization16 17// HackerEarth V4 API Configuration18const CODE_EVAL_URL = process.env.CODE_EVAL_URL || 'https://api.hackerearth.com/v4/partner/code-evaluation/submissions/';19const CLIENT_SECRET = process.env.CLIENT_SECRET;20 21// Status codes mapping22const STATUS_CODES = {23 'AC': 'Accepted',24 'TLE': 'Time Limit Exceeded',25 'MLE': 'Memory Limit Exceeded',26 'RE': 'Runtime Error',27 'CE': 'Compilation Error',28 'REQUEST_FAILED': 'Request Failed'29};30 31/**32 * Parse test input from various formats33 */34const parseTestInput = (raw) => {35 try {36 return JSON.parse(raw);37 } catch (e) {38 const result = {};39 const lines = String(raw || '').split(/\r?\n/).map(l => l.trim()).filter(Boolean);40 for (const line of lines) {41 const m = line.match(/^(\w+)\s*=\s*(.+)$/);42 if (m) {43 const key = m[1];44 let val = m[2].trim();45 const normalized = val.replace(/'/g, '"');46 try {47 result[key] = JSON.parse(normalized);48 } catch (e) {49 const num = Number(val);50 if (!Number.isNaN(num)) {51 result[key] = num;52 } else {53 result[key] = val.replace(/^\"|\"$/g, '');54 }55 }56 } else if (!result.x && /^-?\d+(?:\.\d+)?$/.test(line)) {57 result.x = Number(line);58 }59 }60 return result;61 }62};63 64 65/**66 * Submit code - Docker with HackerEarth fallback67 */68const submitCode = async (sourceCode, language, testInput, memoryLimit = 131072, timeLimit = 3, options = {}) => {69 // Debug: Show executor status70 // Try Docker first if enabled71 if (USE_DOCKER) {72 try {73 console.log('\n๐ณ DOCKER EXECUTION');74 const dockerResult = await submitCodeDocker(sourceCode, language, testInput, memoryLimit, timeLimit, options);75 console.log('โ
Docker: SUCCESS\n');76 return dockerResult;77 } catch (dockerError) {78 console.log('โ Docker: FAILED -', dockerError.message);79 console.log('๐ Fallback: HackerEarth API (using original code)\n');80 // Fall through to HackerEarth with ORIGINAL code (not test harness)81 }82 }83 84 // Use HackerEarth (default or fallback)85 // Note: HackerEarth gets ORIGINAL sourceCode, not test harness86 console.log('\n๐ HACKEREARTH API EXECUTION');87 return await submitCodeHackerEarth(sourceCode, language, testInput, memoryLimit, timeLimit, options);88};89 90/**91 * Docker-based execution92 */93const submitCodeDocker = async (sourceCode, language, testInput, memoryLimit, timeLimit, options) => {94 let processedSourceCode = sourceCode;95 96 // Apply wrappers97 try {98 const wrapped = applyWrappers({ sourceCode, language, topic: options.topic });99 if (wrapped && wrapped.sourceCode) {100 processedSourceCode = wrapped.sourceCode;101 }102 } catch (e) { /* noop */ }103 104 // Execute in Docker105 const result = await dockerExecutor.executeCode({106 sourceCode: processedSourceCode,107 language,108 input: testInput,109 timeLimit,110 memoryLimit: Math.floor(memoryLimit / 1024)111 });112 113 // Docker should ALWAYS return a result - don't fallback unless Docker itself crashes114 // Handle all error cases: TLE, CE, RE, empty output, etc.115 116 // Convert to HackerEarth-compatible format117 const dockerResult = {118 he_id: `docker_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,119 result: {120 run_status: {121 status: result.status, // AC, TLE, CE, RE, ERROR122 output: result.output || '',123 stderr: result.error || '',124 time_used: result.executionTime || 0,125 memory_used: result.memoryUsed || 0126 }127 },128 request_status: {129 code: 'REQUEST_COMPLETED' // Always completed, even if code failed130 }131 };132 133 // Store result for polling134 heClient.storeDockerResult(dockerResult.he_id, dockerResult);135 136 return dockerResult;137};138/**139 * HackerEarth execution (original implementation)140 */141const submitCodeHackerEarth = async (sourceCode, language, testInput, memoryLimit, timeLimit, options) => {142 try {143 let processedSourceCode = sourceCode;144 let usedRunner = false;145 146 // Apply topic-specific wrappers147 try {148 const wrapped = applyWrappers({ sourceCode, language, topic: options.topic });149 if (wrapped && wrapped.sourceCode) {150 processedSourceCode = wrapped.sourceCode;151 usedRunner = wrapped.usedRunner || usedRunner;152 }153 } catch (e) { /* noop */ }154 155 const inputObj = parseTestInput(testInput);156 157 // Process based on language using optimized executors158 if (testInput !== 'all_test_cases') {159 const result = LanguageExecutorFactory.processCode(processedSourceCode, language, inputObj);160 processedSourceCode = result.processedSourceCode;161 usedRunner = result.usedRunner || usedRunner;162 }163 164 const response = await heClient.submitToHackerEarth({165 source: processedSourceCode,166 language,167 memoryLimit,168 timeLimit,169 input: testInput,170 usedRunner171 });172 173 return response;174 } catch (error) {175 console.error('โ Error submitting code:', error.message);176 console.error('โ Error details:', error.response?.data);177 throw error;178 }179};180 181/**182 * Get status of code execution from HackerEarth API183 */184const getStatus = async (heId) => heClient.getStatus(heId);185 186/**187 * Poll for completion of code execution188 */189const pollForCompletion = async (heId, maxAttempts = 10, interval = 1000) => heClient.pollForCompletion(heId, maxAttempts, interval);190 191/**192 * Get output content from HackerEarth URL193 */194const getOutputContent = async (outputUrl) => heClient.extractOutput({ output: outputUrl });195 196/**197 * Normalize output for easier comparison and UI display198 */199const normalizeOutputForCompare = heClient.normalizeOutputForCompare;200 201/**202 * Extract output text from run_status regardless of field name203 */204const extractOutput = async (runStatus) => heClient.extractOutput(runStatus);205 206/**207 * Execute code against multiple test cases - OPTIMIZED VERSION208 */209const executeTestCases = async (sourceCode, language, testCases, options = {}) => {210 // Silent execution211 212 // Auto-enable optimized batch execution for compiled languages213 // BUT respect explicit runAllTestCases: false to prevent infinite loops214 const shouldUseOptimized = options.runAllTestCases === true ||215 (options.runAllTestCases !== false && (216 language.startsWith('JAVA') ||217 language.startsWith('CPP') ||218 language === 'C' ||219 language.startsWith('PYTHON') ||220 language === 'JAVASCRIPT_NODE'221 ));222 223 if (shouldUseOptimized) {224 return await executeAllTestCasesOptimized(sourceCode, language, testCases, options);225 }226 227 const results = {228 summary: {229 total: testCases.length,230 passed: 0,231 failed: 0,232 errors: 0233 },234 cases: []235 };236 237 const concurrency = Math.min(options.concurrency || 3, 5);238 const queue = [];239 240 const pushTask = async (index) => {241 const testCase = testCases[index];242 const caseResult = {243 caseNumber: index + 1,244 input: testCase.input,245 expected: testCase.expected,246 expected_output: testCase.expected, // Add for consistency247 output: '',248 status: 'Error',249 error: null,250 executionTime: null,251 memoryUsed: null,252 isHidden: testCase.isHidden || false,253 pass: false // Add pass field254 };255 256 try {257 const submission = await submitCode(sourceCode, language, testCase.input, undefined, undefined, { topic: options.topic });258 const heId = submission.he_id;259 260 // Optimized polling for HackerEarth261 const maxAttempts = 20;262 const interval = 200;263 264 const finalResult = await pollForCompletion(heId, maxAttempts, interval);265 const runStatus = finalResult.result?.run_status;266 267 if (runStatus) {268 caseResult.executionTime = runStatus.time_used;269 caseResult.memoryUsed = runStatus.memory_used;270 271 if (runStatus.status === 'AC' || runStatus.status === 'CODE_COMPILED') {272 caseResult.output = await extractOutput(runStatus);273 if (typeof caseResult.output === 'string') {274 caseResult.output = caseResult.output.replace(/\[\s+/g, '[').replace(/\s+\]/g, ']').replace(/\s*,\s*/g, ',').trim();275 if (testCase.expected && testCase.expected.startsWith('"') && testCase.expected.endsWith('"')) {276 if (!caseResult.output.startsWith('"')) caseResult.output = `"${caseResult.output}"`;277 } else if (testCase.expected && !testCase.expected.startsWith('"') && caseResult.output.startsWith('"') && caseResult.output.endsWith('"')) {278 caseResult.output = caseResult.output.slice(1, -1);279 }280 }281 282 const normalizedOutput = normalizeOutputForCompare(caseResult.output);283 const normalizedExpected = normalizeOutputForCompare(testCase.expected);284 285 286 287 let isValid = false;288 if (options.verifierType) {289 const verifyResult = AnswerVerifier.verify(290 options.verifierType,291 testCase.input,292 caseResult.output,293 testCase.expected,294 options.verifierMetadata || {}295 );296 isValid = verifyResult.valid;297 } else if (normalizedOutput === normalizedExpected) {298 isValid = true;299 } else {300 // Fallback comparisons - handle quotes and type mismatches301 const outputStr = String(caseResult.output || '');302 const expectedStr = String(testCase.expected || '');303 304 // Remove quotes for comparison305 const cleanOutput = outputStr.replace(/^["']|["']$/g, '');306 const cleanExpected = expectedStr.replace(/^["']|["']$/g, '');307 308 if (cleanOutput === cleanExpected || outputStr === expectedStr) {309 isValid = true;310 } else {311 const outputNum = Number(cleanOutput);312 const expectedNum = Number(cleanExpected);313 if (!isNaN(outputNum) && !isNaN(expectedNum) && outputNum === expectedNum) {314 isValid = true;315 } else {316 const normalizeArrayStr = (str) => str.replace(/\s+/g, '');317 if (normalizeArrayStr(outputStr) === normalizeArrayStr(expectedStr)) {318 isValid = true;319 }320 }321 }322 }323 324 if (isValid) {325 caseResult.status = 'Pass';326 caseResult.pass = true;327 } else {328 caseResult.status = 'Fail';329 caseResult.pass = false;330 }331 } else {332 caseResult.status = STATUS_CODES[runStatus.status] || 'Error';333 caseResult.error = runStatus.stderr || runStatus.compile_status;334 caseResult.output = await extractOutput(runStatus);335 }336 } else {337 caseResult.status = 'Error';338 caseResult.error = 'No run status received';339 }340 } catch (error) {341 caseResult.status = 'Error';342 caseResult.error = error.message;343 }344 345 return caseResult;346 };347 348 const indices = Array.from({ length: testCases.length }, (_, i) => i);349 const active = new Set();350 const resultsArr = new Array(testCases.length);351 352 async function runNext() {353 if (indices.length === 0) return;354 const idx = indices.shift();355 const p = pushTask(idx).then((r) => { resultsArr[idx] = r; active.delete(p); });356 active.add(p);357 if (active.size >= concurrency) await Promise.race(Array.from(active));358 return runNext();359 }360 361 await runNext();362 await Promise.all(Array.from(active));363 364 for (const cr of resultsArr) {365 results.cases.push(cr);366 if (cr.status === 'Pass') results.summary.passed++;367 else if (cr.status === 'Fail') results.summary.failed++;368 else results.summary.errors++;369 }370 371 // โ DON'T filter hidden cases - we need them for submission results372 // results.cases = results.cases.filter(testCase => !testCase.isHidden);373 return results;374};375 376/**377 * OPTIMIZED: Execute all test cases in a single submission378 */379const executeAllTestCasesOptimized = async (sourceCode, language, testCases, options = {}, retryCount = 0) => {380 // Silent execution - logs only in routes381 382 const results = {383 summary: {384 total: testCases.length,385 passed: 0,386 failed: 0,387 errors: 0388 },389 cases: []390 };391 392 try {393 let testHarness = '';394 let testInput = '';395 396 // NEW: Use modular test harness factory397 if (supportsOptimizedExecution(language)) {398 testHarness = createTestHarness(sourceCode, language, testCases);399 testInput = 'all_test_cases';400 } else {401 console.log('โ ๏ธ Language not optimized, falling back to individual submissions');402 return await executeTestCases(sourceCode, language, testCases, { ...options, runAllTestCases: false });403 }404 405 // Test harness created silently406 407 // Smart batching: Check if test harness is too large408 const MAX_HARNESS_SIZE = 950 * 1024; // 950KB limit409 410 if (testHarness.length > MAX_HARNESS_SIZE) {411 console.log(`โ ๏ธ Large harness (${(testHarness.length / 1024).toFixed(2)}KB) - using smart batching...`);412 413 // Split into batches414 const batches = [];415 let currentBatch = [];416 417 for (const tc of testCases) {418 currentBatch.push(tc);419 // NEW: Use modular harness420 const batchHarness = createTestHarness(sourceCode, language, currentBatch);421 422 if (batchHarness.length > MAX_HARNESS_SIZE) {423 currentBatch.pop();424 if (currentBatch.length > 0) {425 batches.push([...currentBatch]);426 }427 currentBatch = [tc];428 }429 }430 431 if (currentBatch.length > 0) {432 batches.push(currentBatch);433 }434 435 console.log(`๐ฆ Split into ${batches.length} batches`);436 437 // Execute each batch438 const allResults = {439 summary: { total: testCases.length, passed: 0, failed: 0, errors: 0 },440 cases: []441 };442 443 for (let i = 0; i < batches.length; i++) {444 console.log(`๐ Batch ${i + 1}/${batches.length}: ${batches[i].length} cases`);445 const batchResults = await executeAllTestCasesOptimized(sourceCode, language, batches[i], options, 0);446 447 allResults.cases.push(...batchResults.cases);448 allResults.summary.passed += batchResults.summary.passed;449 allResults.summary.failed += batchResults.summary.failed;450 allResults.summary.errors += batchResults.summary.errors;451 }452 453 console.log(`โ
All batches complete: ${allResults.summary.passed}/${allResults.summary.total} passed`);454 return allResults;455 }456 457 const submission = await submitCode(testHarness, language, testInput, undefined, undefined, { topic: options.topic });458 const heId = submission.he_id;459 460 // Optimized polling for HackerEarth461 const maxAttempts = 20;462 const interval = 200;463 464 const finalResult = await pollForCompletion(heId, maxAttempts, interval);465 466 const runStatus = finalResult.result?.run_status;467 468 // Check for Docker infrastructure errors (not user code errors or compilation errors)469 const output = await extractOutput(runStatus);470 471 // Compilation errors should be returned to user, not trigger fallback472 // Only fallback for actual Docker infrastructure issues473 const isDockerInfraError = output && (474 output.includes('Docker daemon') ||475 output.includes('docker: Error') ||476 output.includes('Cannot connect to Docker') ||477 output.includes('container failed to start')478 );479 480 // Only fallback if Docker infrastructure error481 if (isDockerInfraError && heId.startsWith('docker_')) {482 console.log('โ Docker infrastructure error detected');483 console.log('๐ Falling back to HackerEarth API...');484 throw new Error('Docker execution failed: ' + (output?.substring(0, 100) || runStatus?.status));485 }486 487 if (runStatus && runStatus.status === 'AC') {488 const parsedResults = parseOptimizedOutput(output, testCases, language, options);489 results.cases = parsedResults.cases;490 results.summary = parsedResults.summary;491 } else if (runStatus && runStatus.status === 'CODE_COMPILED') {492 // CODE_COMPILED without output - try to parse or fallback493 if (output && output.includes('TEST_RESULTS_START')) {494 const parsedResults = parseOptimizedOutput(output, testCases, language, options);495 results.cases = parsedResults.cases;496 results.summary = parsedResults.summary;497 } else {498 // No output, fallback to individual execution499 console.log('โ ๏ธ CODE_COMPILED but no output, falling back...');500 return await executeTestCases(sourceCode, language, testCases, { ...options, runAllTestCases: false });501 }502 } else {503 console.log('โ ๏ธ Optimized execution status:', runStatus?.status);504 505 // Check for compilation errors - return immediately without retry506 if (output && (output.includes('error:') || output.includes('cannot be converted') || 507 output.includes('cannot find symbol') || output.includes('incompatible types'))) {508 console.log('โ ๏ธ Compilation error detected - returning error to user');509 return {510 summary: { total: testCases.length, passed: 0, failed: 0, errors: testCases.length },511 cases: testCases.map((tc, i) => ({512 caseId: i + 1,513 visibility: tc.visibility || 'open',514 status: 'CE',515 pass: false,516 input: tc.input,517 expected: tc.expected,518 expected_output: tc.expected,519 output: output.substring(0, 500),520 timeMs: 0,521 memoryKB: 0,522 errorMsg: 'Compilation Error',523 isHidden: tc.isHidden || false524 }))525 };526 }527 528 console.log('โ ๏ธ Attempting to parse any available output...');529 try {530 if (output && output.trim()) {531 console.log('๐ PARTIAL OUTPUT:', output);532 const parsedResults = parseOptimizedOutput(output, testCases, language, options);533 console.log(`โ
PARTIAL: ${parsedResults.summary.passed}/${parsedResults.summary.total} test cases passed`);534 return parsedResults;535 }536 } catch (error) {537 console.log('โ Failed to parse partial output:', error.message);538 }539 540 // Reduced retries - only retry once if not compilation error541 if (retryCount < 1) {542 return await executeAllTestCasesOptimized(sourceCode, language, testCases, options, retryCount + 1);543 } else {544 console.log('โ ๏ธ Falling back to individual submissions...');545 return await executeTestCases(sourceCode, language, testCases, { ...options, runAllTestCases: false });546 }547 }548 549 } catch (error) {550 console.error('โ Optimized execution error:', error.message);551 552 // If it's a compilation error, return it immediately - don't retry553 if (error.message.includes('error:') || error.message.includes('cannot be converted') || 554 error.message.includes('cannot find symbol') || error.message.includes('incompatible types')) {555 console.log('โ ๏ธ Compilation error detected - returning error to user');556 return {557 summary: { total: testCases.length, passed: 0, failed: 0, errors: testCases.length },558 cases: testCases.map((tc, i) => ({559 caseId: i + 1,560 visibility: tc.visibility || 'open',561 status: 'CE',562 pass: false,563 input: tc.input,564 expected: tc.expected,565 expected_output: tc.expected,566 output: error.message.substring(0, 500),567 timeMs: 0,568 memoryKB: 0,569 errorMsg: 'Compilation Error',570 isHidden: tc.isHidden || false571 }))572 };573 }574 575 // Don't retry if HackerEarth is stuck - waste of time576 if (error.message.includes('timed out') || error.message.includes('stuck')) {577 console.log('โ ๏ธ HackerEarth API is overloaded/stuck');578 console.log('โ ๏ธ Skipping retry, falling back immediately...');579 return await executeTestCases(sourceCode, language, testCases, { ...options, runAllTestCases: false });580 }581 582 console.log('โ Optimized execution failed completely');583 console.log('โ ๏ธ Falling back to individual test case execution...');584 return await executeTestCases(sourceCode, language, testCases, { ...options, runAllTestCases: false });585 }586 587 return results;588};589 590/**591 * Create JavaScript test harness for optimized execution592 */593const createJavaScriptTestHarness = (sourceCode, testCases) => {594 // Add helper function if not present and code uses it595 let harness = '';596 if (sourceCode.includes('isAlphaNumeric') && !sourceCode.includes('function isAlphaNumeric')) {597 harness += '// Helper function for alphanumeric check\n';598 harness += 'function isAlphaNumeric(char) { return /[a-z0-9]/i.test(char); }\n\n';599 }600 601 // Declare common global variables used in memoization602 harness += '// Global variables for memoization (reset between test cases)\n';603 harness += 'let memo, cache, dp;\n\n';604 605 harness += sourceCode + '\n\n';606 harness += '// Test harness for all test cases\n';607 harness += 'const testCases = [\n';608 609 testCases.forEach((testCase, index) => {610 const input = JSON.parse(testCase.input);611 const expected = testCase.expected;612 harness += ` { input: ${JSON.stringify(input)}, expected: ${JSON.stringify(expected)}, index: ${index} },\n`;613 });614 615 harness += `];616 617// Helper to check if string is palindrome618function isPalindromeStr(s) {619 return s === s.split('').reverse().join('');620}621 622// TreeNode helper for binary tree problems623function TreeNode(val, left, right) {624 this.val = (val===undefined ? 0 : val);625 this.left = (left===undefined ? null : left);626 this.right = (right===undefined ? null : right);627}628 629function buildBinaryTree(arr) {630 if (!arr || arr.length === 0) return null;631 const root = new TreeNode(arr[0]);632 const queue = [root];633 let i = 1;634 while (queue.length > 0 && i < arr.length) {635 const node = queue.shift();636 if (i < arr.length && arr[i] !== null) {637 node.left = new TreeNode(arr[i]);638 queue.push(node.left);639 }640 i++;641 if (i < arr.length && arr[i] !== null) {642 node.right = new TreeNode(arr[i]);643 queue.push(node.right);644 }645 i++;646 }647 return root;648}649 650console.log('TEST_RESULTS_START');651for (let i = 0; i < testCases.length; i++) {652 const testCase = testCases[i];653 try {654 // Reset global memoization variables between test cases655 // Initialize as objects/arrays to support different memoization patterns656 memo = {};657 cache = {};658 dp = [];659 660 // Convert tree inputs if needed661 const processedInput = {};662 for (const key in testCase.input) {663 if (key === 'root' && Array.isArray(testCase.input[key])) {664 processedInput[key] = buildBinaryTree(testCase.input[key]);665 } else {666 processedInput[key] = testCase.input[key];667 }668 }669 const result = ${extractFunctionName(sourceCode)}(...Object.values(processedInput));670 let output;671 let expected;672 673 // Format output based on type674 if (Array.isArray(result)) {675 output = '[' + result.join(',') + ']';676 } else if (typeof result === 'string') {677 output = JSON.stringify(result);678 } else if (typeof result === 'boolean') {679 output = String(result);680 } else {681 output = String(result);682 }683 684 // Format expected based on type - ensure it matches output format685 if (typeof testCase.expected === 'boolean') {686 expected = String(testCase.expected);687 } else if (typeof testCase.expected === 'number') {688 expected = String(testCase.expected);689 } else if (typeof testCase.expected === 'string') {690 // Check if it's already a quoted string or a plain string691 if (testCase.expected.startsWith('"') && testCase.expected.endsWith('"')) {692 expected = testCase.expected;693 } else {694 // It's a plain string value like "3" - keep it as is for comparison695 expected = testCase.expected;696 }697 } else {698 expected = String(testCase.expected);699 }700 701 // Special validation for palindrome problems - accept any valid palindrome of same length702 let passed = output === expected;703 if (!passed && typeof result === 'string' && typeof testCase.expected === 'string') {704 const expectedStr = testCase.expected.replace(/^"|"$/g, '');705 if (result.length === expectedStr.length && isPalindromeStr(result) && isPalindromeStr(expectedStr)) {706 passed = true;707 }708 }709 710 console.log(\`TEST_\${i+1}:\${passed ? 'PASS' : 'FAIL'}:\${output}:\${expected}\`);711 } catch (error) {712 console.log(\`TEST_\${i+1}:ERROR:\${error.message}\`);713 }714}715console.log('TEST_RESULTS_END');`;716 717 return harness;718};719 720/**721 * Create Python test harness for optimized execution722 */723const createPythonTestHarness = (sourceCode, testCases) => {724 // Add helper function if not present and code uses it725 let harness = '';726 if ((sourceCode.includes('is_alpha_numeric') || sourceCode.includes('isAlphaNumeric')) &&727 !sourceCode.includes('def is_alpha_numeric')) {728 harness += '# Helper function for alphanumeric check\n';729 harness += 'def is_alpha_numeric(char):\n';730 harness += ' return char.isalnum()\n\n';731 }732 733 harness += sourceCode + '\n\n';734 harness += '# Test harness for all test cases\n';735 harness += 'test_cases = [\n';736 737 testCases.forEach((testCase, index) => {738 const input = JSON.parse(testCase.input);739 const expected = testCase.expected;740 harness += ` {"input": ${JSON.stringify(input)}, "expected": ${JSON.stringify(expected)}, "index": ${index}},\n`;741 });742 743 harness += `]744 745import json746 747def is_palindrome_str(s):748 return s == s[::-1]749 750class TreeNode:751 def __init__(self, val=0, left=None, right=None):752 self.val = val753 self.left = left754 self.right = right755 756def build_binary_tree(arr):757 if not arr or len(arr) == 0:758 return None759 root = TreeNode(arr[0])760 queue = [root]761 i = 1762 while queue and i < len(arr):763 node = queue.pop(0)764 if i < len(arr) and arr[i] is not None:765 node.left = TreeNode(arr[i])766 queue.append(node.left)767 i += 1768 if i < len(arr) and arr[i] is not None:769 node.right = TreeNode(arr[i])770 queue.append(node.right)771 i += 1772 return root773 774print('TEST_RESULTS_START')775for i, test_case in enumerate(test_cases):776 try:777 # Convert tree inputs if needed778 processed_input = {}779 for key, value in test_case['input'].items():780 if key == 'root' and isinstance(value, list):781 processed_input[key] = build_binary_tree(value)782 else:783 processed_input[key] = value784 result = ${extractPythonFunctionName(sourceCode)}(**processed_input)785 if isinstance(result, list):786 output = '[' + ','.join(map(str, result)) + ']'787 elif isinstance(result, str):788 output = json.dumps(result)789 else:790 output = str(result)791 expected = test_case['expected'] if isinstance(test_case['expected'], str) and test_case['expected'].startswith('"') else json.dumps(test_case['expected'])792 793 # Special validation for palindrome problems794 passed = output == expected795 if not passed and isinstance(result, str) and isinstance(test_case['expected'], str):796 expected_str = test_case['expected'].strip('"')797 if len(result) == len(expected_str) and is_palindrome_str(result) and is_palindrome_str(expected_str):798 passed = True799 800 print(f'TEST_{i+1}:{"PASS" if passed else "FAIL"}:{output}:{expected}')801 except Exception as e:802 print(f'TEST_{i+1}:ERROR:{str(e)}')803print('TEST_RESULTS_END')`;804 805 return harness;806};807 808/**809 * Create Java test harness for optimized execution810 */811const createJavaTestHarness = (sourceCode, testCases) => {812 const functionName = extractJavaFunctionName(sourceCode);813 814 let cleanSourceCode = sourceCode815 .replace(/\bpublic\s+class\s+Main\s*\{[\s\S]*?\}/g, '')816 .replace(/\bpublic\s+static\s+void\s+main\s*\([^)]*\)\s*\{[\s\S]*?\}/g, '')817 .replace(/\bclass\s+Main\s*\{[\s\S]*?\}/g, '')818 .replace(/\bpublic\s+class\s+TestHarness\s*\{[\s\S]*?\}/g, '')819 .replace(/\bclass\s+TestHarness\s*\{[\s\S]*?\}/g, '');820 821 // Add helper method if needed822 if (cleanSourceCode.includes('isAlphaNumeric') && !cleanSourceCode.includes('isAlphaNumeric(')) {823 // Helper will be added inside Solution class824 }825 826 let harness = cleanSourceCode + '\n\n';827 harness += 'public class TestHarness {\n';828 harness += ' public static void main(String[] args) {\n';829 harness += ' Solution solution = new Solution();\n';830 harness += ' System.out.println("TEST_RESULTS_START");\n';831 832 testCases.forEach((testCase, index) => {833 const input = JSON.parse(testCase.input);834 const expected = testCase.expected;835 harness += ` // Test case ${index + 1}\n`;836 harness += ` try {\n`;837 838 let methodCall = `solution.${functionName}(`;839 const inputKeys = Object.keys(input);840 841 // Helper to safely convert value to Java literal842 const toJavaValue = (value) => {843 if (Array.isArray(value)) {844 return `new int[]{${value.join(',')}}`;845 } else if (typeof value === 'string') {846 return `"${value}"`;847 } else if (typeof value === 'number') {848 // Check if number is within int range849 if (value > 2147483647 || value < -2147483648) {850 return `(int)${value}L`; // Cast from long to int (will overflow as expected)851 }852 return value;853 } else {854 return value;855 }856 };857 858 if (inputKeys.length === 1) {859 const value = input[inputKeys[0]];860 methodCall += toJavaValue(value);861 } else {862 const args = inputKeys.map(key => toJavaValue(input[key])).join(', ');863 methodCall += args;864 }865 methodCall += ')';866 867 harness += ` Object result = ${methodCall};\n`;868 harness += ` String output;\n`;869 harness += ` if (result instanceof int[]) {\n`;870 harness += ` output = java.util.Arrays.toString((int[])result);\n`;871 harness += ` } else if (result instanceof String) {\n`;872 harness += ` output = "\\"" + result + "\\"";\n`;873 harness += ` } else {\n`;874 harness += ` output = String.valueOf(result);\n`;875 harness += ` }\n`;876 const expectedIsString = typeof expected === 'string' && expected.startsWith('"');877 const expectedStr = expectedIsString ? expected : `"${expected}"`;878 harness += ` String expectedStr = ${expectedStr};\n`;879 harness += ` boolean passed = output.equals(expectedStr);\n`;880 harness += ` // Special validation for palindrome problems\n`;881 harness += ` if (!passed && result instanceof String) {\n`;882 harness += ` String resultStr = (String)result;\n`;883 harness += ` String expStr = expectedStr.replace("\\"", "");\n`;884 harness += ` if (resultStr.length() == expStr.length()) {\n`;885 harness += ` String revResult = new StringBuilder(resultStr).reverse().toString();\n`;886 harness += ` String revExp = new StringBuilder(expStr).reverse().toString();\n`;887 harness += ` if (resultStr.equals(revResult) && expStr.equals(revExp)) {\n`;888 harness += ` passed = true;\n`;889 harness += ` }\n`;890 harness += ` }\n`;891 harness += ` }\n`;892 harness += ` System.out.println("TEST_${index + 1}:" + (passed ? "PASS" : "FAIL") + ":" + output + ":${expected}");\n`;893 harness += ` } catch (Exception e) {\n`;894 harness += ` System.out.println("TEST_${index + 1}:ERROR:" + e.getMessage());\n`;895 harness += ` }\n`;896 });897 898 harness += ' System.out.println("TEST_RESULTS_END");\n';899 harness += ' }\n';900 harness += '}';901 902 return harness;903};904 905/**906 * Create C++ test harness for optimized execution907 */908const createCppTestHarness = (sourceCode, testCases) => {909 const functionName = extractCppFunctionName(sourceCode);910 911 // Simple approach: Split by "int main" and take only the first part912 let cleanSourceCode = sourceCode;913 const mainIndex = cleanSourceCode.indexOf('int main');914 if (mainIndex !== -1) {915 cleanSourceCode = cleanSourceCode.substring(0, mainIndex).trim();916 }917 918 // Ensure all required includes are present919 let includes = '';920 if (!cleanSourceCode.includes('#include <climits>')) includes += '#include <climits>\n';921 if (!cleanSourceCode.includes('#include <cctype>')) includes += '#include <cctype>\n';922 if (!cleanSourceCode.includes('#include <string>')) includes += '#include <string>\n';923 if (!cleanSourceCode.includes('#include <iostream>')) includes += '#include <iostream>\n';924 if (!cleanSourceCode.includes('#include <vector>')) includes += '#include <vector>\n';925 if (!cleanSourceCode.includes('#include <algorithm>')) includes += '#include <algorithm>\n';926 if (!cleanSourceCode.includes('using namespace std')) includes += 'using namespace std;\n';927 928 // Add helper function if needed929 let helpers = '';930 if (cleanSourceCode.includes('isAlphaNumeric') && !cleanSourceCode.includes('bool isAlphaNumeric')) {931 helpers += '\n// Helper function for alphanumeric check\n';932 helpers += 'bool isAlphaNumeric(char c) { return isalnum(c); }\n\n';933 }934 935 let harness = includes + helpers + cleanSourceCode + '\n\n';936 harness += 'int main() {\n';937 harness += ' std::cout << "TEST_RESULTS_START" << std::endl;\n';938 939 testCases.forEach((testCase, index) => {940 const input = JSON.parse(testCase.input);941 const expected = testCase.expected;942 harness += ` // Test case ${index + 1}\n`;943 harness += ` try {\n`;944 945 // Helper to safely convert value to C++ literal946 const toCppValue = (value) => {947 if (Array.isArray(value)) {948 return `{${value.join(',')}}`;949 } else if (typeof value === 'string') {950 return `"${value}"`;951 } else if (typeof value === 'number') {952 // Check if number is within int range953 if (value > 2147483647 || value < -2147483648) {954 return `static_cast<int>(${value}LL)`; // Cast from long long to int955 }956 return value;957 } else {958 return value;959 }960 };961 962 let functionCall = `${functionName}(`;963 const inputKeys = Object.keys(input);964 if (inputKeys.length === 1) {965 const value = input[inputKeys[0]];966 functionCall += toCppValue(value);967 } else {968 const args = inputKeys.map(key => toCppValue(input[key])).join(', ');969 functionCall += args;970 }971 functionCall += ')';972 973 harness += ` auto result = ${functionCall};\n`;974 harness += ` std::string resultStr;\n`;975 harness += ` if constexpr (std::is_same_v<decltype(result), std::string>) {\n`;976 harness += ` resultStr = "\\"" + result + "\\"";\n`;977 harness += ` } else {\n`;978 harness += ` resultStr = std::to_string(result);\n`;979 harness += ` }\n`;980 const expectedIsString = typeof expected === 'string' && expected.startsWith('"');981 const expectedStr = expectedIsString ? expected : `"${expected}"`;982 harness += ` std::string expectedStr = ${expectedStr};\n`;983 harness += ` bool passed = resultStr == expectedStr;\n`;984 harness += ` // Special validation for palindrome problems\n`;985 harness += ` if (!passed && std::is_same_v<decltype(result), std::string>) {\n`;986 harness += ` std::string expStr = expectedStr;\n`;987 harness += ` expStr.erase(std::remove(expStr.begin(), expStr.end(), '"'), expStr.end());\n`;988 harness += ` if (result.length() == expStr.length()) {\n`;989 harness += ` std::string revResult = result;\n`;990 harness += ` std::reverse(revResult.begin(), revResult.end());\n`;991 harness += ` std::string revExp = expStr;\n`;992 harness += ` std::reverse(revExp.begin(), revExp.end());\n`;993 harness += ` if (result == revResult && expStr == revExp) {\n`;994 harness += ` passed = true;\n`;995 harness += ` }\n`;996 harness += ` }\n`;997 harness += ` }\n`;998 harness += ` std::cout << "TEST_${index + 1}:" << (passed ? "PASS" : "FAIL") << ":" << resultStr << ":" << expectedStr << std::endl;\n`;999 harness += ` } catch (...) {\n`;1000 harness += ` std::cout << "TEST_${index + 1}:ERROR:Exception" << std::endl;\n`;1001 harness += ` }\n`;1002 });1003 1004 harness += ' std::cout << "TEST_RESULTS_END" << std::endl;\n';1005 harness += ' return 0;\n';1006 harness += '}';1007 1008 return harness;1009};1010 1011/**1012 * NOTE: validateMultipleOutputs is now imported from validators/outputValidator.js1013 * The old implementation has been removed to avoid duplication1014 */1015 1016/**1017 1018/**1019 * Parse optimized output to extract individual test results1020 * Extracts results between TEST_RESULTS_START and TEST_RESULTS_END1021 */1022const parseOptimizedOutput = (output, testCases, language, options = {}) => {1023 const results = {1024 summary: {1025 total: testCases.length,1026 passed: 0,1027 failed: 0,1028 errors: 01029 },1030 cases: []1031 };1032 1033 console.log(`๐ Parsing test results from output (${output.length} chars)`);1034 console.log(`๐ Raw output:\n${output}`);1035 1036 // Extract content between TEST_RESULTS_START and TEST_RESULTS_END1037 const startMarker = 'TEST_RESULTS_START';1038 const endMarker = 'TEST_RESULTS_END';1039 const startIdx = output.indexOf(startMarker);1040 const endIdx = output.indexOf(endMarker);1041 1042 if (startIdx === -1 || endIdx === -1) {1043 console.log('โ ๏ธ Could not find TEST_RESULTS markers in output');1044 // Fallback: try to parse all lines1045 const lines = output.split('\n');1046 const testResults = lines.filter(line => line.trim().startsWith('TEST_') && line.includes(':'));1047 console.log(` Found ${testResults.length} test result lines without markers`);1048 return parseTestResultLines(testResults, testCases, options);1049 }1050 1051 const testResultsSection = output.substring(startIdx + startMarker.length, endIdx).trim();1052 const testResultLines = testResultsSection.split('\n').filter(line => line.trim().startsWith('TEST_'));1053 1054 return parseTestResultLines(testResultLines, testCases, options);1055};1056 1057/**1058 * Parse individual test result lines1059 * Format: TEST_1:PASS:321:321 or TEST_1:FAIL:123:321 or TEST_1:ERROR:message1060 */1061const parseTestResultLines = (testResultLines, testCases, options = {}) => {1062 const results = {1063 summary: {1064 total: testCases.length,1065 passed: 0,1066 failed: 0,1067 errors: 01068 },1069 cases: []1070 };1071 1072 testResultLines.forEach((resultLine, index) => {1073 const trimmedLine = resultLine.trim();1074 1075 // Parse format: TEST_N:STATUS:OUTPUT:EXPECTED1076 const match = trimmedLine.match(/^TEST_(\d+):([^:]+):(.*)$/);1077 1078 if (!match) {1079 return;1080 }1081 1082 const testNumber = parseInt(match[1], 10);1083 const status = match[2];1084 const remainder = match[3];1085 1086 // Split remainder by last colon to separate output and expected1087 const lastColonIdx = remainder.lastIndexOf(':');1088 let output = remainder;1089 let expected = '';1090 1091 if (lastColonIdx !== -1 && status !== 'ERROR') {1092 output = remainder.substring(0, lastColonIdx);1093 expected = remainder.substring(lastColonIdx + 1);1094 }1095 1096 const testCaseIndex = testNumber - 1;1097 if (testCaseIndex < 0 || testCaseIndex >= testCases.length) {1098 return;1099 }1100 1101 const testCase = testCases[testCaseIndex];1102 let isPassed = status === 'PASS';1103 1104 // Re-validate if status is FAIL but output matches expected1105 if (!isPassed && status === 'FAIL' && output && expected) {1106 // Normalize both for comparison - remove quotes and compare values1107 const normalizedOutput = output.trim().replace(/^["']|["']$/g, '');1108 const normalizedExpected = expected.trim().replace(/^["']|["']$/g, '');1109 1110 // Try direct string comparison first1111 if (normalizedOutput === normalizedExpected) {1112 isPassed = true;1113 }1114 // Try with original values (handles quote differences)1115 else if (output.trim() === expected.trim()) {1116 isPassed = true;1117 }1118 // Try numeric comparison if both can be parsed as numbers1119 else if (!isNaN(normalizedOutput) && !isNaN(normalizedExpected)) {1120 if (Number(normalizedOutput) === Number(normalizedExpected)) {1121 isPassed = true;1122 }1123 }1124 // Check if output is array and expected is array length (count)1125 else if (normalizedOutput.startsWith('[') && !isNaN(normalizedExpected)) {1126 try {1127 const outputArray = JSON.parse(normalizedOutput);1128 if (Array.isArray(outputArray) && outputArray.length === Number(normalizedExpected)) {1129 isPassed = true;1130 }1131 } catch (e) {}1132 }1133 // Check for in-place modification problems (expected = "merged")1134 else if (normalizedExpected === 'merged' && normalizedOutput.startsWith('[')) {1135 try {1136 const outputArr = JSON.parse(normalizedOutput);1137 const inputObj = JSON.parse(testCase.input);1138 1139 // For merge sorted array: check if output is sorted1140 if (inputObj.nums1 && inputObj.nums2 && inputObj.m !== undefined && inputObj.n !== undefined) {1141 const isSorted = outputArr.every((val, i, arr) => i === 0 || arr[i - 1] <= val);1142 const expectedLength = inputObj.m + inputObj.n;1143 1144 if (isSorted && outputArr.length === expectedLength) {1145 isPassed = true;1146 console.log(` ๐ In-place modification: Array is sorted with correct length (${outputArr.length})`);1147 }1148 }1149 } catch (e) {}1150 }1151 // Check for palindrome problems - any palindrome of same length is valid1152 else if (typeof normalizedOutput === 'string' && typeof normalizedExpected === 'string') {1153 const isPalindrome = (s) => s === s.split('').reverse().join('');1154 1155 if (normalizedOutput.length === normalizedExpected.length && 1156 isPalindrome(normalizedOutput) && 1157 isPalindrome(normalizedExpected)) {1158 isPassed = true;1159 console.log(` ๐ Palindrome validation: "${normalizedOutput}" is a valid palindrome (same length as "${normalizedExpected}")`);1160 }1161 }1162 // Dynamic validation for problems with multiple valid outputs1163 if (!isPassed && normalizedOutput.startsWith('[') && normalizedExpected.startsWith('[')) {1164 try {1165 const outputArr = JSON.parse(normalizedOutput);1166 const expectedArr = JSON.parse(normalizedExpected);1167 1168 if (Array.isArray(outputArr) && Array.isArray(expectedArr)) {1169 const inputObj = JSON.parse(testCase.input);1170 1171 // Validate using dynamic validator1172 const validationResult = validateMultipleOutputs(outputArr, expectedArr, inputObj);1173 if (validationResult.isValid) {1174 isPassed = true;1175 console.log(` ๐ Dynamic validation: ${validationResult.message}`);1176 }1177 }1178 } catch (e) {1179 // Not valid JSON arrays, skip this validation1180 }1181 }1182 // Finally, try custom verifier if available and nothing else matched1183 if (!isPassed && options.verifierType) {1184 console.log(` ๐ง Using custom verifier: ${options.verifierType}`);1185 const verifyResult = AnswerVerifier.verify(1186 options.verifierType,1187 testCase.input,1188 normalizedOutput,1189 normalizedExpected,1190 options.verifierMetadata || {}1191 );1192 isPassed = verifyResult.valid;1193 console.log(` ๐ Verifier result: ${verifyResult.valid ? 'โ
VALID' : 'โ INVALID'} - ${verifyResult.reason}`);1194 1195 // Store verification info for frontend1196 if (isPassed && verifyResult.allValidOutputs) {1197 testCase.verificationReason = verifyResult.reason;1198 testCase.validOutputs = verifyResult.allValidOutputs;1199 }1200 }