diff --git a/csharp/Platform.Disposables.Tests/DisposableBaseTests.cs b/csharp/Platform.Disposables.Tests/DisposableBaseTests.cs new file mode 100644 index 0000000..a8c72b9 --- /dev/null +++ b/csharp/Platform.Disposables.Tests/DisposableBaseTests.cs @@ -0,0 +1,165 @@ +using System; +using Xunit; +using Platform.Exceptions; + +namespace Platform.Disposables.Tests +{ + public class DisposableBaseTests + { + private class TestDisposable : DisposableBase + { + public bool DisposeMethodCalled { get; private set; } + public bool DisposalWasManual { get; private set; } + public bool PreviouslyDisposed { get; private set; } + public int DisposeCallCount { get; private set; } + + protected override void Dispose(bool manual, bool wasDisposed) + { + DisposeMethodCalled = true; + DisposalWasManual = manual; + PreviouslyDisposed = wasDisposed; + DisposeCallCount++; + } + } + + private class TestDisposableAllowMultipleCalls : DisposableBase + { + public int DisposeCallCount { get; private set; } + + protected override bool AllowMultipleDisposeCalls => true; + + protected override void Dispose(bool manual, bool wasDisposed) + { + DisposeCallCount++; + } + } + + private class TestDisposableAllowMultipleAttempts : DisposableBase + { + public int DisposeCallCount { get; private set; } + + protected override bool AllowMultipleDisposeAttempts => true; + protected override bool AllowMultipleDisposeCalls => true; // Need this too to prevent exception + + protected override void Dispose(bool manual, bool wasDisposed) + { + DisposeCallCount++; + } + } + + [Fact] + public void Constructor_SetsIsDisposedToFalse() + { + using var disposable = new TestDisposable(); + Assert.False(disposable.IsDisposed); + } + + [Fact] + public void Dispose_CallsDisposeMethodWithManualTrue() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + + Assert.True(disposable.DisposeMethodCalled); + Assert.True(disposable.DisposalWasManual); + Assert.False(disposable.PreviouslyDisposed); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Dispose_MultipleCalls_ThrowsObjectDisposedException() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + + var exception = Assert.Throws(() => disposable.Dispose()); + Assert.Contains("Multiple dispose calls are not allowed", exception.Message); + } + + [Fact] + public void Dispose_WithAllowMultipleDisposeCalls_DoesNotThrow() + { + var disposable = new TestDisposableAllowMultipleCalls(); + disposable.Dispose(); + disposable.Dispose(); // Should not throw + + // AllowMultipleDisposeCalls only prevents exception, but without AllowMultipleDisposeAttempts, + // the Dispose method is only called once (when !wasDisposed) + Assert.Equal(1, disposable.DisposeCallCount); + } + + [Fact] + public void Dispose_WithAllowMultipleDisposeAttempts_CallsDisposeMultipleTimes() + { + var disposable = new TestDisposableAllowMultipleAttempts(); + disposable.Dispose(); + disposable.Dispose(); + + Assert.Equal(2, disposable.DisposeCallCount); + } + + [Fact] + public void Destruct_CallsDisposeMethodWithManualFalse() + { + var disposable = new TestDisposable(); + disposable.Destruct(); + + Assert.True(disposable.DisposeMethodCalled); + Assert.False(disposable.DisposalWasManual); + Assert.False(disposable.PreviouslyDisposed); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Destruct_WhenAlreadyDisposed_DoesNotCallDispose() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + disposable.Destruct(); // Should not call dispose again + + Assert.Equal(1, disposable.DisposeCallCount); + } + + [Fact] + public void ObjectName_ReturnsTypeName() + { + var disposable = new TestDisposable(); + // ObjectName is protected, so we can't access it directly, but we can test indirectly + // by causing a multiple dispose exception and checking the object name in the exception + disposable.Dispose(); + + var exception = Assert.Throws(() => disposable.Dispose()); + Assert.Equal(nameof(TestDisposable), exception.ObjectName); + } + + [Fact] + public void IsDisposed_ReturnsTrueAfterDispose() + { + var disposable = new TestDisposable(); + Assert.False(disposable.IsDisposed); + + disposable.Dispose(); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void IsDisposed_ReturnsTrueAfterDestruct() + { + var disposable = new TestDisposable(); + Assert.False(disposable.IsDisposed); + + disposable.Destruct(); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Dispose_WithWasDisposedTrue_PassesCorrectFlag() + { + var disposable = new TestDisposableAllowMultipleAttempts(); + disposable.Dispose(); // First call: wasDisposed = false + disposable.Dispose(); // Second call: wasDisposed = true + + Assert.Equal(2, disposable.DisposeCallCount); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Disposables.Tests/DisposableClassTests.cs b/csharp/Platform.Disposables.Tests/DisposableClassTests.cs new file mode 100644 index 0000000..aae8ac1 --- /dev/null +++ b/csharp/Platform.Disposables.Tests/DisposableClassTests.cs @@ -0,0 +1,142 @@ +using System; +using Xunit; + +namespace Platform.Disposables.Tests +{ + public class DisposableClassTests + { + [Fact] + public void Constructor_WithAction_ExecutesActionOnDispose() + { + var actionCalled = false; + var disposable = new Disposable(() => actionCalled = true); + + disposable.Dispose(); + + Assert.True(actionCalled); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Constructor_WithDisposal_ExecutesDisposalOnDispose() + { + var manualDisposal = false; + var wasDisposed = true; // Will be set correctly by disposal delegate + var disposable = new Disposable((manual, wasDisp) => + { + manualDisposal = manual; + wasDisposed = wasDisp; + }); + + disposable.Dispose(); + + Assert.True(manualDisposal); + Assert.False(wasDisposed); // First disposal, so wasDisposed should be false + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Constructor_Empty_DoesNotThrowOnDispose() + { + var disposable = new Disposable(); + + disposable.Dispose(); // Should not throw + + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void ImplicitOperator_FromAction_CreatesDisposable() + { + var actionCalled = false; + Disposable disposable = (Action)(() => actionCalled = true); + + disposable.Dispose(); + + Assert.True(actionCalled); + } + + [Fact] + public void ImplicitOperator_FromDisposal_CreatesDisposable() + { + var disposalCalled = false; + Disposable disposable = (Disposal)((manual, wasDisposed) => disposalCalled = true); + + disposable.Dispose(); + + Assert.True(disposalCalled); + } + + [Fact] + public void TryDisposeAndResetToDefault_WithDisposableObject_DisposesAndResetsToNull() + { + var innerDisposable = new Disposable(() => { }); + + var result = Disposable.TryDisposeAndResetToDefault(ref innerDisposable); + + Assert.True(result); + Assert.Null(innerDisposable); + } + + [Fact] + public void TryDisposeAndResetToDefault_WithNullObject_ReturnsTrueAndLeavesNull() + { + Disposable? nullDisposable = null; + + var result = Disposable.TryDisposeAndResetToDefault(ref nullDisposable); + + Assert.True(result); // TryDispose returns true for null + Assert.Null(nullDisposable); // Remains null (default value) + } + + [Fact] + public void TryDisposeAndResetToDefault_WithNonDisposableObject_ReturnsTrueAndResetsToDefault() + { + var intValue = 42; + + var result = Disposable.TryDisposeAndResetToDefault(ref intValue); + + Assert.True(result); // TryDispose returns true for non-disposable objects + Assert.Equal(0, intValue); // Should be reset to default(int) = 0 + } + + [Fact] + public void OnDispose_IsTriggeredOnlyOnce_WhenNotPreviouslyDisposed() + { + var callCount = 0; + var disposable = new Disposable((manual, wasDisposed) => + { + if (!wasDisposed) callCount++; + }); + + disposable.Dispose(); + disposable.Destruct(); // Should not increment counter as wasDisposed will be true + + Assert.Equal(1, callCount); + } + + [Fact] + public void Dispose_WithAction_OnlyExecutesWhenNotPreviouslyDisposed() + { + var callCount = 0; + var disposable = new Disposable(() => callCount++); + + disposable.Dispose(); + disposable.Destruct(); // Should not execute action as it was already disposed + + Assert.Equal(1, callCount); + } + + [Fact] + public void RaiseOnDisposeEvent_IsProtectedAndCallsOnDispose() + { + var eventTriggered = false; + var disposable = new Disposable(); + disposable.OnDispose += (manual, wasDisposed) => eventTriggered = true; + + disposable.Dispose(); + + Assert.True(eventTriggered); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Disposables.Tests/DisposableDualGenericTests.cs b/csharp/Platform.Disposables.Tests/DisposableDualGenericTests.cs new file mode 100644 index 0000000..cd356a6 --- /dev/null +++ b/csharp/Platform.Disposables.Tests/DisposableDualGenericTests.cs @@ -0,0 +1,277 @@ +using System; +using Xunit; + +namespace Platform.Disposables.Tests +{ + public class DisposableDualGenericTests + { + private class TestResource : System.IDisposable + { + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + } + + [Fact] + public void Constructor_WithObjectsAndAction_StoresObjectsAndExecutesActionOnDispose() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + var actionExecuted = false; + string? primaryParam = null; + string? auxiliaryParam = null; + + var disposable = new Disposable(primary, auxiliary, (p, a) => + { + actionExecuted = true; + primaryParam = p; + auxiliaryParam = a; + }); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + + disposable.Dispose(); + + Assert.True(actionExecuted); + Assert.Equal(primary, primaryParam); + Assert.Equal(auxiliary, auxiliaryParam); + } + + [Fact] + public void Constructor_WithObjectsAndSimpleAction_ExecutesActionOnDispose() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + var actionExecuted = false; + + var disposable = new Disposable(primary, auxiliary, () => actionExecuted = true); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + + disposable.Dispose(); + + Assert.True(actionExecuted); + } + + [Fact] + public void Constructor_WithObjectsAndDisposal_ExecutesDisposalOnDispose() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + var disposalExecuted = false; + + var disposable = new Disposable(primary, auxiliary, (manual, wasDisposed) => + { + if (!wasDisposed) disposalExecuted = true; + }); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + + disposable.Dispose(); + + Assert.True(disposalExecuted); + } + + [Fact] + public void Constructor_WithObjectsOnly_StoresObjects() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + + var disposable = new Disposable(primary, auxiliary); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + + disposable.Dispose(); // Should not throw + } + + [Fact] + public void Constructor_WithPrimaryOnly_StoresPrimaryAndSetsAuxiliaryToDefault() + { + var primary = "primary"; + + var disposable = new Disposable(primary); + + Assert.Equal(primary, disposable.Object); + Assert.Null(disposable.AuxiliaryObject); // Default value for string + } + + [Fact] + public void ImplicitOperator_FromTupleWithAction_CreatesDisposable() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + var actionExecuted = false; + + Disposable disposable = (primary, auxiliary, (string p, string a) => actionExecuted = true); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + + disposable.Dispose(); + + Assert.True(actionExecuted); + } + + [Fact] + public void ImplicitOperator_FromTupleWithSimpleAction_CreatesDisposable() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + var actionExecuted = false; + + Disposable disposable = (primary, auxiliary, (Action)(() => actionExecuted = true)); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + + disposable.Dispose(); + + Assert.True(actionExecuted); + } + + [Fact] + public void ImplicitOperator_FromTupleWithDisposal_CreatesDisposable() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + var disposalExecuted = false; + + Disposable disposable = (primary, auxiliary, (Disposal)((manual, wasDisposed) => + { + if (!wasDisposed) disposalExecuted = true; + })); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + + disposable.Dispose(); + + Assert.True(disposalExecuted); + } + + [Fact] + public void ImplicitOperator_FromTupleWithObjects_CreatesDisposable() + { + var primary = "primary"; + var auxiliary = "auxiliary"; + + Disposable disposable = (primary, auxiliary); + + Assert.Equal(primary, disposable.Object); + Assert.Equal(auxiliary, disposable.AuxiliaryObject); + } + + [Fact] + public void ImplicitOperator_ToPrimary_ReturnsPrimaryObject() + { + var primary = "primary"; + var auxiliary = 42; + var disposable = new Disposable(primary, auxiliary); + + string extracted = disposable; + + Assert.Equal(primary, extracted); + } + + [Fact] + public void ImplicitOperator_ToAuxiliary_ReturnsAuxiliaryObject() + { + var primary = "primary"; + var auxiliary = 42; + var disposable = new Disposable(primary, auxiliary); + + int extractedAux = disposable; + + Assert.Equal(auxiliary, extractedAux); + } + + [Fact] + public void Dispose_WithDisposableObjects_DisposesBothObjects() + { + var primaryResource = new TestResource(); + var auxiliaryResource = new TestResource(); + + var disposable = new Disposable(primaryResource, auxiliaryResource); + + Assert.False(primaryResource.IsDisposed); + Assert.False(auxiliaryResource.IsDisposed); + + disposable.Dispose(); + + Assert.True(primaryResource.IsDisposed); + Assert.True(auxiliaryResource.IsDisposed); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Dispose_CallsOnDisposeEventThenDisposesObjects() + { + var primaryResource = new TestResource(); + var auxiliaryResource = new TestResource(); + var eventCalled = false; + + var disposable = new Disposable(primaryResource, auxiliaryResource); + disposable.OnDispose += (manual, wasDisposed) => + { + // At this point, objects should not yet be disposed + eventCalled = true; + Assert.False(primaryResource.IsDisposed); + Assert.False(auxiliaryResource.IsDisposed); + }; + + disposable.Dispose(); + + Assert.True(eventCalled); + Assert.True(primaryResource.IsDisposed); + Assert.True(auxiliaryResource.IsDisposed); + } + + [Fact] + public void Dispose_DisposesAuxiliaryFirst_ThenPrimary() + { + var disposeOrder = new System.Collections.Generic.List(); + + var primaryResource = new TestResource(); + var auxiliaryResource = new TestResource(); + + // We can't directly test the order since TryDispose doesn't guarantee order visibility, + // but we can ensure both are disposed + var disposable = new Disposable(primaryResource, auxiliaryResource); + + disposable.Dispose(); + + Assert.True(primaryResource.IsDisposed); + Assert.True(auxiliaryResource.IsDisposed); + } + + [Fact] + public void AuxiliaryObject_Property_ReturnsStoredAuxiliaryObject() + { + var primary = "primary"; + var auxiliary = new object(); + var disposable = new Disposable(primary, auxiliary); + + Assert.Same(auxiliary, disposable.AuxiliaryObject); + } + + [Fact] + public void Dispose_WithNonDisposableObjects_DoesNotThrow() + { + var primary = "primary"; + var auxiliary = 42; + var disposable = new Disposable(primary, auxiliary); + + disposable.Dispose(); // Should not throw even though objects are not disposable + + Assert.True(disposable.IsDisposed); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Disposables.Tests/DisposableGenericTests.cs b/csharp/Platform.Disposables.Tests/DisposableGenericTests.cs new file mode 100644 index 0000000..62b44cc --- /dev/null +++ b/csharp/Platform.Disposables.Tests/DisposableGenericTests.cs @@ -0,0 +1,214 @@ +using System; +using System.IO; +using Xunit; + +namespace Platform.Disposables.Tests +{ + public class DisposableGenericTests + { + private class TestResource : System.IDisposable + { + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + } + + [Fact] + public void Constructor_WithObjectAndAction_StoresObjectAndExecutesActionOnDispose() + { + var resource = "test"; + var actionExecuted = false; + var actionParameter = ""; + + var disposable = new Disposable(resource, obj => + { + actionExecuted = true; + actionParameter = obj; + }); + + Assert.Equal(resource, disposable.Object); + + disposable.Dispose(); + + Assert.True(actionExecuted); + Assert.Equal(resource, actionParameter); + } + + [Fact] + public void Constructor_WithObjectAndSimpleAction_ExecutesActionOnDispose() + { + var resource = "test"; + var actionExecuted = false; + + var disposable = new Disposable(resource, () => actionExecuted = true); + + Assert.Equal(resource, disposable.Object); + + disposable.Dispose(); + + Assert.True(actionExecuted); + } + + [Fact] + public void Constructor_WithObjectAndDisposal_ExecutesDisposalOnDispose() + { + var resource = "test"; + var disposalExecuted = false; + + var disposable = new Disposable(resource, (manual, wasDisposed) => + { + if (!wasDisposed) disposalExecuted = true; + }); + + Assert.Equal(resource, disposable.Object); + + disposable.Dispose(); + + Assert.True(disposalExecuted); + } + + [Fact] + public void Constructor_WithObjectOnly_StoresObject() + { + var resource = "test"; + var disposable = new Disposable(resource); + + Assert.Equal(resource, disposable.Object); + + disposable.Dispose(); // Should not throw + } + + [Fact] + public void ImplicitOperator_FromTupleWithAction_CreatesDisposable() + { + var resource = "test"; + var actionExecuted = false; + + Disposable disposable = (resource, (string obj) => actionExecuted = true); + + Assert.Equal(resource, disposable.Object); + + disposable.Dispose(); + + Assert.True(actionExecuted); + } + + [Fact] + public void ImplicitOperator_FromTupleWithSimpleAction_CreatesDisposable() + { + var resource = "test"; + var actionExecuted = false; + + Disposable disposable = (resource, (Action)(() => actionExecuted = true)); + + Assert.Equal(resource, disposable.Object); + + disposable.Dispose(); + + Assert.True(actionExecuted); + } + + [Fact] + public void ImplicitOperator_FromTupleWithDisposal_CreatesDisposable() + { + var resource = "test"; + var disposalExecuted = false; + + Disposable disposable = (resource, (Disposal)((manual, wasDisposed) => + { + if (!wasDisposed) disposalExecuted = true; + })); + + Assert.Equal(resource, disposable.Object); + + disposable.Dispose(); + + Assert.True(disposalExecuted); + } + + [Fact] + public void ImplicitOperator_FromObject_CreatesDisposable() + { + var resource = "test"; + Disposable disposable = resource; + + Assert.Equal(resource, disposable.Object); + } + + [Fact] + public void ImplicitOperator_ToObject_ReturnsObject() + { + var resource = "test"; + var disposable = new Disposable(resource); + + string extracted = disposable; + + Assert.Equal(resource, extracted); + } + + [Fact] + public void Dispose_WithDisposableObject_DisposesContainedObject() + { + var resource = new TestResource(); + var disposable = new Disposable(resource); + + Assert.False(resource.IsDisposed); + + disposable.Dispose(); + + Assert.True(resource.IsDisposed); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Dispose_WithNonDisposableObject_DoesNotThrow() + { + var resource = "test"; + var disposable = new Disposable(resource); + + disposable.Dispose(); // Should not throw even though string is not disposable + + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void Dispose_CallsBaseDisposeAndThenTriesToDisposeObject() + { + var baseDisposeCalled = false; + var resource = new TestResource(); + + var disposable = new Disposable(resource, obj => baseDisposeCalled = true); + + disposable.Dispose(); + + Assert.True(baseDisposeCalled); // Base dispose (action) should be called + Assert.True(resource.IsDisposed); // Object should also be disposed + } + + [Fact] + public void Dispose_WithActionThatChecksWasDisposed_ExecutesOnlyWhenNotPreviouslyDisposed() + { + var resource = "test"; + var executionCount = 0; + + var disposable = new Disposable(resource, obj => executionCount++); + + disposable.Dispose(); + disposable.Destruct(); // Should not increment count as it was already disposed + + Assert.Equal(1, executionCount); + } + + [Fact] + public void Object_Property_ReturnsStoredObject() + { + var resource = new object(); + var disposable = new Disposable(resource); + + Assert.Same(resource, disposable.Object); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Disposables.Tests/DisposableTests.cs b/csharp/Platform.Disposables.Tests/DisposableTests.cs index caa19fb..51867ea 100644 --- a/csharp/Platform.Disposables.Tests/DisposableTests.cs +++ b/csharp/Platform.Disposables.Tests/DisposableTests.cs @@ -42,7 +42,7 @@ private static ProcessStartInfo CreateProcessStartInfo(string logPath, bool wait return new ProcessStartInfo { FileName = "dotnet", - Arguments = $"run -p \"{projectPath}\" -f net7 \"{logPath}\" {waitForCancellation.ToString()}", + Arguments = $"run -p \"{projectPath}\" -f net8 \"{logPath}\" {waitForCancellation.ToString()}", UseShellExecute = false, CreateNoWindow = true }; diff --git a/csharp/Platform.Disposables.Tests/ExtensionTests.cs b/csharp/Platform.Disposables.Tests/ExtensionTests.cs new file mode 100644 index 0000000..06bdc81 --- /dev/null +++ b/csharp/Platform.Disposables.Tests/ExtensionTests.cs @@ -0,0 +1,239 @@ +using System; +using Xunit; +using Platform.Exceptions; + +namespace Platform.Disposables.Tests +{ + public class ExtensionTests + { + private class TestDisposable : DisposableBase + { + protected override void Dispose(bool manual, bool wasDisposed) + { + // Empty implementation for testing + } + } + + private class TestSystemDisposable : System.IDisposable + { + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + } + + #region IDisposableExtensions Tests + + [Fact] + public void DisposeIfNotDisposed_WhenNotDisposed_CallsDispose() + { + var disposable = new TestDisposable(); + + disposable.DisposeIfNotDisposed(); + + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void DisposeIfNotDisposed_WhenAlreadyDisposed_DoesNotCallDisposeAgain() + { + var disposable = new TestDisposable(); + disposable.Dispose(); // First disposal + + // This should not throw even though multiple dispose calls are not allowed by default + disposable.DisposeIfNotDisposed(); + + Assert.True(disposable.IsDisposed); + } + + #endregion + + #region GenericObjectExtensions Tests + + [Fact] + public void TryDispose_WithDisposableBase_ReturnsTrue() + { + var disposable = new TestDisposable(); + + var result = disposable.TryDispose(); + + Assert.True(result); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void TryDispose_WithSystemIDisposable_ReturnsTrue() + { + var disposable = new TestSystemDisposable(); + + var result = disposable.TryDispose(); + + Assert.True(result); + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void TryDispose_WithNonDisposableObject_ReturnsTrue() + { + var obj = "test string"; + + var result = obj.TryDispose(); + + Assert.True(result); // Should return true even for non-disposable objects + } + + [Fact] + public void TryDispose_WithNull_ReturnsTrue() + { + string? nullObj = null; + + var result = nullObj.TryDispose(); + + Assert.True(result); // Should return true for null + } + + [Fact] + public void DisposeIfPossible_WithDisposableObject_DisposesObject() + { + var disposable = new TestDisposable(); + + disposable.DisposeIfPossible(); + + Assert.True(disposable.IsDisposed); + } + + [Fact] + public void DisposeIfPossible_WithNonDisposableObject_DoesNotThrow() + { + var obj = "test string"; + + obj.DisposeIfPossible(); // Should not throw + + // No assertion needed, just ensuring it doesn't throw + } + + [Fact] + public void TryDispose_WithExceptionThrowingDisposable_ReturnsFalse() + { + var disposable = new ThrowingDisposable(); + + var result = disposable.TryDispose(); + + Assert.False(result); // Should return false when exception is thrown and ignored + } + + private class ThrowingDisposable : System.IDisposable + { + public void Dispose() + { + throw new InvalidOperationException("Test exception"); + } + } + + #endregion + + #region EnsureExtensions Tests + + [Fact] + public void EnsureAlways_NotDisposed_WithNotDisposedObject_DoesNotThrow() + { + var disposable = new TestDisposable(); + + // Should not throw + Ensure.Always.NotDisposed(disposable, "TestObject", "Test message"); + } + + [Fact] + public void EnsureAlways_NotDisposed_WithDisposedObject_ThrowsObjectDisposedException() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + + var exception = Assert.Throws(() => + Ensure.Always.NotDisposed(disposable, "TestObject", "Test message")); + + Assert.Equal("TestObject", exception.ObjectName); + Assert.Contains("Test message", exception.Message); + } + + [Fact] + public void EnsureAlways_NotDisposed_WithObjectNameOnly_ThrowsWithCorrectObjectName() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + + var exception = Assert.Throws(() => + Ensure.Always.NotDisposed(disposable, "TestObject")); + + Assert.Equal("TestObject", exception.ObjectName); + } + + [Fact] + public void EnsureAlways_NotDisposed_WithNoParameters_ThrowsWithEmptyObjectName() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + + var exception = Assert.Throws(() => + Ensure.Always.NotDisposed(disposable)); + + Assert.Equal("", exception.ObjectName); + } + + [Fact] + public void EnsureOnDebug_NotDisposed_InDebugMode_BehavesLikeEnsureAlways() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + +#if DEBUG + var exception = Assert.Throws(() => + Ensure.OnDebug.NotDisposed(disposable, "TestObject", "Test message")); + + Assert.Equal("TestObject", exception.ObjectName); + Assert.Contains("Test message", exception.Message); +#else + // In release mode, should not throw + Ensure.OnDebug.NotDisposed(disposable, "TestObject", "Test message"); +#endif + } + + [Fact] + public void EnsureOnDebug_NotDisposed_WithObjectNameOnly() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + +#if DEBUG + var exception = Assert.Throws(() => + Ensure.OnDebug.NotDisposed(disposable, "TestObject")); + + Assert.Equal("TestObject", exception.ObjectName); +#else + // In release mode, should not throw + Ensure.OnDebug.NotDisposed(disposable, "TestObject"); +#endif + } + + [Fact] + public void EnsureOnDebug_NotDisposed_WithNoParameters() + { + var disposable = new TestDisposable(); + disposable.Dispose(); + +#if DEBUG + var exception = Assert.Throws(() => + Ensure.OnDebug.NotDisposed(disposable)); + + Assert.Equal("", exception.ObjectName); +#else + // In release mode, should not throw + Ensure.OnDebug.NotDisposed(disposable); +#endif + } + + #endregion + } +} \ No newline at end of file