using System; using System.Diagnostics; using System.IO; using System.IO.Compression; using LiquidCode.Tester.Worker.Services; using LiquidCode.Tester.Worker.Services.Sandbox; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Xunit; namespace LiquidCode.Tester.Worker.Tests; public class PolygonPackageIntegrationTests { private readonly PackageParserService _parserService; private readonly SandboxOptions _sandboxOptions; public PolygonPackageIntegrationTests() { var configuration = BuildConfiguration(); var loggerFactory = LoggerFactory.Create(builder => { builder.SetMinimumLevel(LogLevel.Debug); builder.AddConsole(); }); _sandboxOptions = new SandboxOptions(); configuration.GetSection("Sandbox").Bind(_sandboxOptions); if (!_sandboxOptions.Enabled) { _sandboxOptions.Enabled = true; } var cppCompilation = new CppCompilationService(loggerFactory.CreateLogger(), configuration); var sandbox = new ExecutionSandbox(Options.Create(_sandboxOptions), loggerFactory.CreateLogger()); var cppExecution = new CppExecutionService(sandbox, loggerFactory.CreateLogger()); var services = new ServiceCollection(); services.AddSingleton(configuration); services.AddSingleton(cppCompilation); services.AddSingleton(sandbox); services.AddSingleton(cppExecution); var serviceProvider = services.BuildServiceProvider(); var compilationFactory = new CompilationServiceFactory(serviceProvider, loggerFactory.CreateLogger()); var executionFactory = new ExecutionServiceFactory(serviceProvider, loggerFactory.CreateLogger()); var answerGenerator = new AnswerGenerationService(compilationFactory, executionFactory, loggerFactory.CreateLogger()); var polygonParser = new PolygonProblemXmlParser(loggerFactory.CreateLogger()); _parserService = new PackageParserService( loggerFactory.CreateLogger(), polygonParser, answerGenerator, cppCompilation); } [Fact] public async Task ParsePolygonPackageAsync_GeneratesTestsCheckerAndAnswers() { if (!IsExecutableAvailable("g++")) { return; } var dockerExecutable = string.IsNullOrWhiteSpace(_sandboxOptions.DockerExecutable) ? "docker" : _sandboxOptions.DockerExecutable; if (!IsExecutableAvailable(dockerExecutable)) { return; } Assert.True( _sandboxOptions.Languages.TryGetValue("cpp", out var cppLanguage) && !string.IsNullOrWhiteSpace(cppLanguage.ExecutionImage), "Sandbox configuration must specify an execution image for C++."); var packageDirectory = ResolvePackageDirectory("exam-queue-17"); Assert.True(Directory.Exists(packageDirectory)); await using var packageStream = CreateZipFromDirectory(packageDirectory); var package = await _parserService.ParsePackageAsync(packageStream); try { Assert.Equal(51, package.TestCases.Count); Assert.NotNull(package.CheckerPath); Assert.True(File.Exists(package.CheckerPath), $"Checker not present at {package.CheckerPath}"); foreach (var testCase in package.TestCases) { Assert.True(File.Exists(testCase.InputFilePath), $"Missing input {testCase.InputFilePath}"); Assert.True(File.Exists(testCase.OutputFilePath), $"Missing output {testCase.OutputFilePath}"); Assert.True(new FileInfo(testCase.InputFilePath).Length > 0); Assert.True(new FileInfo(testCase.OutputFilePath).Length > 0); } } finally { if (!KeepTemporaryExtraction()) { if (!string.IsNullOrEmpty(package.ExtractionRoot) && Directory.Exists(package.ExtractionRoot)) { Directory.Delete(package.ExtractionRoot, recursive: true); } else if (Directory.Exists(package.WorkingDirectory)) { Directory.Delete(package.WorkingDirectory, recursive: true); } } } } private static bool IsExecutableAvailable(string executable) { try { using var process = new Process { StartInfo = new ProcessStartInfo { FileName = executable, Arguments = "--version", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; process.Start(); if (!process.WaitForExit(5000)) { try { process.Kill(entireProcessTree: true); } catch { // ignored } return false; } return process.ExitCode == 0; } catch { return false; } } private static MemoryStream CreateZipFromDirectory(string directoryPath) { var memoryStream = new MemoryStream(); using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true)) { foreach (var file in Directory.GetFiles(directoryPath, "*", SearchOption.AllDirectories)) { var entryName = Path.GetRelativePath(directoryPath, file).Replace("\\", "/"); var entry = archive.CreateEntry(entryName, CompressionLevel.Fastest); using (var entryStream = entry.Open()) using (var fileStream = File.OpenRead(file)) { fileStream.CopyTo(entryStream); } } } memoryStream.Position = 0; return memoryStream; } private static IConfiguration BuildConfiguration() { var repositoryRoot = ResolveRepositoryRoot(); return new ConfigurationBuilder() .SetBasePath(repositoryRoot) .AddJsonFile("src/LiquidCode.Tester.Worker/appsettings.json", optional: false) .AddJsonFile("src/LiquidCode.Tester.Worker/appsettings.Development.json", optional: true) .AddEnvironmentVariables() .Build(); } private static string ResolveRepositoryRoot() { var baseDirectory = AppContext.BaseDirectory; return Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", "..", "..")); } private static string ResolvePackageDirectory(string folderName) { return Path.Combine(ResolveRepositoryRoot(), folderName); } private static bool KeepTemporaryExtraction() => string.Equals(Environment.GetEnvironmentVariable("LC_KEEP_POLYGON_TEMP"), "1", StringComparison.Ordinal); }