diff --git a/src/System.Net.Http.Formatting/Internal/ByteRangeStream.cs b/src/System.Net.Http.Formatting/Internal/ByteRangeStream.cs
index b519144bc..33325d4f4 100644
--- a/src/System.Net.Http.Formatting/Internal/ByteRangeStream.cs
+++ b/src/System.Net.Http.Formatting/Internal/ByteRangeStream.cs
@@ -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
{
///
- /// 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.
///
internal class ByteRangeStream : DelegatingStream
@@ -16,7 +18,7 @@ internal class ByteRangeStream : DelegatingStream
// 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 @@ public override bool CanWrite
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 @@ public override int Read(byte[] buffer, int offset, int count)
return base.Read(buffer, offset, PrepareStreamForRangeRead(count));
}
+ public override Task 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 @@ public override int ReadByte()
{
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 @@ public override void EndWrite(IAsyncResult asyncResult)
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);
}
///
- /// Gets the
+ /// Gets the correct count for the next read operation.
///
/// The count requested to be read by the caller.
/// The remaining bytes to read within the range defined for this stream.
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;
- }
+ return count;
+ }
- // Update current number of bytes read
- _currentCount += effectiveCount;
+ // Reading past the end simply does nothing.
+ if (_currentCount >= _totalCount)
+ {
+ return 0;
}
- // Effective count can never be bigger than int
+ 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;
}
}
diff --git a/src/System.Net.Http.Formatting/Internal/DelegatingStream.cs b/src/System.Net.Http.Formatting/Internal/DelegatingStream.cs
index 9547b6704..ee0c51bde 100644
--- a/src/System.Net.Http.Formatting/Internal/DelegatingStream.cs
+++ b/src/System.Net.Http.Formatting/Internal/DelegatingStream.cs
@@ -9,12 +9,12 @@
namespace System.Net.Http.Internal
{
///
- /// Stream that delegates to inner stream.
+ /// Stream that delegates to inner stream.
/// This is taken from System.Net.Http
///
internal abstract class DelegatingStream : Stream
{
- private Stream _innerStream;
+ private readonly Stream _innerStream;
protected DelegatingStream(Stream innerStream)
{
@@ -119,11 +119,6 @@ public override void Flush()
_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);
diff --git a/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs b/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs
index dab0cdd71..8797f4ffa 100644
--- a/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs
+++ b/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs
@@ -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 @@ internal static string ByteRangeStreamInvalidFrom {
}
}
+ ///
+ /// Looks up a localized string similar to An attempt was made to move the position before the beginning of the stream..
+ ///
+ internal static string ByteRangeStreamInvalidOffset {
+ get {
+ return ResourceManager.GetString("ByteRangeStreamInvalidOffset", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to None of the requested ranges ({0}) overlap with the current extent of the selected resource..
///
diff --git a/src/System.Net.Http.Formatting/Properties/Resources.resx b/src/System.Net.Http.Formatting/Properties/Resources.resx
index 77b63a96a..d20156c5a 100644
--- a/src/System.Net.Http.Formatting/Properties/Resources.resx
+++ b/src/System.Net.Http.Formatting/Properties/Resources.resx
@@ -339,4 +339,7 @@
The '{0}' method in '{1}' returned null. It must return a RemoteStreamResult instance containing a writable stream and a valid URL.
+
+ An attempt was made to move the position before the beginning of the stream.
+
\ No newline at end of file
diff --git a/test/System.Net.Http.Formatting.Test/Internal/ByteRangeStreamTest.cs b/test/System.Net.Http.Formatting.Test/Internal/ByteRangeStreamTest.cs
index bc53fd98d..eb193b51a 100644
--- a/test/System.Net.Http.Formatting.Test/Internal/ByteRangeStreamTest.cs
+++ b/test/System.Net.Http.Formatting.Test/Internal/ByteRangeStreamTest.cs
@@ -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 CopyBoundsData
+ {
+ get
+ {
+ return new TheoryDataSet
+ {
+ { 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 ReadBoundsData
+ {
+ get
+ {
+ return new TheoryDataSet
+ {
+ { 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 ReadBoundsDataWithLimit
+ {
+ get
+ {
+ return new TheoryDataSet
+ {
+ { 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 @@ public void Ctor_ThrowsOnNullRange()
public void Ctor_ThrowsIfCantSeekInnerStream()
{
// Arrange
- Mock mockInnerStream = new Mock();
+ var mockInnerStream = new Mock();
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 @@ public void Ctor_ThrowsIfCantSeekInnerStream()
public void Ctor_ThrowsIfLowerRangeExceedsInnerStream()
{
// Arrange
- Mock mockInnerStream = new Mock();
+ var mockInnerStream = new Mock();
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 @@ public void Ctor_ThrowsIfLowerRangeExceedsInnerStream()
public void Ctor_SetsContentRange()
{
// Arrange
- ContentRangeHeaderValue expectedContentRange = new ContentRangeHeaderValue(5, 9, 20);
- Mock mockInnerStream = new Mock();
+ var expectedContentRange = new ContentRangeHeaderValue(5, 9, 20);
+ var mockInnerStream = new Mock();
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 @@ public void Ctor_SetsContentRange()
public void Ctor_ThrowsIfInnerStreamLengthIsLessThanOne(int innerLength)
{
// Arrange
- Mock mockInnerStream = new Mock();
+ var mockInnerStream = new Mock();
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 mockInnerStream = new Mock();
+ var mockInnerStream = new Mock();
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);
+ using (var rangeStream = new ByteRangeStream(mockInnerStream.Object, range))
+ {
+ // Assert
+ Assert.Equal(expectedLength, rangeStream.Length);
+ }
+ }
- // Assert
- Assert.Equal(expectedLength, rangeStream.Length);
+ [Theory]
+ [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]
- [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 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();
+ 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(() => rangeStream.Position = -1L);
+ }
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData(0L)]
+ [InlineData(7L)]
+ [InlineData(9L)]
+ public void Position_ReturnsZeroInitially(long? from)
+ {
+ // Arrange
+ var mockInnerStream = new Mock();
+ 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();
+ 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();
+ 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 mockInnerStream = new Mock();
+ var mockInnerStream = new Mock();
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.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]
+ [PropertyData("ReadBoundsDataWithLimit")]
+ public async Task ReadAsync_ReadsEffectiveLengthBytes(int from, int to, int innerLength, int effectiveLength)
+ {
+ // Arrange
+ var mockInnerStream = new Mock();
+ 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();
- // Assert
- mockInnerStream.Verify(s => s.Read(data, offset, effectiveLength), Times.Once());
+ 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]
- [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("ReadBoundsData")]
public void ReadByte_ReadsEffectiveLengthTimes(int from, int to, int innerLength, int effectiveLength)
{
// Arrange
- Mock mockInnerStream = new Mock();
+ var mockInnerStream = new Mock();
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))
+ {
+ // Act
+ while (rangeStream.ReadByte() != -1)
+ {
+ counter++;
+ }
+
+ // 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();
+ 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(() => 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();
+ 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))
{
- counter++;
+ // Act
+ var newPosition = rangeStream.Seek(offset, origin);
+
+ // Assert
+ Assert.Equal(expectedPosition, newPosition);
+ Assert.Equal(expectedPosition, rangeStream.Position);
}
+ }
- // Assert
- Assert.Equal(effectiveLength, counter);
- mockInnerStream.Verify(s => s.ReadByte(), Times.Exactly(effectiveLength));
+ [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);
+ }
+ }
}
}
}