revamped and added SC stream tests

This commit is contained in:
Adam Hathcock
2026-02-13 12:44:12 +00:00
parent 69a434b0e7
commit 76352df852
13 changed files with 1498 additions and 1789 deletions

View File

@@ -1,905 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamAsyncTest
{
[Fact]
public async ValueTask TestRewindAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
stream.Rewind(true);
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false));
}
[Fact]
public async ValueTask TestIncompleteRewindAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
stream.Rewind(true);
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false));
}
[Fact]
public async ValueTask TestRecordingAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
stream.Rewind(false);
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
}
[Fact]
public async ValueTask TestAsyncProducesSameResultAsSync()
{
var testData = new byte[100 * 4];
for (int i = 0; i < 100; i++)
{
var bytes = BitConverter.GetBytes(i);
Array.Copy(bytes, 0, testData, i * 4, 4);
}
byte[] syncResult;
byte[] asyncResult;
var ms1 = new MemoryStream(testData);
using (var stream = new SharpCompressStream(ms1))
{
syncResult = ReadAllSync(stream);
}
var ms2 = new MemoryStream(testData);
using (var stream = new SharpCompressStream(ms2))
{
asyncResult = await ReadAllAsync(stream).ConfigureAwait(false);
}
Assert.Equal(syncResult, asyncResult);
}
[Fact]
public async ValueTask TestAsyncWithRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
for (int i = 0; i < 50; i++)
{
bw.Write(i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var buffer = new byte[8];
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(0, BitConverter.ToInt32(buffer, 0));
Assert.Equal(1, BitConverter.ToInt32(buffer, 4));
stream.Rewind();
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(0, BitConverter.ToInt32(buffer, 0));
Assert.Equal(1, BitConverter.ToInt32(buffer, 4));
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(2, BitConverter.ToInt32(buffer, 0));
Assert.Equal(3, BitConverter.ToInt32(buffer, 4));
}
[Fact]
public async ValueTask TestAsyncCancellationSupport()
{
var ms = new MemoryStream(new byte[10000]);
ms.Position = 0;
var stream = new SharpCompressStream(ms);
var cts = new CancellationTokenSource();
var buffer = new byte[4096];
// Just verify that cancellation token can be passed without throwing
int bytesRead = await stream
.ReadAsync(buffer, 0, buffer.Length, cts.Token)
.ConfigureAwait(false);
Assert.Equal(buffer.Length, bytesRead);
}
[Fact]
public async ValueTask TestAsyncEmptyBuffer()
{
var ms = new MemoryStream();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
var buffer = new byte[0];
int bytesRead = await stream.ReadAsync(buffer, 0, 0).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask TestAsyncMultipleReads()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
for (int i = 0; i < 50; i++)
{
bw.Write(i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
var totalData = new byte[50 * 4];
var buffer = new byte[8];
int offset = 0;
int bytesRead;
while (
(bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0
)
{
Array.Copy(buffer, 0, totalData, offset, bytesRead);
offset += bytesRead;
}
Assert.Equal(50 * 4, offset);
Assert.Equal(0, BitConverter.ToInt32(totalData, 0));
Assert.Equal(49, BitConverter.ToInt32(totalData, 49 * 4));
}
[Fact]
public async ValueTask TestAsyncReturnsZeroAtEndOfStream()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
var buffer = new byte[4096];
int bytesRead;
while (
(bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0
) { }
Assert.Equal(0, bytesRead);
bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask TestAsyncPosition()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
for (int i = 0; i < 10; i++)
{
bw.Write(i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
Assert.Equal(0, stream.Position);
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(4, stream.Position);
stream.StartRecording();
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(8, stream.Position);
stream.Rewind();
Assert.Equal(4, stream.Position);
}
#if !LEGACY_DOTNET
[Fact]
public async ValueTask TestAsyncMemoryCancellationSupport()
{
var ms = new MemoryStream(new byte[10000]);
ms.Position = 0;
var stream = new SharpCompressStream(ms);
var cts = new CancellationTokenSource();
var buffer = new byte[4096];
// Just verify that cancellation token can be passed without throwing
int bytesRead = await stream.ReadAsync(buffer.AsMemory(), cts.Token).ConfigureAwait(false);
Assert.Equal(buffer.Length, bytesRead);
}
[Fact]
public async ValueTask TestAsyncMemoryEmptyBuffer()
{
var ms = new MemoryStream();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
var buffer = Memory<byte>.Empty;
int bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask TestAsyncMemoryMultipleReads()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
for (int i = 0; i < 50; i++)
{
bw.Write(i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
var totalData = new byte[50 * 4];
var buffer = new byte[8];
int offset = 0;
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false)) > 0)
{
Array.Copy(buffer, 0, totalData, offset, bytesRead);
offset += bytesRead;
}
Assert.Equal(50 * 4, offset);
Assert.Equal(0, BitConverter.ToInt32(totalData, 0));
Assert.Equal(49, BitConverter.ToInt32(totalData, 49 * 4));
}
#endif
private static async Task<int> ReadInt32Async(Stream stream)
{
var buffer = new byte[4];
var bytesRead = await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
if (bytesRead != 4)
{
throw new EndOfStreamException();
}
return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
}
#if !LEGACY_DOTNET
private static async ValueTask<int> ReadInt32AsyncMemory(Stream stream)
{
var buffer = new byte[4];
var bytesRead = await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false);
if (bytesRead != 4)
{
throw new EndOfStreamException();
}
return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
}
#endif
private static byte[] ReadAllSync(SharpCompressStream stream)
{
var result = new List<byte>();
var buffer = new byte[4096];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
for (int i = 0; i < bytesRead; i++)
{
result.Add(buffer[i]);
}
}
return result.ToArray();
}
private static async Task<byte[]> ReadAllAsync(SharpCompressStream stream)
{
var result = new List<byte>();
var buffer = new byte[4096];
int bytesRead;
while (
(bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0
)
{
for (int i = 0; i < bytesRead; i++)
{
result.Add(buffer[i]);
}
}
return result.ToArray();
}
#if !LEGACY_DOTNET
private static async ValueTask<byte[]> ReadAllAsyncMemory(SharpCompressStream stream)
{
var result = new List<byte>();
var buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false)) > 0)
{
for (int i = 0; i < bytesRead; i++)
{
result.Add(buffer[i]);
}
}
return result.ToArray();
}
#endif
[Fact]
public async ValueTask TestStopRecordingAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
stream.StopRecording();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.False(stream.IsRecording);
}
[Fact]
public async ValueTask TestStopRecordingNoFurtherBufferingAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var buffer = new byte[8];
await stream.ReadAsync(buffer, 0, 8).ConfigureAwait(false);
stream.StopRecording();
await stream.ReadAsync(buffer, 0, 8).ConfigureAwait(false);
Assert.Equal(BitConverter.GetBytes(1), buffer.Take(4).ToArray());
Assert.Equal(BitConverter.GetBytes(2), buffer.Skip(4).Take(4).ToArray());
int bytesRead = await stream.ReadAsync(buffer, 0, 8).ConfigureAwait(false);
Assert.Equal(8, bytesRead);
Assert.False(stream.IsRecording);
}
[Fact]
public async ValueTask TestStopRecordingThenRewindAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Write(8);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
// Read first 4 values (gets buffered)
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
// Stop recording
stream.StopRecording();
Assert.False(stream.IsRecording);
// Rewind to start of buffer
stream.Rewind(true);
// Should be able to read from buffer again
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
// Continue reading remaining data from underlying stream
Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(8, await ReadInt32Async(stream).ConfigureAwait(false));
}
[Fact]
public async ValueTask TestMultipleRewindsAfterStopRecordingAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Write(8);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
// Read first 4 values (gets buffered)
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
// Stop recording
stream.StopRecording();
Assert.False(stream.IsRecording);
// First rewind - read all buffered data, then continue with underlying stream
stream.Rewind();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
// Second rewind - should still be able to read from buffer
stream.Rewind();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
// Third rewind - still works
stream.Rewind();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false));
Assert.Equal(8, await ReadInt32Async(stream).ConfigureAwait(false));
}
[Fact]
public async ValueTask TestStopRecordingTwiceThrowsAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false));
// First StopRecording should succeed
stream.StopRecording();
Assert.False(stream.IsRecording);
// Second StopRecording should throw
Assert.Throws<InvalidOperationException>(() => stream.StopRecording());
}
[Fact]
public async ValueTask TestReadMoreThanBufferSizeAfterRewindAsync()
{
// This test verifies the fix for the bug where reading more bytes than
// are in the buffer after a rewind would only return the buffered bytes
// instead of continuing to read from the underlying stream.
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 29 bytes (simulating Arc header)
for (int i = 0; i < 29; i++)
{
bw.Write((byte)i);
}
// Write 5252 bytes (simulating Arc compressed data)
for (int i = 0; i < 5252; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Simulate factory detection: record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
int probeRead = await stream.ReadAsync(probeBuffer, 0, 512).ConfigureAwait(false);
Assert.Equal(512, probeRead);
// Stop recording and rewind (simulates what ReaderFactory does)
stream.Rewind(true);
// Read header (29 bytes) - should come from buffer
var headerBuffer = new byte[29];
int headerRead = await stream.ReadAsync(headerBuffer, 0, 29).ConfigureAwait(false);
Assert.Equal(29, headerRead);
// Read compressed data (5252 bytes) - buffer has 483 bytes left,
// but we need 5252 bytes. This should read all 5252 bytes, not just 483.
var dataBuffer = new byte[5252];
int dataRead = await stream.ReadAsync(dataBuffer, 0, 5252).ConfigureAwait(false);
Assert.Equal(5252, dataRead);
// Verify we read the correct data
for (int i = 0; i < 5252; i++)
{
Assert.Equal((byte)(i % 256), dataBuffer[i]);
}
// Verify stream position is correct (29 + 5252 = 5281)
Assert.Equal(5281, stream.Position);
}
[Fact]
public async ValueTask TestReadExactlyBufferSizeAfterRewindAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 1024 bytes
for (int i = 0; i < 1024; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
await stream.ReadAsync(probeBuffer, 0, 512).ConfigureAwait(false);
stream.Rewind(true);
// Read exactly the buffer size (512 bytes)
var buffer = new byte[512];
int bytesRead = await stream.ReadAsync(buffer, 0, 512).ConfigureAwait(false);
Assert.Equal(512, bytesRead);
// Verify we read the correct data
for (int i = 0; i < 512; i++)
{
Assert.Equal((byte)(i % 256), buffer[i]);
}
}
[Fact]
public async ValueTask TestReadLessThanBufferSizeAfterRewindAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 1024 bytes
for (int i = 0; i < 1024; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
await stream.ReadAsync(probeBuffer, 0, 512).ConfigureAwait(false);
stream.Rewind(true);
// Read less than buffer size (256 bytes)
var buffer = new byte[256];
int bytesRead = await stream.ReadAsync(buffer, 0, 256).ConfigureAwait(false);
Assert.Equal(256, bytesRead);
// Verify we read the correct data
for (int i = 0; i < 256; i++)
{
Assert.Equal((byte)(i % 256), buffer[i]);
}
}
[Fact]
public async ValueTask TestMultipleReadsExceedingBufferAfterRewindAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 2048 bytes
for (int i = 0; i < 2048; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
await stream.ReadAsync(probeBuffer, 0, 512).ConfigureAwait(false);
stream.Rewind(true);
// Read in chunks that will exceed the buffer
var buffer = new byte[800];
// First read: 800 bytes (512 from buffer + 288 from underlying stream)
int bytesRead1 = await stream.ReadAsync(buffer, 0, 800).ConfigureAwait(false);
Assert.Equal(800, bytesRead1);
// Second read: 800 bytes (all from underlying stream)
int bytesRead2 = await stream.ReadAsync(buffer, 0, 800).ConfigureAwait(false);
Assert.Equal(800, bytesRead2);
// Third read: remaining 448 bytes
int bytesRead3 = await stream.ReadAsync(buffer, 0, 800).ConfigureAwait(false);
Assert.Equal(448, bytesRead3);
// Verify stream position
Assert.Equal(2048, stream.Position);
}
[Fact]
public async ValueTask TestReadPartiallyFromBufferThenUnderlyingStreamAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 1000 bytes with specific pattern
for (int i = 0; i < 1000; i++)
{
bw.Write((byte)i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 100 bytes
stream.StartRecording();
var probeBuffer = new byte[100];
await stream.ReadAsync(probeBuffer, 0, 100).ConfigureAwait(false);
stream.Rewind(true);
// Read 50 bytes (from buffer)
var buffer1 = new byte[50];
int read1 = await stream.ReadAsync(buffer1, 0, 50).ConfigureAwait(false);
Assert.Equal(50, read1);
for (int i = 0; i < 50; i++)
{
Assert.Equal((byte)i, buffer1[i]);
}
// Read 150 bytes (50 from buffer + 100 from underlying stream)
var buffer2 = new byte[150];
int read2 = await stream.ReadAsync(buffer2, 0, 150).ConfigureAwait(false);
Assert.Equal(150, read2);
for (int i = 0; i < 150; i++)
{
Assert.Equal((byte)(i + 50), buffer2[i]);
}
// Verify position
Assert.Equal(200, stream.Position);
}
#if !LEGACY_DOTNET
[Fact]
public async ValueTask TestReadMoreThanBufferSizeAfterRewindMemoryAsync()
{
// Same as TestReadMoreThanBufferSizeAfterRewindAsync but using Memory<byte>
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 29 bytes (simulating Arc header)
for (int i = 0; i < 29; i++)
{
bw.Write((byte)i);
}
// Write 5252 bytes (simulating Arc compressed data)
for (int i = 0; i < 5252; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Simulate factory detection: record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
int probeRead = await stream.ReadAsync(probeBuffer.AsMemory()).ConfigureAwait(false);
Assert.Equal(512, probeRead);
// Stop recording and rewind (simulates what ReaderFactory does)
stream.Rewind(true);
// Read header (29 bytes) - should come from buffer
var headerBuffer = new byte[29];
int headerRead = await stream.ReadAsync(headerBuffer.AsMemory()).ConfigureAwait(false);
Assert.Equal(29, headerRead);
// Read compressed data (5252 bytes) - buffer has 483 bytes left,
// but we need 5252 bytes. This should read all 5252 bytes, not just 483.
var dataBuffer = new byte[5252];
int dataRead = await stream.ReadAsync(dataBuffer.AsMemory()).ConfigureAwait(false);
Assert.Equal(5252, dataRead);
// Verify we read the correct data
for (int i = 0; i < 5252; i++)
{
Assert.Equal((byte)(i % 256), dataBuffer[i]);
}
// Verify stream position is correct (29 + 5252 = 5281)
Assert.Equal(5281, stream.Position);
}
[Fact]
public async ValueTask TestMultipleReadsExceedingBufferAfterRewindMemoryAsync()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 2048 bytes
for (int i = 0; i < 2048; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
await stream.ReadAsync(probeBuffer.AsMemory()).ConfigureAwait(false);
stream.Rewind(true);
// Read in chunks that will exceed the buffer
var buffer = new byte[800];
// First read: 800 bytes (512 from buffer + 288 from underlying stream)
int bytesRead1 = await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false);
Assert.Equal(800, bytesRead1);
// Second read: 800 bytes (all from underlying stream)
int bytesRead2 = await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false);
Assert.Equal(800, bytesRead2);
// Third read: remaining 448 bytes
int bytesRead3 = await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false);
Assert.Equal(448, bytesRead3);
// Verify stream position
Assert.Equal(2048, stream.Position);
}
#endif
}

