From 76352df852888621e214d4d225fc16faf5dbbc0a Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 13 Feb 2026 12:44:12 +0000 Subject: [PATCH] revamped and added SC stream tests --- .../Streams/SharpCompressStreamAsyncTest.cs | 905 ------------------ .../SharpCompressStreamEdgeAsyncTest.cs | 113 +++ .../Streams/SharpCompressStreamEdgeTest.cs | 99 ++ .../SharpCompressStreamErrorAsyncTest.cs | 145 +++ .../Streams/SharpCompressStreamErrorTest.cs | 187 ++++ .../SharpCompressStreamFactoryAsyncTest.cs | 56 ++ .../Streams/SharpCompressStreamFactoryTest.cs | 148 +++ ...SharpCompressStreamPassthroughAsyncTest.cs | 108 +++ .../SharpCompressStreamPassthroughTest.cs | 185 ++++ .../SharpCompressStreamPropertyTest.cs | 177 ++++ .../SharpCompressStreamSeekAsyncTest.cs | 141 +++ .../Streams/SharpCompressStreamSeekTest.cs | 139 +++ .../Streams/SharpCompressStreamTest.cs | 884 ----------------- 13 files changed, 1498 insertions(+), 1789 deletions(-) delete mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamErrorTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryAsyncTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamPropertyTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamSeekAsyncTest.cs create mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamSeekTest.cs delete mode 100644 tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs deleted file mode 100644 index 6b417f7c..00000000 --- a/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTest.cs +++ /dev/null @@ -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.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 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 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(); - 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 ReadAllAsync(SharpCompressStream stream) - { - var result = new List(); - 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 ReadAllAsyncMemory(SharpCompressStream stream) - { - var result = new List(); - 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(() => 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 - 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 -} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs new file mode 100644 index 00000000..e4ba0596 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeAsyncTest.cs @@ -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(() => 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); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeTest.cs new file mode 100644 index 00000000..c4409af8 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamEdgeTest.cs @@ -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(() => 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(() => 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()); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs new file mode 100644 index 00000000..31df9ac8 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorAsyncTest.cs @@ -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(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(() => 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(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(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()); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorTest.cs new file mode 100644 index 00000000..6ac0ee38 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamErrorTest.cs @@ -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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => 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(() => stream.Length); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryAsyncTest.cs new file mode 100644 index 00000000..f5ba1909 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryAsyncTest.cs @@ -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); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryTest.cs new file mode 100644 index 00000000..4a5d74a9 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamFactoryTest.cs @@ -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(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(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(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(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(stream); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs new file mode 100644 index 00000000..7e8b93d4 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughAsyncTest.cs @@ -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(async () => + await stream.DisposeAsync().ConfigureAwait(false) + ) + .ConfigureAwait(false); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughTest.cs new file mode 100644 index 00000000..154043e0 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamPassthroughTest.cs @@ -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(() => 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(() => 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(() => 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); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamPropertyTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamPropertyTest.cs new file mode 100644 index 00000000..2262960d --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamPropertyTest.cs @@ -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(); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamSeekAsyncTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamSeekAsyncTest.cs new file mode 100644 index 00000000..9d17febf --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamSeekAsyncTest.cs @@ -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]); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamSeekTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamSeekTest.cs new file mode 100644 index 00000000..a4ba77e7 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamSeekTest.cs @@ -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(() => stream.Position = 0); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs deleted file mode 100644 index 3e846f39..00000000 --- a/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs +++ /dev/null @@ -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(() => 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(() => 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(() => 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); - } - } -}