Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Expose/add tests for Memory-based Stream overloads (#23776)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub authored Sep 11, 2017
1 parent a368dbe commit 4001151
Show file tree
Hide file tree
Showing 7 changed files with 471 additions and 13 deletions.
4 changes: 0 additions & 4 deletions src/Common/tests/System/IO/DelegateStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,12 @@ public DelegateStream(
_positionSetFunc = positionSetFunc ?? (_ => { throw new NotSupportedException(); });
_positionGetFunc = positionGetFunc ?? (() => { throw new NotSupportedException(); });

if (readAsyncFunc != null && readFunc == null)
throw new InvalidOperationException("If reads are supported, must provide a synchronous read implementation");
_readFunc = readFunc;
_readAsyncFunc = readAsyncFunc ?? ((buffer, offset, count, token) => base.ReadAsync(buffer, offset, count, token));

_seekFunc = seekFunc ?? ((_, __) => { throw new NotSupportedException(); });
_setLengthFunc = setLengthFunc ?? (_ => { throw new NotSupportedException(); });

if (writeAsyncFunc != null && writeFunc == null)
throw new InvalidOperationException("If writes are supported, must provide a synchronous write implementation");
_writeFunc = writeFunc;
_writeAsyncFunc = writeAsyncFunc ?? ((buffer, offset, count, token) => base.WriteAsync(buffer, offset, count, token));
}
Expand Down
147 changes: 147 additions & 0 deletions src/System.IO.FileSystem/tests/FileStream/ReadWriteSpan.netcoreapp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace System.IO.Tests
Expand Down Expand Up @@ -123,6 +125,119 @@ public void NonEmptyWrite_WritesExpectedData()
Assert.Equal(TestBuffer, buffer);
}
}

[Fact]
public void DisposedStream_ReadWriteAsync_Throws()
{
var fs = CreateFileStream(GetTestFilePath(), FileMode.Create);
fs.Dispose();
Assert.Throws<ObjectDisposedException>(() => { fs.ReadAsync(new Memory<byte>(new byte[1])); });
Assert.Throws<ObjectDisposedException>(() => { fs.WriteAsync(new ReadOnlyMemory<byte>(new byte[1])); });
}

[Fact]
public async Task EmptyFile_ReadAsync_Succeeds()
{
using (var fs = CreateFileStream(GetTestFilePath(), FileMode.Create))
{
// use a recognizable pattern
var buffer = (byte[])TestBuffer.Clone();

Assert.Equal(0, await fs.ReadAsync(Memory<byte>.Empty));
Assert.Equal(0, await fs.ReadAsync(new Memory<byte>(buffer, 0, 1)));
Assert.Equal(TestBuffer, buffer);

Assert.Equal(0, await fs.ReadAsync(new Memory<byte>(buffer, 0, buffer.Length)));
Assert.Equal(TestBuffer, buffer);

Assert.Equal(0, await fs.ReadAsync(new Memory<byte>(buffer, buffer.Length - 1, 1)));
Assert.Equal(TestBuffer, buffer);

Assert.Equal(0, await fs.ReadAsync(new Memory<byte>(buffer, buffer.Length / 2, buffer.Length - buffer.Length / 2)));
Assert.Equal(TestBuffer, buffer);
}
}

[Fact]
public async Task NonEmptyFile_ReadAsync_GetsExpectedData()
{
string fileName = GetTestFilePath();
File.WriteAllBytes(fileName, TestBuffer);

using (var fs = CreateFileStream(fileName, FileMode.Open))
{
var buffer = new byte[TestBuffer.Length];
Assert.Equal(TestBuffer.Length, await fs.ReadAsync(new Memory<byte>(buffer, 0, buffer.Length)));
Assert.Equal(TestBuffer, buffer);

// Larger than needed buffer, read into beginning, rest remains untouched
fs.Position = 0;
buffer = new byte[TestBuffer.Length * 2];
Assert.Equal(TestBuffer.Length, await fs.ReadAsync(new Memory<byte>(buffer)));
Assert.Equal(TestBuffer, buffer.Take(TestBuffer.Length));
Assert.Equal(new byte[buffer.Length - TestBuffer.Length], buffer.Skip(TestBuffer.Length));

// Larger than needed buffer, read into middle, beginning and end remain untouched
fs.Position = 0;
buffer = new byte[TestBuffer.Length * 2];
Assert.Equal(TestBuffer.Length, await fs.ReadAsync(new Memory<byte>(buffer, 2, buffer.Length - 2)));
Assert.Equal(TestBuffer, buffer.Skip(2).Take(TestBuffer.Length));
Assert.Equal(new byte[2], buffer.Take(2));
Assert.Equal(new byte[buffer.Length - TestBuffer.Length - 2], buffer.Skip(2 + TestBuffer.Length));
}
}

