namespace LiquidCode.Tester.Worker.Services; /// /// Service for generating answer files by running the main solution from Polygon package /// public class AnswerGenerationService { private readonly ICompilationServiceFactory _compilationFactory; private readonly IExecutionServiceFactory _executionFactory; private readonly ILogger _logger; public AnswerGenerationService( ICompilationServiceFactory compilationFactory, IExecutionServiceFactory executionFactory, ILogger logger) { _compilationFactory = compilationFactory; _executionFactory = executionFactory; _logger = logger; } public async Task GenerateAnswersAsync( PolygonProblemDescriptor descriptor, string workingDirectory, List inputFilePaths, List answerFilePaths) { if (string.IsNullOrEmpty(descriptor.MainSolutionPath)) { _logger.LogWarning("No main solution specified, cannot generate answers"); return false; } var solutionPath = Path.Combine(workingDirectory, descriptor.MainSolutionPath); if (!File.Exists(solutionPath)) { _logger.LogWarning("Main solution file not found: {Path}", solutionPath); return false; } // Determine language and version from solution type var (language, version) = ParseSolutionType(descriptor.MainSolutionType ?? ""); if (language == null) { _logger.LogWarning("Unsupported solution type: {Type}", descriptor.MainSolutionType); return false; } _logger.LogInformation("Generating answers using {Language} {Version} solution: {Path}", language, version, descriptor.MainSolutionPath); try { // Read solution source code var sourceCode = await File.ReadAllTextAsync(solutionPath); // Get compilation service var compilationService = _compilationFactory.GetCompilationService(language); var executionService = _executionFactory.GetExecutionService(language); // Compile solution _logger.LogInformation("Compiling main solution..."); var compilationResult = await compilationService.CompileAsync( sourceCode, Path.GetDirectoryName(solutionPath)!, version); if (!compilationResult.Success) { _logger.LogError("Failed to compile main solution: {Error}", compilationResult.CompilerOutput); return false; } _logger.LogInformation("Main solution compiled successfully"); // Generate answers for each test int generatedCount = 0; for (int i = 0; i < inputFilePaths.Count; i++) { var inputPath = inputFilePaths[i]; var answerPath = answerFilePaths[i]; if (!File.Exists(inputPath)) { _logger.LogWarning("Input file not found: {Path}", inputPath); continue; } _logger.LogDebug("Generating answer {Index}/{Total}: {AnswerPath}", i + 1, inputFilePaths.Count, answerPath); // Execute solution with input var executionResult = await executionService.ExecuteAsync( compilationResult.ExecutablePath!, inputPath, descriptor.TimeLimitMs * 2, // Give extra time for answer generation descriptor.MemoryLimitMb * 2); if (!executionResult.Success || executionResult.RuntimeError) { _logger.LogWarning("Failed to generate answer for {InputPath}: {Error}", inputPath, executionResult.ErrorMessage); continue; } // Save output as answer file var answerDir = Path.GetDirectoryName(answerPath); if (!string.IsNullOrEmpty(answerDir) && !Directory.Exists(answerDir)) { Directory.CreateDirectory(answerDir); } await File.WriteAllTextAsync(answerPath, executionResult.Output); generatedCount++; _logger.LogDebug("Generated answer {Index}/{Total}", i + 1, inputFilePaths.Count); } _logger.LogInformation("Generated {Count} answer files out of {Total} tests", generatedCount, inputFilePaths.Count); return generatedCount > 0; } catch (Exception ex) { _logger.LogError(ex, "Error generating answers"); return false; } } private (string? language, string version) ParseSolutionType(string solutionType) { // Polygon solution types: python.2, python.3, cpp.g++17, cpp.g++20, java7, java8, etc. if (string.IsNullOrEmpty(solutionType)) { return (null, ""); } if (solutionType.StartsWith("python")) { var versionPart = solutionType.Replace("python", string.Empty, StringComparison.OrdinalIgnoreCase) .Trim('.', ' '); if (string.IsNullOrWhiteSpace(versionPart)) { return ("python", "3"); } // Normalize python version; Polygon often uses python.3 or python3.10 versionPart = versionPart.TrimStart('.'); if (!versionPart.Contains('.')) { // Assume major version provided, default to CPython minor 10 versionPart = versionPart switch { "2" => "2.7", "3" => "3.10", _ => $"3.{versionPart}" }; } return ("python", versionPart); } if (solutionType.StartsWith("cpp.")) { var standard = ExtractCppStandard(solutionType); return ("cpp", standard); } if (solutionType.StartsWith("java")) { // java7, java8, java11 if (solutionType.Contains("11")) return ("java", "11"); if (solutionType.Contains("8")) return ("java", "8"); return ("java", "11"); // Default to Java 11 } if (solutionType.StartsWith("csharp")) { return ("csharp", "9"); // Default to C# 9 } if (solutionType.StartsWith("kotlin")) { return ("kotlin", "1.9"); // Default to Kotlin 1.9 } _logger.LogWarning("Unknown solution type: {Type}", solutionType); return (null, ""); } private static string ExtractCppStandard(string solutionType) { var knownStandards = new[] { "26", "23", "20", "17", "14", "11", "03", "98" }; foreach (var standard in knownStandards) { if (solutionType.Contains($"++{standard}", StringComparison.OrdinalIgnoreCase) || solutionType.Contains($"c++{standard}", StringComparison.OrdinalIgnoreCase)) { // Normalize 03 to 03, 98 stays 98 return standard.TrimStart('0'); } } // Default to modern standard if not specified return "17"; } }