Flesh out `ByteRangeStream` methods to make it always limit reads (#213)
* Flesh out `ByteRangeStream` methods to make it always limit reads - #206 - `Seek(...)` was a no-op, `Position` was incorrect if `_lowerbounds != 0`, `ReadAsync(...)` read entire inner `Stream` - rewrite `ByteRangeStreamTest` to cover new `ByteRangeStream` members and hold fewer resources - remove `DelegatingStream.CopyToAsync(...)` override because it copied entire inner `Stream` and was not needed - base implementation invokes `ReadAsync(...)`
This commit is contained in:
Родитель
cfda5e5e44
Коммит
39d3064baf
|
@ -3,12 +3,14 @@
|
|||
|
||||
using System.IO;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace System.Net.Http.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Stream which only exposes a read-only only range view of an
|
||||
/// Stream which only exposes a read-only only range view of an
|
||||
/// inner stream.
|
||||
/// </summary>
|
||||
internal class ByteRangeStream : DelegatingStream
|
||||
|
@ -16,7 +18,7 @@ namespace System.Net.Http.Internal
|
|||
// The offset stream position at which the range starts.
|
||||
private readonly long _lowerbounds;
|
||||
|
||||
// The total number of bytes within the range.
|
||||
// The total number of bytes within the range.
|
||||
private readonly long _totalCount;
|
||||
|
||||
// The current number of bytes read into the range
|
||||
|
@ -92,6 +94,23 @@ namespace System.Net.Http.Internal
|
|||
get { return false; }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentCount;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 0L);
|
||||
}
|
||||
|
||||
_currentCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return base.BeginRead(buffer, offset, PrepareStreamForRangeRead(count), callback, state);
|
||||
|
@ -102,6 +121,11 @@ namespace System.Net.Http.Internal
|
|||
return base.Read(buffer, offset, PrepareStreamForRangeRead(count));
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return base.ReadAsync(buffer, offset, PrepareStreamForRangeRead(count), cancellationToken);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
int effectiveCount = PrepareStreamForRangeRead(1);
|
||||
|
@ -109,9 +133,35 @@ namespace System.Net.Http.Internal
|
|||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return base.ReadByte();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
_currentCount = offset;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
_currentCount = _currentCount + offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
_currentCount = _totalCount + offset;
|
||||
break;
|
||||
default:
|
||||
throw Error.InvalidEnumArgument("origin", (int)origin, typeof(SeekOrigin));
|
||||
}
|
||||
|
||||
if (_currentCount < 0L)
|
||||
{
|
||||
throw new IOException(Properties.Resources.ByteRangeStreamInvalidOffset);
|
||||
}
|
||||
|
||||
return _currentCount;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
|
||||
|
@ -132,33 +182,49 @@ namespace System.Net.Http.Internal
|
|||
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
throw Error.NotSupported(Properties.Resources.ByteRangeStreamReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the
|
||||
/// Gets the correct count for the next read operation.
|
||||
/// </summary>
|
||||
/// <param name="count">The count requested to be read by the caller.</param>
|
||||
/// <returns>The remaining bytes to read within the range defined for this stream.</returns>
|
||||
private int PrepareStreamForRangeRead(int count)
|
||||
{
|
||||
long effectiveCount = Math.Min(count, _totalCount - _currentCount);
|
||||
if (effectiveCount > 0)
|
||||
// A negative count causes base.Raad* methods to throw an ArgumentOutOfRangeException.
|
||||
if (count <= 0)
|
||||
{
|
||||
// Check if we should update the stream position
|
||||
long position = InnerStream.Position;
|
||||
if (_lowerbounds + _currentCount != position)
|
||||
{
|
||||
InnerStream.Position = _lowerbounds + _currentCount;
|
||||
}
|
||||
|
||||
// Update current number of bytes read
|
||||
_currentCount += effectiveCount;
|
||||
return count;
|
||||
}
|
||||
|
||||
// Effective count can never be bigger than int
|
||||
// Reading past the end simply does nothing.
|
||||
if (_currentCount >= _totalCount)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long effectiveCount = Math.Min(count, _totalCount - _currentCount);
|
||||
|
||||
// Check if we should update the inner stream's position.
|
||||
var newPosition = _lowerbounds + _currentCount;
|
||||
var position = InnerStream.Position;
|
||||
if (newPosition != position)
|
||||
{
|
||||
InnerStream.Position = newPosition;
|
||||
}
|
||||
|
||||
// Update current number of bytes read.
|
||||
_currentCount += effectiveCount;
|
||||
|
||||
// Effective count can never be bigger than int.
|
||||
return (int)effectiveCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,12 @@ using System.Web.Http;
|
|||
namespace System.Net.Http.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Stream that delegates to inner stream.
|
||||
/// Stream that delegates to inner stream.
|
||||
/// This is taken from System.Net.Http
|
||||
/// </summary>
|
||||
internal abstract class DelegatingStream : Stream
|
||||
{
|
||||
private Stream _innerStream;
|
||||
private readonly Stream _innerStream;
|
||||
|
||||
protected DelegatingStream(Stream innerStream)
|
||||
{
|
||||
|
@ -119,11 +119,6 @@ namespace System.Net.Http.Internal
|
|||
_innerStream.Flush();
|
||||
}
|
||||
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken);
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return _innerStream.FlushAsync(cancellationToken);
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace System.Net.Http.Properties {
|
|||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
@ -137,6 +137,15 @@ namespace System.Net.Http.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An attempt was made to move the position before the beginning of the stream..
|
||||
/// </summary>
|
||||
internal static string ByteRangeStreamInvalidOffset {
|
||||
get {
|
||||
return ResourceManager.GetString("ByteRangeStreamInvalidOffset", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to None of the requested ranges ({0}) overlap with the current extent of the selected resource..
|
||||
/// </summary>
|
||||
|
|
|
@ -339,4 +339,7 @@
|
|||
<data name="RemoteStreamInfoCannotBeNull" xml:space="preserve">
|
||||
<value>The '{0}' method in '{1}' returned null. It must return a RemoteStreamResult instance containing a writable stream and a valid URL.</value>
|
||||
</data>
|
||||
<data name="ByteRangeStreamInvalidOffset" xml:space="preserve">
|
||||
<value>An attempt was made to move the position before the beginning of the stream.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
using System.IO;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.TestCommon;
|
||||
using Moq;
|
||||
|
||||
|
@ -10,10 +12,63 @@ namespace System.Net.Http.Internal
|
|||
{
|
||||
public class ByteRangeStreamTest
|
||||
{
|
||||
// from, to, expectedText
|
||||
public static TheoryDataSet<long?, long?, string> CopyBoundsData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryDataSet<long?, long?, string>
|
||||
{
|
||||
{ null, 23, "This is the whole text." },
|
||||
{ 0, null, "This is the whole text." },
|
||||
{ 0, 22, "This is the whole text." },
|
||||
{ 0, 3, "This" },
|
||||
{ 12, 16, "whole" },
|
||||
{ null, 5, "text." },
|
||||
{ 18, null, "text." },
|
||||
{ 18, 22, "text." },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// from, to, innerLength, effectiveLength
|
||||
public static TheoryDataSet<int, int, int, int> ReadBoundsData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryDataSet<int, int, int, int>
|
||||
{
|
||||
{ 0, 9, 20, 10 },
|
||||
{ 8, 8, 10, 1 },
|
||||
{ 0, 19, 20, 20 },
|
||||
{ 0, 29, 40, 30 },
|
||||
{ 0, 29, 20, 20 },
|
||||
{ 19, 29, 20, 1 },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// from, to, innerLength, effectiveLength for reads limited by byte[] size.
|
||||
public static TheoryDataSet<int, int, int, int> ReadBoundsDataWithLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryDataSet<int, int, int, int>
|
||||
{
|
||||
{ 0, 9, 20, 10 },
|
||||
{ 8, 8, 10, 1 },
|
||||
{ 0, 19, 20, 20 },
|
||||
{ 0, 29, 40, 25 },
|
||||
{ 0, 29, 20, 20 },
|
||||
{ 19, 29, 20, 1 },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ctor_ThrowsOnNullInnerStream()
|
||||
{
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(0, 10);
|
||||
var range = new RangeItemHeaderValue(0, 10);
|
||||
Assert.ThrowsArgumentNull(() => new ByteRangeStream(innerStream: null, range: range), "innerStream");
|
||||
}
|
||||
|
||||
|
@ -27,9 +82,9 @@ namespace System.Net.Http.Internal
|
|||
public void Ctor_ThrowsIfCantSeekInnerStream()
|
||||
{
|
||||
// Arrange
|
||||
Mock<Stream> mockInnerStream = new Mock<Stream>();
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(false);
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(0, 10);
|
||||
var range = new RangeItemHeaderValue(0, 10);
|
||||
|
||||
// Act/Assert
|
||||
Assert.ThrowsArgument(() => new ByteRangeStream(mockInnerStream.Object, range), "innerStream");
|
||||
|
@ -39,10 +94,10 @@ namespace System.Net.Http.Internal
|
|||
public void Ctor_ThrowsIfLowerRangeExceedsInnerStream()
|
||||
{
|
||||
// Arrange
|
||||
Mock<Stream> mockInnerStream = new Mock<Stream>();
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(5);
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(10, 20);
|
||||
var range = new RangeItemHeaderValue(10, 20);
|
||||
|
||||
// Act/Assert
|
||||
Assert.ThrowsArgumentOutOfRange(() => new ByteRangeStream(mockInnerStream.Object, range), "range",
|
||||
|
@ -53,17 +108,18 @@ namespace System.Net.Http.Internal
|
|||
public void Ctor_SetsContentRange()
|
||||
{
|
||||
// Arrange
|
||||
ContentRangeHeaderValue expectedContentRange = new ContentRangeHeaderValue(5, 9, 20);
|
||||
Mock<Stream> mockInnerStream = new Mock<Stream>();
|
||||
var expectedContentRange = new ContentRangeHeaderValue(5, 9, 20);
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(20);
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(5, 9);
|
||||
var range = new RangeItemHeaderValue(5, 9);
|
||||
|
||||
// Act
|
||||
ByteRangeStream rangeStream = new ByteRangeStream(mockInnerStream.Object, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedContentRange, rangeStream.ContentRange);
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal(expectedContentRange, rangeStream.ContentRange);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -72,87 +128,462 @@ namespace System.Net.Http.Internal
|
|||
public void Ctor_ThrowsIfInnerStreamLengthIsLessThanOne(int innerLength)
|
||||
{
|
||||
// Arrange
|
||||
Mock<Stream> mockInnerStream = new Mock<Stream>();
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(innerLength);
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(null, 0);
|
||||
var range = new RangeItemHeaderValue(null, 0);
|
||||
|
||||
// Act/Assert
|
||||
Assert.ThrowsArgumentOutOfRange(() => new ByteRangeStream(mockInnerStream.Object, range), "innerStream",
|
||||
"The stream over which 'ByteRangeStream' provides a range view must have a length greater than or equal to 1.",
|
||||
false, innerLength);
|
||||
Assert.ThrowsArgumentOutOfRange(
|
||||
() => new ByteRangeStream(mockInnerStream.Object, range),
|
||||
"innerStream",
|
||||
"The stream over which 'ByteRangeStream' provides a range view must have a length greater than or " +
|
||||
"equal to 1.",
|
||||
false,
|
||||
innerLength);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 9, 20, 10)]
|
||||
[InlineData(8, 8, 10, 1)]
|
||||
[InlineData(0, 19, 20, 20)]
|
||||
[PropertyData("ReadBoundsData")]
|
||||
public void Ctor_SetsLength(int from, int to, int innerLength, int expectedLength)
|
||||
{
|
||||
// Arrange
|
||||
Mock<Stream> mockInnerStream = new Mock<Stream>();
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(innerLength);
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(from, to);
|
||||
var range = new RangeItemHeaderValue(from, to);
|
||||
|
||||
// Act
|
||||
ByteRangeStream rangeStream = new ByteRangeStream(mockInnerStream.Object, range);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedLength, rangeStream.Length);
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal(expectedLength, rangeStream.Length);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 9, 20, 10)]
|
||||
[InlineData(8, 8, 10, 1)]
|
||||
[InlineData(0, 19, 20, 20)]
|
||||
[InlineData(0, 29, 40, 25)]
|
||||
[InlineData(0, 29, 20, 20)]
|
||||
[InlineData(19, 29, 20, 1)]
|
||||
[PropertyData("CopyBoundsData")]
|
||||
public async Task CopyTo_ReadsSpecifiedRange(long? from, long? to, string expectedText)
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "This is the whole text.";
|
||||
var range = new RangeItemHeaderValue(from, to);
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
using (var targetStream = new MemoryStream())
|
||||
using (var reader = new StreamReader(targetStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
// Act
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
rangeStream.CopyTo(targetStream);
|
||||
}
|
||||
|
||||
// Assert
|
||||
targetStream.Position = 0L;
|
||||
var text = await reader.ReadToEndAsync();
|
||||
Assert.Equal(expectedText, text);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[PropertyData("CopyBoundsData")]
|
||||
public async Task CopyToAsync_ReadsSpecifiedRange(long? from, long? to, string expectedText)
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "This is the whole text.";
|
||||
var range = new RangeItemHeaderValue(from, to);
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
using (var targetStream = new MemoryStream())
|
||||
using (var reader = new StreamReader(targetStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
// Act
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
await rangeStream.CopyToAsync(targetStream);
|
||||
}
|
||||
|
||||
// Assert
|
||||
targetStream.Position = 0L;
|
||||
var text = await reader.ReadToEndAsync();
|
||||
Assert.Equal(expectedText, text);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Position_ThrowsOnNegativeValue()
|
||||
{
|
||||
// Arrange
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(10L);
|
||||
var range = new RangeItemHeaderValue(0, 25L);
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => rangeStream.Position = -1L);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(0L)]
|
||||
[InlineData(7L)]
|
||||
[InlineData(9L)]
|
||||
public void Position_ReturnsZeroInitially(long? from)
|
||||
{
|
||||
// Arrange
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(10L);
|
||||
var range = new RangeItemHeaderValue(from, 25L);
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act
|
||||
var position = rangeStream.Position;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0L, position);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Position_CanBeSetAfterLength()
|
||||
{
|
||||
// Arrange
|
||||
var expectedPosition = 300L;
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(10L);
|
||||
var range = new RangeItemHeaderValue(0L, 10L);
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act
|
||||
rangeStream.Position = expectedPosition;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedPosition, rangeStream.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Position_PositionsNextRead()
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "890123456789";
|
||||
var range = new RangeItemHeaderValue(2L, null);
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
// Act
|
||||
rangeStream.Position = 5L;
|
||||
|
||||
// Assert
|
||||
var read = rangeStream.ReadByte();
|
||||
Assert.Equal('5', (char)read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[PropertyData("ReadBoundsDataWithLimit")]
|
||||
public void BeginRead_ReadsEffectiveLengthBytes(int from, int to, int innerLength, int effectiveLength)
|
||||
{
|
||||
// Arrange
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(innerLength);
|
||||
var range = new RangeItemHeaderValue(from, to);
|
||||
var data = new byte[25];
|
||||
var offset = 5;
|
||||
var callback = new AsyncCallback(_ => { });
|
||||
var userState = new object();
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act
|
||||
var result = rangeStream.BeginRead(data, offset, data.Length, callback, userState);
|
||||
rangeStream.EndRead(result);
|
||||
|
||||
// Assert
|
||||
mockInnerStream.Verify(
|
||||
s => s.BeginRead(data, offset, effectiveLength, callback, userState),
|
||||
Times.Once());
|
||||
Assert.Equal(effectiveLength, rangeStream.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BeginRead_CanReadAfterLength()
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "This is the whole text.";
|
||||
var range = new RangeItemHeaderValue(0L, null);
|
||||
var data = new byte[25];
|
||||
var callback = new AsyncCallback(_ => { });
|
||||
var userState = new object();
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
rangeStream.Position = 50L;
|
||||
|
||||
// Act
|
||||
var result = rangeStream.BeginRead(data, 0, data.Length, callback, userState);
|
||||
var read = rangeStream.EndRead(result);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[PropertyData("ReadBoundsDataWithLimit")]
|
||||
public void Read_ReadsEffectiveLengthBytes(int from, int to, int innerLength, int effectiveLength)
|
||||
{
|
||||
// Arrange
|
||||
Mock<Stream> mockInnerStream = new Mock<Stream>();
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(innerLength);
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(from, to);
|
||||
byte[] data = new byte[25];
|
||||
int offset = 5;
|
||||
var range = new RangeItemHeaderValue(from, to);
|
||||
var data = new byte[25];
|
||||
var offset = 5;
|
||||
|
||||
// Act
|
||||
ByteRangeStream rangeStream = new ByteRangeStream(mockInnerStream.Object, range);
|
||||
rangeStream.Read(data, offset, data.Length);
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act
|
||||
rangeStream.Read(data, offset, data.Length);
|
||||
|
||||
// Assert
|
||||
mockInnerStream.Verify(s => s.Read(data, offset, effectiveLength), Times.Once());
|
||||
// Assert
|
||||
mockInnerStream.Verify(s => s.Read(data, offset, effectiveLength), Times.Once());
|
||||
Assert.Equal(effectiveLength, rangeStream.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Read_CanReadAfterLength()
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "This is the whole text.";
|
||||
var range = new RangeItemHeaderValue(0L, null);
|
||||
var data = new byte[25];
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
rangeStream.Position = 50L;
|
||||
|
||||
// Act
|
||||
var read = rangeStream.Read(data, 0, data.Length);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 9, 20, 10)]
|
||||
[InlineData(8, 8, 10, 1)]
|
||||
[InlineData(0, 19, 20, 20)]
|
||||
[InlineData(0, 29, 40, 30)]
|
||||
[InlineData(0, 29, 20, 20)]
|
||||
[InlineData(19, 29, 20, 1)]
|
||||
[PropertyData("ReadBoundsDataWithLimit")]
|
||||
public async Task ReadAsync_ReadsEffectiveLengthBytes(int from, int to, int innerLength, int effectiveLength)
|
||||
{
|
||||
// Arrange
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(innerLength);
|
||||
var range = new RangeItemHeaderValue(from, to);
|
||||
var data = new byte[25];
|
||||
var offset = 5;
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act
|
||||
await rangeStream.ReadAsync(data, offset, data.Length);
|
||||
|
||||
// Assert
|
||||
mockInnerStream.Verify(
|
||||
s => s.ReadAsync(data, offset, effectiveLength, CancellationToken.None),
|
||||
Times.Once());
|
||||
Assert.Equal(effectiveLength, rangeStream.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsync_CanReadAfterLength()
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "This is the whole text.";
|
||||
var range = new RangeItemHeaderValue(0L, null);
|
||||
var data = new byte[25];
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
rangeStream.Position = 50L;
|
||||
|
||||
// Act
|
||||
var read = await rangeStream.ReadAsync(data, 0, data.Length);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[PropertyData("ReadBoundsData")]
|
||||
public void ReadByte_ReadsEffectiveLengthTimes(int from, int to, int innerLength, int effectiveLength)
|
||||
{
|
||||
// Arrange
|
||||
Mock<Stream> mockInnerStream = new Mock<Stream>();
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(innerLength);
|
||||
RangeItemHeaderValue range = new RangeItemHeaderValue(from, to);
|
||||
var range = new RangeItemHeaderValue(from, to);
|
||||
var counter = 0;
|
||||
|
||||
// Act
|
||||
ByteRangeStream rangeStream = new ByteRangeStream(mockInnerStream.Object, range);
|
||||
int counter = 0;
|
||||
while (rangeStream.ReadByte() != -1)
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
counter++;
|
||||
}
|
||||
// Act
|
||||
while (rangeStream.ReadByte() != -1)
|
||||
{
|
||||
counter++;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(effectiveLength, counter);
|
||||
mockInnerStream.Verify(s => s.ReadByte(), Times.Exactly(effectiveLength));
|
||||
// Assert
|
||||
mockInnerStream.Verify(s => s.ReadByte(), Times.Exactly(effectiveLength));
|
||||
Assert.Equal(effectiveLength, counter);
|
||||
Assert.Equal(effectiveLength, rangeStream.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadByte_CanReadAfterLength()
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "This is the whole text.";
|
||||
var range = new RangeItemHeaderValue(0L, null);
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
rangeStream.Position = 50L;
|
||||
|
||||
// Act
|
||||
var read = rangeStream.ReadByte();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(-1, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, SeekOrigin.Begin)]
|
||||
[InlineData(-1, SeekOrigin.Current)]
|
||||
[InlineData(-11, SeekOrigin.End)]
|
||||
public void Seek_ThrowsIfBeforeOrigin(int offset, SeekOrigin origin)
|
||||
{
|
||||
// Arrange
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(10L);
|
||||
var range = new RangeItemHeaderValue(0, 25L);
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<IOException>(() => rangeStream.Seek(offset, origin));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(25, SeekOrigin.Begin)]
|
||||
[InlineData(25, SeekOrigin.Current)]
|
||||
[InlineData(15, SeekOrigin.End)]
|
||||
public void Seek_CanMoveAfterLength(int offset, SeekOrigin origin)
|
||||
{
|
||||
// Arrange
|
||||
var expectedPosition = 25L;
|
||||
var mockInnerStream = new Mock<Stream>();
|
||||
mockInnerStream.Setup(s => s.CanSeek).Returns(true);
|
||||
mockInnerStream.Setup(s => s.Length).Returns(10L);
|
||||
var range = new RangeItemHeaderValue(0L, 10L);
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
|
||||
{
|
||||
// Act
|
||||
var newPosition = rangeStream.Seek(offset, origin);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedPosition, newPosition);
|
||||
Assert.Equal(expectedPosition, rangeStream.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(5, SeekOrigin.Begin)]
|
||||
[InlineData(5, SeekOrigin.Current)]
|
||||
[InlineData(-5, SeekOrigin.End)]
|
||||
public async Task Seek_PositionsNextRead(int offset, SeekOrigin origin)
|
||||
{
|
||||
// Arrange
|
||||
var originalText = "890123456789";
|
||||
var range = new RangeItemHeaderValue(2L, null);
|
||||
|
||||
using (var innerStream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(innerStream))
|
||||
{
|
||||
await writer.WriteAsync(originalText);
|
||||
await writer.FlushAsync();
|
||||
|
||||
using (var rangeStream = new ByteRangeStream(innerStream, range))
|
||||
{
|
||||
// Act
|
||||
rangeStream.Seek(offset, origin);
|
||||
|
||||
// Assert
|
||||
var read = rangeStream.ReadByte();
|
||||
Assert.Equal('5', (char)read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче