Files
LiquidCode.Tester/src/LiquidCode.Tester.Worker/Services/AnswerGenerationService.cs
Roman Pytkov e154890897
Some checks failed
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 1m12s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Has been cancelled
Штуки
2025-11-02 19:31:34 +03:00

214 lines
7.4 KiB
C#

namespace LiquidCode.Tester.Worker.Services;
/// <summary>
/// Service for generating answer files by running the main solution from Polygon package
/// </summary>
public class AnswerGenerationService
{
private readonly ICompilationServiceFactory _compilationFactory;
private readonly IExecutionServiceFactory _executionFactory;
private readonly ILogger<AnswerGenerationService> _logger;
public AnswerGenerationService(
ICompilationServiceFactory compilationFactory,
IExecutionServiceFactory executionFactory,
ILogger<AnswerGenerationService> logger)
{
_compilationFactory = compilationFactory;
_executionFactory = executionFactory;
_logger = logger;
}
public async Task<bool> GenerateAnswersAsync(
PolygonProblemDescriptor descriptor,
string workingDirectory,
List<string> inputFilePaths,
List<string> 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";
}
}