remove k8s configs, update worker for multi-languages support, add local-submit option

This commit is contained in:
prixod
2025-10-27 21:28:46 +04:00
parent 7f0e7fbe20
commit 6041acb8ed
38 changed files with 2205 additions and 342 deletions

View File

@@ -0,0 +1,128 @@
using LiquidCode.Tester.Worker.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
namespace LiquidCode.Tester.Worker.Tests;
public class CompilationServiceFactoryTests
{
private readonly IServiceProvider _serviceProvider;
private readonly CompilationServiceFactory _factory;
public CompilationServiceFactoryTests()
{
var services = new ServiceCollection();
// Mock configuration
var configuration = new Mock<IConfiguration>();
services.AddSingleton(configuration.Object);
// Mock logger
var loggerFactory = new Mock<ILoggerFactory>();
loggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>()))
.Returns(new Mock<ILogger>().Object);
services.AddSingleton(loggerFactory.Object);
services.AddLogging();
// Register compilation services
services.AddSingleton<CppCompilationService>();
services.AddSingleton<JavaCompilationService>();
services.AddSingleton<KotlinCompilationService>();
services.AddSingleton<CSharpCompilationService>();
services.AddSingleton<PythonCompilationService>();
_serviceProvider = services.BuildServiceProvider();
_factory = new CompilationServiceFactory(
_serviceProvider,
_serviceProvider.GetRequiredService<ILogger<CompilationServiceFactory>>());
}
[Theory]
[InlineData("C++", typeof(CppCompilationService))]
[InlineData("c++", typeof(CppCompilationService))]
[InlineData("cpp", typeof(CppCompilationService))]
[InlineData("CPP", typeof(CppCompilationService))]
public void GetCompilationService_CppLanguage_ReturnsCppCompilationService(string language, Type expectedType)
{
// Act
var service = _factory.GetCompilationService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Java", typeof(JavaCompilationService))]
[InlineData("java", typeof(JavaCompilationService))]
[InlineData("JAVA", typeof(JavaCompilationService))]
public void GetCompilationService_JavaLanguage_ReturnsJavaCompilationService(string language, Type expectedType)
{
// Act
var service = _factory.GetCompilationService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Kotlin", typeof(KotlinCompilationService))]
[InlineData("kotlin", typeof(KotlinCompilationService))]
[InlineData("KOTLIN", typeof(KotlinCompilationService))]
public void GetCompilationService_KotlinLanguage_ReturnsKotlinCompilationService(string language, Type expectedType)
{
// Act
var service = _factory.GetCompilationService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("C#", typeof(CSharpCompilationService))]
[InlineData("c#", typeof(CSharpCompilationService))]
[InlineData("csharp", typeof(CSharpCompilationService))]
[InlineData("CSharp", typeof(CSharpCompilationService))]
[InlineData("CSHARP", typeof(CSharpCompilationService))]
public void GetCompilationService_CSharpLanguage_ReturnsCSharpCompilationService(string language, Type expectedType)
{
// Act
var service = _factory.GetCompilationService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Python", typeof(PythonCompilationService))]
[InlineData("python", typeof(PythonCompilationService))]
[InlineData("PYTHON", typeof(PythonCompilationService))]
public void GetCompilationService_PythonLanguage_ReturnsPythonCompilationService(string language, Type expectedType)
{
// Act
var service = _factory.GetCompilationService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Go")]
[InlineData("Rust")]
[InlineData("JavaScript")]
[InlineData("")]
[InlineData(" ")]
public void GetCompilationService_UnsupportedLanguage_ThrowsNotSupportedException(string language)
{
// Act & Assert
Assert.Throws<NotSupportedException>(() => _factory.GetCompilationService(language));
}
[Fact]
public void GetCompilationService_NullLanguage_ThrowsException()
{
// Act & Assert
Assert.Throws<NullReferenceException>(() => _factory.GetCompilationService(null!));
}
}

View File

@@ -0,0 +1,128 @@
using LiquidCode.Tester.Worker.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
namespace LiquidCode.Tester.Worker.Tests;
public class ExecutionServiceFactoryTests
{
private readonly IServiceProvider _serviceProvider;
private readonly ExecutionServiceFactory _factory;
public ExecutionServiceFactoryTests()
{
var services = new ServiceCollection();
// Mock configuration
var configuration = new Mock<IConfiguration>();
services.AddSingleton(configuration.Object);
// Mock logger
var loggerFactory = new Mock<ILoggerFactory>();
loggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>()))
.Returns(new Mock<ILogger>().Object);
services.AddSingleton(loggerFactory.Object);
services.AddLogging();
// Register execution services
services.AddSingleton<CppExecutionService>();
services.AddSingleton<JavaExecutionService>();
services.AddSingleton<KotlinExecutionService>();
services.AddSingleton<CSharpExecutionService>();
services.AddSingleton<PythonExecutionService>();
_serviceProvider = services.BuildServiceProvider();
_factory = new ExecutionServiceFactory(
_serviceProvider,
_serviceProvider.GetRequiredService<ILogger<ExecutionServiceFactory>>());
}
[Theory]
[InlineData("C++", typeof(CppExecutionService))]
[InlineData("c++", typeof(CppExecutionService))]
[InlineData("cpp", typeof(CppExecutionService))]
[InlineData("CPP", typeof(CppExecutionService))]
public void GetExecutionService_CppLanguage_ReturnsCppExecutionService(string language, Type expectedType)
{
// Act
var service = _factory.GetExecutionService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Java", typeof(JavaExecutionService))]
[InlineData("java", typeof(JavaExecutionService))]
[InlineData("JAVA", typeof(JavaExecutionService))]
public void GetExecutionService_JavaLanguage_ReturnsJavaExecutionService(string language, Type expectedType)
{
// Act
var service = _factory.GetExecutionService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Kotlin", typeof(KotlinExecutionService))]
[InlineData("kotlin", typeof(KotlinExecutionService))]
[InlineData("KOTLIN", typeof(KotlinExecutionService))]
public void GetExecutionService_KotlinLanguage_ReturnsKotlinExecutionService(string language, Type expectedType)
{
// Act
var service = _factory.GetExecutionService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("C#", typeof(CSharpExecutionService))]
[InlineData("c#", typeof(CSharpExecutionService))]
[InlineData("csharp", typeof(CSharpExecutionService))]
[InlineData("CSharp", typeof(CSharpExecutionService))]
[InlineData("CSHARP", typeof(CSharpExecutionService))]
public void GetExecutionService_CSharpLanguage_ReturnsCSharpExecutionService(string language, Type expectedType)
{
// Act
var service = _factory.GetExecutionService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Python", typeof(PythonExecutionService))]
[InlineData("python", typeof(PythonExecutionService))]
[InlineData("PYTHON", typeof(PythonExecutionService))]
public void GetExecutionService_PythonLanguage_ReturnsPythonExecutionService(string language, Type expectedType)
{
// Act
var service = _factory.GetExecutionService(language);
// Assert
Assert.IsType(expectedType, service);
}
[Theory]
[InlineData("Go")]
[InlineData("Rust")]
[InlineData("JavaScript")]
[InlineData("")]
[InlineData(" ")]
public void GetExecutionService_UnsupportedLanguage_ThrowsNotSupportedException(string language)
{
// Act & Assert
Assert.Throws<NotSupportedException>(() => _factory.GetExecutionService(language));
}
[Fact]
public void GetExecutionService_NullLanguage_ThrowsException()
{
// Act & Assert
Assert.Throws<NullReferenceException>(() => _factory.GetExecutionService(null!));
}
}

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\LiquidCode.Tester.Worker\LiquidCode.Tester.Worker.csproj" />
<ProjectReference Include="..\..\src\LiquidCode.Tester.Common\LiquidCode.Tester.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,169 @@
using LiquidCode.Tester.Worker.Services;
using Microsoft.Extensions.Logging;
using Moq;
namespace LiquidCode.Tester.Worker.Tests;
public class OutputCheckerServiceTests : IDisposable
{
private readonly OutputCheckerService _service;
private readonly string _testDirectory;
public OutputCheckerServiceTests()
{
var logger = new Mock<ILogger<OutputCheckerService>>();
_service = new OutputCheckerService(logger.Object);
_testDirectory = Path.Combine(Path.GetTempPath(), "OutputCheckerTests", Guid.NewGuid().ToString());
Directory.CreateDirectory(_testDirectory);
}
[Fact]
public async Task CheckOutputAsync_ExactMatch_ReturnsTrue()
{
// Arrange
var expectedOutput = "Hello, World!";
var actualOutput = "Hello, World!";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.True(result);
}
[Fact]
public async Task CheckOutputAsync_DifferentOutput_ReturnsFalse()
{
// Arrange
var expectedOutput = "Hello, World!";
var actualOutput = "Goodbye, World!";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.False(result);
}
[Fact]
public async Task CheckOutputAsync_TrailingWhitespace_ReturnsTrue()
{
// Arrange
var expectedOutput = "Hello, World!";
var actualOutput = "Hello, World! \n\n";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.True(result);
}
[Fact]
public async Task CheckOutputAsync_LeadingWhitespace_ReturnsTrue()
{
// Arrange
var expectedOutput = "Hello, World!";
var actualOutput = " \n\nHello, World!";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.True(result);
}
[Fact]
public async Task CheckOutputAsync_DifferentLineEndings_ReturnsTrue()
{
// Arrange
var expectedOutput = "Line1\nLine2\nLine3";
var actualOutput = "Line1\r\nLine2\r\nLine3";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.True(result);
}
[Fact]
public async Task CheckOutputAsync_MultipleLines_ExactMatch_ReturnsTrue()
{
// Arrange
var expectedOutput = "Line 1\nLine 2\nLine 3";
var actualOutput = "Line 1\nLine 2\nLine 3";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.True(result);
}
[Fact]
public async Task CheckOutputAsync_EmptyOutputs_ReturnsTrue()
{
// Arrange
var expectedOutput = "";
var actualOutput = "";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.True(result);
}
[Fact]
public async Task CheckOutputAsync_OnlyWhitespace_ReturnsTrue()
{
// Arrange
var expectedOutput = " \n\n ";
var actualOutput = " \n\n ";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.True(result);
}
[Fact]
public async Task CheckOutputAsync_CaseSensitive_ReturnsFalse()
{
// Arrange
var expectedOutput = "Hello, World!";
var actualOutput = "hello, world!";
var expectedFilePath = CreateTempFile(expectedOutput);
// Act
var result = await _service.CheckOutputAsync(actualOutput, expectedFilePath);
// Assert
Assert.False(result);
}
private string CreateTempFile(string content)
{
var filePath = Path.Combine(_testDirectory, $"test_{Guid.NewGuid()}.txt");
File.WriteAllText(filePath, content);
return filePath;
}
public void Dispose()
{
if (Directory.Exists(_testDirectory))
{
Directory.Delete(_testDirectory, true);
}
}
}

View File

@@ -0,0 +1,210 @@
using System.IO.Compression;
using LiquidCode.Tester.Worker.Services;
using Microsoft.Extensions.Logging;
using Moq;
namespace LiquidCode.Tester.Worker.Tests;
public class PackageParserServiceTests : IDisposable
{
private readonly PackageParserService _service;
private readonly string _testDirectory;
public PackageParserServiceTests()
{
var logger = new Mock<ILogger<PackageParserService>>();
_service = new PackageParserService(logger.Object);
_testDirectory = Path.Combine(Path.GetTempPath(), "PackageParserTests", Guid.NewGuid().ToString());
Directory.CreateDirectory(_testDirectory);
}
[Fact]
public async Task ParsePackageAsync_ValidPackageWithTestCases_ParsesSuccessfully()
{
// Arrange
var zipStream = CreateTestPackage(new[]
{
("1.in", "input1"),
("1.out", "output1"),
("2.in", "input2"),
("2.out", "output2"),
("3.in", "input3"),
("3.out", "output3")
});
// Act
var result = await _service.ParsePackageAsync(zipStream);
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.TestCases.Count);
Assert.True(Directory.Exists(result.WorkingDirectory));
// Check test case 1
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));
// Check test case 2
Assert.Equal(2, result.TestCases[1].Number);
Assert.True(File.Exists(result.TestCases[1].InputFilePath));
Assert.True(File.Exists(result.TestCases[1].OutputFilePath));
// Check test case 3
Assert.Equal(3, result.TestCases[2].Number);
Assert.True(File.Exists(result.TestCases[2].InputFilePath));
Assert.True(File.Exists(result.TestCases[2].OutputFilePath));
}
[Fact]
public async Task ParsePackageAsync_PackageWithDefaultLimits_UsesDefaultValues()
{
// Arrange
var zipStream = CreateTestPackage(new[]
{
("1.in", "input1"),
("1.out", "output1")
});
// Act
var result = await _service.ParsePackageAsync(zipStream);
// Assert
Assert.Single(result.TestCases);
Assert.Equal(2000, result.TestCases[0].TimeLimit); // Default time limit
Assert.Equal(256, result.TestCases[0].MemoryLimit); // Default memory limit
}
[Fact]
public async Task ParsePackageAsync_EmptyPackage_ReturnsEmptyTestCasesList()
{
// Arrange
var zipStream = CreateTestPackage(Array.Empty<(string, string)>());
// Act
var result = await _service.ParsePackageAsync(zipStream);
// Assert
Assert.NotNull(result);
Assert.Empty(result.TestCases);
}
[Fact]
public async Task ParsePackageAsync_TestCasesNotInOrder_SortsCorrectly()
{
// Arrange
var zipStream = CreateTestPackage(new[]
{
("3.in", "input3"),
("3.out", "output3"),
("1.in", "input1"),
("1.out", "output1"),
("2.in", "input2"),
("2.out", "output2")
});
// Act
var result = await _service.ParsePackageAsync(zipStream);
// Assert
Assert.Equal(3, result.TestCases.Count);
Assert.Equal(1, result.TestCases[0].Number);
Assert.Equal(2, result.TestCases[1].Number);
Assert.Equal(3, result.TestCases[2].Number);
}
[Fact]
public async Task ParsePackageAsync_MissingOutputFile_SkipsTestCase()
{
// Arrange
var zipStream = CreateTestPackage(new[]
{
("1.in", "input1"),
("1.out", "output1"),
("2.in", "input2"),
// Missing 2.out
("3.in", "input3"),
("3.out", "output3")
});
// Act
var result = await _service.ParsePackageAsync(zipStream);
// Assert
Assert.Equal(2, result.TestCases.Count); // Only test 1 and 3
Assert.Equal(1, result.TestCases[0].Number);
Assert.Equal(3, result.TestCases[1].Number);
}
[Fact]
public async Task ParsePackageAsync_MissingInputFile_SkipsTestCase()
{
// Arrange
var zipStream = CreateTestPackage(new[]
{
("1.in", "input1"),
("1.out", "output1"),
// Missing 2.in
("2.out", "output2"),
("3.in", "input3"),
("3.out", "output3")
});
// Act
var result = await _service.ParsePackageAsync(zipStream);
// Assert
Assert.Equal(2, result.TestCases.Count); // Only test 1 and 3
Assert.Equal(1, result.TestCases[0].Number);
Assert.Equal(3, result.TestCases[1].Number);
}
[Fact]
public async Task ParsePackageAsync_TestsInSubdirectory_ParsesSuccessfully()
{
// Arrange
var zipStream = CreateTestPackage(new[]
{
("tests/1.in", "input1"),
("tests/1.out", "output1"),
("tests/2.in", "input2"),
("tests/2.out", "output2")
});
// Act
var result = await _service.ParsePackageAsync(zipStream);
// Assert
Assert.Equal(2, result.TestCases.Count);
Assert.Equal(1, result.TestCases[0].Number);
Assert.Equal(2, result.TestCases[1].Number);
}
private MemoryStream CreateTestPackage(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))
{
Directory.Delete(_testDirectory, true);
}
}
}