View File

@@ -0,0 +1,113 @@
using System;
using System.IO;
using System.Threading.Tasks;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamEdgeAsyncTest
{
[Fact]
public async ValueTask DisposeAsync_WithLeaveStreamOpenTrue_DoesNotDisposeUnderlying()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
await stream.DisposeAsync().ConfigureAwait(false);
Assert.Equal(0, ms.Position);
Assert.True(ms.CanRead);
}
[Fact]
public async ValueTask DisposeAsync_WithLeaveStreamOpenFalse_DisposesUnderlying()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
await stream.DisposeAsync().ConfigureAwait(false);
Assert.Throws<ObjectDisposedException>(() => ms.Read(new byte[1], 0, 1));
}
[Fact]
public async ValueTask ReadAsync_ZeroCount_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
int bytesRead = await stream.ReadAsync(buffer, 0, 0).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask ReadAsync_AtEndOfStream_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask ReadAsyncMemory_ZeroCount_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
int bytesRead = await stream.ReadAsync(buffer.AsMemory(0, 0)).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask ReadAsyncMemory_AtEndOfStream_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false);
int bytesRead = await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask CopyToAsync_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var destination = new MemoryStream();
await stream.CopyToAsync(destination).ConfigureAwait(false);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, destination.ToArray());
}
[Fact]
public async ValueTask CopyToAsync_WithBufferSize_WorksCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var destination = new MemoryStream();
await stream.CopyToAsync(destination, 2).ConfigureAwait(false);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, destination.ToArray());
}
[Fact]
public async ValueTask WriteAsyncMemory_DelegatesToUnderlying()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
var data = new byte[] { 1, 2, 3, 4, 5 };
await stream.WriteAsync(data.AsMemory()).ConfigureAwait(false);
Assert.Equal(data, ms.ToArray());
}
[Fact]
public async ValueTask FlushAsyncMemory_DelegatesToUnderlying()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
await stream.WriteAsync(new byte[] { 1, 2, 3 }).ConfigureAwait(false);
await stream.FlushAsync().ConfigureAwait(false);
Assert.Equal(3, ms.Length);
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.IO;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamEdgeTest
{
[Fact]
public void Dispose_WithLeaveStreamOpenTrue_DoesNotDisposeUnderlying()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.Dispose();
Assert.Equal(0, ms.Position);
Assert.True(ms.CanRead);
}
[Fact]
public void Dispose_WithLeaveStreamOpenFalse_DisposesUnderlying()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.Dispose();
Assert.Throws<ObjectDisposedException>(() => ms.Read(new byte[1], 0, 1));
}
[Fact]
public void Dispose_WithThrowOnDisposeTrue_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.ThrowOnDispose = true;
Assert.Throws<InvalidOperationException>(() => stream.Dispose());
}
[Fact]
public void Read_ZeroCount_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
int bytesRead = stream.Read(buffer, 0, 0);
Assert.Equal(0, bytesRead);
}
[Fact]
public void Read_AtEndOfStream_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
Assert.Equal(5, bytesRead);
bytesRead = stream.Read(buffer, 0, buffer.Length);
Assert.Equal(0, bytesRead);
}
[Fact]
public void Position_InitialValue_IsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.Equal(0, stream.Position);
}
[Fact]
public void CanRead_AlwaysReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.CanRead);
}
[Fact]
public void CanSeek_PassthroughMode_DelegatesToUnderlying()
{
var seekableMs = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }));
var seekableStream = SharpCompressStream.CreateNonDisposing(seekableMs);
var nonSeekableStream = SharpCompressStream.CreateNonDisposing(nonSeekableMs);
Assert.True(seekableStream.CanSeek);
Assert.False(nonSeekableStream.CanSeek);
}
[Fact]
public void BaseStream_ReturnsUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Same(ms, stream.BaseStream());
}
}

