Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions csharp/Platform.Disposables.Tests/DisposableBaseTests.cs
Original file line number Diff line number Diff line change
@@ -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<ObjectDisposedException>(() => 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<ObjectDisposedException>(() => 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);
}
}
}
142 changes: 142 additions & 0 deletions csharp/Platform.Disposables.Tests/DisposableClassTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading
Loading