namespace LiquidCode.Tester.Worker.Services; public class OutputCheckerService : IOutputCheckerService { private readonly ILogger _logger; private readonly CheckerService _checkerService; public OutputCheckerService(ILogger logger, CheckerService checkerService) { _logger = logger; _checkerService = checkerService; } public async Task 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); } public async Task CheckOutputWithCheckerAsync( string actualOutput, string inputFilePath, string expectedOutputPath, string? checkerPath) { // If custom checker is available, use it if (!string.IsNullOrEmpty(checkerPath) && File.Exists(checkerPath)) { _logger.LogDebug("Using custom checker: {CheckerPath}", checkerPath); var checkerResult = await _checkerService.CheckAsync( checkerPath, inputFilePath, actualOutput, expectedOutputPath); if (!checkerResult.Accepted) { _logger.LogWarning("Custom checker verdict: {Verdict} - {Message}", checkerResult.Verdict, checkerResult.Message); } return checkerResult.Accepted; } // Fall back to standard string comparison _logger.LogDebug("No custom checker, using standard comparison"); return await CheckOutputAsync(actualOutput, expectedOutputPath); } }