Skip to content

Commit

Permalink
feat: add ctor parameter to keep the underlying stream open on dispos…
Browse files Browse the repository at this point in the history
…al (#95)
  • Loading branch information
skwasjer authored Dec 24, 2023
1 parent e5edef2 commit 5e3c57f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 27 deletions.
61 changes: 34 additions & 27 deletions src/MockHttp/IO/RateLimitedStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,28 @@ namespace MockHttp.IO;
/// </remarks>
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;

/// <summary>
/// Initializes a new instance of the <see cref="RateLimitedStream" />.
/// </summary>
/// <param name="actualStream">The actual stream to wrap.</param>
/// <param name="bitRate">The bit rate to simulate.</param>
/// <param name="leaveOpen"><see langword="true" /> to leave the <paramref name="actualStream" /> open on close/disposal.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="actualStream" /> is null.</exception>
/// <exception cref="ArgumentException">Thrown when the stream is not readable or the bit rate is less than 128.</exception>
public RateLimitedStream(Stream actualStream, int bitRate)
/// <exception cref="ArgumentException">Thrown when the stream is not readable.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the bit rate is less than 128.</exception>
public RateLimitedStream(Stream actualStream, int bitRate, bool leaveOpen = false)
{
_actualStream = actualStream ?? throw new ArgumentNullException(nameof(actualStream));
if (!_actualStream.CanRead)
Expand All @@ -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();
}

/// <inheritdoc />
public override bool CanRead => _actualStream.CanRead;

/// <inheritdoc />
public override bool CanSeek => _actualStream.CanSeek;

/// <inheritdoc />
public override bool CanWrite => false;

/// <inheritdoc />
public override long Length => _actualStream.Length;

/// <inheritdoc />
public override long Position
{
get => _actualStream.Position;
set => _actualStream.Position = value;
}

/// <inheritdoc />
public override void Flush()
{
Expand Down Expand Up @@ -100,31 +123,15 @@ public override void Write(byte[] buffer, int offset, int count)
throw new NotSupportedException("Modifying stream is not supported.");
}

/// <inheritdoc />
public override bool CanRead => _actualStream.CanRead;

/// <inheritdoc />
public override bool CanSeek => _actualStream.CanSeek;

/// <inheritdoc />
public override bool CanWrite => false;

/// <inheritdoc />
public override long Length => _actualStream.Length;

/// <inheritdoc />
public override long Position
{
get => _actualStream.Position;
set => _actualStream.Position = value;
}

/// <inheritdoc />
protected override void Dispose(bool disposing)
{
try
{
_actualStream.Dispose();
if (!_leaveOpen)
{
_actualStream.Dispose();
}
}
finally
{
Expand Down
14 changes: 14 additions & 0 deletions test/MockHttp.Tests/RateLimitedStreamTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MemoryStream>();
streamStub.CanRead.Returns(true);
var sut = new RateLimitedStream(streamStub, 1024, true);

// Act
sut.Dispose();

// Assert
streamStub.DidNotReceive().Close();
}

public void Dispose()
{
// ReSharper disable ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
Expand Down

0 comments on commit 5e3c57f

Please sign in to comment.