diff --git a/CHANGELOG.md b/CHANGELOG.md index 7085117ec..d23ef90d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ # [vNext] ## Improvements: +* Refactor: Introduce virtual timeout properties FormattersBase class to allow individual formatters to override the defaults ## Bug fixes: -*Contributors of this release (in alphabetical order):* +*Contributors of this release (in alphabetical order):* @clrudolphi # v3.3.3 - 2026-01-27 diff --git a/Reqnroll/Formatters/FormatterBase.cs b/Reqnroll/Formatters/FormatterBase.cs index c152ee6e7..fb618d099 100644 --- a/Reqnroll/Formatters/FormatterBase.cs +++ b/Reqnroll/Formatters/FormatterBase.cs @@ -35,6 +35,16 @@ public abstract class FormatterBase : ICucumberMessageFormatter, IDisposable public string Name => _pluginName; + /// + /// Gets the timeout duration to wait for formatter task completion during disposal. + /// + protected virtual TimeSpan DisposeTimeout => TimeSpan.FromSeconds(15); + + /// + /// Gets the timeout duration to wait after cancellation during disposal. + /// + protected virtual TimeSpan DisposeCancellationTimeout => TimeSpan.FromSeconds(15); + protected FormatterBase(IFormattersConfigurationProvider configurationProvider, IFormatterLog logger, string pluginName) { _configurationProvider = configurationProvider; @@ -134,7 +144,7 @@ public virtual void Dispose() Logger.WriteMessage($"DEBUG: Formatters: Dispose is waiting on the formatter task {Name}."); // In this situation, the TestEngine is shutting down and has called Dispose on the global container. // Forcing the Dispose to wait until the formatter has had a chance to complete. - var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15)); + var timeoutTask = Task.Delay(DisposeTimeout); var finishedTask = Task.WhenAny(timeoutTask, _formatterTask).GetAwaiter().GetResult(); if (finishedTask == timeoutTask) { @@ -151,7 +161,7 @@ public virtual void Dispose() _logger.WriteMessage($"DEBUG: Formatters:PluginBase.Dispose - cancellation message can't be sent as the collection is closed."); } _logger.WriteMessage($"DEBUG: Formatters.PluginBase.Dispose - waiting again after cancellation."); - timeoutTask = Task.Delay(TimeSpan.FromSeconds(15)); + timeoutTask = Task.Delay(DisposeCancellationTimeout); finishedTask = Task.WhenAny(timeoutTask, _formatterTask).GetAwaiter().GetResult(); if (finishedTask == timeoutTask) { diff --git a/Tests/Reqnroll.RuntimeTests/Formatters/FileWritingFormatterBaseTests.cs b/Tests/Reqnroll.RuntimeTests/Formatters/FileWritingFormatterBaseTests.cs index 89132f6f1..e5f99688a 100644 --- a/Tests/Reqnroll.RuntimeTests/Formatters/FileWritingFormatterBaseTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Formatters/FileWritingFormatterBaseTests.cs @@ -39,6 +39,9 @@ private class TestFileWritingFormatter : FileWritingFormatterBase public bool FinalizeInitializationCalled = false; public Stream? LastStream; + protected override TimeSpan DisposeTimeout => TimeSpan.FromMilliseconds(100); + protected override TimeSpan DisposeCancellationTimeout => TimeSpan.FromMilliseconds(100); + public TestFileWritingFormatter(IFormattersConfigurationProvider config, IFormatterLog logger, IFileSystem fileSystem) : base(config, logger, fileSystem, "testPlugin", ".txt", "default.txt") { } diff --git a/Tests/Reqnroll.RuntimeTests/Formatters/FormatterBaseTests.cs b/Tests/Reqnroll.RuntimeTests/Formatters/FormatterBaseTests.cs index 9306179c2..9ccb3118b 100644 --- a/Tests/Reqnroll.RuntimeTests/Formatters/FormatterBaseTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Formatters/FormatterBaseTests.cs @@ -28,6 +28,8 @@ private class TestFormatter : FormatterBase public bool ReportInitializedCalled = false; public bool CloseAsyncCalled = false; public bool CompleteWriterOnLaunchInner = false; + protected override TimeSpan DisposeTimeout => TimeSpan.FromMilliseconds(100); + protected override TimeSpan DisposeCancellationTimeout => TimeSpan.FromMilliseconds(100); public TestFormatter(IFormattersConfigurationProvider config, IFormatterLog logger, string name) : base(config, logger, name) { }