diff --git a/src/MockHttp/IO/RateLimitedStream.cs b/src/MockHttp/IO/RateLimitedStream.cs index 292ea3fa..4c82de49 100644 --- a/src/MockHttp/IO/RateLimitedStream.cs +++ b/src/MockHttp/IO/RateLimitedStream.cs @@ -16,25 +16,28 @@ namespace MockHttp.IO; /// public class RateLimitedStream : Stream { - private const int SampleRate = 100; // How often to take samples (per sec). - private static readonly TimeSpan ThrottleInterval = TimeSpan.FromMilliseconds(1000D / SampleRate); internal const int MinBitRate = 128; + private const int SampleRate = 100; // How often to take samples (per sec). private const int MaxBufferSize = 2 << 15; // 128KB + private static readonly TimeSpan ThrottleInterval = TimeSpan.FromMilliseconds(1000D / SampleRate); - private long _totalBytesRead; - - private readonly Stopwatch _stopwatch; private readonly Stream _actualStream; private readonly int _byteRate; + private readonly bool _leaveOpen; + private readonly Stopwatch _stopwatch; + + private long _totalBytesRead; /// /// Initializes a new instance of the . /// /// The actual stream to wrap. /// The bit rate to simulate. + /// to leave the open on close/disposal. /// Thrown when is null. - /// Thrown when the stream is not readable or the bit rate is less than 128. - public RateLimitedStream(Stream actualStream, int bitRate) + /// Thrown when the stream is not readable. + /// Thrown when the bit rate is less than 128. + public RateLimitedStream(Stream actualStream, int bitRate, bool leaveOpen = false) { _actualStream = actualStream ?? throw new ArgumentNullException(nameof(actualStream)); if (!_actualStream.CanRead) @@ -47,10 +50,30 @@ public RateLimitedStream(Stream actualStream, int bitRate) throw new ArgumentOutOfRangeException(nameof(bitRate), $"Bit rate must be higher than or equal to {MinBitRate}."); } + _leaveOpen = leaveOpen; _byteRate = bitRate / 8; // We are computing bytes transferred. _stopwatch = new Stopwatch(); } + /// + public override bool CanRead => _actualStream.CanRead; + + /// + public override bool CanSeek => _actualStream.CanSeek; + + /// + public override bool CanWrite => false; + + /// + public override long Length => _actualStream.Length; + + /// + public override long Position + { + get => _actualStream.Position; + set => _actualStream.Position = value; + } + /// public override void Flush() { @@ -100,31 +123,15 @@ public override void Write(byte[] buffer, int offset, int count) throw new NotSupportedException("Modifying stream is not supported."); } - /// - public override bool CanRead => _actualStream.CanRead; - - /// - public override bool CanSeek => _actualStream.CanSeek; - - /// - public override bool CanWrite => false; - - /// - public override long Length => _actualStream.Length; - - /// - public override long Position - { - get => _actualStream.Position; - set => _actualStream.Position = value; - } - /// protected override void Dispose(bool disposing) { try { - _actualStream.Dispose(); + if (!_leaveOpen) + { + _actualStream.Dispose(); + } } finally { diff --git a/test/MockHttp.Tests/RateLimitedStreamTests.cs b/test/MockHttp.Tests/RateLimitedStreamTests.cs index 0d941c67..2b694d8c 100644 --- a/test/MockHttp.Tests/RateLimitedStreamTests.cs +++ b/test/MockHttp.Tests/RateLimitedStreamTests.cs @@ -190,6 +190,20 @@ public void When_disposing_it_should_dispose_underlying() streamStub.Received(1).Close(); } + [Fact] + public void Keeps_actual_stream_open_if_desired() + { + using MemoryStream streamStub = Substitute.For(); + streamStub.CanRead.Returns(true); + var sut = new RateLimitedStream(streamStub, 1024, true); + + // Act + sut.Dispose(); + + // Assert + streamStub.DidNotReceive().Close(); + } + public void Dispose() { // ReSharper disable ConditionalAccessQualifierIsNonNullableAccordingToAPIContract