mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 23:15:20 +00:00
fix disposal and add tests
This commit is contained in:
@@ -158,18 +158,18 @@ public partial class SharpCompressStream
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
if (LeaveOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ThrowOnDispose)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Attempt to dispose of a {nameof(SharpCompressStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}"
|
||||
);
|
||||
}
|
||||
_isDisposed = true;
|
||||
await base.DisposeAsync();
|
||||
if (this.LeaveOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Stream.DisposeAsync();
|
||||
if (_buffer != null)
|
||||
{
|
||||
|
||||
@@ -123,8 +123,8 @@ public partial class SharpCompressStream : Stream, IStreamStack
|
||||
)
|
||||
{
|
||||
Stream = stream;
|
||||
this.LeaveOpen = leaveOpen;
|
||||
this.ThrowOnDispose = throwOnDispose;
|
||||
LeaveOpen = leaveOpen;
|
||||
ThrowOnDispose = throwOnDispose;
|
||||
_readOnly = !Stream.CanSeek;
|
||||
|
||||
((IStreamStack)this).SetBuffer(bufferSize, forceBuffer);
|
||||
@@ -145,19 +145,18 @@ public partial class SharpCompressStream : Stream, IStreamStack
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
if (LeaveOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ThrowOnDispose)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Attempt to dispose of a {nameof(SharpCompressStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}"
|
||||
);
|
||||
}
|
||||
_isDisposed = true;
|
||||
base.Dispose(disposing);
|
||||
if (this.LeaveOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (disposing)
|
||||
{
|
||||
Stream.Dispose();
|
||||
@@ -267,7 +266,7 @@ public partial class SharpCompressStream : Stream, IStreamStack
|
||||
targetPos = _internalPosition + offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
targetPos = this.Length + offset;
|
||||
targetPos = Length + offset;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Test.Mocks;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Streams;
|
||||
|
||||
/// <summary>
|
||||
/// Tests that verify Dispose and DisposeAsync behavior of SharpCompressStream.
|
||||
/// </summary>
|
||||
public class SharpCompressStreamDisposalTests
|
||||
{
|
||||
#region Dispose Tests (Synchronous)
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithLeaveOpenFalse_DisposesInnerStream()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(innerStream, leaveOpen: false);
|
||||
|
||||
// Act
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed, "Inner stream should be disposed when leaveOpen=false");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithLeaveOpenTrue_DoesNotDisposeInnerStream()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(innerStream, leaveOpen: true);
|
||||
|
||||
// Act
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.False(
|
||||
innerStream.IsDisposed,
|
||||
"Inner stream should NOT be disposed when leaveOpen=true"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithThrowOnDisposeFalse_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
throwOnDispose: false
|
||||
);
|
||||
|
||||
// Act & Assert (should not throw)
|
||||
sharpStream.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithThrowOnDisposeTrue_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
throwOnDispose: true
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => sharpStream.Dispose());
|
||||
Assert.Contains("ThrowOnDispose", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_MultipleTimes_OnlyDisposesOnce()
|
||||
{
|
||||
// Arrange
|
||||
var countingStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(countingStream, leaveOpen: false);
|
||||
|
||||
// Act
|
||||
sharpStream.Dispose();
|
||||
sharpStream.Dispose(); // Second dispose should be idempotent
|
||||
sharpStream.Dispose(); // Third dispose should be idempotent
|
||||
|
||||
// Assert - should not throw and inner stream should only be disposed once
|
||||
Assert.True(countingStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithBuffering_ReturnsBufferToPool()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x10000];
|
||||
var innerStream = new MemoryStream(data);
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: true,
|
||||
bufferSize: 0x1000
|
||||
);
|
||||
|
||||
// Get initial pool stats
|
||||
var initialRented = ArrayPool<byte>.Shared.ToString();
|
||||
|
||||
// Act
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert - we can't directly check pool state, but we verify no exceptions
|
||||
// and the stream is disposed properly
|
||||
Assert.True(sharpStream.CanRead == false || sharpStream.CanRead == true); // Check no exception on property access
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithoutBuffering_SuccessfullyDisposes()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
bufferSize: 0 // No buffering
|
||||
);
|
||||
|
||||
// Act
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithLeaveOpenTrueAndThrowOnDisposeTrue_SetsDisposedFlagButDoesNotThrow()
|
||||
{
|
||||
// Arrange - special case: LeaveOpen=true takes precedence, so no throw should occur
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: true,
|
||||
throwOnDispose: true
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
// According to code: LeaveOpen is checked before ThrowOnDispose,
|
||||
// so this should not throw
|
||||
sharpStream.Dispose();
|
||||
Assert.False(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_SetsFlagCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: true,
|
||||
throwOnDispose: false
|
||||
);
|
||||
|
||||
// Act
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert - check by attempting to use the stream (should not throw ObjectDisposedException)
|
||||
// Actually, SharpCompressStream delegates to inner stream, so we just check it doesn't crash
|
||||
Assert.NotNull(sharpStream);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithLeaveOpenTrueAndWithBuffer_DoesNotDisposeInnerStreamOrThrow()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x10000];
|
||||
var innerStream = new TestStream(new MemoryStream(data));
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: true,
|
||||
bufferSize: 0x2000
|
||||
);
|
||||
|
||||
// Act - read some data to fill buffer
|
||||
var buffer = new byte[100];
|
||||
sharpStream.Read(buffer, 0, 100);
|
||||
|
||||
// Dispose
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.False(
|
||||
innerStream.IsDisposed,
|
||||
"Inner stream should not be disposed when LeaveOpen=true"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DisposeAsync Tests (Asynchronous)
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithLeaveOpenFalse_DisposesInnerStream()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(innerStream, leaveOpen: false);
|
||||
|
||||
// Act
|
||||
await sharpStream.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed, "Inner stream should be disposed when leaveOpen=false");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithLeaveOpenTrue_DoesNotDisposeInnerStream()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(innerStream, leaveOpen: true);
|
||||
|
||||
// Act
|
||||
await sharpStream.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.False(
|
||||
innerStream.IsDisposed,
|
||||
"Inner stream should NOT be disposed when leaveOpen=true"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithThrowOnDisposeFalse_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
throwOnDispose: false
|
||||
);
|
||||
|
||||
// Act & Assert (should not throw)
|
||||
await sharpStream.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithThrowOnDisposeTrue_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
throwOnDispose: true
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => sharpStream.DisposeAsync().AsTask()
|
||||
);
|
||||
Assert.Contains("ThrowOnDispose", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_MultipleTimes_OnlyDisposesOnce()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(innerStream, leaveOpen: false);
|
||||
|
||||
// Act
|
||||
await sharpStream.DisposeAsync();
|
||||
await sharpStream.DisposeAsync(); // Second dispose should be idempotent
|
||||
await sharpStream.DisposeAsync(); // Third dispose should be idempotent
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithBuffering_ReturnsBufferToPool()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x10000];
|
||||
var innerStream = new MemoryStream(data);
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: true,
|
||||
bufferSize: 0x1000
|
||||
);
|
||||
|
||||
// Act
|
||||
await sharpStream.DisposeAsync();
|
||||
|
||||
// Assert - no exception thrown
|
||||
Assert.NotNull(sharpStream);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithoutBuffering_SuccessfullyDisposes()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
bufferSize: 0 // No buffering
|
||||
);
|
||||
|
||||
// Act
|
||||
await sharpStream.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithLeaveOpenTrueAndThrowOnDisposeTrue_SetsDisposedFlagButDoesNotThrow()
|
||||
{
|
||||
// Arrange - special case: LeaveOpen=true takes precedence, so no throw should occur
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: true,
|
||||
throwOnDispose: true
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
await sharpStream.DisposeAsync();
|
||||
Assert.False(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithLeaveOpenTrueAndWithBuffer_DoesNotDisposeInnerStreamOrThrow()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x10000];
|
||||
var innerStream = new TestStream(new MemoryStream(data));
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: true,
|
||||
bufferSize: 0x2000
|
||||
);
|
||||
|
||||
// Act - read some data to fill buffer
|
||||
var buffer = new byte[100];
|
||||
await sharpStream.ReadAsync(buffer, 0, 100);
|
||||
|
||||
// Dispose
|
||||
await sharpStream.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.False(
|
||||
innerStream.IsDisposed,
|
||||
"Inner stream should not be disposed when LeaveOpen=true"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_AfterReadWithBuffer_ProperlyReturnsBufferAndDisposesStream()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x100000];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = (byte)(i % 256);
|
||||
}
|
||||
|
||||
var innerStream = new TestStream(new MemoryStream(data));
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
bufferSize: 0x10000
|
||||
);
|
||||
|
||||
// Act - perform some reads to exercise the buffer
|
||||
var readBuffer = new byte[0x1000];
|
||||
await sharpStream.ReadAsync(readBuffer, 0, readBuffer.Length);
|
||||
await sharpStream.ReadAsync(readBuffer, 0, readBuffer.Length);
|
||||
|
||||
// Now dispose
|
||||
await sharpStream.DisposeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Integration Tests
|
||||
|
||||
[Fact]
|
||||
public void Dispose_UsingStatement_ProperlyDisposesStream()
|
||||
{
|
||||
// Arrange & Act
|
||||
TestStream innerStream;
|
||||
using (var sharpStream = new SharpCompressStream(
|
||||
innerStream = new TestStream(new MemoryStream()),
|
||||
leaveOpen: false
|
||||
))
|
||||
{
|
||||
// Use the stream
|
||||
var buffer = new byte[10];
|
||||
sharpStream.Read(buffer, 0, 0); // No-op read
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_UsingAsyncStatement_ProperlyDisposesStream()
|
||||
{
|
||||
// Arrange & Act
|
||||
TestStream innerStream;
|
||||
await using (var sharpStream = new SharpCompressStream(
|
||||
innerStream = new TestStream(new MemoryStream()),
|
||||
leaveOpen: false
|
||||
))
|
||||
{
|
||||
// Use the stream
|
||||
var buffer = new byte[10];
|
||||
await sharpStream.ReadAsync(buffer, 0, 0); // No-op read
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithCreateFactory_ProperlyDisposes()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = SharpCompressStream.Create(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
throwOnDispose: false,
|
||||
bufferSize: 0x1000
|
||||
);
|
||||
|
||||
// Act
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
public void Dispose_AfterSeekWithBuffer_ProperlyDisposesAndClearsBuffer()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x100000];
|
||||
var innerStream = new TestStream(new MemoryStream(data));
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
bufferSize: 0x10000
|
||||
);
|
||||
|
||||
// Act - perform seek operations
|
||||
sharpStream.Seek(0x50000, SeekOrigin.Begin);
|
||||
var buffer = new byte[100];
|
||||
sharpStream.Read(buffer, 0, 100);
|
||||
|
||||
// Dispose
|
||||
sharpStream.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithZeroBuffer_DoesNotCrash()
|
||||
{
|
||||
// Arrange
|
||||
var innerStream = new TestStream(new MemoryStream());
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
bufferSize: 0 // Zero buffer size
|
||||
);
|
||||
|
||||
// Act & Assert - should not crash
|
||||
sharpStream.Dispose();
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WithLargeBuffer_DoesNotCrash()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x1000000]; // 16MB data
|
||||
var innerStream = new TestStream(new MemoryStream(data));
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
bufferSize: 0x100000 // 1MB buffer
|
||||
);
|
||||
|
||||
// Act & Assert - should not crash
|
||||
sharpStream.Dispose();
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WithLargeBuffer_DoesNotCrash()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[0x1000000]; // 16MB data
|
||||
var innerStream = new TestStream(new MemoryStream(data));
|
||||
var sharpStream = new SharpCompressStream(
|
||||
innerStream,
|
||||
leaveOpen: false,
|
||||
bufferSize: 0x100000 // 1MB buffer
|
||||
);
|
||||
|
||||
// Act & Assert - should not crash
|
||||
await sharpStream.DisposeAsync();
|
||||
Assert.True(innerStream.IsDisposed);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user