From 3ae913a179470a4c0cf112f12f8c80e55ea3a9a9 Mon Sep 17 00:00:00 2001 From: obligaron Date: Wed, 22 May 2024 15:51:24 +0200 Subject: [PATCH] Dispose TestThread-Container correctly (#135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dispose TestThread-Container correctly * use _testRunnerRegistry directly to access test thread containers, add unit test * undo system tests * cleanup TestRunnerManagerTests * cleanup TestRunnerManager --------- Co-authored-by: Gáspár Nagy --- CHANGELOG.md | 1 + Reqnroll/ITestRunner.cs | 1 + .../Infrastructure/ITestExecutionEngine.cs | 1 + Reqnroll/TestRunner.cs | 12 +- Reqnroll/TestRunnerManager.cs | 406 +++++++++--------- .../TestRunnerManagerTest.cs | 61 --- .../TestRunnerManagerTests.cs | 82 ++++ 7 files changed, 294 insertions(+), 270 deletions(-) delete mode 100644 Tests/Reqnroll.RuntimeTests/TestRunnerManagerTest.cs create mode 100644 Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1da9a27b3..b4baff459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Fix: StackOverflowException when using `[StepArgumentTransformation]` with same input and output type, for example string (#71) * Fix: Autofac without hook does not run GlobalDependencies (#127) * Fix: Reqnroll.Autofac shows wrongly ambiguous step definition (#56) +* Fix: Dispose objects registred in test thread container at the end of test execution (#123) # v1.0.1 - 2024-02-16 diff --git a/Reqnroll/ITestRunner.cs b/Reqnroll/ITestRunner.cs index 4d5cbffb0..b8a7daff1 100644 --- a/Reqnroll/ITestRunner.cs +++ b/Reqnroll/ITestRunner.cs @@ -10,6 +10,7 @@ public interface ITestRunner string TestWorkerId { get; } FeatureContext FeatureContext { get; } ScenarioContext ScenarioContext { get; } + ITestThreadContext TestThreadContext { get; } void InitializeTestRunner(string testWorkerId); diff --git a/Reqnroll/Infrastructure/ITestExecutionEngine.cs b/Reqnroll/Infrastructure/ITestExecutionEngine.cs index 387510b15..fdb15e429 100644 --- a/Reqnroll/Infrastructure/ITestExecutionEngine.cs +++ b/Reqnroll/Infrastructure/ITestExecutionEngine.cs @@ -7,6 +7,7 @@ public interface ITestExecutionEngine { FeatureContext FeatureContext { get; } ScenarioContext ScenarioContext { get; } + ITestThreadContext TestThreadContext { get; } Task OnTestRunStartAsync(); Task OnTestRunEndAsync(); diff --git a/Reqnroll/TestRunner.cs b/Reqnroll/TestRunner.cs index 2cb29ff3b..b215f3823 100644 --- a/Reqnroll/TestRunner.cs +++ b/Reqnroll/TestRunner.cs @@ -15,15 +15,11 @@ public TestRunner(ITestExecutionEngine executionEngine) _executionEngine = executionEngine; } - public FeatureContext FeatureContext - { - get { return _executionEngine.FeatureContext; } - } + public FeatureContext FeatureContext => _executionEngine.FeatureContext; - public ScenarioContext ScenarioContext - { - get { return _executionEngine.ScenarioContext; } - } + public ScenarioContext ScenarioContext => _executionEngine.ScenarioContext; + + public ITestThreadContext TestThreadContext => _executionEngine.TestThreadContext; public async Task OnTestRunStartAsync() { diff --git a/Reqnroll/TestRunnerManager.cs b/Reqnroll/TestRunnerManager.cs index 32b3f8e28..861339de0 100644 --- a/Reqnroll/TestRunnerManager.cs +++ b/Reqnroll/TestRunnerManager.cs @@ -13,279 +13,283 @@ using Reqnroll.Infrastructure; using Reqnroll.Tracing; -namespace Reqnroll +namespace Reqnroll; + +public class TestRunnerManager : ITestRunnerManager { - public class TestRunnerManager : ITestRunnerManager - { - public const string TestRunStartWorkerId = "TestRunStart"; + public const string TestRunStartWorkerId = "TestRunStart"; - protected readonly IObjectContainer _globalContainer; - protected readonly IContainerBuilder _containerBuilder; - protected readonly ReqnrollConfiguration _reqnrollConfiguration; - protected readonly IRuntimeBindingRegistryBuilder _bindingRegistryBuilder; - protected readonly ITestTracer _testTracer; + protected readonly IObjectContainer _globalContainer; + protected readonly IContainerBuilder _containerBuilder; + protected readonly ReqnrollConfiguration _reqnrollConfiguration; + protected readonly IRuntimeBindingRegistryBuilder _bindingRegistryBuilder; + protected readonly ITestTracer _testTracer; - private readonly ConcurrentDictionary _testRunnerRegistry = new(); - public bool IsTestRunInitialized { get; private set; } - private int _wasDisposed = 0; - private int _wasSingletonInstanceDisabled = 0; - private readonly object createTestRunnerLockObject = new(); + private readonly ConcurrentDictionary _testRunnerRegistry = new(); + public bool IsTestRunInitialized { get; private set; } + private int _wasDisposed = 0; + private int _wasSingletonInstanceDisabled = 0; + private readonly object _createTestRunnerLockObject = new(); - public Assembly TestAssembly { get; private set; } - public Assembly[] BindingAssemblies { get; private set; } + public Assembly TestAssembly { get; private set; } + public Assembly[] BindingAssemblies { get; private set; } - public bool IsMultiThreaded => GetWorkerTestRunnerCount() > 1; + public bool IsMultiThreaded => GetWorkerTestRunnerCount() > 1; - public TestRunnerManager(IObjectContainer globalContainer, IContainerBuilder containerBuilder, ReqnrollConfiguration reqnrollConfiguration, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, - ITestTracer testTracer) - { - _globalContainer = globalContainer; - _containerBuilder = containerBuilder; - _reqnrollConfiguration = reqnrollConfiguration; - _bindingRegistryBuilder = bindingRegistryBuilder; - _testTracer = testTracer; - } + public TestRunnerManager(IObjectContainer globalContainer, IContainerBuilder containerBuilder, ReqnrollConfiguration reqnrollConfiguration, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, + ITestTracer testTracer) + { + _globalContainer = globalContainer; + _containerBuilder = containerBuilder; + _reqnrollConfiguration = reqnrollConfiguration; + _bindingRegistryBuilder = bindingRegistryBuilder; + _testTracer = testTracer; + } - private int GetWorkerTestRunnerCount() - { - var hasTestRunStartWorker = _testRunnerRegistry.ContainsKey(TestRunStartWorkerId); - return _testRunnerRegistry.Count - (hasTestRunStartWorker ? 1 : 0); - } + private int GetWorkerTestRunnerCount() + { + var hasTestRunStartWorker = _testRunnerRegistry.ContainsKey(TestRunStartWorkerId); + return _testRunnerRegistry.Count - (hasTestRunStartWorker ? 1 : 0); + } - public virtual ITestRunner CreateTestRunner(string testWorkerId = "default-worker") - { - var testRunner = CreateTestRunnerInstance(); - testRunner.InitializeTestRunner(testWorkerId); + public virtual ITestRunner CreateTestRunner(string testWorkerId = "default-worker") + { + var testRunner = CreateTestRunnerInstance(); + testRunner.InitializeTestRunner(testWorkerId); - if (!IsTestRunInitialized) + if (!IsTestRunInitialized) + { + lock (_createTestRunnerLockObject) { - lock (createTestRunnerLockObject) + if (!IsTestRunInitialized) { - if (!IsTestRunInitialized) - { - InitializeBindingRegistry(testRunner); - IsTestRunInitialized = true; - } + InitializeBindingRegistry(testRunner); + IsTestRunInitialized = true; } } - - return testRunner; } - protected virtual void InitializeBindingRegistry(ITestRunner testRunner) - { - BindingAssemblies = _bindingRegistryBuilder.GetBindingAssemblies(TestAssembly); - BuildBindingRegistry(BindingAssemblies); - - void DomainUnload(object sender, EventArgs e) - { - OnDomainUnloadAsync().Wait(); - } + return testRunner; + } - AppDomain.CurrentDomain.DomainUnload += DomainUnload; - AppDomain.CurrentDomain.ProcessExit += DomainUnload; - } + protected virtual void InitializeBindingRegistry(ITestRunner testRunner) + { + BindingAssemblies = _bindingRegistryBuilder.GetBindingAssemblies(TestAssembly); + BuildBindingRegistry(BindingAssemblies); - protected virtual void BuildBindingRegistry(IEnumerable bindingAssemblies) + void DomainUnload(object sender, EventArgs e) { - foreach (Assembly assembly in bindingAssemblies) - { - _bindingRegistryBuilder.BuildBindingsFromAssembly(assembly); - } - _bindingRegistryBuilder.BuildingCompleted(); + OnDomainUnloadAsync().Wait(); } - protected internal virtual async Task OnDomainUnloadAsync() - { - await DisposeAsync(); - } + AppDomain.CurrentDomain.DomainUnload += DomainUnload; + AppDomain.CurrentDomain.ProcessExit += DomainUnload; + } - public async Task FireTestRunEndAsync() + protected virtual void BuildBindingRegistry(IEnumerable bindingAssemblies) + { + foreach (Assembly assembly in bindingAssemblies) { - // this method must not be called multiple times - var onTestRunnerEndExecutionHost = _testRunnerRegistry.Values.FirstOrDefault(); - if (onTestRunnerEndExecutionHost != null) - await onTestRunnerEndExecutionHost.OnTestRunEndAsync(); + _bindingRegistryBuilder.BuildBindingsFromAssembly(assembly); } + _bindingRegistryBuilder.BuildingCompleted(); + } - public async Task FireTestRunStartAsync() - { - // this method must not be called multiple times - var onTestRunnerStartExecutionHost = _testRunnerRegistry.Values.FirstOrDefault(); - if (onTestRunnerStartExecutionHost != null) - await onTestRunnerStartExecutionHost.OnTestRunStartAsync(); - } + protected internal virtual async Task OnDomainUnloadAsync() + { + await DisposeAsync(); + } - protected virtual ITestRunner CreateTestRunnerInstance() - { - var testThreadContainer = _containerBuilder.CreateTestThreadContainer(_globalContainer); + public async Task FireTestRunEndAsync() + { + // this method must not be called multiple times + var onTestRunnerEndExecutionHost = _testRunnerRegistry.Values.FirstOrDefault(); + if (onTestRunnerEndExecutionHost != null) + await onTestRunnerEndExecutionHost.OnTestRunEndAsync(); + } - return testThreadContainer.Resolve(); - } + public async Task FireTestRunStartAsync() + { + // this method must not be called multiple times + var onTestRunnerStartExecutionHost = _testRunnerRegistry.Values.FirstOrDefault(); + if (onTestRunnerStartExecutionHost != null) + await onTestRunnerStartExecutionHost.OnTestRunStartAsync(); + } - public void Initialize(Assembly assignedTestAssembly) - { - TestAssembly = assignedTestAssembly; - } + protected virtual ITestRunner CreateTestRunnerInstance() + { + var testThreadContainer = _containerBuilder.CreateTestThreadContainer(_globalContainer); - public virtual ITestRunner GetTestRunner(string testWorkerId) + return testThreadContainer.Resolve(); + } + + public void Initialize(Assembly assignedTestAssembly) + { + TestAssembly = assignedTestAssembly; + } + + public virtual ITestRunner GetTestRunner(string testWorkerId) + { + testWorkerId ??= Guid.NewGuid().ToString(); //Creates a Test Runner with a unique test thread + try { - testWorkerId ??= Guid.NewGuid().ToString(); //Creates a Test Runner with a unique test thread - try - { - return GetTestRunnerWithoutExceptionHandling(testWorkerId); - } - catch (Exception ex) - { - _testTracer.TraceError(ex,TimeSpan.Zero); - throw; - } + return GetTestRunnerWithoutExceptionHandling(testWorkerId); } - - private ITestRunner GetTestRunnerWithoutExceptionHandling(string testWorkerId) + catch (Exception ex) { - if (testWorkerId == null) - throw new ArgumentNullException(nameof(testWorkerId)); + _testTracer.TraceError(ex,TimeSpan.Zero); + throw; + } + } - bool wasAdded = false; + private ITestRunner GetTestRunnerWithoutExceptionHandling(string testWorkerId) + { + if (testWorkerId == null) + throw new ArgumentNullException(nameof(testWorkerId)); - var testRunner = _testRunnerRegistry.GetOrAdd( - testWorkerId, - workerId => - { - wasAdded = true; - return CreateTestRunner(workerId); - }); + bool wasAdded = false; - if (wasAdded && IsMultiThreaded && Interlocked.CompareExchange(ref _wasSingletonInstanceDisabled, 1, 0) == 0) + var testRunner = _testRunnerRegistry.GetOrAdd( + testWorkerId, + workerId => { - FeatureContext.DisableSingletonInstance(); - ScenarioContext.DisableSingletonInstance(); - ScenarioStepContext.DisableSingletonInstance(); - } + wasAdded = true; + return CreateTestRunner(workerId); + }); - return testRunner; - } - - public virtual async Task DisposeAsync() + if (wasAdded && IsMultiThreaded && Interlocked.CompareExchange(ref _wasSingletonInstanceDisabled, 1, 0) == 0) { - if (Interlocked.CompareExchange(ref _wasDisposed, 1, 0) == 0) - { - await FireTestRunEndAsync(); - - // this call dispose on this object, but the disposeLockObj will avoid double execution - _globalContainer.Dispose(); - - _testRunnerRegistry.Clear(); - OnTestRunnerManagerDisposed(this); - } + FeatureContext.DisableSingletonInstance(); + ScenarioContext.DisableSingletonInstance(); + ScenarioStepContext.DisableSingletonInstance(); } - #region Static API - - private static readonly ConcurrentDictionary _testRunnerManagerRegistry = new(); + return testRunner; + } - public static ITestRunnerManager GetTestRunnerManager(Assembly testAssembly = null, IContainerBuilder containerBuilder = null, bool createIfMissing = true) + public virtual async Task DisposeAsync() + { + if (Interlocked.CompareExchange(ref _wasDisposed, 1, 0) == 0) { - testAssembly ??= GetCallingAssembly(); + await FireTestRunEndAsync(); - if (!createIfMissing) + foreach (var testRunner in _testRunnerRegistry.Values) { - return _testRunnerManagerRegistry.TryGetValue(testAssembly, out var value) ? value : null; + testRunner.TestThreadContext.TestThreadContainer.Dispose(); } - var testRunnerManager = _testRunnerManagerRegistry.GetOrAdd( - testAssembly, - assembly => CreateTestRunnerManager(assembly, containerBuilder)); - return testRunnerManager; + // this call dispose on this object, but the disposeLockObj will avoid double execution + _globalContainer.Dispose(); + + _testRunnerRegistry.Clear(); + OnTestRunnerManagerDisposed(this); } + } - /// - /// This is a workaround method solving not correctly working Assembly.GetCallingAssembly() when called from async method (due to state machine). - /// - private static Assembly GetCallingAssembly([CallerMemberName] string callingMethodName = null) + #region Static API + + private static readonly ConcurrentDictionary _testRunnerManagerRegistry = new(); + + public static ITestRunnerManager GetTestRunnerManager(Assembly testAssembly = null, IContainerBuilder containerBuilder = null, bool createIfMissing = true) + { + testAssembly ??= GetCallingAssembly(); + + if (!createIfMissing) { - var stackTrace = new StackTrace(); + return _testRunnerManagerRegistry.TryGetValue(testAssembly, out var value) ? value : null; + } - var callingMethodIndex = -1; + var testRunnerManager = _testRunnerManagerRegistry.GetOrAdd( + testAssembly, + assembly => CreateTestRunnerManager(assembly, containerBuilder)); + return testRunnerManager; + } - for (var i = 0; i < stackTrace.FrameCount; i++) - { - var frame = stackTrace.GetFrame(i); + /// + /// This is a workaround method solving not correctly working Assembly.GetCallingAssembly() when called from async method (due to state machine). + /// + private static Assembly GetCallingAssembly([CallerMemberName] string callingMethodName = null) + { + var stackTrace = new StackTrace(); - if (frame.GetMethod().Name == callingMethodName) - { - callingMethodIndex = i; - break; - } - } + var callingMethodIndex = -1; - Assembly result = null; + for (var i = 0; i < stackTrace.FrameCount; i++) + { + var frame = stackTrace.GetFrame(i); - if (callingMethodIndex >= 0 && callingMethodIndex + 1 < stackTrace.FrameCount) + if (frame.GetMethod().Name == callingMethodName) { - result = stackTrace.GetFrame(callingMethodIndex + 1).GetMethod().DeclaringType?.Assembly; + callingMethodIndex = i; + break; } - - return result ?? GetCallingAssembly(); } - - private static ITestRunnerManager CreateTestRunnerManager(Assembly testAssembly, IContainerBuilder containerBuilder = null) - { - containerBuilder ??= new ContainerBuilder(); - var container = containerBuilder.CreateGlobalContainer(testAssembly); - var testRunnerManager = container.Resolve(); - testRunnerManager.Initialize(testAssembly); - return testRunnerManager; - } + Assembly result = null; - public static async Task OnTestRunEndAsync(Assembly testAssembly = null, IContainerBuilder containerBuilder = null) + if (callingMethodIndex >= 0 && callingMethodIndex + 1 < stackTrace.FrameCount) { - testAssembly ??= GetCallingAssembly(); - var testRunnerManager = GetTestRunnerManager(testAssembly, createIfMissing: false, containerBuilder: containerBuilder); - if (testRunnerManager != null) - { - await testRunnerManager.FireTestRunEndAsync(); - await testRunnerManager.DisposeAsync(); - } + result = stackTrace.GetFrame(callingMethodIndex + 1).GetMethod().DeclaringType?.Assembly; } - public static async Task OnTestRunStartAsync(Assembly testAssembly = null, string testWorkerId = null, IContainerBuilder containerBuilder = null) - { - testAssembly ??= GetCallingAssembly(); - var testRunnerManager = GetTestRunnerManager(testAssembly, createIfMissing: true, containerBuilder: containerBuilder); - testRunnerManager.GetTestRunner(testWorkerId ?? TestRunStartWorkerId); + return result ?? GetCallingAssembly(); + } + + private static ITestRunnerManager CreateTestRunnerManager(Assembly testAssembly, IContainerBuilder containerBuilder = null) + { + containerBuilder ??= new ContainerBuilder(); - await testRunnerManager.FireTestRunStartAsync(); - } + var container = containerBuilder.CreateGlobalContainer(testAssembly); + var testRunnerManager = container.Resolve(); + testRunnerManager.Initialize(testAssembly); + return testRunnerManager; + } - public static ITestRunner GetTestRunnerForAssembly(Assembly testAssembly = null, string testWorkerId = null, IContainerBuilder containerBuilder = null) + public static async Task OnTestRunEndAsync(Assembly testAssembly = null, IContainerBuilder containerBuilder = null) + { + testAssembly ??= GetCallingAssembly(); + var testRunnerManager = GetTestRunnerManager(testAssembly, createIfMissing: false, containerBuilder: containerBuilder); + if (testRunnerManager != null) { - testAssembly ??= GetCallingAssembly(); - var testRunnerManager = GetTestRunnerManager(testAssembly, containerBuilder); - return testRunnerManager.GetTestRunner(testWorkerId); + // DisposeAsync invokes FireTestRunEndAsync + await testRunnerManager.DisposeAsync(); } + } - internal static async Task ResetAsync() + public static async Task OnTestRunStartAsync(Assembly testAssembly = null, string testWorkerId = null, IContainerBuilder containerBuilder = null) + { + testAssembly ??= GetCallingAssembly(); + var testRunnerManager = GetTestRunnerManager(testAssembly, createIfMissing: true, containerBuilder: containerBuilder); + testRunnerManager.GetTestRunner(testWorkerId ?? TestRunStartWorkerId); + + await testRunnerManager.FireTestRunStartAsync(); + } + + public static ITestRunner GetTestRunnerForAssembly(Assembly testAssembly = null, string testWorkerId = null, IContainerBuilder containerBuilder = null) + { + testAssembly ??= GetCallingAssembly(); + var testRunnerManager = GetTestRunnerManager(testAssembly, containerBuilder); + return testRunnerManager.GetTestRunner(testWorkerId); + } + + internal static async Task ResetAsync() + { + while (!_testRunnerManagerRegistry.IsEmpty) { - while (!_testRunnerManagerRegistry.IsEmpty) + foreach (var assembly in _testRunnerManagerRegistry.Keys.ToArray()) { - foreach (var assembly in _testRunnerManagerRegistry.Keys.ToArray()) + if (_testRunnerManagerRegistry.TryRemove(assembly, out var testRunnerManager)) { - if (_testRunnerManagerRegistry.TryRemove(assembly, out var testRunnerManager)) - { - await testRunnerManager.DisposeAsync(); - } + await testRunnerManager.DisposeAsync(); } } } + } - private static void OnTestRunnerManagerDisposed(TestRunnerManager testRunnerManager) - { - _testRunnerManagerRegistry.TryRemove(testRunnerManager.TestAssembly, out _); - } - - #endregion + private static void OnTestRunnerManagerDisposed(TestRunnerManager testRunnerManager) + { + _testRunnerManagerRegistry.TryRemove(testRunnerManager.TestAssembly, out _); } + + #endregion } \ No newline at end of file diff --git a/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTest.cs b/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTest.cs deleted file mode 100644 index e59e62106..000000000 --- a/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTest.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Reflection; -using FluentAssertions; -using Xunit; -using Reqnroll.Infrastructure; - -namespace Reqnroll.RuntimeTests -{ - /// - /// Testing instance members of TestRunnerManager - /// - - public class TestRunnerManagerTest - { - private readonly Assembly anAssembly = Assembly.GetExecutingAssembly(); - private TestRunnerManager testRunnerManager; - - public TestRunnerManagerTest() - { - var globalContainer = new RuntimeTestsContainerBuilder().CreateGlobalContainer(typeof(TestRunnerManagerTest).Assembly); - testRunnerManager = globalContainer.Resolve(); - testRunnerManager.Initialize(anAssembly); - } - - [Fact] - public void CreateTestRunner_should_be_able_to_create_a_testrunner() - { - var testRunner = testRunnerManager.CreateTestRunner("0"); - - testRunner.Should().NotBeNull(); - testRunner.Should().BeOfType(); - } - - [Fact] - public void GetTestRunner_should_be_able_to_create_a_testrunner() - { - var testRunner = testRunnerManager.GetTestRunner("0"); - - testRunner.Should().NotBeNull(); - testRunner.Should().BeOfType(); - } - - [Fact] - public void GetTestRunner_should_cache_instance() - { - var testRunner1 = testRunnerManager.GetTestRunner("0"); - var testRunner2 = testRunnerManager.GetTestRunner("0"); - - - testRunner1.Should().Be(testRunner2); - } - - [Fact] - public void Should_return_different_instances_for_different_thread_ids() - { - var testRunner1 = testRunnerManager.GetTestRunner("0"); - var testRunner2 = testRunnerManager.GetTestRunner("1"); - - testRunner1.Should().NotBe(testRunner2); - } - } -} \ No newline at end of file diff --git a/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs b/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs new file mode 100644 index 000000000..cd2429250 --- /dev/null +++ b/Tests/Reqnroll.RuntimeTests/TestRunnerManagerTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using FluentAssertions; +using Xunit; + +namespace Reqnroll.RuntimeTests; + +public class TestRunnerManagerTests +{ + private readonly Assembly _anAssembly = Assembly.GetExecutingAssembly(); + private readonly TestRunnerManager _testRunnerManager; + + public TestRunnerManagerTests() + { + _testRunnerManager = (TestRunnerManager)TestRunnerManager.GetTestRunnerManager(_anAssembly, new RuntimeTestsContainerBuilder()); + } + + [Fact] + public void CreateTestRunner_should_be_able_to_create_a_TestRunner() + { + var testRunner = _testRunnerManager.CreateTestRunner("0"); + + testRunner.Should().NotBeNull(); + testRunner.Should().BeOfType(); + } + + [Fact] + public void GetTestRunner_should_be_able_to_create_a_TestRunner() + { + var testRunner = _testRunnerManager.GetTestRunner("0"); + + testRunner.Should().NotBeNull(); + testRunner.Should().BeOfType(); + } + + [Fact] + public void GetTestRunner_should_cache_instance() + { + var testRunner1 = _testRunnerManager.GetTestRunner("0"); + var testRunner2 = _testRunnerManager.GetTestRunner("0"); + + + testRunner1.Should().Be(testRunner2); + } + + [Fact] + public void Should_return_different_instances_for_different_thread_ids() + { + var testRunner1 = _testRunnerManager.GetTestRunner("0"); + var testRunner2 = _testRunnerManager.GetTestRunner("1"); + + testRunner1.Should().NotBe(testRunner2); + } + + class DisposableClass : IDisposable + { + public bool IsDisposed { get; private set; } + public void Dispose() + { + IsDisposed = true; + } + } + + [Fact] + public async Task Should_dispose_test_thread_container_at_after_test_run() + { + var testRunner1 = _testRunnerManager.GetTestRunner("0"); + var testRunner2 = _testRunnerManager.GetTestRunner("1"); + + var disposableClass1 = new DisposableClass(); + testRunner1.TestThreadContext.TestThreadContainer.RegisterInstanceAs(disposableClass1, dispose: true); + + var disposableClass2 = new DisposableClass(); + testRunner2.TestThreadContext.TestThreadContainer.RegisterInstanceAs(disposableClass2, dispose: true); + + await TestRunnerManager.OnTestRunEndAsync(_anAssembly); + + disposableClass1.IsDisposed.Should().BeTrue(); + disposableClass2.IsDisposed.Should().BeTrue(); + } +} \ No newline at end of file