add disposal tests and fix lzipstream

This commit is contained in:
Adam Hathcock
2026-01-06 13:13:34 +00:00
parent 48dbdbfed5
commit 64b209a772
2 changed files with 195 additions and 0 deletions

View File

@@ -45,10 +45,12 @@ public sealed class LZipStream : Stream, IStreamStack
private bool _finished;
private long _writeCount;
private readonly Stream? _originalStream;
public LZipStream(Stream stream, CompressionMode mode)
{
Mode = mode;
_originalStream = stream;
if (mode == CompressionMode.Decompress)
{
@@ -125,6 +127,10 @@ public sealed class LZipStream : Stream, IStreamStack
{
Finish();
_stream.Dispose();
if (Mode == CompressionMode.Compress)
{
_originalStream?.Dispose();
}
}
}

View File

@@ -0,0 +1,189 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.PPMd;
using SharpCompress.Compressors.Reduce;
using SharpCompress.Compressors.ZStandard;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class DisposalTests
{
private void VerifyStreamDisposal(
Func<Stream, bool, Stream> createStream,
bool supportsLeaveOpen = true
)
{
// 1. Test Dispose behavior (should dispose inner stream)
{
using var innerStream = new TestStream(new MemoryStream());
// createStream(stream, leaveOpen: false)
var stream = createStream(innerStream, false);
stream.Dispose();
// Some streams might not support disposal of inner stream (e.g. PpmdStream apparently)
// But for those that satisfy the pattern, we assert true.
Assert.True(
innerStream.IsDisposed,
"Stream should have been disposed when leaveOpen=false"
);
}
// 2. Test LeaveOpen behavior (should NOT dispose inner stream)
if (supportsLeaveOpen)
{
using var innerStream = new TestStream(new MemoryStream());
// createStream(stream, leaveOpen: true)
var stream = createStream(innerStream, true);
stream.Dispose();
Assert.False(
innerStream.IsDisposed,
"Stream should NOT have been disposed when leaveOpen=true"
);
}
}
private void VerifyAlwaysDispose(Func<Stream, Stream> createStream)
{
using var innerStream = new TestStream(new MemoryStream());
var stream = createStream(innerStream);
stream.Dispose();
Assert.True(innerStream.IsDisposed, "Stream should have been disposed (AlwaysDispose)");
}
private void VerifyNeverDispose(Func<Stream, Stream> createStream)
{
using var innerStream = new TestStream(new MemoryStream());
var stream = createStream(innerStream);
stream.Dispose();
Assert.False(innerStream.IsDisposed, "Stream should NOT have been disposed (NeverDispose)");
}
[Fact]
public void SourceStream_Disposal()
{
VerifyStreamDisposal(
(stream, leaveOpen) =>
new SourceStream(
stream,
i => null,
new ReaderOptions { LeaveStreamOpen = leaveOpen }
)
);
}
[Fact]
public void ProgressReportingStream_Disposal()
{
VerifyStreamDisposal(
(stream, leaveOpen) =>
new ProgressReportingStream(
stream,
new Progress<ProgressReport>(),
"",
0,
leaveOpen: leaveOpen
)
);
}
[Fact]
public void DataDescriptorStream_Disposal()
{
// DataDescriptorStream DOES dispose inner stream
VerifyAlwaysDispose(stream => new DataDescriptorStream(stream));
}
[Fact]
public void DeflateStream_Disposal()
{
// DeflateStream in SharpCompress always disposes inner stream
VerifyAlwaysDispose(stream => new DeflateStream(stream, CompressionMode.Compress));
}
[Fact]
public void GZipStream_Disposal()
{
// GZipStream in SharpCompress always disposes inner stream
VerifyAlwaysDispose(stream => new GZipStream(stream, CompressionMode.Compress));
}
[Fact]
public void LzwStream_Disposal()
{
VerifyStreamDisposal(
(stream, leaveOpen) =>
{
var lzw = new LzwStream(stream);
lzw.IsStreamOwner = !leaveOpen;
return lzw;
}
);
}
[Fact]
public void PpmdStream_Disposal()
{
// PpmdStream seems to not dispose inner stream based on code analysis
// It takes PpmdProperties which we need to mock or create.
var props = new PpmdProperties();
VerifyNeverDispose(stream => new PpmdStream(props, stream, false));
}
[Fact]
public void LzmaStream_Disposal()
{
// LzmaStream always disposes inner stream
// Need to provide valid properties to avoid crash in constructor (invalid window size)
// 5 bytes: 1 byte properties + 4 bytes dictionary size (little endian)
// Dictionary size = 1024 (0x400) -> 00 04 00 00
var lzmaProps = new byte[] { 0, 0, 4, 0, 0 };
VerifyAlwaysDispose(stream => new LzmaStream(lzmaProps, stream));
}
[Fact]
public void LZipStream_Disposal()
{
// LZipStream always disposes inner stream
// Use Compress mode to avoid need for valid input header
VerifyAlwaysDispose(stream => new LZipStream(stream, CompressionMode.Compress));
}
[Fact]
public void ReduceStream_Disposal()
{
// ReduceStream does not dispose inner stream
VerifyNeverDispose(stream => new ReduceStream(stream, 0, 0, 1));
}
[Fact]
public void ZStandard_CompressionStream_Disposal()
{
VerifyStreamDisposal(
(stream, leaveOpen) =>
new CompressionStream(stream, level: 0, bufferSize: 0, leaveOpen: leaveOpen)
);
}
[Fact]
public void ZStandard_DecompressionStream_Disposal()
{
VerifyStreamDisposal(
(stream, leaveOpen) =>
new DecompressionStream(
stream,
bufferSize: 0,
checkEndOfStream: false,
leaveOpen: leaveOpen
)
);
}
}