add compile & test worker
This commit is contained in:
177
src/LiquidCode.Tester.Worker/Services/TestingService.cs
Normal file
177
src/LiquidCode.Tester.Worker/Services/TestingService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user