[Fact]
public void ReadOnly_WriteAsync_Throws()
{
string fileName = GetTestFilePath();
File.WriteAllBytes(fileName, TestBuffer);

using (var fs = CreateFileStream(fileName, FileMode.Open, FileAccess.Read))
{
Assert.Throws<NotSupportedException>(() => { fs.WriteAsync(new ReadOnlyMemory<byte>(new byte[1])); });
fs.Dispose();
Assert.Throws<ObjectDisposedException>(() => { fs.WriteAsync(new ReadOnlyMemory<byte>(new byte[1])); }); // Disposed checking happens first
}
}

[Fact]
public void WriteOnly_ReadAsync_Throws()
{
using (var fs = CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.Write))
{
Assert.Throws<NotSupportedException>(() => { fs.ReadAsync(new Memory<byte>(new byte[1])); });
fs.Dispose();
Assert.Throws<ObjectDisposedException>(() => { fs.ReadAsync(new Memory<byte>(new byte[1])); });// Disposed checking happens first
}
}

[Fact]
public async Task EmptyWriteAsync_NoDataWritten()
{
using (var fs = CreateFileStream(GetTestFilePath(), FileMode.Create))
{
await fs.WriteAsync(Memory<byte>.Empty);
Assert.Equal(0, fs.Length);
Assert.Equal(0, fs.Position);
}
}

[Fact]
public async Task NonEmptyWriteAsync_WritesExpectedData()
{
using (var fs = CreateFileStream(GetTestFilePath(), FileMode.Create))
{
await fs.WriteAsync(new Memory<byte>(TestBuffer));
Assert.Equal(TestBuffer.Length, fs.Length);
Assert.Equal(TestBuffer.Length, fs.Position);

fs.Position = 0;
var buffer = new byte[TestBuffer.Length];
Assert.Equal(TestBuffer.Length, await fs.ReadAsync(new Memory<byte>(buffer)));
Assert.Equal(TestBuffer, buffer);
}
}
}

public class Sync_FileStream_ReadWrite_Span : FileStream_ReadWrite_Span
Expand Down Expand Up @@ -160,6 +275,25 @@ public void CallSpanReadWriteOnDerivedFileStream_ArrayMethodsUsed()
Assert.True(fs.ReadArrayInvoked);
}
}

[Fact]
public async Task CallMemoryReadWriteAsyncOnDerivedFileStream_ArrayMethodsUsed()
{
using (var fs = (DerivedFileStream)CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite))
{
Assert.False(fs.WriteAsyncArrayInvoked);
Assert.False(fs.ReadAsyncArrayInvoked);

await fs.WriteAsync(new ReadOnlyMemory<byte>(new byte[1]));
Assert.True(fs.WriteAsyncArrayInvoked);
Assert.False(fs.ReadAsyncArrayInvoked);

fs.Position = 0;
await fs.ReadAsync(new Memory<byte>(new byte[1]));
Assert.True(fs.WriteAsyncArrayInvoked);
Assert.True(fs.ReadAsyncArrayInvoked);
}
}
}

public sealed class Async_DerivedFileStream_ReadWrite_Span : Async_FileStream_ReadWrite_Span
Expand All @@ -171,6 +305,7 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
internal sealed class DerivedFileStream : FileStream
{
public bool ReadArrayInvoked = false, WriteArrayInvoked = false;
public bool ReadAsyncArrayInvoked = false, WriteAsyncArrayInvoked = false;

public DerivedFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) :
base(path, mode, access, share, bufferSize, options)
Expand All @@ -188,5 +323,17 @@ public override void Write(byte[] array, int offset, int count)
WriteArrayInvoked = true;
base.Write(array, offset, count);
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ReadAsyncArrayInvoked = true;
return base.ReadAsync(buffer, offset, count, cancellationToken);
}

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
WriteAsyncArrayInvoked = true;
return base.WriteAsync(buffer, offset, count, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ public override int Read(UnmanagedMemoryStream stream, byte[] array, int offset,
public override void Write(UnmanagedMemoryStream stream, byte[] array, int offset, int count) =>
stream.Write(new Span<byte>(array, offset, count));
}

public sealed class MemoryUmsReadWriteTests : UmsReadWriteTests
{
public override int Read(UnmanagedMemoryStream stream, byte[] array, int offset, int count) =>
stream.ReadAsync(new Memory<byte>(array, offset, count)).GetAwaiter().GetResult();
public override void Write(UnmanagedMemoryStream stream, byte[] array, int offset, int count) =>
stream.WriteAsync(new Memory<byte>(array, offset, count)).GetAwaiter().GetResult();
}
}
101 changes: 92 additions & 9 deletions src/System.IO/tests/MemoryStream/MemoryStreamTests.netcoreapp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

