From 05d2d91b376d95693330dafefa8b14a9c62562ea Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Mon, 1 Sep 2025 22:48:32 +0100 Subject: [PATCH 01/22] Make object containers IAsyncDisposable Add support for calling DisposeAsync on managed objects as well as Dispose --- .../GenerateFeatureFileCodeBehindTask.cs | 56 ++++++++-- ...nerateFeatureFileCodeBehindTaskExecutor.cs | 4 +- ...nerateFeatureFileCodeBehindTaskExecutor.cs | 5 +- .../Reqnroll.Tools.MsBuild.Generation.csproj | 10 +- Reqnroll/BoDi/IObjectContainer.cs | 2 +- Reqnroll/BoDi/ObjectContainer.cs | 23 +++- Reqnroll/Infrastructure/ContextManager.cs | 60 ++++++---- Reqnroll/Infrastructure/IContextManager.cs | 9 +- .../Infrastructure/ITestExecutionEngine.cs | 2 +- .../Infrastructure/TestExecutionEngine.cs | 10 +- Reqnroll/TestRunner.cs | 2 +- Reqnroll/TestRunnerManager.cs | 4 +- .../CucumberExpressionIntegrationTests.cs | 2 +- .../BoDi/DisposeTests.cs | 103 ++++++++++++++---- .../Reqnroll.RuntimeTests/BoDi/TestClasses.cs | 27 ++++- .../Infrastructure/ContextManagerTests.cs | 17 +-- .../TestExecutionEngineTests.cs | 14 +-- .../Infrastructure/TestThreadContextTests.cs | 11 +- .../TestThreadExecutionEventPublisherTests.cs | 10 +- .../ScenarioContext_BindingInstancesTest.cs | 5 +- .../ScenarioStepContextTests.cs | 19 ++-- .../StepExecutionTestsBase.cs | 4 +- 22 files changed, 281 insertions(+), 118 deletions(-) diff --git a/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs b/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs index b3085a3ba..9a1f10633 100644 --- a/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs +++ b/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs @@ -1,16 +1,52 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; +#if NETFRAMEWORK +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +#endif using Reqnroll.Analytics; using Reqnroll.CommonModels; using Reqnroll.Generator; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; namespace Reqnroll.Tools.MsBuild.Generation { - public class GenerateFeatureFileCodeBehindTask : Task + internal static class AsyncRunner + { +#if NETFRAMEWORK + private static readonly Lazy _factory = new(() => + { + // If we're running inside VS, ThreadHelper.JoinableTaskFactory will be initialized + // Otherwise, create our own context/factory for headless builds + return TryGetVsFactory() ?? new JoinableTaskFactory(new JoinableTaskContext()); + }); + + private static JoinableTaskFactory TryGetVsFactory() + { + try + { + // ThreadHelper.JoinableTaskFactory throws if not initialized + return ThreadHelper.JoinableTaskFactory; + } + catch + { + return null; + } + } + + public static T RunAndJoin(Func> func) => _factory.Value.Run(func); + +#else + + public static T RunAndJoin(Func> func) => func().GetAwaiter().GetResult(); + +#endif + } + + public class GenerateFeatureFileCodeBehindTask : Microsoft.Build.Utilities.Task { public IFeatureFileCodeBehindGenerator CodeBehindGenerator { get; set; } public IAnalyticsTransmitter AnalyticsTransmitter { get; set; } @@ -36,7 +72,9 @@ public class GenerateFeatureFileCodeBehindTask : Task public string TargetFramework { get; set; } public string ProjectGuid { get; set; } - public override bool Execute() + public override bool Execute() => AsyncRunner.RunAndJoin(ExecuteAsync); + + public async Task ExecuteAsync() { var generateFeatureFileCodeBehindTaskContainerBuilder = new GenerateFeatureFileCodeBehindTaskContainerBuilder(); var generatorPlugins = GeneratorPlugins?.Select(gp => gp.ItemSpec).Select(p => new GeneratorPluginInfo(p)).ToArray() ?? Array.Empty(); @@ -46,13 +84,13 @@ public override bool Execute() var generateFeatureFileCodeBehindTaskConfiguration = new GenerateFeatureFileCodeBehindTaskConfiguration(AnalyticsTransmitter, CodeBehindGenerator); var generateFeatureFileCodeBehindTaskInfo = new ReqnrollProjectInfo(generatorPlugins, featureFiles, ProjectPath, ProjectFolder, ProjectGuid, AssemblyName, OutputPath, IntermediateOutputPath, RootNamespace, TargetFrameworks, TargetFramework); - using (var taskRootContainer = generateFeatureFileCodeBehindTaskContainerBuilder.BuildRootContainer(Log, generateFeatureFileCodeBehindTaskInfo, msbuildInformationProvider, generateFeatureFileCodeBehindTaskConfiguration)) + await using (var taskRootContainer = generateFeatureFileCodeBehindTaskContainerBuilder.BuildRootContainer(Log, generateFeatureFileCodeBehindTaskInfo, msbuildInformationProvider, generateFeatureFileCodeBehindTaskConfiguration)) { var assemblyResolveLoggerFactory = taskRootContainer.Resolve(); using (assemblyResolveLoggerFactory.Build()) { var taskExecutor = taskRootContainer.Resolve(); - var executeResult = taskExecutor.Execute(); + var executeResult = taskExecutor.ExecuteAsync(); if (!(executeResult is ISuccess> success)) { diff --git a/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTaskExecutor.cs b/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTaskExecutor.cs index da4f2d008..fe8f037f1 100644 --- a/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTaskExecutor.cs +++ b/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTaskExecutor.cs @@ -38,7 +38,7 @@ public GenerateFeatureFileCodeBehindTaskExecutor( _exceptionTaskLogger = exceptionTaskLogger; } - public IResult> Execute() + public async Task>> ExecuteAsync() { _processInfoDumper.DumpProcessInfo(); _taskLoggingWrapper.LogMessage("Starting GenerateFeatureFileCodeBehind"); @@ -47,7 +47,7 @@ public IResult> Execute() { var reqnrollProject = _reqnrollProjectProvider.GetReqnrollProject(); - using var generatorContainer = _wrappedGeneratorContainerBuilder.BuildGeneratorContainer( + await using var generatorContainer = _wrappedGeneratorContainerBuilder.BuildGeneratorContainer( reqnrollProject.ProjectSettings.ConfigurationHolder, reqnrollProject.ProjectSettings, _reqnrollProjectInfo.GeneratorPlugins, diff --git a/Reqnroll.Tools.MsBuild.Generation/IGenerateFeatureFileCodeBehindTaskExecutor.cs b/Reqnroll.Tools.MsBuild.Generation/IGenerateFeatureFileCodeBehindTaskExecutor.cs index dcfb1f96f..cb23dd3d2 100644 --- a/Reqnroll.Tools.MsBuild.Generation/IGenerateFeatureFileCodeBehindTaskExecutor.cs +++ b/Reqnroll.Tools.MsBuild.Generation/IGenerateFeatureFileCodeBehindTaskExecutor.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; using Microsoft.Build.Framework; using Reqnroll.CommonModels; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Reqnroll.Tools.MsBuild.Generation { public interface IGenerateFeatureFileCodeBehindTaskExecutor { - IResult> Execute(); + Task>> ExecuteAsync(); } } diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj index d2d992be3..874022f90 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj @@ -13,8 +13,6 @@ - - @@ -32,10 +30,13 @@ - - + + + + + @@ -44,7 +45,6 @@ Microsoft.Build.Utilities.Core - diff --git a/Reqnroll/BoDi/IObjectContainer.cs b/Reqnroll/BoDi/IObjectContainer.cs index a9651afc0..fdda73ba6 100644 --- a/Reqnroll/BoDi/IObjectContainer.cs +++ b/Reqnroll/BoDi/IObjectContainer.cs @@ -3,7 +3,7 @@ namespace Reqnroll.BoDi; -public interface IObjectContainer : IDisposable +public interface IObjectContainer : IAsyncDisposable { /// /// Fired when a new object is created directly by the container. It is not invoked for resolving instance and factory registrations. diff --git a/Reqnroll/BoDi/ObjectContainer.cs b/Reqnroll/BoDi/ObjectContainer.cs index 1c5fc60b1..b94576997 100644 --- a/Reqnroll/BoDi/ObjectContainer.cs +++ b/Reqnroll/BoDi/ObjectContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; namespace Reqnroll.BoDi; @@ -744,15 +745,31 @@ private void AssertNotDisposed() throw new ObjectContainerException("Object container disposed", null); } - public void Dispose() + public async ValueTask DisposeAsync() { if (_isDisposed) + { return; + } _isDisposed = true; - foreach (var obj in _objectPool.Values.OfType().Where(o => !ReferenceEquals(o, this))) - obj.Dispose(); + foreach (var obj in _objectPool.Values) + { + if (ReferenceEquals(obj, this)) + { + continue; + } + + if (obj is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync(); + } + else if (obj is IDisposable disposable) + { + disposable.Dispose(); + } + } _objectPool.Clear(); _registrations.Clear(); diff --git a/Reqnroll/Infrastructure/ContextManager.cs b/Reqnroll/Infrastructure/ContextManager.cs index 34fda0dda..dd9be4cc5 100644 --- a/Reqnroll/Infrastructure/ContextManager.cs +++ b/Reqnroll/Infrastructure/ContextManager.cs @@ -5,52 +5,60 @@ using Reqnroll.Bindings; using Reqnroll.Configuration; using Reqnroll.Tracing; +using System.Threading.Tasks; namespace Reqnroll.Infrastructure; -public class ContextManager : IContextManager, IDisposable +public class ContextManager : IContextManager, IAsyncDisposable { - private class InternalContextManager(ITestTracer testTracer) : IDisposable + private class InternalContextManager(ITestTracer testTracer) : IAsyncDisposable where TContext : ReqnrollContext { private IObjectContainer _objectContainer; public TContext Instance { get; private set; } - public void Init(TContext newInstance, IObjectContainer newObjectContainer) + public async Task InitAsync(TContext newInstance, IObjectContainer newObjectContainer) { if (Instance != null) { testTracer.TraceWarning($"The previous {typeof(TContext).Name} was not disposed."); - DisposeInstance(); + await DisposeInstanceAsync(); } Instance = newInstance; _objectContainer = newObjectContainer; } - public void Cleanup() + public ValueTask CleanupAsync() { if (Instance == null) { testTracer.TraceWarning($"The previous {typeof(TContext).Name} was already disposed."); - return; + return default; } - DisposeInstance(); + + return DisposeInstanceAsync(); } - private void DisposeInstance() + private async ValueTask DisposeInstanceAsync() { - _objectContainer?.Dispose(); + if (_objectContainer != null) + { + await _objectContainer.DisposeAsync(); + } + Instance = null; _objectContainer = null; } - public void Dispose() + public ValueTask DisposeAsync() { if (Instance != null) { - DisposeInstance(); + return DisposeInstanceAsync(); } + + return default; } } @@ -151,32 +159,32 @@ private void InitializeTestThreadContext() TestThreadContext = testThreadContext; } - public void InitializeFeatureContext(FeatureInfo featureInfo) + public async Task InitializeFeatureContextAsync(FeatureInfo featureInfo) { var featureContainer = _containerBuilder.CreateFeatureContainer(_testThreadContainer, featureInfo); var reqnrollConfiguration = _testThreadContainer.Resolve(); var newContext = new FeatureContext(featureContainer, featureInfo, reqnrollConfiguration); // make sure that the FeatureContext can also be resolved through the interface as well RegisterInstanceAsInterfaceAndObjectType(featureContainer, newContext, dispose: true); - _featureContextManager.Init(newContext, featureContainer); + await _featureContextManager.InitAsync(newContext, featureContainer); #pragma warning disable 618 FeatureContext.Current = newContext; #pragma warning restore 618 } - public void CleanupFeatureContext() + public ValueTask CleanupFeatureContextAsync() { - _featureContextManager.Cleanup(); + return _featureContextManager.CleanupAsync(); } - public void InitializeScenarioContext(ScenarioInfo scenarioInfo, RuleInfo ruleInfo) + public async Task InitializeScenarioContextAsync(ScenarioInfo scenarioInfo, RuleInfo ruleInfo) { var scenarioContainer = _containerBuilder.CreateScenarioContainer(FeatureContext.FeatureContainer, scenarioInfo); var testObjectResolver = scenarioContainer.Resolve(); var newContext = new ScenarioContext(scenarioContainer, scenarioInfo, ruleInfo, testObjectResolver); // make sure that the ScenarioContext can also be resolved through the interface as well RegisterInstanceAsInterfaceAndObjectType(scenarioContainer, newContext, dispose: true); - _scenarioContextManager.Init(newContext, scenarioContainer); + await _scenarioContextManager.InitAsync(newContext, scenarioContainer); #pragma warning disable 618 ScenarioContext.Current = newContext; #pragma warning restore 618 @@ -191,9 +199,9 @@ private void ResetCurrentStepStack() ScenarioStepContext.Current = null; } - public void CleanupScenarioContext() + public ValueTask CleanupScenarioContextAsync() { - _scenarioContextManager.Cleanup(); + return _scenarioContextManager.CleanupAsync(); } public void InitializeStepContext(StepInfo stepInfo) @@ -212,10 +220,18 @@ public void CleanupStepContext() // we do not reset CurrentTopLevelStepDefinitionType in order to "remember" last top level type for "And" and "But" steps } - public void Dispose() + public async ValueTask DisposeAsync() { - _featureContextManager?.Dispose(); - _scenarioContextManager?.Dispose(); + if (_featureContextManager != null) + { + await _featureContextManager.DisposeAsync(); + } + + if (_scenarioContextManager != null) + { + await _scenarioContextManager.DisposeAsync(); + } + _stepContextManager?.Dispose(); } } \ No newline at end of file diff --git a/Reqnroll/Infrastructure/IContextManager.cs b/Reqnroll/Infrastructure/IContextManager.cs index fba5919fd..3201dfa29 100644 --- a/Reqnroll/Infrastructure/IContextManager.cs +++ b/Reqnroll/Infrastructure/IContextManager.cs @@ -1,4 +1,5 @@ using Reqnroll.Bindings; +using System.Threading.Tasks; namespace Reqnroll.Infrastructure { @@ -10,11 +11,11 @@ public interface IContextManager ScenarioStepContext StepContext { get; } StepDefinitionType? CurrentTopLevelStepDefinitionType { get; } - void InitializeFeatureContext(FeatureInfo featureInfo); - void CleanupFeatureContext(); + Task InitializeFeatureContextAsync(FeatureInfo featureInfo); + ValueTask CleanupFeatureContextAsync(); - void InitializeScenarioContext(ScenarioInfo scenarioInfo, RuleInfo ruleInfo); - void CleanupScenarioContext(); + Task InitializeScenarioContextAsync(ScenarioInfo scenarioInfo, RuleInfo ruleInfo); + ValueTask CleanupScenarioContextAsync(); void InitializeStepContext(StepInfo stepInfo); void CleanupStepContext(); diff --git a/Reqnroll/Infrastructure/ITestExecutionEngine.cs b/Reqnroll/Infrastructure/ITestExecutionEngine.cs index 0ebe77e73..fd9386ddd 100644 --- a/Reqnroll/Infrastructure/ITestExecutionEngine.cs +++ b/Reqnroll/Infrastructure/ITestExecutionEngine.cs @@ -15,7 +15,7 @@ public interface ITestExecutionEngine Task OnFeatureStartAsync(FeatureInfo featureInfo); Task OnFeatureEndAsync(); - void OnScenarioInitialize(ScenarioInfo scenarioInfo, RuleInfo ruleInfo); + Task OnScenarioInitializeAsync(ScenarioInfo scenarioInfo, RuleInfo ruleInfo); Task OnScenarioStartAsync(); Task OnAfterLastStepAsync(); Task OnScenarioEndAsync(); diff --git a/Reqnroll/Infrastructure/TestExecutionEngine.cs b/Reqnroll/Infrastructure/TestExecutionEngine.cs index 0335f8e7c..373ce7e04 100644 --- a/Reqnroll/Infrastructure/TestExecutionEngine.cs +++ b/Reqnroll/Infrastructure/TestExecutionEngine.cs @@ -138,7 +138,7 @@ public virtual async Task OnTestRunEndAsync() public virtual async Task OnFeatureStartAsync(FeatureInfo featureInfo) { - _contextManager.InitializeFeatureContext(featureInfo); + await _contextManager.InitializeFeatureContextAsync(featureInfo); await _testThreadExecutionEventPublisher.PublishEventAsync(new FeatureStartedEvent(FeatureContext)); @@ -173,13 +173,13 @@ public virtual async Task OnFeatureEndAsync() await _testThreadExecutionEventPublisher.PublishEventAsync(new FeatureFinishedEvent(FeatureContext)); - _contextManager.CleanupFeatureContext(); + await _contextManager.CleanupFeatureContextAsync(); } } - public virtual void OnScenarioInitialize(ScenarioInfo scenarioInfo, RuleInfo ruleInfo) + public virtual Task OnScenarioInitializeAsync(ScenarioInfo scenarioInfo, RuleInfo ruleInfo) { - _contextManager.InitializeScenarioContext(scenarioInfo, ruleInfo); + return _contextManager.InitializeScenarioContextAsync(scenarioInfo, ruleInfo); } public virtual async Task OnScenarioStartAsync() @@ -258,7 +258,7 @@ public virtual async Task OnScenarioEndAsync() { await _testThreadExecutionEventPublisher.PublishEventAsync(new ScenarioFinishedEvent(FeatureContext, ScenarioContext)); - _contextManager.CleanupScenarioContext(); + await _contextManager.CleanupScenarioContextAsync(); } } diff --git a/Reqnroll/TestRunner.cs b/Reqnroll/TestRunner.cs index 7b79456f5..5c35964a5 100644 --- a/Reqnroll/TestRunner.cs +++ b/Reqnroll/TestRunner.cs @@ -39,7 +39,7 @@ public async Task OnFeatureEndAsync() public void OnScenarioInitialize(ScenarioInfo scenarioInfo, RuleInfo ruleInfo) { - _executionEngine.OnScenarioInitialize(scenarioInfo, ruleInfo); + _executionEngine.OnScenarioInitializeAsync(scenarioInfo, ruleInfo); } public async Task OnScenarioStartAsync() diff --git a/Reqnroll/TestRunnerManager.cs b/Reqnroll/TestRunnerManager.cs index fe1eb9ab7..f9cae6893 100644 --- a/Reqnroll/TestRunnerManager.cs +++ b/Reqnroll/TestRunnerManager.cs @@ -355,7 +355,7 @@ public virtual async Task DisposeAsync() { foreach (var item in testWorkerContainers) { - item.Key.Dispose(); + await item.Key.DisposeAsync(); _availableTestWorkerContainers.TryRemove(item.Key, out _); } testWorkerContainers = _availableTestWorkerContainers.ToArray(); @@ -369,7 +369,7 @@ public virtual async Task DisposeAsync() } // this call dispose on this object, but _wasDisposed will avoid double execution - _globalContainer.Dispose(); + await _globalContainer.DisposeAsync(); OnTestRunnerManagerDisposed(this); diff --git a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs index 4b77d1970..5963128e8 100644 --- a/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Bindings/CucumberExpressions/CucumberExpressionIntegrationTests.cs @@ -204,7 +204,7 @@ private async Task PerformStepExecution(string methodName, strin await engine.OnTestRunStartAsync(); await engine.OnFeatureStartAsync(new FeatureInfo(CultureInfo.GetCultureInfo(culture), ".", "Sample feature", null, ProgrammingLanguage.CSharp)); await engine.OnScenarioStartAsync(); - engine.OnScenarioInitialize(new ScenarioInfo("Sample scenario", null, null, null), null); + await engine.OnScenarioInitializeAsync(new ScenarioInfo("Sample scenario", null, null, null), null); await engine.StepAsync(StepDefinitionKeyword.Given, "Given ", stepText, null, null); var contextManager = testThreadContainer.Resolve(); diff --git a/Tests/Reqnroll.RuntimeTests/BoDi/DisposeTests.cs b/Tests/Reqnroll.RuntimeTests/BoDi/DisposeTests.cs index 9d581bd46..26dbd996f 100644 --- a/Tests/Reqnroll.RuntimeTests/BoDi/DisposeTests.cs +++ b/Tests/Reqnroll.RuntimeTests/BoDi/DisposeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using FluentAssertions; using Reqnroll.BoDi; using Xunit; @@ -8,68 +9,68 @@ namespace Reqnroll.RuntimeTests.BoDi public class DisposeTests { [Fact] - public void ContainerShouldBeDisposable() + public void ContainerShouldBeAsyncDisposable() { var container = new ObjectContainer(); - container.Should().BeAssignableTo(); + container.Should().BeAssignableTo(); } [Fact] - public void ContainerShouldThrowExceptionWhenDisposedAndCallingResolve() + public async Task ContainerShouldThrowExceptionWhenAsyncDisposedAndCallingResolve() { var container = new ObjectContainer(); - container.Dispose(); + await container.DisposeAsync(); Action act = () => container.Resolve(); act.Should().ThrowExactly("Object container disposed"); } [Fact] - public void ContainerShouldThrowExceptionWhenDisposedAndCallingRegisterInstanceAs() + public async Task ContainerShouldThrowExceptionWhenAsyncDisposedAndCallingRegisterInstanceAs() { var container = new ObjectContainer(); - container.Dispose(); + await container.DisposeAsync(); - Action act = () => container.RegisterInstanceAs(new DisposableClass1()); + Action act = () => container.RegisterInstanceAs(new AsyncDisposableClass1()); act.Should().ThrowExactly("Object container disposed"); } [Fact] - public void ContainerShouldThrowExceptionWhenDisposedAndCallingRegisterFactoryAs() + public async Task ContainerShouldThrowExceptionWhenAsyncDisposedAndCallingRegisterFactoryAs() { var container = new ObjectContainer(); - container.Dispose(); + await container.DisposeAsync(); - Action act = () => container.RegisterFactoryAs(() => new DisposableClass1()); + Action act = () => container.RegisterFactoryAs(() => new AsyncDisposableClass1()); act.Should().ThrowExactly("Object container disposed"); } [Fact] - public void ContainerShouldThrowExceptionWhenDisposedAndCallingRegisterTypeAs() + public async Task ContainerShouldThrowExceptionWhenAsyncDisposedAndCallingRegisterTypeAs() { var container = new ObjectContainer(); - container.Dispose(); + await container.DisposeAsync(); - Action act = () => container.RegisterTypeAs(typeof(DisposableClass1)); + Action act = () => container.RegisterTypeAs(typeof(AsyncDisposableClass1)); act.Should().ThrowExactly("Object container disposed"); } [Fact] - public void ShouldDisposeCreatedObjects() + public async Task ShouldDisposeCreatedObjects() { var container = new ObjectContainer(); container.RegisterTypeAs(); var obj = container.Resolve(); - container.Dispose(); + await container.DisposeAsync(); obj.WasDisposed.Should().BeTrue(); } [Fact] - public void ShouldDisposeInstanceRegistrations() + public async Task ShouldDisposeInstanceRegistrations() { var container = new ObjectContainer(); var obj = new DisposableClass1(); @@ -77,13 +78,13 @@ public void ShouldDisposeInstanceRegistrations() container.Resolve(); - container.Dispose(); + await container.DisposeAsync(); obj.WasDisposed.Should().BeTrue(); } [Fact] - public void ShouldNotDisposeObjectsRegisteredAsInstance() + public async Task ShouldNotDisposeObjectsRegisteredAsInstance() { var container = new ObjectContainer(); var obj = new DisposableClass1(); @@ -91,13 +92,13 @@ public void ShouldNotDisposeObjectsRegisteredAsInstance() container.Resolve(); - container.Dispose(); + await container.DisposeAsync(); obj.WasDisposed.Should().BeFalse(); } [Fact] - public void ShouldNotDisposeObjectsFromBaseContainer() + public async Task ShouldNotDisposeObjectsFromBaseContainer() { var baseContainer = new ObjectContainer(); baseContainer.RegisterTypeAs(); @@ -106,9 +107,69 @@ public void ShouldNotDisposeObjectsFromBaseContainer() baseContainer.Resolve(); var obj = container.Resolve(); - container.Dispose(); + await container.DisposeAsync(); obj.WasDisposed.Should().BeFalse(); } + + [Fact] + public async Task ShouldAsyncDisposeCreatedObjects() + { + var container = new ObjectContainer(); + container.RegisterTypeAs(); + + var obj = container.Resolve(); + + await container.DisposeAsync(); + + obj.WasDisposed.Should().BeFalse(); + obj.WasDisposedAsync.Should().BeTrue(); + } + + [Fact] + public async Task ShouldAsyncDisposeInstanceRegistrations() + { + var container = new ObjectContainer(); + var obj = new AsyncDisposableClass1(); + container.RegisterInstanceAs(obj, dispose: true); + + container.Resolve(); + + await container.DisposeAsync(); + + obj.WasDisposed.Should().BeFalse(); + obj.WasDisposedAsync.Should().BeTrue(); + } + + [Fact] + public async Task ShouldNotAsyncDisposeObjectsRegisteredAsInstance() + { + var container = new ObjectContainer(); + var obj = new AsyncDisposableClass1(); + container.RegisterInstanceAs(obj); + + container.Resolve(); + + await container.DisposeAsync(); + + obj.WasDisposed.Should().BeFalse(); + obj.WasDisposedAsync.Should().BeFalse(); + } + + [Fact] + public async Task ShouldNotAsyncDisposeObjectsFromBaseContainer() + { + var baseContainer = new ObjectContainer(); + baseContainer.RegisterTypeAs(); + var container = new ObjectContainer(baseContainer); + + baseContainer.Resolve(); + var obj = container.Resolve(); + + await container.DisposeAsync(); + + obj.WasDisposed.Should().BeFalse(); + obj.WasDisposedAsync.Should().BeFalse(); + } } } diff --git a/Tests/Reqnroll.RuntimeTests/BoDi/TestClasses.cs b/Tests/Reqnroll.RuntimeTests/BoDi/TestClasses.cs index 133d45df5..6e764a7b6 100644 --- a/Tests/Reqnroll.RuntimeTests/BoDi/TestClasses.cs +++ b/Tests/Reqnroll.RuntimeTests/BoDi/TestClasses.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Reqnroll.RuntimeTests.BoDi { @@ -185,7 +186,7 @@ public interface IDisposableClass bool WasDisposed { get; } } - public class DisposableClass1 : IDisposableClass, IDisposable + public sealed class DisposableClass1 : IDisposableClass, IDisposable { public bool WasDisposed { get; private set; } @@ -195,6 +196,30 @@ public void Dispose() } } + public interface IAsyncDisposableClass + { + bool WasDisposed { get; } + bool WasDisposedAsync { get; } + } + + public sealed class AsyncDisposableClass1 : IAsyncDisposableClass, IDisposable, IAsyncDisposable + { + public bool WasDisposed { get; private set; } + + public bool WasDisposedAsync { get; private set; } + + public void Dispose() + { + WasDisposed = true; + } + + public ValueTask DisposeAsync() + { + WasDisposedAsync = true; + return ValueTask.CompletedTask; + } + } + public enum MyEnumKey { One, diff --git a/Tests/Reqnroll.RuntimeTests/Infrastructure/ContextManagerTests.cs b/Tests/Reqnroll.RuntimeTests/Infrastructure/ContextManagerTests.cs index 67c43cdf7..ba8873d4c 100644 --- a/Tests/Reqnroll.RuntimeTests/Infrastructure/ContextManagerTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Infrastructure/ContextManagerTests.cs @@ -4,6 +4,7 @@ using Reqnroll.Infrastructure; using Reqnroll.Tracing; using System.Globalization; +using System.Threading.Tasks; using Xunit; namespace Reqnroll.RuntimeTests.Infrastructure; @@ -15,15 +16,15 @@ public ContextManager CreateContextManager(IObjectContainer testThreadContainer return new ContextManager(new Mock().Object, testThreadContainer ?? TestThreadContainer, ContainerBuilderStub); } - private static void InitializeFeatureContext(ContextManager sut) + private static Task InitializeFeatureContext(ContextManager sut) { - sut.InitializeFeatureContext(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "F", null)); + return sut.InitializeFeatureContextAsync(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "F", null)); } - private void InitializeScenarioContext(ContextManager sut) + private Task InitializeScenarioContext(ContextManager sut) { InitializeFeatureContext(sut); - sut.InitializeScenarioContext(new ScenarioInfo("the next scenario", "description of the next scenario", null, null), null); + return sut.InitializeScenarioContextAsync(new ScenarioInfo("the next scenario", "description of the next scenario", null, null), null); } [Fact] @@ -52,7 +53,7 @@ public void Should_dispose_scenario_context_when_scenario_context_cleaned_up() var scenarioContext = sut.ScenarioContext; scenarioContext.Should().NotBeNull(); - sut.CleanupScenarioContext(); + sut.CleanupScenarioContextAsync(); scenarioContext.IsDisposed.Should().BeTrue(); } @@ -98,7 +99,7 @@ public void Should_dispose_feature_context_when_feature_context_cleaned_up() var featureContext = sut.FeatureContext; featureContext.Should().NotBeNull(); - sut.CleanupFeatureContext(); + sut.CleanupFeatureContextAsync(); featureContext.IsDisposed.Should().BeTrue(); } @@ -149,7 +150,7 @@ public void Should_be_able_to_resolve_test_thread_context_from_scenario_containe } [Fact] - public void Should_dispose_test_thread_context_when_test_thread_context_cleaned_up() + public async Task Should_dispose_test_thread_context_when_test_thread_context_cleaned_up() { var sut = CreateContextManager(); // the test thread context is already initialized in the constructor of ContextManager @@ -158,7 +159,7 @@ public void Should_dispose_test_thread_context_when_test_thread_context_cleaned_ testThreadContext.Should().NotBeNull(); var testThreadContainer = testThreadContext.TestThreadContainer; - testThreadContainer.Dispose(); + await testThreadContainer.DisposeAsync(); testThreadContext.IsDisposed.Should().BeTrue(); } diff --git a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs index 922e609e1..b063d8c26 100644 --- a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestExecutionEngineTests.cs @@ -671,11 +671,11 @@ public async Task Should_cleanup_scenario_context_on_scenario_end() var testExecutionEngine = CreateTestExecutionEngine(); RegisterStepDefinition(); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioStartAsync(); await testExecutionEngine.OnScenarioEndAsync(); - _contextManagerStub.Verify(cm => cm.CleanupScenarioContext(), Times.Once); + _contextManagerStub.Verify(cm => cm.CleanupScenarioContextAsync(), Times.Once); } [Fact] @@ -690,12 +690,12 @@ public async Task Should_cleanup_scenario_context_after_AfterScenario_hook_error .Throws(new Exception("simulated error")); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioStartAsync(); Func act = async () => await testExecutionEngine.OnScenarioEndAsync(); await act.Should().ThrowAsync().WithMessage("simulated error"); - _contextManagerStub.Verify(cm => cm.CleanupScenarioContext(), Times.Once); + _contextManagerStub.Verify(cm => cm.CleanupScenarioContextAsync(), Times.Once); } [Fact] @@ -775,7 +775,7 @@ public async Task Should_resolve_BeforeAfterScenario_hook_parameter_from_scenari var beforeHook = CreateParametrizedHookMock(_beforeScenarioEvents, typeof(DummyClass)); var afterHook = CreateParametrizedHookMock(_afterScenarioEvents, typeof(DummyClass)); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioStartAsync(); await testExecutionEngine.OnScenarioEndAsync(); @@ -799,7 +799,7 @@ public async Task Should_be_possible_to_register_instance_in_scenario_container_ It.IsAny(),It.IsAny(), It.IsAny())) .Callback(() => actualInstance = testExecutionEngine.ScenarioContext.ScenarioContainer.Resolve()); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); testExecutionEngine.ScenarioContext.ScenarioContainer.RegisterInstanceAs(instanceToAddBeforeScenarioEventFiring); await testExecutionEngine.OnScenarioStartAsync(); actualInstance.Should().BeSameAs(instanceToAddBeforeScenarioEventFiring); @@ -857,7 +857,7 @@ await FluentActions.Awaiting(testExecutionEngine.OnFeatureEndAsync) .Should().ThrowAsync("execution of the step should have failed because of the exception thrown by the before scenario block hook"); _methodBindingInvokerMock.Verify(i => i.InvokeBindingAsync(hookMock.Object, _contextManagerStub.Object, null, _testTracerStub.Object, It.IsAny()), Times.Once()); - _contextManagerStub.Verify(cm => cm.CleanupFeatureContext()); + _contextManagerStub.Verify(cm => cm.CleanupFeatureContextAsync()); } [Fact] diff --git a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadContextTests.cs b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadContextTests.cs index c20c20cc8..bda461919 100644 --- a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadContextTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadContextTests.cs @@ -4,6 +4,7 @@ using Xunit; using Reqnroll.Infrastructure; using Reqnroll.Tracing; +using System.Threading.Tasks; namespace Reqnroll.RuntimeTests.Infrastructure { @@ -36,7 +37,7 @@ public void Should_expose_the_test_thread_container() } [Fact] - public void Should_disposing_event_fired_when_test_thread_container_disposes() + public async Task Should_disposing_event_fired_when_test_thread_container_disposes() { bool wasDisposingFired = false; ContextManagerStub.TestThreadContext.Should().NotBeNull(); @@ -46,21 +47,21 @@ public void Should_disposing_event_fired_when_test_thread_container_disposes() wasDisposingFired = true; }; - TestThreadContainer.Dispose(); + await TestThreadContainer.DisposeAsync(); wasDisposingFired.Should().BeTrue(); } [Fact] - public void Should_be_able_to_resolve_from_scenario_container() + public async Task Should_be_able_to_resolve_from_scenario_container() { // this basically tests the special registration in DefaultDependencyProvider var containerBuilder = new RuntimeTestsContainerBuilder(); var testThreadContainer = containerBuilder.CreateTestThreadContainer(containerBuilder.CreateGlobalContainer(typeof(TestThreadContextTests).Assembly)); var contextManager = CreateContextManager(testThreadContainer); - contextManager.InitializeFeatureContext(new FeatureInfo(FeatureLanguage, "", "test feature", null)); - contextManager.InitializeScenarioContext(new ScenarioInfo("test scenario", "test_description", null, null), null); + await contextManager.InitializeFeatureContextAsync(new FeatureInfo(FeatureLanguage, "", "test feature", null)); + await contextManager.InitializeScenarioContextAsync(new ScenarioInfo("test scenario", "test_description", null, null), null); contextManager.TestThreadContext.Should().NotBeNull(); diff --git a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs index 5741eb699..72568e3c0 100644 --- a/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs +++ b/Tests/Reqnroll.RuntimeTests/Infrastructure/TestThreadExecutionEventPublisherTests.cs @@ -214,7 +214,7 @@ public async Task Should_publish_scenario_started_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioStartAsync(); _testThreadExecutionEventPublisher.Verify(te => @@ -229,7 +229,7 @@ public async Task Should_publish_scenario_finished_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioStartAsync(); await testExecutionEngine.OnAfterLastStepAsync(); await testExecutionEngine.OnScenarioEndAsync(); @@ -250,7 +250,7 @@ public async Task Should_publish_scenario_finished_event_even_if_the_after_scena _methodBindingInvokerMock.Setup(i => i.InvokeBindingAsync(hookMock.Object, _contextManagerStub.Object, null, _testTracerStub.Object, It.IsAny())) .Throws(new Exception("simulated hook error")); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioStartAsync(); await testExecutionEngine.OnAfterLastStepAsync(); await FluentActions.Awaiting(testExecutionEngine.OnScenarioEndAsync) @@ -272,7 +272,7 @@ public async Task Should_publish_hook_started_finished_events() await testExecutionEngine.OnTestRunStartAsync(); await testExecutionEngine.OnFeatureStartAsync(_featureInfo); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioStartAsync(); await testExecutionEngine.StepAsync(StepDefinitionKeyword.Given, null, "foo", null, null); await testExecutionEngine.OnAfterLastStepAsync(); @@ -296,7 +296,7 @@ public async Task Should_publish_scenario_skipped_event() { var testExecutionEngine = CreateTestExecutionEngine(); - testExecutionEngine.OnScenarioInitialize(_scenarioInfo, _ruleInfo); + await testExecutionEngine.OnScenarioInitializeAsync(_scenarioInfo, _ruleInfo); await testExecutionEngine.OnScenarioSkippedAsync(); await testExecutionEngine.OnAfterLastStepAsync(); await testExecutionEngine.OnScenarioEndAsync(); diff --git a/Tests/Reqnroll.RuntimeTests/ScenarioContext_BindingInstancesTest.cs b/Tests/Reqnroll.RuntimeTests/ScenarioContext_BindingInstancesTest.cs index bd5ce660d..fb24ca888 100644 --- a/Tests/Reqnroll.RuntimeTests/ScenarioContext_BindingInstancesTest.cs +++ b/Tests/Reqnroll.RuntimeTests/ScenarioContext_BindingInstancesTest.cs @@ -4,6 +4,7 @@ using FluentAssertions; using Moq; using Reqnroll.Infrastructure; +using System.Threading.Tasks; namespace Reqnroll.RuntimeTests { @@ -89,13 +90,13 @@ public void GetBindingInstance_should_return_instance_through_TestObjectResolver } [Fact] - public void Should_dispose_disposable_binding_instances() + public async Task Should_dispose_disposable_binding_instances() { var scenarioContext = CreateScenarioContext(); var displosableInstance = (DisplosableClass)scenarioContext.GetBindingInstance(typeof(DisplosableClass)); - scenarioContext.ScenarioContainer.Dispose(); + await scenarioContext.ScenarioContainer.DisposeAsync(); displosableInstance.WasDisposed.Should().BeTrue(); } diff --git a/Tests/Reqnroll.RuntimeTests/ScenarioStepContextTests.cs b/Tests/Reqnroll.RuntimeTests/ScenarioStepContextTests.cs index 9b24bd938..7ea2b1d3b 100644 --- a/Tests/Reqnroll.RuntimeTests/ScenarioStepContextTests.cs +++ b/Tests/Reqnroll.RuntimeTests/ScenarioStepContextTests.cs @@ -7,6 +7,7 @@ using Reqnroll.Bindings; using Reqnroll.Infrastructure; using Reqnroll.Tracing; +using System.Threading.Tasks; namespace Reqnroll.RuntimeTests { @@ -282,11 +283,11 @@ public void CurrentTopLevelStepDefinitionType_AfterInitializingNewScenarioContex { var mockTracer = new Mock(); var contextManager = ResolveContextManager(mockTracer.Object); - contextManager.InitializeFeatureContext(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "F", null)); + contextManager.InitializeFeatureContextAsync(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "F", null)); contextManager.InitializeStepContext(CreateStepInfo("I have called initialize once")); //// Do not call CleanupStepContext, in order to simulate an inconsistent state - contextManager.InitializeScenarioContext(new ScenarioInfo("the next scenario", "description of the next scenario", null, null), null); + contextManager.InitializeScenarioContextAsync(new ScenarioInfo("the next scenario", "description of the next scenario", null, null), null); var actualCurrentTopLevelStepDefinitionType = contextManager.CurrentTopLevelStepDefinitionType; @@ -294,21 +295,21 @@ public void CurrentTopLevelStepDefinitionType_AfterInitializingNewScenarioContex } [Fact] - public void Dispose_InInconsistentState_ShouldNotThrowException() + public async Task DisposeAsync_InInconsistentState_ShouldNotThrowException() { var mockTracer = new Mock(); var contextManager = ResolveContextManager(mockTracer.Object); contextManager.InitializeStepContext(CreateStepInfo("I have called initialize once")); //// do not call CleanupStepContext to simulate inconsistent state + + Func disposeAction = async () => await ((IAsyncDisposable) contextManager).DisposeAsync(); - Action disposeAction = () => ((IDisposable) contextManager).Dispose(); - - disposeAction.Should().NotThrow(); + await disposeAction.Should().NotThrowAsync(); } [Fact] - public void Dispose_InConsistentState_ShouldNotThrowException() + public async Task DisposeAsync_InConsistentState_ShouldNotThrowException() { var mockTracer = new Mock(); var contextManager = ResolveContextManager(mockTracer.Object); @@ -317,9 +318,9 @@ public void Dispose_InConsistentState_ShouldNotThrowException() contextManager.CleanupStepContext(); - Action disposeAction = () => ((IDisposable)contextManager).Dispose(); + Func disposeAction = async () => await ((IAsyncDisposable)contextManager).DisposeAsync(); - disposeAction.Should().NotThrow(); + await disposeAction.Should().NotThrowAsync(); } [Fact] diff --git a/Tests/Reqnroll.RuntimeTests/StepExecutionTestsBase.cs b/Tests/Reqnroll.RuntimeTests/StepExecutionTestsBase.cs index c7aee603e..e415c22e4 100644 --- a/Tests/Reqnroll.RuntimeTests/StepExecutionTestsBase.cs +++ b/Tests/Reqnroll.RuntimeTests/StepExecutionTestsBase.cs @@ -123,8 +123,8 @@ public async Task InitializeAsync() ContextManagerStub = new ContextManager(new Mock().Object, TestThreadContainer, ContainerBuilderStub); - ContextManagerStub.InitializeFeatureContext(new FeatureInfo(FeatureLanguage, string.Empty, "test feature", null)); - ContextManagerStub.InitializeScenarioContext(new ScenarioInfo("test scenario", "test scenario description", null, null), null); + await ContextManagerStub.InitializeFeatureContextAsync(new FeatureInfo(FeatureLanguage, string.Empty, "test feature", null)); + await ContextManagerStub.InitializeScenarioContextAsync(new ScenarioInfo("test scenario", "test scenario description", null, null), null); StepArgumentTypeConverterStub = new Mock(); } From f0e0b969820dfb244ee34a250554fde3008e357d Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Mon, 1 Sep 2025 23:20:13 +0100 Subject: [PATCH 02/22] Fix MSBuild Task package references --- .../Reqnroll.Tools.MsBuild.Generation.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj index 874022f90..f1600b21b 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj @@ -34,8 +34,8 @@ - - + + From 26e7dcb48d0ab8027ff4ed1e714b042ca3083b0e Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Mon, 1 Sep 2025 23:20:31 +0100 Subject: [PATCH 03/22] Add missing task awaiting --- .../GenerateFeatureFileCodeBehindTask.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs b/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs index 9a1f10633..396c48630 100644 --- a/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs +++ b/Reqnroll.Tools.MsBuild.Generation/GenerateFeatureFileCodeBehindTask.cs @@ -77,30 +77,33 @@ public class GenerateFeatureFileCodeBehindTask : Microsoft.Build.Utilities.Task public async Task ExecuteAsync() { var generateFeatureFileCodeBehindTaskContainerBuilder = new GenerateFeatureFileCodeBehindTaskContainerBuilder(); - var generatorPlugins = GeneratorPlugins?.Select(gp => gp.ItemSpec).Select(p => new GeneratorPluginInfo(p)).ToArray() ?? Array.Empty(); - var featureFiles = FeatureFiles?.Select(i => i.ItemSpec).ToArray() ?? Array.Empty(); + var generatorPlugins = GeneratorPlugins?.Select(gp => gp.ItemSpec).Select(p => new GeneratorPluginInfo(p)).ToArray() ?? []; + var featureFiles = FeatureFiles?.Select(i => i.ItemSpec).ToArray() ?? []; var msbuildInformationProvider = new MSBuildInformationProvider(MSBuildVersion); var generateFeatureFileCodeBehindTaskConfiguration = new GenerateFeatureFileCodeBehindTaskConfiguration(AnalyticsTransmitter, CodeBehindGenerator); var generateFeatureFileCodeBehindTaskInfo = new ReqnrollProjectInfo(generatorPlugins, featureFiles, ProjectPath, ProjectFolder, ProjectGuid, AssemblyName, OutputPath, IntermediateOutputPath, RootNamespace, TargetFrameworks, TargetFramework); - await using (var taskRootContainer = generateFeatureFileCodeBehindTaskContainerBuilder.BuildRootContainer(Log, generateFeatureFileCodeBehindTaskInfo, msbuildInformationProvider, generateFeatureFileCodeBehindTaskConfiguration)) - { - var assemblyResolveLoggerFactory = taskRootContainer.Resolve(); - using (assemblyResolveLoggerFactory.Build()) - { - var taskExecutor = taskRootContainer.Resolve(); - var executeResult = taskExecutor.ExecuteAsync(); + await using var taskRootContainer = generateFeatureFileCodeBehindTaskContainerBuilder.BuildRootContainer( + Log, + generateFeatureFileCodeBehindTaskInfo, + msbuildInformationProvider, + generateFeatureFileCodeBehindTaskConfiguration); - if (!(executeResult is ISuccess> success)) - { - return false; - } + var assemblyResolveLoggerFactory = taskRootContainer.Resolve(); - GeneratedFiles = success.Result.ToArray(); + using (assemblyResolveLoggerFactory.Build()) + { + var taskExecutor = taskRootContainer.Resolve(); + var executeResult = await taskExecutor.ExecuteAsync(); - return true; + if (executeResult is not ISuccess> success) + { + return false; } + + GeneratedFiles = [.. success.Result]; + return true; } } } From 7488f8368e88cd140ed94d637a0d7b1186dcd53d Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Wed, 3 Sep 2025 12:32:07 +0100 Subject: [PATCH 04/22] Update docs regarding IDisposable and IAsyncDisposable support. --- docs/automation/context-injection.md | 2 +- docs/execution/parallel-execution.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/automation/context-injection.md b/docs/automation/context-injection.md index ae3dd52ad..77da476a1 100644 --- a/docs/automation/context-injection.md +++ b/docs/automation/context-injection.md @@ -11,7 +11,7 @@ To use context injection: Rules: * The life-time of these objects is limited to a scenario's execution. -* If the injected objects implement `IDisposable`, they will be disposed after the scenario is executed. +* If the injected objects implement `IDisposable` or `IAsyncDisposable`, they will be disposed after the scenario is executed. If they implement both, only `DisposeAsync` will be called. * The injection is resolved recursively, i.e. the injected class can also have dependencies. * Resolution is done using public constructors only. * If there are multiple public constructors, Reqnroll takes the first one. diff --git a/docs/execution/parallel-execution.md b/docs/execution/parallel-execution.md index 4a6d07aa7..a9b27432d 100644 --- a/docs/execution/parallel-execution.md +++ b/docs/execution/parallel-execution.md @@ -48,7 +48,7 @@ When using Reqnroll we can consider the parallel scheduling on the level of scen * However, if you still want to use method-level parallelism and a `FeatureContext` in your test suite, then **the following things will be true**: * The `FeatureContext` and feature-level DI container will remain consistent **per feature, per test thread**. This means that anything you register in the feature container will be resolvable in the `[AfterFeature]` **per test thread**. * A given `[BeforeFeature]` or `[AfterFeature]` will only be executed once **per test thread** that runs a scenario of a feature. - * Types you register in the feature-level DI container that implement `IDisposable` will still be disposed **per feature, per test thread**. (Keep this in mind if you try to work around this parallelism behavior to regain singleton-like behavior. E.g. by using static instances, `Lazy<>`, thread-safe collections, etc.) + * Types you register in the feature-level DI container that implement `IDisposable` or `IAsyncDisposable` will still be disposed **per feature, per test thread**. (Keep this in mind if you try to work around this parallelism behavior to regain singleton-like behavior. E.g. by using static instances, `Lazy<>`, thread-safe collections, etc.) * Scenarios and their related hooks (Before/After scenario, scenario block, step) are isolated in the different threads during execution and do not block each other. Each thread has a separate (and isolated) `ScenarioContext`. * The test trace listener (that outputs the scenario execution trace to the console by default) is invoked asynchronously from the multiple threads and the trace messages are queued and passed to the listener in serialized form. If the test trace listener implements `Reqnroll.Tracing.IThreadSafeTraceListener`, the messages are sent directly from the threads. From 9f73a4d867c67d4be222da51618c63b66a0cd1d1 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Wed, 3 Sep 2025 12:36:04 +0100 Subject: [PATCH 05/22] Add IAsyncDisposable support to change log --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77a59d83a..f9f4842b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Improvements: * Formatters: Upgraded conformance test to Cucumber/Cucumber-compatibility-Kit v21.0.0 +* Added support for asynchronously disposing objects implementing IAsyncDisposable when the Reqnroll object container is disposed. ## Bug fixes: @@ -11,7 +12,7 @@ * Fix: Embedded feature file resource generates different resource names in Windows/Linux (#799) * Fix: Formatters: The Build Info banner of HTML reports shows UNKNOWN banner when the Build Server cannot be determined (#800) -*Contributors of this release (in alphabetical order):* @gasparnagy, @clrudolphi +*Contributors of this release (in alphabetical order):* @Code-Grump, @clrudolphi, @gasparnagy # v3.0.0 - 2025-08-21 From 3394d4276c88ded85afc5da0f8fa319f99d92c94 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Wed, 3 Sep 2025 23:08:19 +0100 Subject: [PATCH 06/22] Convert OnScenarioInitialize path to asynchronous --- .../Generation/UnitTestFeatureGenerator.cs | 15 +++++++++------ Reqnroll/ITestRunner.cs | 2 +- Reqnroll/TestRunner.cs | 4 ++-- .../TestRunnerManagerTests.cs | 8 ++++---- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs b/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs index bdeb056fd..f7ee8b92f 100644 --- a/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs +++ b/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs @@ -511,12 +511,15 @@ private void SetupScenarioInitializeMethod(TestClassGenerationContext generation //testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); - scenarioInitializeMethod.Statements.Add( - new CodeMethodInvokeExpression( - testRunnerField, - nameof(ITestRunner.OnScenarioInitialize), - new CodeVariableReferenceExpression("scenarioInfo"), - new CodeVariableReferenceExpression("ruleInfo"))); + var expression = new CodeMethodInvokeExpression( + testRunnerField, + nameof(ITestRunner.OnScenarioInitializeAsync), + new CodeVariableReferenceExpression("scenarioInfo"), + new CodeVariableReferenceExpression("ruleInfo")); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(expression); + + scenarioInitializeMethod.Statements.Add(expression); } private void SetupScenarioStartMethod(TestClassGenerationContext generationContext) diff --git a/Reqnroll/ITestRunner.cs b/Reqnroll/ITestRunner.cs index 766b12c32..d87c8422b 100644 --- a/Reqnroll/ITestRunner.cs +++ b/Reqnroll/ITestRunner.cs @@ -18,7 +18,7 @@ public interface ITestRunner Task OnFeatureStartAsync(FeatureInfo featureInfo); Task OnFeatureEndAsync(); - void OnScenarioInitialize(ScenarioInfo scenarioInfo, RuleInfo ruleInfo); + Task OnScenarioInitializeAsync(ScenarioInfo scenarioInfo, RuleInfo ruleInfo); Task OnScenarioStartAsync(); Task CollectScenarioErrorsAsync(); diff --git a/Reqnroll/TestRunner.cs b/Reqnroll/TestRunner.cs index 5c35964a5..c13988a96 100644 --- a/Reqnroll/TestRunner.cs +++ b/Reqnroll/TestRunner.cs @@ -37,9 +37,9 @@ public async Task OnFeatureEndAsync() await _executionEngine.OnFeatureEndAsync(); } - public void OnScenarioInitialize(ScenarioInfo scenarioInfo, RuleInfo ruleInfo) + public async Task OnScenarioInitializeAsync(ScenarioInfo scenarioInfo, RuleInfo ruleInfo) { - _executionEngine.OnScenarioInitializeAsync(scenarioInfo, ruleInfo); + await _executionEngine.OnScenarioInitializeAsync(scenarioInfo, ruleInfo); } public async Task OnScenarioStartAsync() diff --git a/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs b/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs index 81d20810f..890cbafc2 100644 --- a/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs +++ b/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs @@ -291,13 +291,13 @@ public async Task Should_resolve_a_test_runner_specific_test_tracer() { var testRunner1 = TestRunnerManager.GetTestRunnerForAssembly(_anAssembly, new RuntimeTestsContainerBuilder()); await testRunner1.OnFeatureStartAsync(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "sds", "sss")); - testRunner1.OnScenarioInitialize(new ScenarioInfo("foo", "foo_desc", null, null), null); + await testRunner1.OnScenarioInitializeAsync(new ScenarioInfo("foo", "foo_desc", null, null), null); await testRunner1.OnScenarioStartAsync(); var tracer1 = testRunner1.ScenarioContext.ScenarioContainer.Resolve(); var testRunner2 = TestRunnerManager.GetTestRunnerForAssembly(_anAssembly, new RuntimeTestsContainerBuilder()); await testRunner2.OnFeatureStartAsync(new FeatureInfo(new CultureInfo("en-US", false), string.Empty, "sds", "sss")); - testRunner2.OnScenarioInitialize(new ScenarioInfo("foo", "foo_desc", null, null), null); + await testRunner2.OnScenarioInitializeAsync(new ScenarioInfo("foo", "foo_desc", null, null), null); await testRunner1.OnScenarioStartAsync(); var tracer2 = testRunner2.ScenarioContext.ScenarioContainer.Resolve(); @@ -316,7 +316,7 @@ public async Task Should_support_out_of_order_feature_execution() // Feature 1 started var testRunnerFeature1Scenario = TestRunnerManager.GetTestRunnerForAssembly(_anAssembly, new RuntimeTestsContainerBuilder(), featureHint: feature1); await testRunnerFeature1Scenario.OnFeatureStartAsync(feature1); - testRunnerFeature1Scenario.OnScenarioInitialize(new ScenarioInfo("foo1.1", "foo_desc", null, null), null); + await testRunnerFeature1Scenario.OnScenarioInitializeAsync(new ScenarioInfo("foo1.1", "foo_desc", null, null), null); await testRunnerFeature1Scenario.OnScenarioStartAsync(); await testRunnerFeature1Scenario.OnScenarioEndAsync(); TestRunnerManager.ReleaseTestRunner(testRunnerFeature1Scenario); @@ -326,7 +326,7 @@ public async Task Should_support_out_of_order_feature_execution() var testRunnerFeature2Scenario = TestRunnerManager.GetTestRunnerForAssembly(_anAssembly, new RuntimeTestsContainerBuilder(), featureHint: feature2); testRunnerFeature2Scenario.Should().NotBeSameAs(testRunnerFeature1Scenario, because: "because one testrunner should be reserved for feature1"); await testRunnerFeature2Scenario.OnFeatureStartAsync(feature2); - testRunnerFeature2Scenario.OnScenarioInitialize(new ScenarioInfo("foo2.1", "foo_desc", null, null), null); + await testRunnerFeature2Scenario.OnScenarioInitializeAsync(new ScenarioInfo("foo2.1", "foo_desc", null, null), null); await testRunnerFeature2Scenario.OnScenarioStartAsync(); await testRunnerFeature2Scenario.OnScenarioEndAsync(); TestRunnerManager.ReleaseTestRunner(testRunnerFeature2Scenario); From 89de9d2f22abd35660bac0b417961ceacb350765 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 6 Sep 2025 01:01:38 +0100 Subject: [PATCH 07/22] Switch licence file out for SPDX identifier in NuGet packages --- Directory.Build.props | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 62c5e4b69..1d36a8d73 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,7 +26,6 @@ - all runtime; build; native; contentfiles; analyzers @@ -43,7 +42,7 @@ reqnroll bdd gherkin cucumber Reqnroll aims at bridging the communication gap between domain experts and developers by binding business readable behavior specifications to the underlying implementation. Our mission is to provide a pragmatic and frictionless approach to Acceptance Test Driven Development and Behavior Driven Development for .NET projects today." $(Reqnroll_Copyright) - LICENSE + BSD-3-Clause From 5881874476f986e35c1bf07a4e6a8ecac4d7e4b3 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 6 Sep 2025 01:07:02 +0100 Subject: [PATCH 08/22] Switch MSBuild.Generation project to using csproj for package composition --- .../Reqnroll.Tools.MsBuild.Generation.csproj | 83 +++++++++++-------- .../Reqnroll.Tools.MsBuild.Generation.nuspec | 34 -------- .../Reqnroll.Tools.MsBuild.Generation.props | 12 --- .../Reqnroll.Tools.MsBuild.Generation.tasks | 18 +++- 4 files changed, 65 insertions(+), 82 deletions(-) delete mode 100644 Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj index d2d992be3..e70748dac 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj @@ -4,37 +4,67 @@ $(Reqnroll_KeyFile) $(Reqnroll_EnableStrongNameSigning) $(Reqnroll_PublicSign) - $(MSBuildThisFileDirectory)Reqnroll.Tools.MsBuild.Generation.nuspec - true - true + true false - + + true + true + build + true + + + Package to enable the code-behind file generation during build time http://reqnroll.net/documentation/Generate-Tests-from-MsBuild/ + en-US + assets/icon.png + + + + + + + + - - - + + - - - All - - + + $(TargetsForTfmSpecificBuildOutput);MarkTaskAssembliesForPack + + + + + + + + + + + + + + + + - + - - - - + + + @@ -48,27 +78,12 @@ - + MSBuild:Compile - + MSBuild:Compile - - - - - - - - - - - \ No newline at end of file diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec deleted file mode 100644 index 524c4881a..000000000 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.nuspec +++ /dev/null @@ -1,34 +0,0 @@ - - - - Reqnroll.Tools.MsBuild.Generation - $version$ - Reqnroll.Tools.MsBuild.Generation - $author$ - $owner$ - Package to enable the code-behind file generation during build time http://reqnroll.net/documentation/Generate-Tests-from-MsBuild/ - en-US - https://www.reqnroll.net - - images\reqnroll-icon.png - false - BSD-3-Clause - reqnroll msbuild - $copyright$ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props index bee50ade9..32ffc3592 100644 --- a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props +++ b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props @@ -69,18 +69,6 @@ - - - <_Reqnroll_TaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core' And '$(_Reqnroll_TaskFolder)' == ''">netstandard2.0 - <_Reqnroll_TaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core' And '$(_Reqnroll_TaskFolder)' == ''">net462 - <_Reqnroll_TaskAssembly Condition=" '$(_Reqnroll_TaskAssembly)' == '' ">..\tasks\$(_Reqnroll_TaskFolder)\Reqnroll.Tools.MsBuild.Generation.dll - - - - <_Reqnroll_TaskFactory Condition="'$(_Reqnroll_TaskFactory)' == '' And $([MSBuild]::IsOsPlatform('Windows'))">TaskHostFactory - <_Reqnroll_TaskFactory Condition="'$(_Reqnroll_TaskFactory)' == '' And !($([MSBuild]::IsOsPlatform('Windows')))">AssemblyTaskFactory - - diff --git a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.tasks b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.tasks index 32f5cf879..59c730558 100644 --- a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.tasks +++ b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.tasks @@ -1,6 +1,20 @@ - - + + + netstandard2.0/ + net462/ + Reqnroll.Tools.MsBuild.Generation.dll + $(ReqnrollTasksDir)$(ReqnrollGenerationTasksAssemblyFile) + + + + TaskHostFactory + AssemblyTaskFactory + + + + + <_ReqnrollTasksImported>true From a481888d7c11a0c6ea49ae31f6d4927dbc79eeac Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 6 Sep 2025 01:24:07 +0100 Subject: [PATCH 09/22] Clean up self-test capability --- ...nroll.Tools.MsBuild.Generation.OwnTests.props | 14 ++++++-------- ...nroll.Tools.MsBuild.Generation.OwnTests.tasks | 16 ---------------- 2 files changed, 6 insertions(+), 24 deletions(-) delete mode 100644 Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.tasks diff --git a/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props b/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props index e10d3f487..33a71193a 100644 --- a/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props +++ b/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props @@ -1,18 +1,16 @@ - - <_ReqnrollTasksImported>true - - - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(Reqnroll_Core_Tools_TFM) - <_Reqnroll_Needed_MSBuildGenerator Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(Reqnroll_FullFramework_Tools_TFM) + $(Reqnroll_Core_Tools_TFM) + $(Reqnroll_FullFramework_Tools_TFM) + - <_Reqnroll_TaskAssembly>$(MSBuildThisFileDirectory)../../../Reqnroll.Tools.MsBuild.Generation/bin/$(Configuration)/$(_Reqnroll_Needed_MSBuildGenerator)/tasks/Reqnroll.Tools.MsBuild.Generation.dll + $(MSBuildThisFileDirectory)../../../Reqnroll.Tools.MsBuild.Generation/bin/$(Configuration)/$(ReqnrollTestTFM)/ - + + diff --git a/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.tasks b/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.tasks deleted file mode 100644 index 3f2d25ad9..000000000 --- a/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.tasks +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - <_ReqnrollTasksImported>true - <_ReqnrollTasksOwnTestImported>true - - \ No newline at end of file From f815d7a4b22fd36abeca72c56940c565e65cb8c5 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 6 Sep 2025 16:18:28 +0100 Subject: [PATCH 10/22] Simplification of direct MSBuild generation integration in tests --- .../Reqnroll.Assist.Dynamic.Specs.csproj | 12 +++++- ...Data.ReqnrollPlugin.IntegrationTest.csproj | 41 +++++++++++-------- .../build/Reqnroll.MsTest.targets | 17 +++++++- .../Reqnroll.Tools.MsBuild.Generation.props | 16 ++++++-- .../Reqnroll.Tools.MsBuild.Generation.targets | 5 ++- .../Reqnroll.Tools.MsBuild.Generation.tasks | 22 ---------- Tests/Reqnroll.Specs/Reqnroll.Specs.csproj | 29 ++++++------- ...ll.Tools.MsBuild.Generation.OwnTests.props | 16 -------- 8 files changed, 76 insertions(+), 82 deletions(-) delete mode 100644 Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.tasks delete mode 100644 Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props diff --git a/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj b/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj index cc73e87e2..2d965a172 100644 --- a/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj +++ b/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj @@ -1,8 +1,18 @@  - + + $(Reqnroll_Core_Tools_TFM) + $(Reqnroll_FullFramework_Tools_TFM) + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../../../Reqnroll.Tools.MsBuild.Generation/')) + $(ReqnrollGenerationProjectDir)bin/$(Configuration)/$(ReqnrollTestTFM)/ + + + + net8.0 disable diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj index 882b9bca5..308eed276 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj @@ -1,7 +1,17 @@  - + + + $(Reqnroll_Core_Tools_TFM) + $(Reqnroll_FullFramework_Tools_TFM) + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../../../Reqnroll.Tools.MsBuild.Generation/')) + $(ReqnrollGenerationProjectDir)bin/$(Configuration)/$(ReqnrollTestTFM)/ + + + net8.0 @@ -19,29 +29,24 @@ + + + - - - - - - - - - - PreBuild; - $(BuildDependsOn) - - - PreBuild; - $(RebuildDependsOn) - - + \ No newline at end of file diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets index 7be4cb065..a0f28a58e 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets @@ -30,8 +30,21 @@ <_Reqnroll_EffectiveRootNamespace Condition="'$(RootNamespace)' == ''">Reqnroll.GeneratedTests - - + + + + + + + diff --git a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props index 32ffc3592..d12c99b18 100644 --- a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props +++ b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.props @@ -1,11 +1,23 @@ + true $(MSBuildThisFileDirectory)CPS\Buildsystem\CpsExtension.DesignTime.targets + + $(MSBuildThisFileDirectory)netstandard2.0/ + $(MSBuildThisFileDirectory)net462/ + Reqnroll.Tools.MsBuild.Generation.dll + $(ReqnrollGenerationTasksDir)$(ReqnrollGenerationTasksAssemblyFile) + + + + TaskHostFactory + AssemblyTaskFactory + false @@ -18,8 +30,6 @@ false false - - <_ReqnrollPropsImported Condition="'$(_ReqnrollPropsImported)'==''">true - - TaskHostFactory - AssemblyTaskFactory - - - - - - - - <_ReqnrollTasksImported>true - - \ No newline at end of file diff --git a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj index fd40ce5be..12be7ea19 100644 --- a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj +++ b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj @@ -35,6 +35,16 @@ + + @@ -58,26 +68,11 @@ - - - - - - - - - - PreBuild; - $(BuildDependsOn) - - - PreBuild; - $(RebuildDependsOn) - - + + diff --git a/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props b/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props deleted file mode 100644 index 33a71193a..000000000 --- a/Tests/Reqnroll.Specs/build/Reqnroll.Tools.MsBuild.Generation.OwnTests.props +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - $(Reqnroll_Core_Tools_TFM) - $(Reqnroll_FullFramework_Tools_TFM) - - - - $(MSBuildThisFileDirectory)../../../Reqnroll.Tools.MsBuild.Generation/bin/$(Configuration)/$(ReqnrollTestTFM)/ - - - - - From 780e0d2c8488a324785b9b2ff3598f32c09664e8 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 6 Sep 2025 16:37:42 +0100 Subject: [PATCH 11/22] Remove diagnostic messages --- .../build/Reqnroll.MsTest.targets | 3 --- 1 file changed, 3 deletions(-) diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets index a0f28a58e..93a3ff517 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets @@ -33,9 +33,6 @@ - - - Date: Sat, 6 Sep 2025 16:38:20 +0100 Subject: [PATCH 12/22] Clean up direct build imports --- Tests/Reqnroll.Specs/Reqnroll.Specs.csproj | 28 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj index 12be7ea19..f7b3bc5c9 100644 --- a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj +++ b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj @@ -1,7 +1,17 @@  - + + + $(Reqnroll_Core_Tools_TFM) + $(Reqnroll_FullFramework_Tools_TFM) + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../../Reqnroll.Tools.MsBuild.Generation/')) + $(ReqnrollGenerationProjectDir)bin/$(Configuration)/$(ReqnrollTestTFM)/ + + + net8.0 @@ -60,8 +70,18 @@ - - + + + @@ -72,7 +92,7 @@ - + From 2bd9e46654096077c99e21760a72b4343c34df3b Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 6 Sep 2025 19:13:39 +0100 Subject: [PATCH 13/22] Set up deffered task loading for tests --- .../Reqnroll.Assist.Dynamic.Specs.csproj | 32 ++++++------------- ...Data.ReqnrollPlugin.IntegrationTest.csproj | 15 +++------ .../Reqnroll.Tools.MsBuild.Generation.targets | 29 +++++++++++++++-- Tests/Reqnroll.Specs/Reqnroll.Specs.csproj | 4 ++- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj b/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj index 2d965a172..afe94ba90 100644 --- a/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj +++ b/Plugins/Reqnroll.Assist.Dynamic/Reqnroll.Assist.Dynamic.Specs/Reqnroll.Assist.Dynamic.Specs.csproj @@ -7,11 +7,10 @@ $(Reqnroll_FullFramework_Tools_TFM) $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../../../Reqnroll.Tools.MsBuild.Generation/')) $(ReqnrollGenerationProjectDir)bin/$(Configuration)/$(ReqnrollTestTFM)/ + <_ReqnrollDeferTaskLoading>true - + net8.0 @@ -31,33 +30,22 @@ - + + false + false + false + + - - - - - - + - - - - - PreBuild; - $(BuildDependsOn) - - - PreBuild; - $(RebuildDependsOn) - - + diff --git a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj index 308eed276..e9be374ef 100644 --- a/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj +++ b/Plugins/Reqnroll.ExternalData/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest/Reqnroll.ExternalData.ReqnrollPlugin.IntegrationTest.csproj @@ -30,16 +30,11 @@ - - + + false + false + false + diff --git a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets index ea9c70a35..c09766574 100644 --- a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets +++ b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets @@ -2,10 +2,22 @@ <_Reqnroll_Tools_MsBuild_Generation_Imported>true + + + <_ReqnrollDeferTaskLoading>false - - + + + false @@ -65,9 +77,20 @@ + + + + + + DependsOnTargets="BeforeUpdateFeatureFilesInProject;LoadReqnrollTasks"> diff --git a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj index f7b3bc5c9..84f3cad30 100644 --- a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj +++ b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj @@ -7,6 +7,7 @@ $(Reqnroll_FullFramework_Tools_TFM) $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../../Reqnroll.Tools.MsBuild.Generation/')) $(ReqnrollGenerationProjectDir)bin/$(Configuration)/$(ReqnrollTestTFM)/ + <_ReqnrollDeferTaskLoading>true @@ -89,7 +91,7 @@ - + From 9f69115661f90fa48538d09543fab9494dd6f48c Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 6 Sep 2025 23:53:34 +0100 Subject: [PATCH 14/22] Simplified code-generation tasks to run late as possible: just before compile. --- .../build/Reqnroll.MsTest.targets | 18 +------- .../build/Reqnroll.NUnit.targets | 30 ++++++------- .../build/Reqnroll.TUnit.targets | 30 ++++++------- .../build/Reqnroll.xUnit.targets | 28 ++++++------- .../Reqnroll.Tools.MsBuild.Generation.targets | 42 ++++++------------- Tests/Reqnroll.Specs/Reqnroll.Specs.csproj | 21 +++++----- 6 files changed, 62 insertions(+), 107 deletions(-) diff --git a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets index 93a3ff517..aa8f53e14 100644 --- a/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets +++ b/Plugins/Reqnroll.MSTest.Generator.ReqnrollPlugin/build/Reqnroll.MsTest.targets @@ -1,20 +1,5 @@ - - - GenerateReqnrollAssemblyHooksFileTask; - $(BuildDependsOn) - - - $(CleanDependsOn) - - - GenerateReqnrollAssemblyHooksFileTask; - $(RebuildDependsOn) - - - - <_Reqnroll_MsTestGeneratorPlugin>netstandard2.0 @@ -32,7 +17,8 @@ + Condition="'$(GenerateReqnrollAssemblyHooksFile)' == 'true' AND '$(_Reqnroll_Tools_MsBuild_Generation_Imported)' == 'true'" + BeforeTargets="CoreCompile"> - - - GenerateReqnrollAssemblyHooksFileTask; - $(BuildDependsOn) - - - $(CleanDependsOn) - - - GenerateReqnrollAssemblyHooksFileTask; - $(RebuildDependsOn) - - - - <_Reqnroll_NUnitGeneratorPlugin>netstandard2.0 @@ -30,8 +15,19 @@ <_Reqnroll_EffectiveRootNamespace Condition="'$(RootNamespace)' == ''">Reqnroll.GeneratedTests - - + + + + diff --git a/Plugins/Reqnroll.TUnit.Generator.ReqnrollPlugin/build/Reqnroll.TUnit.targets b/Plugins/Reqnroll.TUnit.Generator.ReqnrollPlugin/build/Reqnroll.TUnit.targets index 75b782c9c..fe0d3d43c 100644 --- a/Plugins/Reqnroll.TUnit.Generator.ReqnrollPlugin/build/Reqnroll.TUnit.targets +++ b/Plugins/Reqnroll.TUnit.Generator.ReqnrollPlugin/build/Reqnroll.TUnit.targets @@ -1,20 +1,5 @@ - - - GenerateReqnrollAssemblyHooksFileTask; - $(BuildDependsOn) - - - $(CleanDependsOn) - - - GenerateReqnrollAssemblyHooksFileTask; - $(RebuildDependsOn) - - - - <_Reqnroll_TUnitGeneratorPluginPath>$(MSBuildThisFileDirectory)\netstandard2.0\Reqnroll.TUnit.Generator.ReqnrollPlugin.dll <_Reqnroll_TUnitRuntimePluginPath>$(MSBuildThisFileDirectory)\..\lib\netstandard2.0\Reqnroll.TUnit.ReqnrollPlugin.dll @@ -26,8 +11,19 @@ <_Reqnroll_EffectiveRootNamespace Condition="'$(RootNamespace)' == ''">Reqnroll.GeneratedTests - - + + + + diff --git a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets index cb9573508..119c9bd32 100644 --- a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets +++ b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets @@ -1,19 +1,5 @@ - - - GenerateReqnrollAssemblyHooksFileTask; - $(BuildDependsOn) - - - $(CleanDependsOn) - - - GenerateReqnrollAssemblyHooksFileTask; - $(RebuildDependsOn) - - - <_Reqnroll_xUnitGeneratorPlugin>netstandard2.0 @@ -30,8 +16,18 @@ <_Reqnroll_EffectiveRootNamespace Condition="'$(RootNamespace)' == ''">Reqnroll.GeneratedTests - - + + + diff --git a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets index c09766574..51f610f20 100644 --- a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets +++ b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets @@ -2,22 +2,17 @@ <_Reqnroll_Tools_MsBuild_Generation_Imported>true - - - <_ReqnrollDeferTaskLoading>false + TaskFactory="$(ReqnrollGenerationTaskFactory)" /> + TaskFactory="$(ReqnrollGenerationTaskFactory)" /> false @@ -34,13 +29,6 @@ - - BeforeUpdateFeatureFilesInProject; - UpdateFeatureFilesInProject; - IncludeCodeBehindFilesInProject; - AfterUpdateFeatureFilesInProject; - $(BuildDependsOn) - CleanFeatureFilesInProject; $(CleanDependsOn) @@ -77,20 +65,8 @@ - - - - - - + DependsOnTargets="BeforeUpdateFeatureFilesInProject"> @@ -163,11 +139,14 @@ - + - + @@ -185,7 +164,10 @@ - + diff --git a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj index 84f3cad30..8555bd85f 100644 --- a/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj +++ b/Tests/Reqnroll.Specs/Reqnroll.Specs.csproj @@ -46,16 +46,16 @@ - - + + false + false + false + + + false + false + false + @@ -73,7 +73,6 @@ From 6edd718fc5293a14dd74f748d7c582b360ee4861 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sun, 7 Sep 2025 00:01:29 +0100 Subject: [PATCH 15/22] Add line breaks for clarity --- .../build/Reqnroll.xUnit.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets index 119c9bd32..4b9d44cb3 100644 --- a/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets +++ b/Plugins/Reqnroll.xUnit.Generator.ReqnrollPlugin/build/Reqnroll.xUnit.targets @@ -28,6 +28,7 @@ TextToReplace="PROJECT_ROOT_NAMESPACE" TextToReplaceWith="$(_Reqnroll_EffectiveRootNamespace.Replace('.', '_'))" WriteOnlyWhenChanged="true" /> + From 898d8fd2519da6ad84dfd42f3ef3160da63b2156 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sun, 7 Sep 2025 00:36:40 +0100 Subject: [PATCH 16/22] Correct issue with analyzer being transiently added to projects --- .../Reqnroll.Tools.MsBuild.Generation.csproj | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj index e70748dac..b33e161fb 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj @@ -60,11 +60,12 @@ - - - - - + + + + + + From de8095d42fe9dac0d50c4058ad32e7d9ed173d00 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Mon, 8 Sep 2025 23:42:04 +0100 Subject: [PATCH 17/22] Clean up DLL selection for packing --- .../Reqnroll.Tools.MsBuild.Generation.csproj | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj index b33e161fb..b8faea5b9 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj @@ -13,6 +13,7 @@ true true build + false true @@ -33,25 +34,40 @@ - $(TargetsForTfmSpecificBuildOutput);MarkTaskAssembliesForPack + $(TargetsForTfmSpecificBuildOutput);ResolveRequiredReferences - - + + - - - - + + + + + + + + + + + + + + + Include="@(ReferenceCopyLocalPaths)" + TargetPath="%(ReferenceCopyLocalPaths.DestinationSubPath)"/> From 0fa2208a5897ec408f11a49f5014030414921a55 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Wed, 10 Sep 2025 23:28:09 +0100 Subject: [PATCH 18/22] Fix resource embedding happening too late in the build process --- .../Reqnroll.Tools.MsBuild.Generation.targets | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets index 51f610f20..3060d2967 100644 --- a/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets +++ b/Reqnroll.Tools.MsBuild.Generation/build/Reqnroll.Tools.MsBuild.Generation.targets @@ -143,10 +143,15 @@ - + + + IncludeCodeBehindFilesInProject; + $(CoreCompileDependsOn) + + + + + @@ -156,7 +161,17 @@ %(ReqnrollFeatureFiles.NormalizedLogicalName) - + + + + + EmbedCucumberMessages; + $(AssignTargetPathsDependsOn) + + + + + %(ReqnrollGeneratedFiles.EmbeddedMessagesResourceName) From 4f6e002862ee903323c46542209c51409139a81a Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 13 Sep 2025 16:38:29 +0100 Subject: [PATCH 19/22] Add awaiting of scenario initialize to core generator --- .../Generation/GeneratorConstants.cs | 2 +- .../Generation/UnitTestFeatureGenerator.cs | 2 ++ .../Generation/UnitTestMethodGenerator.cs | 15 +++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Reqnroll.Generator/Generation/GeneratorConstants.cs b/Reqnroll.Generator/Generation/GeneratorConstants.cs index 818ba822f..74df3332a 100644 --- a/Reqnroll.Generator/Generation/GeneratorConstants.cs +++ b/Reqnroll.Generator/Generation/GeneratorConstants.cs @@ -4,7 +4,7 @@ public class GeneratorConstants { public const string DEFAULT_NAMESPACE = "ReqnrollTests"; public const string TEST_NAME_FORMAT = "{0}"; - public const string SCENARIO_INITIALIZE_NAME = "ScenarioInitialize"; + public const string SCENARIO_INITIALIZE_NAME = "ScenarioInitializeAsync"; public const string SCENARIO_START_NAME = "ScenarioStartAsync"; public const string SCENARIO_CLEANUP_NAME = "ScenarioCleanupAsync"; public const string TEST_INITIALIZE_NAME = "TestInitializeAsync"; diff --git a/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs b/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs index f7ee8b92f..9abf1c008 100644 --- a/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs +++ b/Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs @@ -509,6 +509,8 @@ private void SetupScenarioInitializeMethod(TestClassGenerationContext generation scenarioInitializeMethod.Parameters.Add( new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(RuleInfo), CodeTypeReferenceOptions.GlobalReference), "ruleInfo")); + _codeDomHelper.MarkCodeMemberMethodAsAsync(scenarioInitializeMethod); + //testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); var testRunnerField = _scenarioPartHelper.GetTestRunnerExpression(); var expression = new CodeMethodInvokeExpression( diff --git a/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs b/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs index fb82d3573..558ededa4 100644 --- a/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs +++ b/Reqnroll.Generator/Generation/UnitTestMethodGenerator.cs @@ -346,12 +346,15 @@ internal void GenerateScenarioInitializeCall(TestClassGenerationContext generati using (new SourceLineScope(_reqnrollConfiguration, _codeDomHelper, statements, generationContext.Document.SourceFilePath, scenario.Location)) { - statements.Add(new CodeExpressionStatement( - new CodeMethodInvokeExpression( - new CodeThisReferenceExpression(), - generationContext.ScenarioInitializeMethod.Name, - new CodeVariableReferenceExpression("scenarioInfo"), - new CodeVariableReferenceExpression("ruleInfo")))); + var callScenarioInitializeExpression = new CodeMethodInvokeExpression( + new CodeThisReferenceExpression(), + generationContext.ScenarioInitializeMethod.Name, + new CodeVariableReferenceExpression("scenarioInfo"), + new CodeVariableReferenceExpression("ruleInfo")); + + _codeDomHelper.MarkCodeMethodInvokeExpressionAsAwait(callScenarioInitializeExpression); + + statements.Add(new CodeExpressionStatement(callScenarioInitializeExpression)); } testMethod.Statements.AddRange(statements.ToArray()); From cc578ed27a6f06e6fa6e1beb7496980d9988e1e7 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 13 Sep 2025 16:47:14 +0100 Subject: [PATCH 20/22] Remove VisualStudio package references --- .../Reqnroll.Tools.MsBuild.Generation.csproj | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj index d1b10da3d..a4bdbf30d 100644 --- a/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj +++ b/Reqnroll.Tools.MsBuild.Generation/Reqnroll.Tools.MsBuild.Generation.csproj @@ -78,15 +78,8 @@ - - - From 4512731daf54ef8d7927ccb624f0f11bf482426f Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Sat, 13 Sep 2025 22:57:03 +0100 Subject: [PATCH 21/22] Fix expectation in XUnit generator tests to expect async method name --- .../UnitTestProvider/XUnit2TestGeneratorProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Reqnroll.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs b/Tests/Reqnroll.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs index f5ed1b4fe..759e495de 100644 --- a/Tests/Reqnroll.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs +++ b/Tests/Reqnroll.GeneratorTests/UnitTestProvider/XUnit2TestGeneratorProviderTests.cs @@ -376,7 +376,7 @@ public void XUnit2TestGeneratorProvider_Should_register_testOutputHelper_on_scen // ASSERT code.Should().NotBeNull(); - var scenarioStartMethod = code.Class().Members().Single(m => m.Name == @"ScenarioInitialize"); + var scenarioStartMethod = code.Class().Members().Single(m => m.Name == @"ScenarioInitializeAsync"); scenarioStartMethod.Statements.Count.Should().Be(2); var expression = scenarioStartMethod.Statements[1].Should().BeOfType() From 5a559fb1a84bab92894703a20328a3474ab16737 Mon Sep 17 00:00:00 2001 From: Paul Turner Date: Fri, 26 Sep 2025 17:53:32 +0100 Subject: [PATCH 22/22] Suppress VisualStudio threading analyzer warning We're handling all synchronization properly in this class --- Reqnroll.Tools.MsBuild.Generation/AsyncRunner.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Reqnroll.Tools.MsBuild.Generation/AsyncRunner.cs b/Reqnroll.Tools.MsBuild.Generation/AsyncRunner.cs index 66bdbeb56..03032d5e7 100644 --- a/Reqnroll.Tools.MsBuild.Generation/AsyncRunner.cs +++ b/Reqnroll.Tools.MsBuild.Generation/AsyncRunner.cs @@ -67,6 +67,8 @@ public static T RunAndJoin(Func> func) } #endif +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits return func().GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits } }