update polygon package parsing & testing
This commit is contained in:
@@ -12,7 +12,9 @@ public class OutputCheckerServiceTests : IDisposable
|
||||
public OutputCheckerServiceTests()
|
||||
{
|
||||
var logger = new Mock<ILogger<OutputCheckerService>>();
|
||||
_service = new OutputCheckerService(logger.Object);
|
||||
var checkerLogger = new Mock<ILogger<CheckerService>>();
|
||||
var checkerService = new CheckerService(checkerLogger.Object);
|
||||
_service = new OutputCheckerService(logger.Object, checkerService);
|
||||
_testDirectory = Path.Combine(Path.GetTempPath(), "OutputCheckerTests", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(_testDirectory);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,23 @@ public class PackageParserServiceTests : IDisposable
|
||||
public PackageParserServiceTests()
|
||||
{
|
||||
var logger = new Mock<ILogger<PackageParserService>>();
|
||||
_service = new PackageParserService(logger.Object);
|
||||
var xmlLogger = new Mock<ILogger<PolygonProblemXmlParser>>();
|
||||
var answerGenLogger = new Mock<ILogger<AnswerGenerationService>>();
|
||||
var cppLogger = new Mock<ILogger<CppCompilationService>>();
|
||||
var cppConfigMock = new Mock<Microsoft.Extensions.Configuration.IConfiguration>();
|
||||
|
||||
var polygonParser = new PolygonProblemXmlParser(xmlLogger.Object);
|
||||
|
||||
var compilationFactory = new Mock<ICompilationServiceFactory>();
|
||||
var executionFactory = new Mock<IExecutionServiceFactory>();
|
||||
var answerGenerator = new AnswerGenerationService(
|
||||
compilationFactory.Object,
|
||||
executionFactory.Object,
|
||||
answerGenLogger.Object);
|
||||
|
||||
var cppCompilation = new CppCompilationService(cppLogger.Object, cppConfigMock.Object);
|
||||
|
||||
_service = new PackageParserService(logger.Object, polygonParser, answerGenerator, cppCompilation);
|
||||
_testDirectory = Path.Combine(Path.GetTempPath(), "PackageParserTests", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(_testDirectory);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
using System.IO.Compression;
|
||||
using LiquidCode.Tester.Worker.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace LiquidCode.Tester.Worker.Tests;
|
||||
|
||||
public class PolygonPackageParserTests : IDisposable
|
||||
{
|
||||
private readonly PackageParserService _service;
|
||||
private readonly string _testDirectory;
|
||||
|
||||
public PolygonPackageParserTests()
|
||||
{
|
||||
var logger = new Mock<ILogger<PackageParserService>>();
|
||||
var xmlLogger = new Mock<ILogger<PolygonProblemXmlParser>>();
|
||||
var answerGenLogger = new Mock<ILogger<AnswerGenerationService>>();
|
||||
var cppLogger = new Mock<ILogger<CppCompilationService>>();
|
||||
var cppConfigMock = new Mock<Microsoft.Extensions.Configuration.IConfiguration>();
|
||||
|
||||
var polygonParser = new PolygonProblemXmlParser(xmlLogger.Object);
|
||||
|
||||
var compilationFactory = new Mock<ICompilationServiceFactory>();
|
||||
var executionFactory = new Mock<IExecutionServiceFactory>();
|
||||
var answerGenerator = new AnswerGenerationService(
|
||||
compilationFactory.Object,
|
||||
executionFactory.Object,
|
||||
answerGenLogger.Object);
|
||||
|
||||
var cppCompilation = new CppCompilationService(cppLogger.Object, cppConfigMock.Object);
|
||||
|
||||
_service = new PackageParserService(logger.Object, polygonParser, answerGenerator, cppCompilation);
|
||||
_testDirectory = Path.Combine(Path.GetTempPath(), "PolygonPackageTests", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(_testDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParsePackageAsync_PolygonPackageWithProblemXml_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
var problemXml = @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""no""?>
|
||||
<problem revision=""7"" short-name=""test-problem"">
|
||||
<judging>
|
||||
<testset name=""tests"">
|
||||
<time-limit>1000</time-limit>
|
||||
<memory-limit>268435456</memory-limit>
|
||||
<test-count>2</test-count>
|
||||
<input-path-pattern>tests/%02d</input-path-pattern>
|
||||
<answer-path-pattern>tests/%02d.a</answer-path-pattern>
|
||||
</testset>
|
||||
</judging>
|
||||
</problem>";
|
||||
|
||||
var zipStream = CreatePolygonPackage(problemXml, new[]
|
||||
{
|
||||
("tests/01", "input1"),
|
||||
("tests/01.a", "output1"),
|
||||
("tests/02", "input2"),
|
||||
("tests/02.a", "output2")
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await _service.ParsePackageAsync(zipStream);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.TestCases.Count);
|
||||
Assert.Equal(1000, result.DefaultTimeLimit);
|
||||
Assert.Equal(256, result.DefaultMemoryLimit);
|
||||
|
||||
// Verify first test
|
||||
Assert.Equal(1, result.TestCases[0].Number);
|
||||
Assert.True(File.Exists(result.TestCases[0].InputFilePath));
|
||||
Assert.True(File.Exists(result.TestCases[0].OutputFilePath));
|
||||
Assert.Equal("input1", await File.ReadAllTextAsync(result.TestCases[0].InputFilePath));
|
||||
Assert.Equal("output1", await File.ReadAllTextAsync(result.TestCases[0].OutputFilePath));
|
||||
|
||||
// Verify second test
|
||||
Assert.Equal(2, result.TestCases[1].Number);
|
||||
Assert.True(File.Exists(result.TestCases[1].InputFilePath));
|
||||
Assert.True(File.Exists(result.TestCases[1].OutputFilePath));
|
||||
|
||||
// Cleanup
|
||||
if (Directory.Exists(result.WorkingDirectory))
|
||||
{
|
||||
Directory.Delete(result.WorkingDirectory, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParsePackageAsync_PolygonPackageMissingAnswerFiles_SkipsTests()
|
||||
{
|
||||
// Arrange
|
||||
var problemXml = @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""no""?>
|
||||
<problem revision=""7"" short-name=""test-problem"">
|
||||
<judging>
|
||||
<testset name=""tests"">
|
||||
<time-limit>2000</time-limit>
|
||||
<memory-limit>536870912</memory-limit>
|
||||
<test-count>3</test-count>
|
||||
<input-path-pattern>tests/%02d</input-path-pattern>
|
||||
<answer-path-pattern>tests/%02d.a</answer-path-pattern>
|
||||
</testset>
|
||||
</judging>
|
||||
</problem>";
|
||||
|
||||
var zipStream = CreatePolygonPackage(problemXml, new[]
|
||||
{
|
||||
("tests/01", "input1"),
|
||||
("tests/01.a", "output1"),
|
||||
("tests/02", "input2"),
|
||||
// Missing 02.a
|
||||
("tests/03", "input3"),
|
||||
("tests/03.a", "output3")
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await _service.ParsePackageAsync(zipStream);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.TestCases.Count); // Only tests with answer files
|
||||
Assert.Equal(1, result.TestCases[0].Number);
|
||||
Assert.Equal(3, result.TestCases[1].Number); // Test 2 skipped
|
||||
|
||||
// Cleanup
|
||||
if (Directory.Exists(result.WorkingDirectory))
|
||||
{
|
||||
Directory.Delete(result.WorkingDirectory, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParsePackageAsync_NoProblemXml_FallsBackToLegacyFormat()
|
||||
{
|
||||
// Arrange - create package without problem.xml
|
||||
var zipStream = CreateLegacyPackage(new[]
|
||||
{
|
||||
("tests/test1.in", "input1"),
|
||||
("tests/test1.out", "output1"),
|
||||
("tests/test2.in", "input2"),
|
||||
("tests/test2.out", "output2")
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await _service.ParsePackageAsync(zipStream);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.TestCases.Count);
|
||||
Assert.Equal(2000, result.DefaultTimeLimit); // Default values
|
||||
Assert.Equal(256, result.DefaultMemoryLimit);
|
||||
|
||||
// Cleanup
|
||||
if (Directory.Exists(result.WorkingDirectory))
|
||||
{
|
||||
Directory.Delete(result.WorkingDirectory, true);
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryStream CreatePolygonPackage(string problemXml, IEnumerable<(string fileName, string content)> files)
|
||||
{
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||
{
|
||||
// Add problem.xml
|
||||
var xmlEntry = archive.CreateEntry("problem.xml");
|
||||
using var xmlStream = xmlEntry.Open();
|
||||
using var xmlWriter = new StreamWriter(xmlStream);
|
||||
xmlWriter.Write(problemXml);
|
||||
|
||||
// Add test files
|
||||
foreach (var (fileName, content) in files)
|
||||
{
|
||||
var entry = archive.CreateEntry(fileName);
|
||||
using var entryStream = entry.Open();
|
||||
using var writer = new StreamWriter(entryStream);
|
||||
writer.Write(content);
|
||||
}
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
private MemoryStream CreateLegacyPackage(IEnumerable<(string fileName, string content)> files)
|
||||
{
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||
{
|
||||
foreach (var (fileName, content) in files)
|
||||
{
|
||||
var entry = archive.CreateEntry(fileName);
|
||||
using var entryStream = entry.Open();
|
||||
using var writer = new StreamWriter(entryStream);
|
||||
writer.Write(content);
|
||||
}
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_testDirectory))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(_testDirectory, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
tests/LiquidCode.Tester.Worker.Tests/TestingServiceTests.cs
Normal file
211
tests/LiquidCode.Tester.Worker.Tests/TestingServiceTests.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System.IO.Compression;
|
||||
using LiquidCode.Tester.Common.Models;
|
||||
using LiquidCode.Tester.Worker.Controllers;
|
||||
using LiquidCode.Tester.Worker.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace LiquidCode.Tester.Worker.Tests;
|
||||
|
||||
public class TestingServiceTests : IDisposable
|
||||
{
|
||||
private readonly Mock<IPackageParserService> _packageParserMock;
|
||||
private readonly Mock<ICompilationServiceFactory> _compilationFactoryMock;
|
||||
private readonly Mock<IExecutionServiceFactory> _executionFactoryMock;
|
||||
private readonly Mock<IOutputCheckerService> _outputCheckerMock;
|
||||
private readonly Mock<ICallbackService> _callbackServiceMock;
|
||||
private readonly Mock<ILogger<TestingService>> _loggerMock;
|
||||
private readonly TestingService _service;
|
||||
private readonly string _testDirectory;
|
||||
|
||||
public TestingServiceTests()
|
||||
{
|
||||
_packageParserMock = new Mock<IPackageParserService>();
|
||||
_compilationFactoryMock = new Mock<ICompilationServiceFactory>();
|
||||
_executionFactoryMock = new Mock<IExecutionServiceFactory>();
|
||||
_outputCheckerMock = new Mock<IOutputCheckerService>();
|
||||
_callbackServiceMock = new Mock<ICallbackService>();
|
||||
_loggerMock = new Mock<ILogger<TestingService>>();
|
||||
|
||||
_service = new TestingService(
|
||||
_packageParserMock.Object,
|
||||
_compilationFactoryMock.Object,
|
||||
_executionFactoryMock.Object,
|
||||
_outputCheckerMock.Object,
|
||||
_callbackServiceMock.Object,
|
||||
_loggerMock.Object
|
||||
);
|
||||
|
||||
_testDirectory = Path.Combine(Path.GetTempPath(), "TestingServiceTests", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(_testDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessSubmitAsync_EmptyPackage_ReturnsUnknownError()
|
||||
{
|
||||
// Arrange
|
||||
var packageFilePath = Path.Combine(_testDirectory, "empty_package.zip");
|
||||
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 emptyPackage = new ProblemPackage
|
||||
{
|
||||
WorkingDirectory = _testDirectory,
|
||||
TestCases = new List<TestCase>() // Empty list!
|
||||
};
|
||||
|
||||
_packageParserMock
|
||||
.Setup(x => x.ParsePackageAsync(It.IsAny<Stream>()))
|
||||
.ReturnsAsync(emptyPackage);
|
||||
|
||||
// Act
|
||||
await _service.ProcessSubmitAsync(request);
|
||||
|
||||
// Assert - verify callback was called with error
|
||||
_callbackServiceMock.Verify(
|
||||
x => x.SendStatusAsync(
|
||||
request.CallbackUrl,
|
||||
It.Is<TesterResponseModel>(r =>
|
||||
r.State == State.Done &&
|
||||
r.ErrorCode == ErrorCode.UnknownError &&
|
||||
r.Message == "No test cases found in package"
|
||||
)
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
|
||||
// Verify compilation was NOT attempted
|
||||
_compilationFactoryMock.Verify(
|
||||
x => x.GetCompilationService(It.IsAny<string>()),
|
||||
Times.Never
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessSubmitAsync_ValidPackage_RunsAllTests()
|
||||
{
|
||||
// Arrange
|
||||
var packageFilePath = Path.Combine(_testDirectory, "valid_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");
|
||||
|
||||
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,
|
||||
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>();
|
||||
|
||||
_packageParserMock
|
||||
.Setup(x => x.ParsePackageAsync(It.IsAny<Stream>()))
|
||||
.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.CheckOutputAsync(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
// Act
|
||||
await _service.ProcessSubmitAsync(request);
|
||||
|
||||
// Assert - verify callback was called with success
|
||||
_callbackServiceMock.Verify(
|
||||
x => x.SendStatusAsync(
|
||||
request.CallbackUrl,
|
||||
It.Is<TesterResponseModel>(r =>
|
||||
r.State == State.Done &&
|
||||
r.ErrorCode == ErrorCode.None &&
|
||||
r.Message == "All tests passed"
|
||||
)
|
||||
),
|
||||
Times.AtLeastOnce
|
||||
);
|
||||
}
|
||||
|
||||
private async Task CreateEmptyPackage(string filePath)
|
||||
{
|
||||
using var fileStream = File.Create(filePath);
|
||||
using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create);
|
||||
// Create empty archive
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_testDirectory))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(_testDirectory, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup errors in tests
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user