View File

@@ -0,0 +1,145 @@
using System;
using System.IO;
using System.Threading.Tasks;
using SharpCompress.IO;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamErrorAsyncTest
{
private class NonSeekableStreamWrapper : Stream
{
private readonly Stream _baseStream;
public NonSeekableStreamWrapper(Stream baseStream) => _baseStream = baseStream;
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) =>
throw new NotSupportedException();
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
_baseStream.Write(buffer, offset, count);
protected override void Dispose(bool disposing)
{
if (disposing)
_baseStream.Dispose();
base.Dispose(disposing);
}
}
[Fact]
public async ValueTask DisposeAsync_WithThrowOnDisposeTrue_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.ThrowOnDispose = true;
await Assert
.ThrowsAsync<InvalidOperationException>(async () =>
await stream.DisposeAsync().ConfigureAwait(false)
)
.ConfigureAwait(false);
}
[Fact]
public async ValueTask CreateNonDisposing_ReadAsync_ZeroCount_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
int bytesRead = await stream.ReadAsync(buffer, 0, 0).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask CreateNonDisposing_ReadAsync_AtEndOfStream_ReturnsZero()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[10];
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(0, bytesRead);
}
[Fact]
public async ValueTask Create_AsyncReadWithRecording_WorksCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(4, stream.Position);
}
[Fact]
public async ValueTask Create_AsyncReadWithBufferOverflow_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[256]);
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 64);
stream.StartRecording();
var buffer = new byte[32];
for (int i = 0; i < 3; i++)
{
await stream.ReadAsync(buffer, 0, 32).ConfigureAwait(false);
}
stream.Rewind();
Assert.Throws<InvalidOperationException>(() => stream.Rewind());
}
[Fact]
public async ValueTask FlushAsync_NonPassthrough_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
await Assert
.ThrowsAsync<NotSupportedException>(async () =>
await stream.FlushAsync().ConfigureAwait(false)
)
.ConfigureAwait(false);
}
[Fact]
public async ValueTask WriteAsync_NonPassthrough_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
await Assert
.ThrowsAsync<NotSupportedException>(async () =>
await stream.WriteAsync(new byte[] { 1 }, 0, 1).ConfigureAwait(false)
)
.ConfigureAwait(false);
}
[Fact]
public async ValueTask CopyToAsync_WithPassthrough_CopiesAllData()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var destination = new MemoryStream();
await stream.CopyToAsync(destination).ConfigureAwait(false);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, destination.ToArray());
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.IO;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamErrorTest
{
private class NonSeekableStreamWrapper : Stream
{
private readonly Stream _baseStream;
public NonSeekableStreamWrapper(Stream baseStream)
{
_baseStream = baseStream;
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) =>
throw new NotSupportedException();
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
_baseStream.Write(buffer, offset, count);
protected override void Dispose(bool disposing)
{
if (disposing)
{
_baseStream.Dispose();
}
base.Dispose(disposing);
}
}
[Fact]
public void Rewind_WithoutStartRecording_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.Throws<InvalidOperationException>(() => stream.Rewind());
}
[Fact]
public void Rewind_PassthroughMode_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Throws<InvalidOperationException>(() => stream.Rewind());
}
[Fact]
public void StartRecording_Twice_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
Assert.Throws<InvalidOperationException>(() => stream.StartRecording());
}
[Fact]
public void StartRecording_PassthroughMode_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Throws<InvalidOperationException>(() => stream.StartRecording());
}
[Fact]
public void StopRecording_WithoutRecording_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.Throws<InvalidOperationException>(() => stream.StopRecording());
}
[Fact]
public void StopRecording_PassthroughMode_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Throws<InvalidOperationException>(() => stream.StopRecording());
}
[Fact]
public void StopRecording_Twice_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
stream.Read(new byte[4], 0, 4);
stream.StopRecording();
Assert.Throws<InvalidOperationException>(() => stream.StopRecording());
}
[Fact]
public void Seek_BeyondRecordedRange_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
stream.Read(new byte[4], 0, 4);
Assert.Throws<NotSupportedException>(() => stream.Position = 100);
}
[Fact]
public void Seek_FromEnd_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.Throws<NotSupportedException>(() => stream.Seek(-1, SeekOrigin.End));
}
[Fact]
public void Position_SetNegative_Throws()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
stream.Read(new byte[4], 0, 4);
Assert.Throws<NotSupportedException>(() => stream.Position = -1);
}
[Fact]
public void Flush_NonPassthrough_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.Throws<NotSupportedException>(() => stream.Flush());
}
[Fact]
public void Write_NonPassthrough_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.Throws<NotSupportedException>(() => stream.Write(new byte[] { 1 }, 0, 1));
}
[Fact]
public void SetLength_NonPassthrough_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.Throws<NotSupportedException>(() => stream.SetLength(100));
}
[Fact]
public void Length_NonPassthroughWithoutBuffer_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = new SharpCompressStream(nonSeekableMs);
Assert.Throws<NotSupportedException>(() => stream.Length);
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Threading.Tasks;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamFactoryAsyncTest
{
[Fact]
public async ValueTask Create_AsyncReadWithSeekableStream_WorksCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.Create(ms);
var buffer = new byte[5];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(5, bytesRead);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, buffer);
}
[Fact]
public async ValueTask Create_AsyncReadWithNonSeekableStream_BufferedCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
var buffer = new byte[5];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(5, bytesRead);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, buffer);
}
[Fact]
public async ValueTask Create_WithBufferAsync_WorksCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(4, stream.Position);
}
[Fact]
public async ValueTask Create_AsyncReadSeekable_PositionUpdatesCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var stream = SharpCompressStream.Create(ms);
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(4, stream.Position);
}
}

