CoolFace
Apppublic

sanket3280/code-execution

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
UnifiedExecutionService.js341 linesDownload Raw Back to unified
1/**2 * Unified Execution Service3 * Orchestrates code execution using Docker or HackerEarth strategy4 */5 6const DataStructureSerializer = require('../serializers/DataStructureSerializer');7const CodeWrapperFactory = require('../wrappers/CodeWrapperFactory');8const DockerExecutionStrategy = require('../strategies/DockerExecutionStrategy');9const { executionConfig } = require('../../../config/execution.config');10const executionLogger = require('../../../utils/executionLogger.unified');11const crypto = require('crypto');12 13class UnifiedExecutionService {14  constructor() {15    this.serializer = new DataStructureSerializer();16    this.wrapperFactory = new CodeWrapperFactory();17    this.dockerStrategy = new DockerExecutionStrategy(this.wrapperFactory, this.serializer);18    // HackerEarth strategy will be added later19    this.heStrategy = null;20  }21 22  /**23   * Execute code against test cases using optimal strategy24   * @param {Object} options - Execution options25   * @param {string} options.sourceCode - User's source code26   * @param {string} options.language - Language code (JAVASCRIPT_NODE, PYTHON3, etc.)27   * @param {Array} options.testCases - Array of {input, expected}28   * @param {Object} options.metadata - Problem metadata29   * @param {boolean} options.runAllTestCases - Execute all at once (true) or batch (false)30   * @returns {Promise<ExecutionResult>}31   */32  async executeTestCases(options) {33    const executionId = crypto.randomBytes(8).toString('hex');34    const startTime = Date.now();35    36    // Validate input37    this.validateInput(options);38    39    const { sourceCode, language, testCases, metadata = {}, runAllTestCases = true } = options;40    41    // Log execution start42    executionLogger.logExecutionStart(executionId, {43      language,44      testCaseCount: testCases.length,45      strategy: executionConfig.strategy46    });47    48    try {49      // Select execution strategy50      const strategy = await this.selectStrategy();51      52      // Execute using selected strategy53      let results;54      try {55        results = await strategy.execute(sourceCode, language, testCases, metadata);56      } catch (error) {57        // Try fallback if enabled58        if (executionConfig.enableFallback) {59          const fallbackStrategy = await this.getFallbackStrategy(strategy);60          if (fallbackStrategy) {61            executionLogger.logStrategyFallback(62              executionId,63              strategy.getName(),64              fallbackStrategy.getName(),65              error.message66            );67            results = await fallbackStrategy.execute(sourceCode, language, testCases, metadata);68          } else {69            throw error;70          }71        } else {72          throw error;73        }74      }75      76      // Aggregate results77      const aggregated = this.aggregateResults(results, testCases);78      79      // Add execution metadata80      const executionTime = Date.now() - startTime;81      aggregated.executionTime = executionTime;82      aggregated.strategy = strategy.getName();83      aggregated.executionId = executionId;84      85      // Log completion86      executionLogger.logExecutionComplete(executionId, aggregated);87      88      // Log performance metrics89      if (aggregated.avgTimeMs !== undefined) {90        executionLogger.logPerformanceMetrics(executionId, aggregated);91      }92      93      return aggregated;94    } catch (error) {95      executionLogger.logExecutionError(executionId, error, executionConfig.strategy);96      throw error;97    }98  }99 100  /**101   * Validate input parameters102   */103  validateInput(options) {104    const { sourceCode, language, testCases } = options;105    106    if (!sourceCode || typeof sourceCode !== 'string' || sourceCode.trim().length === 0) {107      throw new Error('Source code is required and must be a non-empty string');108    }109    110    if (sourceCode.length > executionConfig.limits.maxSourceCodeSize) {111      throw new Error(`Source code exceeds maximum size of ${executionConfig.limits.maxSourceCodeSize} bytes`);112    }113    114    if (!language || typeof language !== 'string') {115      throw new Error('Language is required and must be a string');116    }117    118    if (!this.wrapperFactory.isLanguageSupported(language)) {119      throw new Error(`Language ${language} is not supported`);120    }121    122    if (!Array.isArray(testCases) || testCases.length === 0) {123      throw new Error('Test cases must be a non-empty array');124    }125    126    if (testCases.length > executionConfig.limits.maxTestCases) {127      throw new Error(`Test case count exceeds maximum of ${executionConfig.limits.maxTestCases}`);128    }129    130    // Validate each test case131    testCases.forEach((tc, index) => {132      if (!tc || typeof tc !== 'object') {133        throw new Error(`Test case ${index} must be an object`);134      }135      if (!('input' in tc)) {136        throw new Error(`Test case ${index} must have an 'input' property`);137      }138    });139  }140 141  /**142   * Select execution strategy based on configuration143   */144  async selectStrategy() {145    const strategy = executionConfig.strategy;146    147    if (strategy === 'docker') {148      if (await this.dockerStrategy.isAvailable()) {149        return this.dockerStrategy;150      }151      throw new Error('Docker strategy selected but Docker is not available');152    }153    154    if (strategy === 'hackerearth') {155      if (this.heStrategy && await this.heStrategy.isAvailable()) {156        return this.heStrategy;157      }158      throw new Error('HackerEarth strategy selected but not configured');159    }160    161    // Auto mode: prefer Docker, fallback to HackerEarth162    if (strategy === 'auto') {163      if (await this.dockerStrategy.isAvailable()) {164        return this.dockerStrategy;165      }166      if (this.heStrategy && await this.heStrategy.isAvailable()) {167        return this.heStrategy;168      }169      throw new Error('No execution strategy available');170    }171    172    throw new Error(`Unknown execution strategy: ${strategy}`);173  }174 175  /**176   * Get fallback strategy177   */178  async getFallbackStrategy(currentStrategy) {179    if (!executionConfig.enableFallback) {180      return null;181    }182    183    // If current is Docker, try HackerEarth184    if (currentStrategy === this.dockerStrategy) {185      if (this.heStrategy && await this.heStrategy.isAvailable()) {186        return this.heStrategy;187      }188    }189    190    // If current is HackerEarth, try Docker191    if (currentStrategy === this.heStrategy) {192      if (await this.dockerStrategy.isAvailable()) {193        return this.dockerStrategy;194      }195    }196    197    return null;198  }199 200  /**201   * Aggregate individual test case results into summary202   */203  aggregateResults(results, testCases) {204    const total = results.length;205    let passed = 0;206    let failed = 0;207    let errors = 0;208    209    let totalTime = 0;210    let maxTime = 0;211    let totalMemory = 0;212    let maxMemory = 0;213    214    const cases = [];215    216    // Check for global error (CE, TLE, RE affecting all tests)217    const hasGlobalError = results.length > 0 && results.every(r =>218      r.status === 'CE' || r.status === 'TLE' || r.status === 'RE' || r.status === 'Error'219    );220    221    let globalError = null;222    if (hasGlobalError && results.length > 0) {223      const firstResult = results[0];224      globalError = {225        type: firstResult.status,226        message: firstResult.error || 'Execution failed',227        details: firstResult.error || ''228      };229    }230    231    results.forEach((result, index) => {232      const testCase = testCases[index] || {};233      234      // Determine pass/fail235      const pass = result.status === 'AC' || result.pass === true;236      237      if (pass) {238        passed++;239      } else if (result.status === 'RE' || result.status === 'CE' || result.status === 'TLE') {240        errors++;241      } else {242        failed++;243      }244      245      // Aggregate metrics246      const timeMs = result.executionTime || 0;247      const memoryKB = result.memoryUsed || 0;248      249      totalTime += timeMs;250      maxTime = Math.max(maxTime, timeMs);251      totalMemory += memoryKB;252      maxMemory = Math.max(maxMemory, memoryKB);253      254      // Build case result255      cases.push({256        caseId: index + 1,257        status: result.status || (pass ? 'AC' : 'WA'),258        pass,259        input: testCase.input,260        expected: testCase.expected,261        output: result.output,262        timeMs,263        memoryKB,264        error: result.error || null265      });266    });267    268    // Calculate averages269    const avgTimeMs = total > 0 ? Math.round(totalTime / total) : 0;270    const avgMemoryKB = total > 0 ? Math.round(totalMemory / total) : 0;271    272    // Estimate complexity273    const timeComplexity = this.estimateTimeComplexity(results);274    const spaceComplexity = this.estimateSpaceComplexity(results);275    276    return {277      summary: {278        total,279        passed,280        failed,281        errors282      },283      globalError,284      timeComplexity,285      spaceComplexity,286      avgTimeMs,287      maxTimeMs: maxTime,288      avgMemoryKB,289      maxMemoryKB: maxMemory,290      performanceLog: this.generatePerformanceLog(avgTimeMs, maxTime, avgMemoryKB, maxMemory),291      cases: hasGlobalError ? [] : cases292    };293  }294 295  /**296   * Estimate time complexity based on execution times297   */298  estimateTimeComplexity(results) {299    // Simple heuristic based on execution time variance300    if (results.length < 2) return 'O(1)';301    302    const times = results.map(r => r.executionTime || 0);303    const avgTime = times.reduce((a, b) => a + b, 0) / times.length;304    const variance = times.reduce((sum, t) => sum + Math.pow(t - avgTime, 2), 0) / times.length;305    306    // High variance suggests O(n) or worse307    if (variance > avgTime * 0.5) {308      return 'O(n)';309    }310    311    return 'O(1)';312  }313 314  /**315   * Estimate space complexity based on memory usage316   */317  estimateSpaceComplexity(results) {318    // Simple heuristic based on memory usage319    if (results.length < 2) return 'O(1)';320    321    const memories = results.map(r => r.memoryUsed || 0);322    const avgMemory = memories.reduce((a, b) => a + b, 0) / memories.length;323    324    // High memory usage suggests O(n)325    if (avgMemory > 1024) { // > 1MB326      return 'O(n)';327    }328    329    return 'O(1)';330  }331 332  /**333   * Generate performance log text334   */335  generatePerformanceLog(avgTimeMs, maxTimeMs, avgMemoryKB, maxMemoryKB) {336    return `Average execution time: ${avgTimeMs}ms, Max: ${maxTimeMs}ms. Average memory: ${avgMemoryKB}KB, Max: ${maxMemoryKB}KB.`;337  }338}339 340module.exports = UnifiedExecutionService;341