namespace TeamCity.VSTest.TestLogger; using System; using System.Collections.Generic; using System.IO; using System.Text; using Microsoft.VisualStudio.TestPlatform.ObjectModel; internal class FailedTestsReportWriter : IFailedTestsReportWriter { private readonly IOptions _options; private readonly IBytesWriterFactory _bytesWriterFactory; private readonly IBytesWriter? _reportWriter; private readonly HashSet _reportedTests; public FailedTestsReportWriter(IOptions options, IBytesWriterFactory bytesWriterFactory) { _options = options; _bytesWriterFactory = bytesWriterFactory; _reportWriter = InitFileMessageWriter(); _reportedTests = new HashSet(); } private bool EnsureFailedTestsFileSavePathDirectoryExists() { if (string.IsNullOrEmpty(_options.FailedTestsReportSavePath)) return false; try { if (!Directory.Exists(_options.FailedTestsReportSavePath)) Directory.CreateDirectory(_options.FailedTestsReportSavePath); return true; } catch { return false; } } public void ReportFailedTest(TestCase testCase) { if (_reportWriter == null) return; if (Strings.IsNullOrWhiteSpace(testCase.FullyQualifiedName)) return; var testName = GetTestNameForRetry(testCase.FullyQualifiedName); if (!_reportedTests.Add(testName)) return; var bytesToWrite = Encoding.UTF8.GetBytes(testName + Environment.NewLine); _reportWriter.Write(bytesToWrite); _reportWriter.Flush(); } /// /// For MSTest and XUnit FullyQualifiedName is supported as is /// In case of NUnit we have to remove arguments from FullyQualifiedName /// private string GetTestNameForRetry(string fullyQualifiedName) { // Starting from TC version 2024.12.1 this logic was moved to dotnet-plugin if (_options.Version.CompareTo(new TeamCityVersion("2024.12")) > 0) return fullyQualifiedName; var name = fullyQualifiedName.Trim(); var argsPosition = name.IndexOf("(", StringComparison.Ordinal); var hasArgs = argsPosition >= 0; return hasArgs ? name.Substring(0, argsPosition) : name; } private IBytesWriter? InitFileMessageWriter() { if (!EnsureFailedTestsFileSavePathDirectoryExists()) return null; var messagesFilePath = Path.Combine(_options.FailedTestsReportSavePath, Guid.NewGuid().ToString("n")) + ".txt"; return _bytesWriterFactory.Create(messagesFilePath); } public void Dispose() => _reportWriter?.Dispose(); }