Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ctor parameter to keep the underlying stream open on disposal #95

Merged
merged 1 commit into from
Dec 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading