add compile & test worker

This commit is contained in:
prixod
2025-10-24 23:46:51 +04:00
parent 3d854c3470
commit 6cead15a5f
19 changed files with 849 additions and 13 deletions

View File

@@ -0,0 +1,177 @@
using LiquidCode.Tester.Common.Models;
using LiquidCode.Tester.Worker.Controllers;
namespace LiquidCode.Tester.Worker.Services;
public class TestingService : ITestingService
{
private readonly IPackageParserService _packageParser;
private readonly ICompilationService _compilationService;
private readonly IExecutionService _executionService;
private readonly IOutputCheckerService _outputChecker;
private readonly ICallbackService _callbackService;
private readonly ILogger<TestingService> _logger;
public TestingService(
IPackageParserService packageParser,
ICompilationService compilationService,
IExecutionService executionService,
IOutputCheckerService outputChecker,
ICallbackService callbackService,
ILogger<TestingService> logger)
{
_packageParser = packageParser;
_compilationService = compilationService;
_executionService = executionService;
_outputChecker = outputChecker;
_callbackService = callbackService;
_logger = logger;
}
public async Task ProcessSubmitAsync(TestRequest request)
{
_logger.LogInformation("Starting to process submit {SubmitId}", request.Id);
try
{
// Send initial status
await SendStatusAsync(request, State.Waiting, ErrorCode.None, "Submit received", 0, 0);
// Parse package
ProblemPackage package;
if (request.Package != null)
{
using var packageStream = request.Package.OpenReadStream();
package = await _packageParser.ParsePackageAsync(packageStream);
}
else
{
throw new InvalidOperationException("No package provided");
}
_logger.LogInformation("Package parsed, found {TestCount} tests", package.TestCases.Count);
// Send compiling status
await SendStatusAsync(request, State.Compiling, ErrorCode.None, "Compiling solution", 0, package.TestCases.Count);
// Compile user solution
var compilationResult = await _compilationService.CompileAsync(request.SourceCode, package.WorkingDirectory);
if (!compilationResult.Success)
{
_logger.LogWarning("Compilation failed for submit {SubmitId}", request.Id);
await SendStatusAsync(request, State.Done, ErrorCode.CompileError,
$"Compilation failed: {compilationResult.CompilerOutput}", 0, package.TestCases.Count);
return;
}
_logger.LogInformation("Compilation successful");
// Send testing status
await SendStatusAsync(request, State.Testing, ErrorCode.None, "Running tests", 0, package.TestCases.Count);
// Run tests
for (int i = 0; i < package.TestCases.Count; i++)
{
var testCase = package.TestCases[i];
_logger.LogInformation("Running test {TestNumber}/{TotalTests}", testCase.Number, package.TestCases.Count);
await SendStatusAsync(request, State.Testing, ErrorCode.None,
$"Running test {testCase.Number}", testCase.Number, package.TestCases.Count);
// Execute solution
var executionResult = await _executionService.ExecuteAsync(
compilationResult.ExecutablePath!,
testCase.InputFilePath,
testCase.TimeLimit,
testCase.MemoryLimit);
// Check for execution errors
if (executionResult.TimeLimitExceeded)
{
_logger.LogWarning("Time limit exceeded on test {TestNumber}", testCase.Number);
await SendStatusAsync(request, State.Done, ErrorCode.TimeLimitError,
$"Time limit exceeded on test {testCase.Number}", testCase.Number, package.TestCases.Count);
CleanupWorkingDirectory(package.WorkingDirectory);
return;
}
if (executionResult.MemoryLimitExceeded)
{
_logger.LogWarning("Memory limit exceeded on test {TestNumber}", testCase.Number);
await SendStatusAsync(request, State.Done, ErrorCode.MemoryError,
$"Memory limit exceeded on test {testCase.Number}", testCase.Number, package.TestCases.Count);
CleanupWorkingDirectory(package.WorkingDirectory);
return;
}
if (executionResult.RuntimeError)
{
_logger.LogWarning("Runtime error on test {TestNumber}: {Error}", testCase.Number, executionResult.ErrorMessage);
await SendStatusAsync(request, State.Done, ErrorCode.RuntimeError,
$"Runtime error on test {testCase.Number}: {executionResult.ErrorMessage}", testCase.Number, package.TestCases.Count);
CleanupWorkingDirectory(package.WorkingDirectory);
return;
}
// Check output
var outputCorrect = await _outputChecker.CheckOutputAsync(executionResult.Output, testCase.OutputFilePath);
if (!outputCorrect)
{
_logger.LogWarning("Wrong answer on test {TestNumber}", testCase.Number);
await SendStatusAsync(request, State.Done, ErrorCode.IncorrectAnswer,
$"Wrong answer on test {testCase.Number}", testCase.Number, package.TestCases.Count);
CleanupWorkingDirectory(package.WorkingDirectory);
return;
}
_logger.LogInformation("Test {TestNumber} passed", testCase.Number);
}
// All tests passed!
_logger.LogInformation("All tests passed for submit {SubmitId}", request.Id);
await SendStatusAsync(request, State.Done, ErrorCode.None,
"All tests passed", package.TestCases.Count, package.TestCases.Count);
// Cleanup
CleanupWorkingDirectory(package.WorkingDirectory);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing submit {SubmitId}", request.Id);
await SendStatusAsync(request, State.Done, ErrorCode.UnknownError,
$"Internal error: {ex.Message}", 0, 0);
}
}
private async Task SendStatusAsync(TestRequest request, State state, ErrorCode errorCode, string message, int currentTest, int totalTests)
{
var response = new TesterResponseModel(
SubmitId: request.Id,
State: state,
ErrorCode: errorCode,
Message: message,
CurrentTest: currentTest,
AmountOfTests: totalTests
);
await _callbackService.SendStatusAsync(request.CallbackUrl, response);
}
private void CleanupWorkingDirectory(string workingDirectory)
{
try
{
if (Directory.Exists(workingDirectory))
{
Directory.Delete(workingDirectory, recursive: true);
_logger.LogInformation("Cleaned up working directory {WorkingDirectory}", workingDirectory);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to cleanup working directory {WorkingDirectory}", workingDirectory);
}
}
}