add compile & test worker
This commit is contained in:
41
.idea/.idea.LiquidCode.Tester/.idea/workspace.xml
generated
41
.idea/.idea.LiquidCode.Tester/.idea/workspace.xml
generated
@@ -12,14 +12,7 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="1d3190f0-8175-44b9-bab6-12e025e4819d" name="Changes" comment="">
|
<list default="true" id="1d3190f0-8175-44b9-bab6-12e025e4819d" name="Changes" comment="">
|
||||||
<change beforePath="$PROJECT_DIR$/global.json" beforeDir="false" afterPath="$PROJECT_DIR$/global.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Worker/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/LiquidCode.Tester.Worker/Program.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Common/LiquidCode.Tester.Common.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/LiquidCode.Tester.Common/LiquidCode.Tester.Common.csproj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Common/Program.cs" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/LiquidCode.Tester.Gateway.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/LiquidCode.Tester.Gateway.csproj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/Program.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/appsettings.Development.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/appsettings.Development.json" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/appsettings.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/LiquidCode.Tester.Gateway/appsettings.json" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/LiquidCode.Tester.Worker/LiquidCode.Tester.Worker.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/LiquidCode.Tester.Worker/LiquidCode.Tester.Worker.csproj" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -32,6 +25,23 @@
|
|||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="HighlightingSettingsPerFile">
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/global.json" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/global.json" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Common/Models/ErrorCode.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/Controllers/TesterController.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/Program.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/Program.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/Services/IPackageDownloadService.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/Services/IWorkerClientService.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/Services/PackageDownloadService.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/Services/WorkerClientService.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/appsettings.Development.json" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/appsettings.Development.json" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/appsettings.Development.json" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/appsettings.json" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="mock://C:/Users/prixod/source/repos/LiquidCode.Tester/src/LiquidCode.Tester.Gateway/appsettings.json" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
</component>
|
||||||
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
|
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
|
||||||
<component name="ProjectColorInfo"><![CDATA[{
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
"associatedIndex": 1
|
"associatedIndex": 1
|
||||||
@@ -265,7 +275,7 @@
|
|||||||
<option name="number" value="Default" />
|
<option name="number" value="Default" />
|
||||||
<option name="presentableId" value="Default" />
|
<option name="presentableId" value="Default" />
|
||||||
<updated>1761331001679</updated>
|
<updated>1761331001679</updated>
|
||||||
<workItem from="1761331002792" duration="2278000" />
|
<workItem from="1761331002792" duration="3067000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="init">
|
<task id="LOCAL-00001" summary="init">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -275,7 +285,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1761333540672</updated>
|
<updated>1761333540672</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="2" />
|
<task id="LOCAL-00002" summary="update">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1761334197106</created>
|
||||||
|
<option name="number" value="00002" />
|
||||||
|
<option name="presentableId" value="LOCAL-00002" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1761334197106</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="3" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -297,7 +315,8 @@
|
|||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||||
<MESSAGE value="init" />
|
<MESSAGE value="init" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="init" />
|
<MESSAGE value="update" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="update" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
|
|||||||
10
src/LiquidCode.Tester.Common/Models/ProblemPackage.cs
Normal file
10
src/LiquidCode.Tester.Common/Models/ProblemPackage.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace LiquidCode.Tester.Common.Models;
|
||||||
|
|
||||||
|
public class ProblemPackage
|
||||||
|
{
|
||||||
|
public string WorkingDirectory { get; set; } = string.Empty;
|
||||||
|
public List<TestCase> TestCases { get; set; } = new();
|
||||||
|
public string? CheckerPath { get; set; }
|
||||||
|
public int DefaultTimeLimit { get; set; } = 2000; // milliseconds
|
||||||
|
public int DefaultMemoryLimit { get; set; } = 256; // MB
|
||||||
|
}
|
||||||
10
src/LiquidCode.Tester.Common/Models/TestCase.cs
Normal file
10
src/LiquidCode.Tester.Common/Models/TestCase.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace LiquidCode.Tester.Common.Models;
|
||||||
|
|
||||||
|
public class TestCase
|
||||||
|
{
|
||||||
|
public int Number { get; set; }
|
||||||
|
public string InputFilePath { get; set; } = string.Empty;
|
||||||
|
public string OutputFilePath { get; set; } = string.Empty;
|
||||||
|
public int TimeLimit { get; set; } // milliseconds
|
||||||
|
public int MemoryLimit { get; set; } // MB
|
||||||
|
}
|
||||||
64
src/LiquidCode.Tester.Worker/Controllers/TestController.cs
Normal file
64
src/LiquidCode.Tester.Worker/Controllers/TestController.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using LiquidCode.Tester.Worker.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class TestController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ITestingService _testingService;
|
||||||
|
private readonly ILogger<TestController> _logger;
|
||||||
|
|
||||||
|
public TestController(ITestingService testingService, ILogger<TestController> logger)
|
||||||
|
{
|
||||||
|
_testingService = testingService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Test([FromForm] TestRequest request)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Received test request for submit {SubmitId}", request.Id);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Start testing in background
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _testingService.ProcessSubmitAsync(request);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error processing submit {SubmitId}", request.Id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Accepted(new { message = "Test request accepted", submitId = request.Id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to accept test request for submit {SubmitId}", request.Id);
|
||||||
|
return StatusCode(500, new { error = "Failed to accept test request", details = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("health")]
|
||||||
|
public IActionResult Health()
|
||||||
|
{
|
||||||
|
return Ok(new { status = "healthy", service = "cpp-worker", timestamp = DateTime.UtcNow });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestRequest
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long MissionId { get; set; }
|
||||||
|
public string Language { get; set; } = string.Empty;
|
||||||
|
public string LanguageVersion { get; set; } = string.Empty;
|
||||||
|
public string SourceCode { get; set; } = string.Empty;
|
||||||
|
public string CallbackUrl { get; set; } = string.Empty;
|
||||||
|
public IFormFile? Package { get; set; }
|
||||||
|
}
|
||||||
@@ -1,3 +1,30 @@
|
|||||||
// See https://aka.ms/new-console-template for more information
|
using LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
Console.WriteLine("Hello, World!");
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddOpenApi();
|
||||||
|
|
||||||
|
// Add HttpClient
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
|
// Register application services
|
||||||
|
builder.Services.AddSingleton<IPackageParserService, PackageParserService>();
|
||||||
|
builder.Services.AddSingleton<ICompilationService, CppCompilationService>();
|
||||||
|
builder.Services.AddSingleton<IExecutionService, CppExecutionService>();
|
||||||
|
builder.Services.AddSingleton<IOutputCheckerService, OutputCheckerService>();
|
||||||
|
builder.Services.AddSingleton<ICallbackService, CallbackService>();
|
||||||
|
builder.Services.AddSingleton<ITestingService, TestingService>();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.MapOpenApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
39
src/LiquidCode.Tester.Worker/Services/CallbackService.cs
Normal file
39
src/LiquidCode.Tester.Worker/Services/CallbackService.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using LiquidCode.Tester.Common.Models;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public class CallbackService : ICallbackService
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly ILogger<CallbackService> _logger;
|
||||||
|
|
||||||
|
public CallbackService(IHttpClientFactory httpClientFactory, ILogger<CallbackService> logger)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendStatusAsync(string callbackUrl, TesterResponseModel response)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Sending status update to {CallbackUrl} for submit {SubmitId}", callbackUrl, response.SubmitId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var httpClient = _httpClientFactory.CreateClient();
|
||||||
|
var json = JsonSerializer.Serialize(response);
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var httpResponse = await httpClient.PostAsync(callbackUrl, content);
|
||||||
|
httpResponse.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
_logger.LogInformation("Status update sent successfully");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to send status update to {CallbackUrl}", callbackUrl);
|
||||||
|
// Don't throw - callback failures shouldn't stop testing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public class CppCompilationService : ICompilationService
|
||||||
|
{
|
||||||
|
private readonly ILogger<CppCompilationService> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public CppCompilationService(ILogger<CppCompilationService> logger, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory)
|
||||||
|
{
|
||||||
|
var sourceFilePath = Path.Combine(workingDirectory, "solution.cpp");
|
||||||
|
var executablePath = Path.Combine(workingDirectory, "solution");
|
||||||
|
|
||||||
|
_logger.LogInformation("Compiling C++ code in {WorkingDirectory}", workingDirectory);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Write source code to file
|
||||||
|
await File.WriteAllTextAsync(sourceFilePath, sourceCode);
|
||||||
|
|
||||||
|
// Compile using g++
|
||||||
|
var compiler = _configuration["Cpp:Compiler"] ?? "g++";
|
||||||
|
var compilerFlags = _configuration["Cpp:CompilerFlags"] ?? "-O2 -std=c++17 -Wall";
|
||||||
|
|
||||||
|
var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = compiler,
|
||||||
|
Arguments = $"{compilerFlags} {sourceFilePath} -o {executablePath}",
|
||||||
|
WorkingDirectory = workingDirectory,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
var output = await process.StandardOutput.ReadToEndAsync();
|
||||||
|
var error = await process.StandardError.ReadToEndAsync();
|
||||||
|
|
||||||
|
await process.WaitForExitAsync();
|
||||||
|
|
||||||
|
var compilerOutput = $"{output}\n{error}".Trim();
|
||||||
|
|
||||||
|
if (process.ExitCode == 0 && File.Exists(executablePath))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Compilation successful");
|
||||||
|
return new CompilationResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
ExecutablePath = executablePath,
|
||||||
|
CompilerOutput = compilerOutput
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Compilation failed with exit code {ExitCode}", process.ExitCode);
|
||||||
|
return new CompilationResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = "Compilation failed",
|
||||||
|
CompilerOutput = compilerOutput
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during compilation");
|
||||||
|
return new CompilationResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = $"Compilation error: {ex.Message}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/LiquidCode.Tester.Worker/Services/CppExecutionService.cs
Normal file
114
src/LiquidCode.Tester.Worker/Services/CppExecutionService.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public class CppExecutionService : IExecutionService
|
||||||
|
{
|
||||||
|
private readonly ILogger<CppExecutionService> _logger;
|
||||||
|
|
||||||
|
public CppExecutionService(ILogger<CppExecutionService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ExecutionResult> ExecuteAsync(string executablePath, string inputFilePath, int timeLimitMs, int memoryLimitMb)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Executing {Executable} with input {Input}, time limit {TimeLimit}ms, memory limit {MemoryLimit}MB",
|
||||||
|
executablePath, inputFilePath, timeLimitMs, memoryLimitMb);
|
||||||
|
|
||||||
|
var result = new ExecutionResult();
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var inputStream = File.OpenRead(inputFilePath);
|
||||||
|
|
||||||
|
var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = executablePath,
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
// Copy input to stdin
|
||||||
|
await inputStream.CopyToAsync(process.StandardInput.BaseStream);
|
||||||
|
process.StandardInput.Close();
|
||||||
|
|
||||||
|
// Read output
|
||||||
|
var outputTask = process.StandardOutput.ReadToEndAsync();
|
||||||
|
var errorTask = process.StandardError.ReadToEndAsync();
|
||||||
|
|
||||||
|
// Wait for completion with timeout
|
||||||
|
var completedInTime = await Task.Run(() => process.WaitForExit(timeLimitMs));
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
if (!completedInTime)
|
||||||
|
{
|
||||||
|
// Time limit exceeded
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Kill(entireProcessTree: true);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
result.TimeLimitExceeded = true;
|
||||||
|
result.ErrorMessage = "Time limit exceeded";
|
||||||
|
_logger.LogWarning("Execution exceeded time limit");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Output = await outputTask;
|
||||||
|
result.ErrorOutput = await errorTask;
|
||||||
|
result.ExitCode = process.ExitCode;
|
||||||
|
|
||||||
|
// Check for runtime error
|
||||||
|
if (process.ExitCode != 0)
|
||||||
|
{
|
||||||
|
result.RuntimeError = true;
|
||||||
|
result.ErrorMessage = $"Runtime error (exit code {process.ExitCode})";
|
||||||
|
_logger.LogWarning("Runtime error with exit code {ExitCode}", process.ExitCode);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate memory usage (basic approach - in real scenario we'd use cgroups)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.MemoryUsedMb = process.PeakWorkingSet64 / (1024 * 1024);
|
||||||
|
if (result.MemoryUsedMb > memoryLimitMb)
|
||||||
|
{
|
||||||
|
result.MemoryLimitExceeded = true;
|
||||||
|
result.ErrorMessage = "Memory limit exceeded";
|
||||||
|
_logger.LogWarning("Memory limit exceeded: {Used}MB > {Limit}MB", result.MemoryUsedMb, memoryLimitMb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Could not measure memory usage");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Success = true;
|
||||||
|
_logger.LogInformation("Execution completed successfully in {Time}ms", result.ExecutionTimeMs);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
stopwatch.Stop();
|
||||||
|
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
|
||||||
|
result.RuntimeError = true;
|
||||||
|
result.ErrorMessage = $"Execution error: {ex.Message}";
|
||||||
|
_logger.LogError(ex, "Error during execution");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/LiquidCode.Tester.Worker/Services/ICallbackService.cs
Normal file
11
src/LiquidCode.Tester.Worker/Services/ICallbackService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using LiquidCode.Tester.Common.Models;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public interface ICallbackService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a status update to the callback URL
|
||||||
|
/// </summary>
|
||||||
|
Task SendStatusAsync(string callbackUrl, TesterResponseModel response);
|
||||||
|
}
|
||||||
20
src/LiquidCode.Tester.Worker/Services/ICompilationService.cs
Normal file
20
src/LiquidCode.Tester.Worker/Services/ICompilationService.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public interface ICompilationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Compiles source code to an executable
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceCode">Source code to compile</param>
|
||||||
|
/// <param name="workingDirectory">Directory to compile in</param>
|
||||||
|
/// <returns>Result containing success status, executable path, and error messages</returns>
|
||||||
|
Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompilationResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? ExecutablePath { get; set; }
|
||||||
|
public string ErrorMessage { get; set; } = string.Empty;
|
||||||
|
public string CompilerOutput { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
28
src/LiquidCode.Tester.Worker/Services/IExecutionService.cs
Normal file
28
src/LiquidCode.Tester.Worker/Services/IExecutionService.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public interface IExecutionService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a compiled program with input and captures output
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="executablePath">Path to the executable</param>
|
||||||
|
/// <param name="inputFilePath">Path to the input file</param>
|
||||||
|
/// <param name="timeLimitMs">Time limit in milliseconds</param>
|
||||||
|
/// <param name="memoryLimitMb">Memory limit in megabytes</param>
|
||||||
|
/// <returns>Execution result</returns>
|
||||||
|
Task<ExecutionResult> ExecuteAsync(string executablePath, string inputFilePath, int timeLimitMs, int memoryLimitMb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExecutionResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Output { get; set; } = string.Empty;
|
||||||
|
public string ErrorOutput { get; set; } = string.Empty;
|
||||||
|
public int ExitCode { get; set; }
|
||||||
|
public long ExecutionTimeMs { get; set; }
|
||||||
|
public long MemoryUsedMb { get; set; }
|
||||||
|
public bool TimeLimitExceeded { get; set; }
|
||||||
|
public bool MemoryLimitExceeded { get; set; }
|
||||||
|
public bool RuntimeError { get; set; }
|
||||||
|
public string ErrorMessage { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public interface IOutputCheckerService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Compares actual output with expected output
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actualOutput">Output from user's solution</param>
|
||||||
|
/// <param name="expectedOutputPath">Path to expected output file</param>
|
||||||
|
/// <returns>True if outputs match</returns>
|
||||||
|
Task<bool> CheckOutputAsync(string actualOutput, string expectedOutputPath);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using LiquidCode.Tester.Common.Models;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public interface IPackageParserService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts and parses a Polygon problem package
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packageStream">Stream containing the package ZIP file</param>
|
||||||
|
/// <returns>Parsed problem package information</returns>
|
||||||
|
Task<ProblemPackage> ParsePackageAsync(Stream packageStream);
|
||||||
|
}
|
||||||
11
src/LiquidCode.Tester.Worker/Services/ITestingService.cs
Normal file
11
src/LiquidCode.Tester.Worker/Services/ITestingService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using LiquidCode.Tester.Worker.Controllers;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public interface ITestingService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a complete submit: parse package, compile, test, send callbacks
|
||||||
|
/// </summary>
|
||||||
|
Task ProcessSubmitAsync(TestRequest request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public class OutputCheckerService : IOutputCheckerService
|
||||||
|
{
|
||||||
|
private readonly ILogger<OutputCheckerService> _logger;
|
||||||
|
|
||||||
|
public OutputCheckerService(ILogger<OutputCheckerService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CheckOutputAsync(string actualOutput, string expectedOutputPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var expectedOutput = await File.ReadAllTextAsync(expectedOutputPath);
|
||||||
|
|
||||||
|
// Normalize outputs for comparison
|
||||||
|
var normalizedActual = NormalizeOutput(actualOutput);
|
||||||
|
var normalizedExpected = NormalizeOutput(expectedOutput);
|
||||||
|
|
||||||
|
var match = normalizedActual == normalizedExpected;
|
||||||
|
|
||||||
|
if (!match)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Output mismatch. Expected length: {ExpectedLength}, Actual length: {ActualLength}",
|
||||||
|
normalizedExpected.Length, normalizedActual.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error checking output against {ExpectedFile}", expectedOutputPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string NormalizeOutput(string output)
|
||||||
|
{
|
||||||
|
// Remove trailing whitespace from each line and normalize line endings
|
||||||
|
var lines = output.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
|
||||||
|
.Select(line => line.TrimEnd())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Remove trailing empty lines
|
||||||
|
while (lines.Count > 0 && string.IsNullOrWhiteSpace(lines[^1]))
|
||||||
|
{
|
||||||
|
lines.RemoveAt(lines.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join("\n", lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/LiquidCode.Tester.Worker/Services/PackageParserService.cs
Normal file
120
src/LiquidCode.Tester.Worker/Services/PackageParserService.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using LiquidCode.Tester.Common.Models;
|
||||||
|
|
||||||
|
namespace LiquidCode.Tester.Worker.Services;
|
||||||
|
|
||||||
|
public class PackageParserService : IPackageParserService
|
||||||
|
{
|
||||||
|
private readonly ILogger<PackageParserService> _logger;
|
||||||
|
|
||||||
|
public PackageParserService(ILogger<PackageParserService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProblemPackage> ParsePackageAsync(Stream packageStream)
|
||||||
|
{
|
||||||
|
var workingDirectory = Path.Combine(Path.GetTempPath(), $"problem_{Guid.NewGuid()}");
|
||||||
|
Directory.CreateDirectory(workingDirectory);
|
||||||
|
|
||||||
|
_logger.LogInformation("Extracting package to {WorkingDirectory}", workingDirectory);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Extract ZIP archive
|
||||||
|
using var archive = new ZipArchive(packageStream, ZipArchiveMode.Read);
|
||||||
|
archive.ExtractToDirectory(workingDirectory);
|
||||||
|
|
||||||
|
var package = new ProblemPackage
|
||||||
|
{
|
||||||
|
WorkingDirectory = workingDirectory
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find tests directory
|
||||||
|
var testsDir = Path.Combine(workingDirectory, "tests");
|
||||||
|
if (!Directory.Exists(testsDir))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Tests directory not found, searching for test files in root");
|
||||||
|
testsDir = workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse test cases
|
||||||
|
var inputFiles = Directory.GetFiles(testsDir, "*", SearchOption.AllDirectories)
|
||||||
|
.Where(f => Path.GetFileName(f).EndsWith(".in") || Path.GetFileName(f).Contains("input"))
|
||||||
|
.OrderBy(f => f)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < inputFiles.Count; i++)
|
||||||
|
{
|
||||||
|
var inputFile = inputFiles[i];
|
||||||
|
var outputFile = FindCorrespondingOutputFile(inputFile);
|
||||||
|
|
||||||
|
if (outputFile == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("No output file found for input {InputFile}", inputFile);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
package.TestCases.Add(new TestCase
|
||||||
|
{
|
||||||
|
Number = i + 1,
|
||||||
|
InputFilePath = inputFile,
|
||||||
|
OutputFilePath = outputFile,
|
||||||
|
TimeLimit = package.DefaultTimeLimit,
|
||||||
|
MemoryLimit = package.DefaultMemoryLimit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for checker
|
||||||
|
var checkerCandidates = new[] { "check.cpp", "checker.cpp", "check", "checker" };
|
||||||
|
foreach (var candidate in checkerCandidates)
|
||||||
|
{
|
||||||
|
var checkerPath = Path.Combine(workingDirectory, candidate);
|
||||||
|
if (File.Exists(checkerPath))
|
||||||
|
{
|
||||||
|
package.CheckerPath = checkerPath;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Parsed package with {TestCount} tests", package.TestCases.Count);
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to parse package");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? FindCorrespondingOutputFile(string inputFile)
|
||||||
|
{
|
||||||
|
var directory = Path.GetDirectoryName(inputFile)!;
|
||||||
|
var fileName = Path.GetFileNameWithoutExtension(inputFile);
|
||||||
|
var extension = Path.GetExtension(inputFile);
|
||||||
|
|
||||||
|
// Try various output file naming patterns
|
||||||
|
var patterns = new[]
|
||||||
|
{
|
||||||
|
fileName.Replace("input", "output") + ".out",
|
||||||
|
fileName.Replace("input", "output") + ".a",
|
||||||
|
fileName.Replace("input", "answer") + ".out",
|
||||||
|
fileName.Replace("input", "answer") + ".a",
|
||||||
|
fileName + ".out",
|
||||||
|
fileName + ".a",
|
||||||
|
fileName.Replace(".in", ".out"),
|
||||||
|
fileName.Replace(".in", ".a")
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var pattern in patterns)
|
||||||
|
{
|
||||||
|
var candidate = Path.Combine(directory, pattern);
|
||||||
|
if (File.Exists(candidate))
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug",
|
||||||
|
"Microsoft.AspNetCore": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/LiquidCode.Tester.Worker/appsettings.json
Normal file
13
src/LiquidCode.Tester.Worker/appsettings.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Cpp": {
|
||||||
|
"Compiler": "g++",
|
||||||
|
"CompilerFlags": "-O2 -std=c++17 -Wall"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user