178 lines
7.4 KiB
C#
178 lines
7.4 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|