View File

@@ -0,0 +1,148 @@
using System;
using System.IO;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamFactoryTest
{
private class IStreamStackMock : Stream, IStreamStack
{
private readonly Stream _baseStream;
public IStreamStackMock(Stream baseStream)
{
_baseStream = baseStream;
}
public Stream BaseStream() => _baseStream;
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => _baseStream.Position;
set => _baseStream.Position = value;
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) =>
_baseStream.Seek(offset, origin);
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
_baseStream.Write(buffer, offset, count);
}
[Fact]
public void Create_WithSeekableStream_ReturnsSeekableSharpCompressStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.Create(ms);
Assert.IsType<SeekableSharpCompressStream>(stream);
}
[Fact]
public void Create_WithNonSeekableStream_ReturnsSharpCompressStreamWithBuffer()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs);
Assert.IsType<SharpCompressStream>(stream);
Assert.NotNull(stream);
}
[Fact]
public void Create_WithSharpCompressStreamPassthrough_UnwrapsAndCreatesNew()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var passthroughStream = SharpCompressStream.CreateNonDisposing(ms);
var stream = SharpCompressStream.Create(passthroughStream);
Assert.NotSame(passthroughStream, stream);
Assert.IsType<SeekableSharpCompressStream>(stream);
}
[Fact]
public void Create_WithSharpCompressStreamNonPassthrough_ReturnsSameInstance()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var sharpStream = SharpCompressStream.Create(nonSeekableMs, 128);
var stream = SharpCompressStream.Create(sharpStream);
Assert.Same(sharpStream, stream);
}
[Fact]
public void Create_WithIStreamStack_UnwrapsSharpCompressStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var sharpStream = SharpCompressStream.CreateNonDisposing(ms);
var wrappedStream = new IStreamStackMock(sharpStream);
var stream = SharpCompressStream.Create(wrappedStream);
Assert.Same(sharpStream, stream);
}
[Fact]
public void Create_WithBufferSize_UsesCustomBufferSize()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.NotNull(stream);
stream.StartRecording();
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
Assert.Equal(4, stream.Position);
}
[Fact]
public void Create_WithLeaveStreamOpenTrue_PreservesSetting()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var passthroughStream = SharpCompressStream.CreateNonDisposing(ms);
var stream = SharpCompressStream.Create(passthroughStream);
Assert.True(stream.LeaveStreamOpen);
}
[Fact]
public void Create_WithSeekablePassthroughStream_CreatesSeekableWrapper()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var passthroughStream = SharpCompressStream.CreateNonDisposing(ms);
var stream = SharpCompressStream.Create(passthroughStream);
Assert.IsType<SeekableSharpCompressStream>(stream);
}
[Fact]
public void Create_WithIStreamStack_ReturnsUnderlyingSharpCompressStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var sharpStream = SharpCompressStream.Create(ms);
var wrappedStream = new IStreamStackMock(sharpStream);
var result = SharpCompressStream.Create(wrappedStream);
Assert.Same(sharpStream, result);
}
[Fact]
public void Create_WithNonSeekablePassthroughStream_CreatesBufferedWrapper()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var passthroughStream = SharpCompressStream.CreateNonDisposing(nonSeekableMs);
var stream = SharpCompressStream.Create(passthroughStream);
Assert.IsType<SharpCompressStream>(stream);
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.IO;
using System.Threading.Tasks;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamPassthroughAsyncTest
{
[Fact]
public async ValueTask CreateNonDisposing_ReadAsync_DelegatesDirectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[5];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Assert.Equal(5, bytesRead);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, buffer);
}
[Fact]
public async ValueTask CreateNonDisposing_WriteAsync_DelegatesDirectly()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
var data = new byte[] { 1, 2, 3, 4, 5 };
await stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
Assert.Equal(data, ms.ToArray());
}
[Fact]
public async ValueTask CreateNonDisposing_FlushAsync_DelegatesDirectly()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
await stream.WriteAsync(new byte[] { 1, 2, 3 }, 0, 3).ConfigureAwait(false);
await stream.FlushAsync().ConfigureAwait(false);
Assert.Equal(3, ms.Length);
}
[Fact]
public async ValueTask CreateNonDisposing_CanReadAsync_ReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.CanRead);
}
[Fact]
public async ValueTask CreateNonDisposing_ReadAsync_WithCancellationToken()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[5];
var cts = new System.Threading.CancellationTokenSource();
int bytesRead = await stream
.ReadAsync(buffer, 0, buffer.Length, cts.Token)
.ConfigureAwait(false);
Assert.Equal(5, bytesRead);
}
[Fact]
public async ValueTask CreateNonDisposing_WriteAsync_WithCancellationToken()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
var data = new byte[] { 1, 2, 3, 4, 5 };
var cts = new System.Threading.CancellationTokenSource();
await stream.WriteAsync(data, 0, data.Length, cts.Token).ConfigureAwait(false);
Assert.Equal(data, ms.ToArray());
}
[Fact]
public async ValueTask CreateNonDisposing_FlushAsync_WithCancellationToken()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
await stream.WriteAsync(new byte[] { 1, 2, 3 }, 0, 3).ConfigureAwait(false);
var cts = new System.Threading.CancellationTokenSource();
await stream.FlushAsync(cts.Token).ConfigureAwait(false);
Assert.Equal(3, ms.Length);
}
[Fact]
public async ValueTask CreateNonDisposing_DoesNotDisposeUnderlying_Async()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
await stream.DisposeAsync().ConfigureAwait(false);
Assert.Equal(0, ms.Position);
Assert.True(ms.CanRead);
}
[Fact]
public async ValueTask CreateNonDisposing_DisposeAsync_WithThrowOnDisposeTrue_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.ThrowOnDispose = true;
await Assert
.ThrowsAsync<InvalidOperationException>(async () =>
await stream.DisposeAsync().ConfigureAwait(false)
)
.ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.IO;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamPassthroughTest
{
[Fact]
public void CreateNonDisposing_LeaveStreamOpen_ReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.LeaveStreamOpen);
}
[Fact]
public void CreateNonDisposing_IsPassthrough_ReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.IsPassthrough);
}
[Fact]
public void CreateNonDisposing_CanRead_ReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.CanRead);
}
[Fact]
public void CreateNonDisposing_CanSeek_DelegatesToUnderlyingSeekableStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Equal(ms.CanSeek, stream.CanSeek);
Assert.True(stream.CanSeek);
}
[Fact]
public void CreateNonDisposing_CanSeek_DelegatesToUnderlyingNonSeekableStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.CreateNonDisposing(nonSeekableMs);
Assert.Equal(nonSeekableMs.CanSeek, stream.CanSeek);
Assert.False(stream.CanSeek);
}
[Fact]
public void CreateNonDisposing_CanWrite_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Equal(ms.CanWrite, stream.CanWrite);
Assert.True(stream.CanWrite);
}
[Fact]
public void CreateNonDisposing_Read_DelegatesDirectlyWithoutBuffering()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
var buffer = new byte[5];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
Assert.Equal(5, bytesRead);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, buffer);
}
[Fact]
public void CreateNonDisposing_PositionGet_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
ms.Position = 2;
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Equal(ms.Position, stream.Position);
Assert.Equal(2, stream.Position);
}
[Fact]
public void CreateNonDisposing_PositionSet_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.Position = 3;
Assert.Equal(3, ms.Position);
Assert.Equal(3, stream.Position);
}
[Fact]
public void CreateNonDisposing_DoesNotDisposeUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.Dispose();
Assert.Equal(0, ms.Position);
Assert.True(ms.CanRead);
}
[Fact]
public void CreateNonDisposing_Length_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Equal(ms.Length, stream.Length);
Assert.Equal(5, stream.Length);
}
[Fact]
public void CreateNonDisposing_Seek_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
long result = stream.Seek(3, SeekOrigin.Begin);
Assert.Equal(3, result);
Assert.Equal(3, ms.Position);
Assert.Equal(3, stream.Position);
}
[Fact]
public void CreateNonDisposing_Flush_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.Write(new byte[] { 1, 2, 3 }, 0, 3);
stream.Flush();
Assert.Equal(3, ms.Length);
}
[Fact]
public void CreateNonDisposing_SetLength_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
stream.SetLength(20);
Assert.Equal(20, stream.Length);
Assert.Equal(20, ms.Length);
}
[Fact]
public void CreateNonDisposing_Write_DelegatesToUnderlyingStream()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
var data = new byte[] { 1, 2, 3, 4, 5 };
stream.Write(data, 0, data.Length);
Assert.Equal(data, ms.ToArray());
}
[Fact]
public void CreateNonDisposing_StartRecording_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Throws<InvalidOperationException>(() => stream.StartRecording());
}
[Fact]
public void CreateNonDisposing_Rewind_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Throws<InvalidOperationException>(() => stream.Rewind());
}
[Fact]
public void CreateNonDisposing_StopRecording_ThrowsInvalidOperation()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Throws<InvalidOperationException>(() => stream.StopRecording());
}
[Fact]
public void CreateNonDisposing_IsRecording_AlwaysFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.False(stream.IsRecording);
}
}

