diff --git a/src/SharpCompress/Common/Volume.cs b/src/SharpCompress/Common/Volume.cs
index db545865..5ffaf9a0 100644
--- a/src/SharpCompress/Common/Volume.cs
+++ b/src/SharpCompress/Common/Volume.cs
@@ -18,7 +18,7 @@ public abstract partial class Volume : IVolume, IAsyncDisposable
_baseStream = stream;
if (ReaderOptions.LeaveStreamOpen)
{
- stream = SharpCompressStream.Create(stream, leaveOpen: true);
+ stream = new NonDisposingStream(stream);
}
if (stream is IStreamStack ss)
diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs
index d838173b..589b8c10 100644
--- a/src/SharpCompress/Factories/Factory.cs
+++ b/src/SharpCompress/Factories/Factory.cs
@@ -87,7 +87,7 @@ public abstract class Factory : IFactory
stream.Rewind();
if (IsArchive(stream, options.Password))
{
- stream.Rewind();
+ stream.StopRecording();
reader = readerFactory.OpenReader(stream, options);
return true;
}
diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs
index c69b9a2a..40907e1d 100644
--- a/src/SharpCompress/Factories/GZipFactory.cs
+++ b/src/SharpCompress/Factories/GZipFactory.cs
@@ -120,11 +120,11 @@ public class GZipFactory
var testStream = new GZipStream(rewindableStream, CompressionMode.Decompress);
if (TarArchive.IsTarFile(testStream))
{
- rewindableStream.Rewind();
+ rewindableStream.StopRecording();
reader = new TarReader(rewindableStream, options, CompressionType.GZip);
return true;
}
- rewindableStream.Rewind();
+ rewindableStream.StopRecording();
reader = OpenReader(rewindableStream, options);
return true;
}
diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs
index 3653f570..189ad3b4 100644
--- a/src/SharpCompress/Factories/TarFactory.cs
+++ b/src/SharpCompress/Factories/TarFactory.cs
@@ -168,7 +168,7 @@ public class TarFactory
var decompressedStream = wrapper.CreateStream(rewindableStream);
if (TarArchive.IsTarFile(decompressedStream))
{
- rewindableStream.Rewind();
+ rewindableStream.StopRecording();
return new TarReader(rewindableStream, options, wrapper.CompressionType);
}
}
@@ -185,18 +185,19 @@ public class TarFactory
{
cancellationToken.ThrowIfCancellationRequested();
options ??= new ReaderOptions();
- var rewindableStream = new SharpCompressStream(stream);
- var pos = rewindableStream.GetPosition();
+ var rewindableStream = new RewindableStream(stream);
+ rewindableStream.StartRecording();
foreach (var wrapper in TarWrapper.Wrappers)
{
- rewindableStream.StackSeek(pos);
+ rewindableStream.Rewind();
if (await wrapper.IsMatchAsync(rewindableStream, cancellationToken))
{
- rewindableStream.StackSeek(pos);
+ rewindableStream.Rewind();
var decompressedStream = wrapper.CreateStream(rewindableStream);
if (await TarArchive.IsTarFileAsync(decompressedStream, cancellationToken))
{
- rewindableStream.StackSeek(pos);
+ rewindableStream.Rewind();
+ rewindableStream.StopRecording();
return new TarReader(rewindableStream, options, wrapper.CompressionType);
}
}
diff --git a/src/SharpCompress/IO/NonDisposingStream.cs b/src/SharpCompress/IO/NonDisposingStream.cs
new file mode 100644
index 00000000..35ddeb76
--- /dev/null
+++ b/src/SharpCompress/IO/NonDisposingStream.cs
@@ -0,0 +1,216 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SharpCompress.IO;
+
+///
+/// A stream wrapper that prevents disposal of the underlying stream.
+/// This is useful when working with compression streams directly and you want
+/// to keep the base stream open after the compression stream is disposed.
+///
+internal class NonDisposingStream : Stream
+{
+ private readonly Stream _stream;
+ private bool _isDisposed;
+
+ ///
+ /// Gets or sets a value indicating whether to throw an exception when the stream is disposed.
+ /// This is useful for testing to ensure streams are not disposed prematurely.
+ ///
+ public bool ThrowOnDispose { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The stream to wrap. This stream will NOT be disposed when this wrapper is disposed.
+ /// Thrown when is null.
+ public NonDisposingStream(Stream stream)
+ {
+ _stream = stream ?? throw new ArgumentNullException(nameof(stream));
+ }
+
+ public override bool CanRead => !_isDisposed && _stream.CanRead;
+
+ public override bool CanSeek => !_isDisposed && _stream.CanSeek;
+
+ public override bool CanWrite => !_isDisposed && _stream.CanWrite;
+
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return _stream.Length;
+ }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return _stream.Position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ _stream.Position = value;
+ }
+ }
+
+ public override void Flush()
+ {
+ ThrowIfDisposed();
+ _stream.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ ThrowIfDisposed();
+ return _stream.Read(buffer, offset, count);
+ }
+
+#if !LEGACY_DOTNET
+ public override int Read(Span buffer)
+ {
+ ThrowIfDisposed();
+ return _stream.Read(buffer);
+ }
+#endif
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ return _stream.Seek(offset, origin);
+ }
+
+ public override void SetLength(long value)
+ {
+ ThrowIfDisposed();
+ _stream.SetLength(value);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ ThrowIfDisposed();
+ _stream.Write(buffer, offset, count);
+ }
+
+#if !LEGACY_DOTNET
+ public override void Write(ReadOnlySpan buffer)
+ {
+ ThrowIfDisposed();
+ _stream.Write(buffer);
+ }
+#endif
+
+ public override Task ReadAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.ReadAsync(buffer, offset, count, cancellationToken);
+ }
+
+#if !LEGACY_DOTNET
+ public override ValueTask ReadAsync(
+ Memory buffer,
+ CancellationToken cancellationToken = default
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.ReadAsync(buffer, cancellationToken);
+ }
+#endif
+
+ public override Task WriteAsync(
+ byte[] buffer,
+ int offset,
+ int count,
+ CancellationToken cancellationToken
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.WriteAsync(buffer, offset, count, cancellationToken);
+ }
+
+#if !LEGACY_DOTNET
+ public override ValueTask WriteAsync(
+ ReadOnlyMemory buffer,
+ CancellationToken cancellationToken = default
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.WriteAsync(buffer, cancellationToken);
+ }
+#endif
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ ThrowIfDisposed();
+ return _stream.FlushAsync(cancellationToken);
+ }
+
+ public override Task CopyToAsync(
+ Stream destination,
+ int bufferSize,
+ CancellationToken cancellationToken
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.CopyToAsync(destination, bufferSize, cancellationToken);
+ }
+
+ ///
+ /// Disposes this wrapper without disposing the underlying stream.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (ThrowOnDispose)
+ {
+ throw new InvalidOperationException(
+ $"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is true"
+ );
+ }
+ _isDisposed = true;
+ // Intentionally do NOT dispose _stream
+ }
+ base.Dispose(disposing);
+ }
+
+#if !LEGACY_DOTNET
+ ///
+ /// Asynchronously disposes this wrapper without disposing the underlying stream.
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ if (!_isDisposed)
+ {
+ if (ThrowOnDispose)
+ {
+ throw new InvalidOperationException(
+ $"Attempt to dispose of a {nameof(NonDisposingStream)} when {nameof(ThrowOnDispose)} is true"
+ );
+ }
+ _isDisposed = true;
+ // Intentionally do NOT dispose _stream
+ }
+ await base.DisposeAsync();
+ }
+#endif
+
+ private void ThrowIfDisposed()
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException(nameof(NonDisposingStream));
+ }
+ }
+}
diff --git a/src/SharpCompress/IO/RewindableStream.cs b/src/SharpCompress/IO/RewindableStream.cs
index 1688efdb..935972c3 100644
--- a/src/SharpCompress/IO/RewindableStream.cs
+++ b/src/SharpCompress/IO/RewindableStream.cs
@@ -84,6 +84,13 @@ namespace SharpCompress.IO
IsRecording = true;
}
+ public void StopRecording()
+ {
+ _isRewound = true;
+ IsRecording = false;
+ _bufferPosition = 0;
+ }
+
public override bool CanRead => true;
public override bool CanSeek => stream.CanSeek;
diff --git a/src/SharpCompress/Readers/ReaderFactory.Async.cs b/src/SharpCompress/Readers/ReaderFactory.Async.cs
index b879b2ba..f279895e 100644
--- a/src/SharpCompress/Readers/ReaderFactory.Async.cs
+++ b/src/SharpCompress/Readers/ReaderFactory.Async.cs
@@ -54,8 +54,8 @@ public static partial class ReaderFactory
stream.NotNull(nameof(stream));
options ??= new ReaderOptions() { LeaveStreamOpen = false };
- var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize);
- long pos = bStream.GetPosition();
+ var bStream = new RewindableStream(stream);
+ bStream.StartRecording();
var factories = Factory.Factories.OfType();
@@ -68,7 +68,7 @@ public static partial class ReaderFactory
);
if (testedFactory is IReaderFactory readerFactory)
{
- bStream.StackSeek(pos);
+ bStream.Rewind();
if (
await testedFactory.IsArchiveAsync(
bStream,
@@ -76,11 +76,12 @@ public static partial class ReaderFactory
)
)
{
- bStream.StackSeek(pos);
+ bStream.Rewind();
+ bStream.StopRecording();
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
}
}
- bStream.StackSeek(pos);
+ bStream.Rewind();
}
foreach (var factory in factories)
@@ -89,13 +90,14 @@ public static partial class ReaderFactory
{
continue; // Already tested above
}
- bStream.StackSeek(pos);
+ bStream.Rewind();
if (
factory is IReaderFactory readerFactory
&& await factory.IsArchiveAsync(bStream, cancellationToken: cancellationToken)
)
{
- bStream.StackSeek(pos);
+ bStream.Rewind();
+ bStream.StopRecording();
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
}
}
diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs
index ba3a01bd..00f04404 100644
--- a/src/SharpCompress/Readers/ReaderFactory.cs
+++ b/src/SharpCompress/Readers/ReaderFactory.cs
@@ -52,7 +52,7 @@ public static partial class ReaderFactory
&& reader != null
)
{
- bStream.Rewind();
+ bStream.StopRecording();
return reader;
}
}
diff --git a/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs b/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs
index 2b80ce2d..32961ebc 100644
--- a/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs
+++ b/tests/SharpCompress.Test/Mocks/ForwardOnlyStream.cs
@@ -2,113 +2,163 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using SharpCompress.Common;
-using SharpCompress.IO;
namespace SharpCompress.Test.Mocks;
-public class ForwardOnlyStream : SharpCompressStream, IStreamStack
+///
+/// A forward-only stream wrapper that delegates directly to the underlying stream
+/// without any buffering. Supports reading and writing but not seeking.
+///
+public class ForwardOnlyStream : Stream
{
- private readonly Stream stream;
-#if DEBUG_STREAMS
- long IStreamStack.InstanceId { get; set; }
-#endif
-
- Stream IStreamStack.BaseStream() => stream;
-
- int IStreamStack.BufferSize
- {
- get => 0;
- set { }
- }
- int IStreamStack.BufferPosition
- {
- get => 0;
- set { }
- }
-
- void IStreamStack.SetPosition(long position) { }
-
- public bool IsDisposed { get; private set; }
+ private readonly Stream _stream;
+ private bool _isDisposed;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying stream to wrap.
+ /// Buffer size parameter (ignored - this implementation does not buffer).
+ /// Thrown when is null.
public ForwardOnlyStream(Stream stream, int? bufferSize = null)
- : base(stream, bufferSize: bufferSize ?? Constants.BufferSize)
{
- this.stream = stream;
-#if DEBUG_STREAMS
- this.DebugConstruct(typeof(ForwardOnlyStream));
-#endif
+ _stream = stream ?? throw new ArgumentNullException(nameof(stream));
+ // bufferSize is ignored - this implementation does not buffer
}
- protected override void Dispose(bool disposing)
- {
- if (!IsDisposed)
- {
- if (disposing)
- {
-#if DEBUG_STREAMS
- this.DebugDispose(typeof(ForwardOnlyStream));
-#endif
- stream.Dispose();
- IsDisposed = true;
- base.Dispose(disposing);
- }
- }
- }
-
- public override bool CanRead => true;
+ public override bool CanRead => !_isDisposed && _stream.CanRead;
public override bool CanSeek => false;
- public override bool CanWrite => true;
- public override void Flush() { }
+ public override bool CanWrite => !_isDisposed && _stream.CanWrite;
- public override long Length => throw new NotSupportedException();
+ public override long Length
+ {
+ get => throw new NotSupportedException("Length is not supported on a forward-only stream.");
+ }
public override long Position
{
- get => stream.Position;
- set => throw new NotSupportedException();
+ get =>
+ throw new NotSupportedException("Position is not supported on a forward-only stream.");
+ set =>
+ throw new NotSupportedException("Position is not supported on a forward-only stream.");
}
- public override int Read(byte[] buffer, int offset, int count) =>
- stream.Read(buffer, offset, count);
+ public override void Flush()
+ {
+ ThrowIfDisposed();
+ _stream.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ ThrowIfDisposed();
+ return _stream.Read(buffer, offset, count);
+ }
public override Task ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
- ) => stream.ReadAsync(buffer, offset, count, cancellationToken);
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.ReadAsync(buffer, offset, count, cancellationToken);
+ }
#if !LEGACY_DOTNET
public override ValueTask ReadAsync(
Memory buffer,
CancellationToken cancellationToken = default
- ) => stream.ReadAsync(buffer, cancellationToken);
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.ReadAsync(buffer, cancellationToken);
+ }
#endif
- public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Seek is not supported on a forward-only stream.");
- public override void SetLength(long value) => throw new NotSupportedException();
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("SetLength is not supported on a forward-only stream.");
- public override void Write(byte[] buffer, int offset, int count) =>
- stream.Write(buffer, offset, count);
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ ThrowIfDisposed();
+ _stream.Write(buffer, offset, count);
+ }
public override Task WriteAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
- ) => stream.WriteAsync(buffer, offset, count, cancellationToken);
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.WriteAsync(buffer, offset, count, cancellationToken);
+ }
#if !LEGACY_DOTNET
public override ValueTask WriteAsync(
ReadOnlyMemory buffer,
CancellationToken cancellationToken = default
- ) => stream.WriteAsync(buffer, cancellationToken);
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.WriteAsync(buffer, cancellationToken);
+ }
#endif
- public override Task FlushAsync(CancellationToken cancellationToken) =>
- stream.FlushAsync(cancellationToken);
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ ThrowIfDisposed();
+ return _stream.FlushAsync(cancellationToken);
+ }
+
+ public override Task CopyToAsync(
+ Stream destination,
+ int bufferSize,
+ CancellationToken cancellationToken
+ )
+ {
+ ThrowIfDisposed();
+ return _stream.CopyToAsync(destination, bufferSize, cancellationToken);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ {
+ _stream.Dispose();
+ }
+ _isDisposed = true;
+ base.Dispose(disposing);
+ }
+ }
+
+#if !LEGACY_DOTNET
+ public override async ValueTask DisposeAsync()
+ {
+ if (!_isDisposed)
+ {
+ await _stream.DisposeAsync();
+ _isDisposed = true;
+ }
+ await base.DisposeAsync();
+ }
+#endif
+
+ private void ThrowIfDisposed()
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException(nameof(ForwardOnlyStream));
+ }
+ }
}
diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs
index 84afa636..d357ec3f 100644
--- a/tests/SharpCompress.Test/ReaderTests.cs
+++ b/tests/SharpCompress.Test/ReaderTests.cs
@@ -164,11 +164,8 @@ public abstract class ReaderTests : TestBase
using var file = File.OpenRead(testArchive);
#if !LEGACY_DOTNET
- await using var protectedStream = SharpCompressStream.Create(
- new ForwardOnlyStream(file, options.BufferSize),
- leaveOpen: true,
- throwOnDispose: true,
- bufferSize: options.BufferSize
+ await using var protectedStream = new NonDisposingStream(
+ new ForwardOnlyStream(file, options.BufferSize)
);
await using var testStream = new TestStream(protectedStream);
#else
diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs b/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs
index 692b5b01..c5de62ba 100644
--- a/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs
+++ b/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
@@ -398,4 +399,70 @@ public class RewindableStreamAsyncTest
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 RewindableStream(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 RewindableStream(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);
+ }
}
diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs b/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs
index bf6a74a2..138daf42 100644
--- a/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs
+++ b/tests/SharpCompress.Test/Streams/RewindableStreamTest.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Linq;
using SharpCompress.IO;
using Xunit;
@@ -194,4 +195,106 @@ public class RewindableStreamTest
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 RewindableStream(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 RewindableStream(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 RewindableStream(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
}