UNPKG

local-leetcode-trainer

Version:

A complete local LeetCode practice environment with multi-language support - use your IDE, collaborate with AI, submit with confidence

547 lines (477 loc) 14.4 kB
/** * Test Case Generator for Enhanced Fallback Problems * Generates comprehensive test cases including edge cases and stress tests */ class TestCaseGenerator { /** * Generate comprehensive test cases from problem data */ static generateTestCases(problem) { const testCases = []; // Add example test cases if (problem.examples) { for (const example of problem.examples) { try { const testCase = this.parseExampleToTestCase(example); if (testCase) { testCases.push(testCase); } } catch (error) { console.warn(`Failed to parse example: ${error.message}`); } } } // Add predefined test cases if available if (problem.testCases) { testCases.push(...problem.testCases); } // Generate additional edge cases based on constraints const edgeCases = this.generateEdgeCases(problem); testCases.push(...edgeCases); // Generate stress tests const stressTests = this.generateStressTests(problem); testCases.push(...stressTests); return this.validateAndCleanTestCases(testCases); } /** * Parse example to test case */ static parseExampleToTestCase(example) { try { const input = this.parseInput(example.input); const expected = this.parseOutput(example.output); return { input: Array.isArray(input) ? input : [input], expected: expected, description: example.explanation || 'Example test case', category: 'basic', isEdgeCase: false }; } catch (error) { console.warn(`Failed to parse example: ${error.message}`); return null; } } /** * Parse input string to actual values */ static parseInput(inputStr) { // Handle multiple parameters - need to be careful with commas inside arrays/objects const params = []; // First try to match parameter = value patterns, handling nested structures const paramPattern = /(\w+)\s*=\s*/g; let match; let lastIndex = 0; while ((match = paramPattern.exec(inputStr)) !== null) { const paramName = match[1]; const valueStart = match.index + match[0].length; // Find the end of this parameter's value let valueEnd = inputStr.length; let nextMatch = paramPattern.exec(inputStr); if (nextMatch) { valueEnd = nextMatch.index; paramPattern.lastIndex = nextMatch.index; // Reset for next iteration } const paramValue = inputStr.substring(valueStart, valueEnd).trim(); // Remove trailing comma if present const cleanValue = paramValue.replace(/,\s*$/, ''); params.push(this.parseValue(cleanValue)); if (!nextMatch) break; } if (params.length > 0) { return params; } // Single parameter without parameter name const cleaned = inputStr.replace(/^\w+\s*=\s*/, ''); return [this.parseValue(cleaned)]; } /** * Parse output string to expected value */ static parseOutput(outputStr) { return this.parseValue(outputStr); } /** * Parse a value from string representation */ static parseValue(valueStr) { const trimmed = valueStr.trim(); try { return JSON.parse(trimmed); } catch { // Handle special cases if (trimmed.toLowerCase() === 'true') return true; if (trimmed.toLowerCase() === 'false') return false; if (trimmed.toLowerCase() === 'null') return null; // Try to parse as number const num = parseFloat(trimmed); if (!isNaN(num)) return num; // Handle arrays manually if (trimmed.includes('[') && trimmed.includes(']')) { try { // Find the complete array by matching brackets let bracketCount = 0; let startIndex = trimmed.indexOf('['); let endIndex = -1; for (let i = startIndex; i < trimmed.length; i++) { if (trimmed[i] === '[') bracketCount++; if (trimmed[i] === ']') bracketCount--; if (bracketCount === 0) { endIndex = i; break; } } if (endIndex !== -1) { const arrayStr = trimmed.substring(startIndex, endIndex + 1); // Try JSON.parse first for proper array parsing try { return JSON.parse(arrayStr); } catch { // Fallback to manual parsing const content = arrayStr.slice(1, -1); // Remove [ and ] if (content.trim() === '') return []; return content.split(',').map(item => { const trimmedItem = item.trim(); if (trimmedItem === 'null') return null; if (trimmedItem === 'true') return true; if (trimmedItem === 'false') return false; const num = parseFloat(trimmedItem); if (!isNaN(num)) return num; return trimmedItem.replace(/^["']|["']$/g, ''); }); } } } catch (error) { console.warn(`Failed to parse array: ${error.message}`); } } // Return as string, removing quotes return trimmed.replace(/^["']|["']$/g, ''); } } /** * Generate edge cases based on constraints */ static generateEdgeCases(problem) { const edgeCases = []; if (!problem.constraints) return edgeCases; for (const constraint of problem.constraints) { const cases = this.parseConstraintForEdgeCases(constraint, problem); edgeCases.push(...cases); } return edgeCases; } /** * Parse constraint to generate edge cases */ static parseConstraintForEdgeCases(constraint, problem) { const cases = []; // Array length constraints const lengthMatch = constraint.match(/(\w+)\.length\s*[<>=]+\s*(\d+)/); if (lengthMatch) { const arrayName = lengthMatch[1]; const maxLength = parseInt(lengthMatch[2]); // Minimum length case if (constraint.includes('>=') || constraint.includes('<=')) { const minLength = constraint.includes('>=') ? parseInt(constraint.match(/>=\s*(\d+)/)?.[1] || '1') : 1; if (minLength === 1) { cases.push({ input: this.generateArrayInput(1, problem), expected: null, // Would need problem-specific logic description: 'Minimum array length', category: 'edge', isEdgeCase: true }); } } // Empty array case (if allowed) if (constraint.includes('>=') && constraint.includes('0')) { cases.push({ input: this.generateArrayInput(0, problem), expected: null, description: 'Empty array', category: 'edge', isEdgeCase: true }); } } // Value range constraints const rangeMatch = constraint.match(/(-?\d+)\s*<=\s*\w+\s*<=\s*(-?\d+)/); if (rangeMatch) { const min = parseInt(rangeMatch[1]); const max = parseInt(rangeMatch[2]); cases.push({ input: [min], expected: null, description: `Minimum value: ${min}`, category: 'edge', isEdgeCase: true }); cases.push({ input: [max], expected: null, description: `Maximum value: ${max}`, category: 'edge', isEdgeCase: true }); } // String length constraints const stringLengthMatch = constraint.match(/(\w+)\.length\s*[<>=]+\s*(\d+)/); if (stringLengthMatch && problem.topics?.includes('String')) { const maxLength = parseInt(stringLengthMatch[2]); cases.push({ input: ['a'], expected: null, description: 'Single character string', category: 'edge', isEdgeCase: true }); if (maxLength > 1) { cases.push({ input: ['a'.repeat(Math.min(maxLength, 100))], expected: null, description: 'Long string', category: 'edge', isEdgeCase: true }); } } return cases; } /** * Generate stress tests */ static generateStressTests(problem) { const stressTests = []; // Generate large input tests based on constraints if (problem.constraints) { for (const constraint of problem.constraints) { const stressTest = this.generateStressTestFromConstraint(constraint, problem); if (stressTest) { stressTests.push(stressTest); } } } return stressTests; } /** * Generate stress test from constraint */ static generateStressTestFromConstraint(constraint, problem) { // Large array test const arrayLengthMatch = constraint.match(/(\w+)\.length\s*<=\s*(\d+)/); if (arrayLengthMatch) { const maxLength = parseInt(arrayLengthMatch[2]); const testSize = Math.min(maxLength, 1000); // Cap at 1000 for performance return { input: this.generateArrayInput(testSize, problem), expected: null, description: `Large array with ${testSize} elements`, category: 'stress', isEdgeCase: false }; } // Large string test const stringLengthMatch = constraint.match(/(\w+)\.length\s*<=\s*(\d+)/); if (stringLengthMatch && problem.topics?.includes('String')) { const maxLength = parseInt(stringLengthMatch[2]); const testSize = Math.min(maxLength, 1000); return { input: [this.generateRandomString(testSize)], expected: null, description: `Large string with ${testSize} characters`, category: 'stress', isEdgeCase: false }; } return null; } /** * Generate array input for testing */ static generateArrayInput(length, problem) { if (length === 0) return [[]]; const array = []; for (let i = 0; i < length; i++) { // Generate values based on problem type if (problem.topics?.includes('Array')) { array.push(Math.floor(Math.random() * 100) - 50); // Random integers } else { array.push(i + 1); // Sequential integers } } return [array]; } /** * Generate random string for testing */ static generateRandomString(length) { const chars = 'abcdefghijklmnopqrstuvwxyz'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } /** * Validate and clean test cases */ static validateAndCleanTestCases(testCases) { const validTestCases = []; for (const testCase of testCases) { if (this.isValidTestCase(testCase)) { validTestCases.push(testCase); } } // Remove duplicates return this.removeDuplicateTestCases(validTestCases); } /** * Check if test case is valid */ static isValidTestCase(testCase) { return testCase && testCase.input !== undefined && Array.isArray(testCase.input) && testCase.description && testCase.category; } /** * Remove duplicate test cases */ static removeDuplicateTestCases(testCases) { const seen = new Set(); const unique = []; for (const testCase of testCases) { const key = JSON.stringify(testCase.input); if (!seen.has(key)) { seen.add(key); unique.push(testCase); } } return unique; } /** * Generate test cases for specific problem types */ static generateProblemSpecificTestCases(problem) { const testCases = []; // Array problems if (problem.topics?.includes('Array')) { testCases.push(...this.generateArrayTestCases(problem)); } // String problems if (problem.topics?.includes('String')) { testCases.push(...this.generateStringTestCases(problem)); } // Tree problems if (problem.topics?.includes('Tree')) { testCases.push(...this.generateTreeTestCases(problem)); } // Math problems if (problem.topics?.includes('Math')) { testCases.push(...this.generateMathTestCases(problem)); } return testCases; } /** * Generate array-specific test cases */ static generateArrayTestCases(problem) { return [ { input: [[1, 2, 3, 4, 5]], expected: null, description: 'Sequential array', category: 'basic', isEdgeCase: false }, { input: [[5, 4, 3, 2, 1]], expected: null, description: 'Reverse sequential array', category: 'basic', isEdgeCase: false }, { input: [[1, 1, 1, 1, 1]], expected: null, description: 'All same elements', category: 'edge', isEdgeCase: true } ]; } /** * Generate string-specific test cases */ static generateStringTestCases(problem) { return [ { input: ['abc'], expected: null, description: 'Simple string', category: 'basic', isEdgeCase: false }, { input: [''], expected: null, description: 'Empty string', category: 'edge', isEdgeCase: true }, { input: ['a'], expected: null, description: 'Single character', category: 'edge', isEdgeCase: true } ]; } /** * Generate tree-specific test cases */ static generateTreeTestCases(problem) { return [ { input: [null], expected: null, description: 'Empty tree', category: 'edge', isEdgeCase: true } ]; } /** * Generate math-specific test cases */ static generateMathTestCases(problem) { return [ { input: [0], expected: null, description: 'Zero input', category: 'edge', isEdgeCase: true }, { input: [1], expected: null, description: 'One input', category: 'edge', isEdgeCase: true }, { input: [-1], expected: null, description: 'Negative input', category: 'edge', isEdgeCase: true } ]; } } module.exports = { TestCaseGenerator };