View File

@@ -0,0 +1,177 @@
using System;
using System.IO;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamPropertyTest
{
[Fact]
public void BaseStream_ReturnsUnderlyingStream()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.Same(ms, stream.BaseStream());
}
[Fact]
public void IsPassthrough_CreateNonDisposing_ReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.IsPassthrough);
}
[Fact]
public void IsPassthrough_CreateWithBuffer_ReturnsFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.False(stream.IsPassthrough);
}
[Fact]
public void IsPassthrough_CreateSeekable_ReturnsFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.Create(ms);
Assert.False(stream.IsPassthrough);
}
[Fact]
public void IsRecording_AfterStartRecording_ReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
Assert.True(stream.IsRecording);
}
[Fact]
public void IsRecording_AfterStopRecording_ReturnsFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
stream.Read(new byte[4], 0, 4);
stream.StopRecording();
Assert.False(stream.IsRecording);
}
[Fact]
public void IsRecording_AfterRewindWithStopRecording_ReturnsFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
stream.Read(new byte[4], 0, 4);
stream.Rewind(true);
Assert.False(stream.IsRecording);
}
[Fact]
public void LeaveStreamOpen_CreateNonDisposing_ReturnsTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.LeaveStreamOpen);
}
[Fact]
public void LeaveStreamOpen_CreateWithBuffer_ReturnsFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
Assert.False(stream.LeaveStreamOpen);
}
[Fact]
public void LeaveStreamOpen_CreateSeekable_ReturnsFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.Create(ms);
Assert.False(stream.LeaveStreamOpen);
}
[Fact]
public void CanRead_AlwaysTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.CanRead);
}
[Fact]
public void CanSeek_PassthroughWithSeekable_DelegatesTrue()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.CanSeek);
}
[Fact]
public void CanSeek_PassthroughWithNonSeekable_DelegatesFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var nonSeekableMs = new ForwardOnlyStream(ms);
var stream = SharpCompressStream.CreateNonDisposing(nonSeekableMs);
Assert.False(stream.CanSeek);
}
[Fact]
public void CanWrite_PassthroughWithWritable_DelegatesTrue()
{
var ms = new MemoryStream();
var stream = SharpCompressStream.CreateNonDisposing(ms);
Assert.True(stream.CanWrite);
}
[Fact]
public void CanWrite_PassthroughWithReadOnly_DelegatesFalse()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
var readOnlyMs = new ReadOnlyStreamWrapper(ms);
var stream = SharpCompressStream.CreateNonDisposing(readOnlyMs);
Assert.False(stream.CanWrite);
}
private class ReadOnlyStreamWrapper : Stream
{
private readonly Stream _baseStream;
public ReadOnlyStreamWrapper(Stream baseStream)
{
_baseStream = baseStream;
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => false;
public override long Length => _baseStream.Length;
public override long Position
{
get => _baseStream.Position;
set => _baseStream.Position = value;
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) =>
_baseStream.Seek(offset, origin);
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.IO;
using System.Threading.Tasks;
using SharpCompress.IO;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamSeekAsyncTest
{
private class NonSeekableStreamWrapper : Stream
{
private readonly Stream _baseStream;
public NonSeekableStreamWrapper(Stream baseStream) => _baseStream = baseStream;
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) =>
throw new NotSupportedException();
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
_baseStream.Write(buffer, offset, count);
protected override void Dispose(bool disposing)
{
if (disposing)
_baseStream.Dispose();
base.Dispose(disposing);
}
}
[Fact]
public async ValueTask SeekAsync_AfterReadAsync_MaintainsPosition()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(4, stream.Position);
stream.Seek(-2, SeekOrigin.Current);
Assert.Equal(2, stream.Position);
await stream.ReadAsync(buffer, 0, 2).ConfigureAwait(false);
Assert.Equal(3, buffer[0]);
Assert.Equal(4, buffer[1]);
}
[Fact]
public async ValueTask Position_Set_AfterAsyncRead_WorksCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[8];
await stream.ReadAsync(buffer, 0, 8).ConfigureAwait(false);
stream.Position = 2;
Assert.Equal(2, stream.Position);
var readBuffer = new byte[2];
await stream.ReadAsync(readBuffer, 0, 2).ConfigureAwait(false);
Assert.Equal(3, readBuffer[0]);
Assert.Equal(4, readBuffer[1]);
}
[Fact]
public async ValueTask SeekAsync_ToRecordingStart_AfterAsyncRead_WorksCorrectly()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
stream.Position = 0;
Assert.Equal(0, stream.Position);
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(1, buffer[0]);
Assert.Equal(2, buffer[1]);
Assert.Equal(3, buffer[2]);
Assert.Equal(4, buffer[3]);
}
[Fact]
public async ValueTask SeekAsync_ZeroCurrentOrigin_DoesNotMove()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false);
Assert.Equal(4, stream.Position);
stream.Seek(0, SeekOrigin.Current);
Assert.Equal(4, stream.Position);
}
[Fact]
public async ValueTask SeekAsync_NegativeCurrent_MovesBackward()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[6];
await stream.ReadAsync(buffer, 0, 6).ConfigureAwait(false);
Assert.Equal(6, stream.Position);
stream.Seek(-3, SeekOrigin.Current);
Assert.Equal(3, stream.Position);
var readBuffer = new byte[3];
await stream.ReadAsync(readBuffer, 0, 3).ConfigureAwait(false);
Assert.Equal(4, readBuffer[0]);
Assert.Equal(5, readBuffer[1]);
Assert.Equal(6, readBuffer[2]);
}
}