using Xunit;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace System.IO.Tests
{
Expand Down Expand Up @@ -64,34 +66,115 @@ public void ReadSpan_DataReadAndPositionUpdated_Success()
public void DerivedMemoryStream_ReadWriteSpanCalled_ReadWriteArrayUsed()
{
var s = new ReadWriteOverridingMemoryStream();
Assert.False(s.WriteInvoked);
Assert.False(s.ReadInvoked);
Assert.False(s.WriteArrayInvoked);
Assert.False(s.ReadArrayInvoked);

s.Write((ReadOnlySpan<byte>)new byte[1]);
Assert.True(s.WriteInvoked);
Assert.False(s.ReadInvoked);
Assert.True(s.WriteArrayInvoked);
Assert.False(s.ReadArrayInvoked);

s.Position = 0;
s.Read((Span<byte>)new byte[1]);
Assert.True(s.WriteInvoked);
Assert.True(s.ReadInvoked);
Assert.True(s.WriteArrayInvoked);
Assert.True(s.ReadArrayInvoked);
}

[Fact]
public async Task WriteAsyncReadOnlyMemory_DataWrittenAndPositionUpdated_Success()
{
const int Iters = 100;
var rand = new Random();
byte[] data = Enumerable.Range(0, (Iters * (Iters + 1)) / 2).Select(_ => (byte)rand.Next(256)).ToArray();
var s = new MemoryStream();

int expectedPos = 0;
for (int i = 0; i <= Iters; i++)
{
await s.WriteAsync(new ReadOnlyMemory<byte>(data, expectedPos, i));
expectedPos += i;
Assert.Equal(expectedPos, s.Position);
}

Assert.Equal(data, s.ToArray());
}

[Fact]
public async Task ReadAsyncMemory_DataReadAndPositionUpdated_Success()
{
const int Iters = 100;
var rand = new Random();
byte[] data = Enumerable.Range(0, (Iters * (Iters + 1)) / 2).Select(_ => (byte)rand.Next(256)).ToArray();
var s = new MemoryStream(data);

int expectedPos = 0;
for (int i = 0; i <= Iters; i++)
{
var toRead = new Memory<byte>(new byte[i * 3]); // enough room to read the data and have some offset and have slack at the end

// Do the read and validate we read the expected number of bytes
Assert.Equal(i, await s.ReadAsync(toRead.Slice(i, i)));

// The contents prior to and after the read should be empty.
Assert.Equal<byte>(new byte[i], toRead.Slice(0, i).ToArray());
Assert.Equal<byte>(new byte[i], toRead.Slice(i * 2, i).ToArray());

// And the data read should match what was expected.
Assert.Equal(new Span<byte>(data, expectedPos, i).ToArray(), toRead.Slice(i, i).ToArray());

// Updated position should match
expectedPos += i;
Assert.Equal(expectedPos, s.Position);
}

// A final read should be empty
Assert.Equal(0, await s.ReadAsync(new Memory<byte>(new byte[1])));
}

[Fact]
public async Task DerivedMemoryStream_ReadWriteAsyncMemoryCalled_ReadWriteAsyncArrayUsed()
{
var s = new ReadWriteOverridingMemoryStream();
Assert.False(s.WriteArrayInvoked);
Assert.False(s.ReadArrayInvoked);

await s.WriteAsync((ReadOnlyMemory<byte>)new byte[1]);
Assert.True(s.WriteArrayInvoked);
Assert.False(s.ReadArrayInvoked);

s.Position = 0;
await s.ReadAsync((Memory<byte>)new byte[1]);
Assert.True(s.WriteArrayInvoked);
Assert.True(s.ReadArrayInvoked);
}

private class ReadWriteOverridingMemoryStream : MemoryStream
{
public bool ReadInvoked, WriteInvoked;
public bool ReadArrayInvoked, WriteArrayInvoked;
public bool ReadAsyncArrayInvoked, WriteAsyncArrayInvoked;

public override int Read(byte[] buffer, int offset, int count)
{
ReadInvoked = true;
ReadArrayInvoked = true;
return base.Read(buffer, offset, count);
}

public override void Write(byte[] buffer, int offset, int count)
{
WriteInvoked = true;
WriteArrayInvoked = true;
base.Write(buffer, offset, count);
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ReadAsyncArrayInvoked = true;
return base.ReadAsync(buffer, offset, count, cancellationToken);
}

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
WriteAsyncArrayInvoked = true;
return base.WriteAsync(buffer, offset, count, cancellationToken);
}
}
}
}
Loading

0 comments on commit 4001151

Please sign in to comment.