Adds package caching to the testing service
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 1m31s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 4m44s
All checks were successful
Build and Push Docker Images / build (src/LiquidCode.Tester.Gateway/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-gateway-roman, gateway) (push) Successful in 1m31s
Build and Push Docker Images / build (src/LiquidCode.Tester.Worker/Dockerfile, git.nullptr.top/liquidcode/liquidcode-tester-worker-roman, worker) (push) Successful in 4m44s
Implements a package cache to avoid reparsing and extracting problem packages for subsequent submissions, improving performance and reducing resource consumption. Introduces an interface and a concurrent dictionary-based implementation for the cache. A processing lock is also implemented using a semaphore to avoid concurrent access to the same package.
This commit is contained in:
@@ -15,6 +15,7 @@ public class TestingServiceTests : IDisposable
|
||||
private readonly Mock<IOutputCheckerService> _outputCheckerMock;
|
||||
private readonly Mock<ICallbackService> _callbackServiceMock;
|
||||
private readonly Mock<ILogger<TestingService>> _loggerMock;
|
||||
private readonly IPackageCacheService _packageCache;
|
||||
private readonly TestingService _service;
|
||||
private readonly string _testDirectory;
|
||||
|
||||
@@ -26,9 +27,11 @@ public class TestingServiceTests : IDisposable
|
||||
_outputCheckerMock = new Mock<IOutputCheckerService>();
|
||||
_callbackServiceMock = new Mock<ICallbackService>();
|
||||
_loggerMock = new Mock<ILogger<TestingService>>();
|
||||
_packageCache = new PackageCacheService();
|
||||
|
||||
_service = new TestingService(
|
||||
_packageParserMock.Object,
|
||||
_packageCache,
|
||||
_compilationFactoryMock.Object,
|
||||
_executionFactoryMock.Object,
|
||||
_outputCheckerMock.Object,
|
||||
@@ -104,7 +107,7 @@ public class TestingServiceTests : IDisposable
|
||||
await File.WriteAllTextAsync(inputFile, "test input");
|
||||
await File.WriteAllTextAsync(outputFile, "expected output");
|
||||
await File.WriteAllTextAsync(executablePath, "dummy");
|
||||
await CreateEmptyPackage(packageFilePath);
|
||||
await CreateEmptyPackage(packageFilePath);
|
||||
|
||||
var request = new TestRequest
|
||||
{
|
||||
@@ -194,11 +197,108 @@ public class TestingServiceTests : IDisposable
|
||||
);
|
||||
}
|
||||
|
||||
private async Task CreateEmptyPackage(string filePath)
|
||||
[Fact]
|
||||
public async Task ProcessSubmitAsync_ReusesCachedPackage()
|
||||
{
|
||||
// Arrange
|
||||
var packageFilePath = Path.Combine(_testDirectory, "cached_package.zip");
|
||||
var inputFile = Path.Combine(_testDirectory, "1.in");
|
||||
var outputFile = Path.Combine(_testDirectory, "1.out");
|
||||
var executablePath = Path.Combine(_testDirectory, "solution.exe");
|
||||
|
||||
await File.WriteAllTextAsync(inputFile, "test input");
|
||||
await File.WriteAllTextAsync(outputFile, "expected output");
|
||||
await File.WriteAllTextAsync(executablePath, "dummy");
|
||||
await CreateEmptyPackage(packageFilePath);
|
||||
|
||||
var request = new TestRequest
|
||||
{
|
||||
Id = 123,
|
||||
MissionId = 456,
|
||||
Language = "cpp",
|
||||
LanguageVersion = "17",
|
||||
SourceCode = "int main() { return 0; }",
|
||||
PackageFilePath = packageFilePath,
|
||||
CallbackUrl = "http://localhost/callback"
|
||||
};
|
||||
|
||||
var package = new ProblemPackage
|
||||
{
|
||||
WorkingDirectory = _testDirectory,
|
||||
ExtractionRoot = _testDirectory,
|
||||
TestCases = new List<TestCase>
|
||||
{
|
||||
new TestCase
|
||||
{
|
||||
Number = 1,
|
||||
InputFilePath = inputFile,
|
||||
OutputFilePath = outputFile,
|
||||
TimeLimit = 2000,
|
||||
MemoryLimit = 256
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var compilationService = new Mock<ICompilationService>();
|
||||
var executionService = new Mock<IExecutionService>();
|
||||
|
||||
var parseCalls = 0;
|
||||
|
||||
_packageParserMock
|
||||
.Setup(x => x.ParsePackageAsync(It.IsAny<Stream>()))
|
||||
.Callback(() => parseCalls++)
|
||||
.ReturnsAsync(package);
|
||||
|
||||
_compilationFactoryMock
|
||||
.Setup(x => x.GetCompilationService("cpp"))
|
||||
.Returns(compilationService.Object);
|
||||
|
||||
_executionFactoryMock
|
||||
.Setup(x => x.GetExecutionService("cpp"))
|
||||
.Returns(executionService.Object);
|
||||
|
||||
compilationService
|
||||
.Setup(x => x.CompileAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||
.ReturnsAsync(new CompilationResult
|
||||
{
|
||||
Success = true,
|
||||
ExecutablePath = executablePath
|
||||
});
|
||||
|
||||
executionService
|
||||
.Setup(x => x.ExecuteAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ReturnsAsync(new ExecutionResult
|
||||
{
|
||||
Success = true,
|
||||
Output = "expected output",
|
||||
ExitCode = 0,
|
||||
RuntimeError = false,
|
||||
TimeLimitExceeded = false,
|
||||
MemoryLimitExceeded = false
|
||||
});
|
||||
|
||||
_outputCheckerMock
|
||||
.Setup(x => x.CheckOutputWithCheckerAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string?>()))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
// Act
|
||||
await _service.ProcessSubmitAsync(request);
|
||||
await _service.ProcessSubmitAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, parseCalls);
|
||||
}
|
||||
|
||||
private Task CreateEmptyPackage(string filePath)
|
||||
{
|
||||
using var fileStream = File.Create(filePath);
|
||||
using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create);
|
||||
// Create empty archive
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
Reference in New Issue
Block a user