View File

@@ -0,0 +1,139 @@
using System;
using System.IO;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamSeekTest
{
private class NonSeekableStreamWrapper : Stream
{
private readonly Stream _baseStream;
public NonSeekableStreamWrapper(Stream baseStream)
{
_baseStream = baseStream;
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) =>
throw new NotSupportedException();
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
_baseStream.Write(buffer, offset, count);
protected override void Dispose(bool disposing)
{
if (disposing)
_baseStream.Dispose();
base.Dispose(disposing);
}
}
[Fact]
public void Seek_CurrentOrigin_MovesRelativeToCurrent()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
Assert.Equal(4, stream.Position);
stream.Seek(-2, SeekOrigin.Current);
Assert.Equal(2, stream.Position);
stream.Read(buffer, 0, 2);
Assert.Equal(3, buffer[0]);
Assert.Equal(4, buffer[1]);
}
[Fact]
public void Seek_BeginOrigin_MovesToAbsolutePosition()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[8];
stream.Read(buffer, 0, 8);
stream.Seek(2, SeekOrigin.Begin);
Assert.Equal(2, stream.Position);
var readBuffer = new byte[2];
stream.Read(readBuffer, 0, 2);
Assert.Equal(3, readBuffer[0]);
Assert.Equal(4, readBuffer[1]);
}
[Fact]
public void Seek_ToExactBufferBoundary_Succeeds()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
stream.Seek(4, SeekOrigin.Begin);
Assert.Equal(4, stream.Position);
stream.Read(buffer, 0, 4);
Assert.Equal(5, buffer[0]);
Assert.Equal(6, buffer[1]);
Assert.Equal(7, buffer[2]);
Assert.Equal(8, buffer[3]);
}
[Fact]
public void Position_SetWithinRecordedRange_Succeeds()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[8];
stream.Read(buffer, 0, 8);
stream.Position = 2;
Assert.Equal(2, stream.Position);
var readBuffer = new byte[2];
stream.Read(readBuffer, 0, 2);
Assert.Equal(3, readBuffer[0]);
Assert.Equal(4, readBuffer[1]);
}
[Fact]
public void Position_SetBeforeRecordingStart_ThrowsNotSupported()
{
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
var stream = SharpCompressStream.Create(nonSeekableMs, 128);
stream.StartRecording();
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
Assert.Throws<NotSupportedException>(() => stream.Position = 0);
}
}

View File

@@ -1,884 +0,0 @@
using System;
using System.IO;
using System.Linq;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Streams;
public class SharpCompressStreamTest
{
[Fact]
public void TestRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
stream.Rewind(true);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
}
[Fact]
public void TestIncompleteRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
stream.Rewind(true);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
}
[Fact]
public void TestRecording()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
stream.Rewind(false);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
}
[Fact]
public void TestPosition()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
for (int i = 0; i < 10; i++)
{
bw.Write(i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
Assert.Equal(0, stream.Position);
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
Assert.Equal(4, stream.Position);
stream.StartRecording();
stream.Read(buffer, 0, 4);
Assert.Equal(8, stream.Position);
stream.Rewind();
Assert.Equal(4, stream.Position);
}
[Fact]
public void TestPositionSeek()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
for (int i = 0; i < 10; i++)
{
bw.Write(i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(0, br.ReadInt32());
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
stream.Position = 4;
Assert.Equal(1, br.ReadInt32());
}
[Fact]
public void TestDispose()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
stream.Dispose();
Assert.Throws<ObjectDisposedException>(() => stream.Read(new byte[4], 0, 4));
}
[Fact]
public void TestStopRecordingBasic()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
stream.StopRecording();
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
Assert.False(stream.IsRecording);
}
[Fact]
public void TestStopRecordingNoFurtherBuffering()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
stream.StartRecording();
var buffer = new byte[8];
stream.Read(buffer, 0, 8);
stream.StopRecording();
stream.Read(buffer, 0, 8);
Assert.Equal(BitConverter.GetBytes(1), buffer.Take(4).ToArray());
Assert.Equal(BitConverter.GetBytes(2), buffer.Skip(4).Take(4).ToArray());
int bytesRead = stream.Read(buffer, 0, 8);
Assert.Equal(8, bytesRead);
Assert.False(stream.IsRecording);
bytesRead = stream.Read(buffer, 0, 8);
Assert.Equal(0, bytesRead);
}
#if !LEGACY_DOTNET
[Fact]
public void TestStopRecordingWithSpan()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var buffer = new byte[8];
stream.Read(buffer);
stream.StopRecording();
stream.Read(buffer);
Assert.Equal(BitConverter.GetBytes(1), buffer.Take(4).ToArray());
Assert.Equal(BitConverter.GetBytes(2), buffer.Skip(4).Take(4).ToArray());
int bytesRead = stream.Read(buffer);
Assert.Equal(8, bytesRead);
Assert.Equal(BitConverter.GetBytes(3), buffer.Take(4).ToArray());
Assert.Equal(BitConverter.GetBytes(4), buffer.Skip(4).Take(4).ToArray());
}
#endif
[Fact]
public void TestNonSeekableStream_Rewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var nonSeekableStream = new NonSeekableStreamWrapper(ms);
var stream = new SharpCompressStream(nonSeekableStream);
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
stream.Rewind(true);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
}
[Fact]
public void TestNonSeekableStream_Recording()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var nonSeekableStream = new NonSeekableStreamWrapper(ms);
var stream = new SharpCompressStream(nonSeekableStream);
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
stream.Rewind(false);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
}
[Fact]
public void TestNonSeekableStream_Position()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
for (int i = 0; i < 10; i++)
{
bw.Write(i);
}
bw.Flush();
ms.Position = 0;
var nonSeekableStream = new NonSeekableStreamWrapper(ms);
var stream = new SharpCompressStream(nonSeekableStream);
Assert.Equal(0, stream.Position);
Assert.True(stream.CanSeek);
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
Assert.Equal(4, stream.Position);
stream.StartRecording();
stream.Read(buffer, 0, 4);
Assert.Equal(8, stream.Position);
stream.Rewind();
Assert.Equal(4, stream.Position);
}
[Fact]
public void TestNonSeekableStream_PositionSet_WithinBuffer()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var nonSeekableStream = new NonSeekableStreamWrapper(ms);
var stream = new SharpCompressStream(nonSeekableStream);
stream.StartRecording();
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
Assert.Equal(1, BitConverter.ToInt32(buffer, 0));
stream.Read(buffer, 0, 4);
Assert.Equal(2, BitConverter.ToInt32(buffer, 0));
stream.Position = 0;
Assert.Equal(0, stream.Position);
stream.Read(buffer, 0, 4);
Assert.Equal(1, BitConverter.ToInt32(buffer, 0));
stream.Read(buffer, 0, 4);
Assert.Equal(2, BitConverter.ToInt32(buffer, 0));
}
[Fact]
public void TestNonSeekableStream_PositionSet_OutsideBuffer_Throws()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Flush();
ms.Position = 0;
var nonSeekableStream = new NonSeekableStreamWrapper(ms);
var stream = new SharpCompressStream(nonSeekableStream);
stream.StartRecording();
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
Assert.Throws<NotSupportedException>(() => stream.Position = 100);
}
[Fact]
public void TestNonSeekableStream_StopRecordingBasic()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Flush();
ms.Position = 0;
var nonSeekableStream = new NonSeekableStreamWrapper(ms);
var stream = new SharpCompressStream(nonSeekableStream);
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
stream.StopRecording();
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
Assert.False(stream.IsRecording);
}
[Fact]
public void TestStopRecordingThenRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Write(8);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var br = new BinaryReader(new ForwardOnlyStream(stream));
// Read first 4 values (gets buffered)
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
// Stop recording
stream.StopRecording();
Assert.False(stream.IsRecording);
// Rewind to start of buffer
stream.Rewind(true);
// Should be able to read from buffer again
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
// Rewind to start of buffer
stream.Rewind();
// Should be able to read from buffer again
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
// Continue reading remaining data from underlying stream
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
Assert.Equal(8, br.ReadInt32());
}
[Fact]
public void TestNonSeekableStream_StopRecordingThenRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Write(8);
bw.Flush();
ms.Position = 0;
var nonSeekableStream = new NonSeekableStreamWrapper(ms);
var stream = new SharpCompressStream(nonSeekableStream);
stream.StartRecording();
var br = new BinaryReader(stream);
// Read first 4 values (gets buffered)
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
// Stop recording
stream.StopRecording();
Assert.False(stream.IsRecording);
// Rewind to start of buffer
stream.Rewind(true);
// Should be able to read from buffer again
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
// Continue reading remaining data from underlying stream
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
Assert.Equal(8, br.ReadInt32());
}
[Fact]
public void TestMultipleRewindsAfterStopRecording()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Write(3);
bw.Write(4);
bw.Write(5);
bw.Write(6);
bw.Write(7);
bw.Write(8);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var br = new BinaryReader(stream);
// Read first 4 values (gets buffered)
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
// Stop recording
stream.StopRecording();
Assert.False(stream.IsRecording);
// First rewind - read all buffered data, then continue with underlying stream
stream.Rewind();
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
// Second rewind - should still be able to read from buffer
stream.Rewind();
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
// Third rewind - still works
stream.Rewind();
Assert.Equal(1, br.ReadInt32());
Assert.Equal(2, br.ReadInt32());
Assert.Equal(3, br.ReadInt32());
Assert.Equal(4, br.ReadInt32());
Assert.Equal(5, br.ReadInt32());
Assert.Equal(6, br.ReadInt32());
Assert.Equal(7, br.ReadInt32());
Assert.Equal(8, br.ReadInt32());
}
[Fact]
public void TestStopRecordingTwiceThrows()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(1);
bw.Write(2);
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(ms);
stream.StartRecording();
var br = new BinaryReader(stream);
Assert.Equal(1, br.ReadInt32());
// First StopRecording should succeed
stream.StopRecording();
Assert.False(stream.IsRecording);
// Second StopRecording should throw
Assert.Throws<InvalidOperationException>(() => stream.StopRecording());
}
[Fact]
public void TestReadMoreThanBufferSizeAfterRewind()
{
// This test verifies the fix for the bug where reading more bytes than
// are in the buffer after a rewind would only return the buffered bytes
// instead of continuing to read from the underlying stream.
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 29 bytes (simulating Arc header)
for (int i = 0; i < 29; i++)
{
bw.Write((byte)i);
}
// Write 5252 bytes (simulating Arc compressed data)
for (int i = 0; i < 5252; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Simulate factory detection: record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
int probeRead = stream.Read(probeBuffer, 0, 512);
Assert.Equal(512, probeRead);
// Stop recording and rewind (simulates what ReaderFactory does)
stream.Rewind(true);
// Read header (29 bytes) - should come from buffer
var headerBuffer = new byte[29];
int headerRead = stream.Read(headerBuffer, 0, 29);
Assert.Equal(29, headerRead);
// Read compressed data (5252 bytes) - buffer has 483 bytes left,
// but we need 5252 bytes. This should read all 5252 bytes, not just 483.
var dataBuffer = new byte[5252];
int dataRead = stream.Read(dataBuffer, 0, 5252);
Assert.Equal(5252, dataRead);
// Verify we read the correct data
for (int i = 0; i < 5252; i++)
{
Assert.Equal((byte)(i % 256), dataBuffer[i]);
}
// Verify stream position is correct (29 + 5252 = 5281)
Assert.Equal(5281, stream.Position);
}
[Fact]
public void TestReadExactlyBufferSizeAfterRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 1024 bytes
for (int i = 0; i < 1024; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
stream.Read(probeBuffer, 0, 512);
stream.Rewind(true);
// Read exactly the buffer size (512 bytes)
var buffer = new byte[512];
int bytesRead = stream.Read(buffer, 0, 512);
Assert.Equal(512, bytesRead);
// Verify we read the correct data
for (int i = 0; i < 512; i++)
{
Assert.Equal((byte)(i % 256), buffer[i]);
}
}
[Fact]
public void TestReadLessThanBufferSizeAfterRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 1024 bytes
for (int i = 0; i < 1024; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
stream.Read(probeBuffer, 0, 512);
stream.Rewind(true);
// Read less than buffer size (256 bytes)
var buffer = new byte[256];
int bytesRead = stream.Read(buffer, 0, 256);
Assert.Equal(256, bytesRead);
// Verify we read the correct data
for (int i = 0; i < 256; i++)
{
Assert.Equal((byte)(i % 256), buffer[i]);
}
}
[Fact]
public void TestMultipleReadsExceedingBufferAfterRewind()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 2048 bytes
for (int i = 0; i < 2048; i++)
{
bw.Write((byte)(i % 256));
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 512 bytes
stream.StartRecording();
var probeBuffer = new byte[512];
stream.Read(probeBuffer, 0, 512);
stream.Rewind(true);
// Read in chunks that will exceed the buffer
var buffer = new byte[800];
// First read: 800 bytes (512 from buffer + 288 from underlying stream)
int bytesRead1 = stream.Read(buffer, 0, 800);
Assert.Equal(800, bytesRead1);
// Second read: 800 bytes (all from underlying stream)
int bytesRead2 = stream.Read(buffer, 0, 800);
Assert.Equal(800, bytesRead2);
// Third read: remaining 448 bytes
int bytesRead3 = stream.Read(buffer, 0, 800);
Assert.Equal(448, bytesRead3);
// Verify stream position
Assert.Equal(2048, stream.Position);
}
[Fact]
public void TestReadPartiallyFromBufferThenUnderlyingStream()
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
// Write 1000 bytes with specific pattern
for (int i = 0; i < 1000; i++)
{
bw.Write((byte)i);
}
bw.Flush();
ms.Position = 0;
var stream = new SharpCompressStream(new ForwardOnlyStream(ms));
// Record first 100 bytes
stream.StartRecording();
var probeBuffer = new byte[100];
stream.Read(probeBuffer, 0, 100);
stream.Rewind(true);
// Read 50 bytes (from buffer)
var buffer1 = new byte[50];
int read1 = stream.Read(buffer1, 0, 50);
Assert.Equal(50, read1);
for (int i = 0; i < 50; i++)
{
Assert.Equal((byte)i, buffer1[i]);
}
// Read 150 bytes (50 from buffer + 100 from underlying stream)
var buffer2 = new byte[150];
int read2 = stream.Read(buffer2, 0, 150);
Assert.Equal(150, read2);
for (int i = 0; i < 150; i++)
{
Assert.Equal((byte)(i + 50), buffer2[i]);
}
// Verify position
Assert.Equal(200, stream.Position);
}
private class NonSeekableStreamWrapper : Stream
{
private readonly Stream _baseStream;
public NonSeekableStreamWrapper(Stream baseStream)
{
_baseStream = baseStream;
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) =>
throw new NotSupportedException();
public override void SetLength(long value) => _baseStream.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) =>
_baseStream.Write(buffer, offset, count);
protected override void Dispose(bool disposing)
{
if (disposing)
{
_baseStream.Dispose();
}
base.Dispose(disposing);
}
}
}