|
|
@@ -4,8 +4,10 @@
|
|
|
using System;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
+using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
+using Microsoft.AspNetCore.Testing;
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
using Microsoft.Extensions.Options;
|
|
|
using Microsoft.JSInterop;
|
|
|
@@ -19,34 +21,34 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|
|
private static readonly TestRemoteJSRuntime _jsRuntime = new(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
|
|
|
[Fact]
|
|
|
- public async void CreateRemoteJSDataStreamAsync_CreatesStream()
|
|
|
+ public async Task CreateRemoteJSDataStreamAsync_CreatesStream()
|
|
|
{
|
|
|
// Arrange
|
|
|
var jsStreamReference = Mock.Of<IJSStreamReference>();
|
|
|
|
|
|
// Act
|
|
|
- var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(_jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
|
|
|
+ var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(_jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None).DefaultTimeout();
|
|
|
|
|
|
// Assert
|
|
|
Assert.NotNull(remoteJSDataStream);
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
- public async void ReceiveData_DoesNotFindStream()
|
|
|
+ public async Task ReceiveData_DoesNotFindStream()
|
|
|
{
|
|
|
// Arrange
|
|
|
var chunk = new byte[] { 3, 5, 6, 7 };
|
|
|
var unrecognizedGuid = 10;
|
|
|
|
|
|
// Act
|
|
|
- var success = await RemoteJSDataStream.ReceiveData(_jsRuntime, streamId: unrecognizedGuid, chunkId: 0, chunk, error: null);
|
|
|
+ var success = await RemoteJSDataStream.ReceiveData(_jsRuntime, streamId: unrecognizedGuid, chunkId: 0, chunk, error: null).DefaultTimeout();
|
|
|
|
|
|
// Assert
|
|
|
Assert.False(success);
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
- public async void ReceiveData_SuccessReadsBackStream()
|
|
|
+ public async Task ReceiveData_SuccessReadsBackStream()
|
|
|
{
|
|
|
// Arrange
|
|
|
var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
@@ -59,22 +61,50 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|
|
var sendDataTask = Task.Run(async () =>
|
|
|
{
|
|
|
// Act 1
|
|
|
- var success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null);
|
|
|
+ var success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null).DefaultTimeout();
|
|
|
return success;
|
|
|
});
|
|
|
|
|
|
// Act & Assert 2
|
|
|
using var memoryStream = new MemoryStream();
|
|
|
- await remoteJSDataStream.CopyToAsync(memoryStream);
|
|
|
+ await remoteJSDataStream.CopyToAsync(memoryStream).DefaultTimeout();
|
|
|
Assert.Equal(chunk, memoryStream.ToArray());
|
|
|
|
|
|
// Act & Assert 3
|
|
|
- var sendDataCompleted = await sendDataTask;
|
|
|
+ var sendDataCompleted = await sendDataTask.DefaultTimeout();
|
|
|
Assert.True(sendDataCompleted);
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
- public async void ReceiveData_WithError()
|
|
|
+ public async Task ReceiveData_SuccessReadsBackPipeReader()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
+ var remoteJSDataStream = await CreateRemoteJSDataStreamAsync(jsRuntime);
|
|
|
+ var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
|
|
|
+ var chunk = new byte[100];
|
|
|
+ var random = new Random();
|
|
|
+ random.NextBytes(chunk);
|
|
|
+
|
|
|
+ var sendDataTask = Task.Run(async () =>
|
|
|
+ {
|
|
|
+ // Act 1
|
|
|
+ var success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null).DefaultTimeout();
|
|
|
+ return success;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Act & Assert 2
|
|
|
+ using var memoryStream = new MemoryStream();
|
|
|
+ await remoteJSDataStream.PipeReader.CopyToAsync(memoryStream).DefaultTimeout();
|
|
|
+ Assert.Equal(chunk, memoryStream.ToArray());
|
|
|
+
|
|
|
+ // Act & Assert 3
|
|
|
+ var sendDataCompleted = await sendDataTask.DefaultTimeout();
|
|
|
+ Assert.True(sendDataCompleted);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task ReceiveData_WithError()
|
|
|
{
|
|
|
// Arrange
|
|
|
var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
@@ -82,17 +112,17 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|
|
var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
|
|
|
|
|
|
// Act & Assert 1
|
|
|
- var success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk: null, error: "some error");
|
|
|
+ var success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk: null, error: "some error").DefaultTimeout();
|
|
|
Assert.False(success);
|
|
|
|
|
|
// Act & Assert 2
|
|
|
using var mem = new MemoryStream();
|
|
|
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await remoteJSDataStream.CopyToAsync(mem));
|
|
|
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await remoteJSDataStream.CopyToAsync(mem).DefaultTimeout());
|
|
|
Assert.Equal("An error occurred while reading the remote stream: some error", ex.Message);
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
- public async void ReceiveData_WithZeroLengthChunk()
|
|
|
+ public async Task ReceiveData_WithZeroLengthChunk()
|
|
|
{
|
|
|
// Arrange
|
|
|
var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
@@ -101,42 +131,42 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|
|
var chunk = Array.Empty<byte>();
|
|
|
|
|
|
// Act & Assert 1
|
|
|
- var ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null));
|
|
|
+ var ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null).DefaultTimeout());
|
|
|
Assert.Equal("The incoming data chunk cannot be empty.", ex.Message);
|
|
|
|
|
|
// Act & Assert 2
|
|
|
using var mem = new MemoryStream();
|
|
|
- ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await remoteJSDataStream.CopyToAsync(mem));
|
|
|
+ ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await remoteJSDataStream.CopyToAsync(mem).DefaultTimeout());
|
|
|
Assert.Equal("The incoming data chunk cannot be empty.", ex.Message);
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
- public async void ReceiveData_ProvidedWithMoreBytesThanRemaining()
|
|
|
+ public async Task ReceiveData_ProvidedWithMoreBytesThanRemaining()
|
|
|
{
|
|
|
// Arrange
|
|
|
var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
var jsStreamReference = Mock.Of<IJSStreamReference>();
|
|
|
- var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
|
|
|
+ var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None);
|
|
|
var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
|
|
|
var chunk = new byte[110]; // 100 byte totalLength for stream
|
|
|
|
|
|
// Act & Assert 1
|
|
|
- var ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null));
|
|
|
+ var ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null).DefaultTimeout());
|
|
|
Assert.Equal("The incoming data stream declared a length 100, but 110 bytes were sent.", ex.Message);
|
|
|
|
|
|
// Act & Assert 2
|
|
|
using var mem = new MemoryStream();
|
|
|
- ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await remoteJSDataStream.CopyToAsync(mem));
|
|
|
+ ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await remoteJSDataStream.CopyToAsync(mem).DefaultTimeout());
|
|
|
Assert.Equal("The incoming data stream declared a length 100, but 110 bytes were sent.", ex.Message);
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
- public async void ReceiveData_ProvidedWithOutOfOrderChunk_SimulatesSignalRDisconnect()
|
|
|
+ public async Task ReceiveData_ProvidedWithOutOfOrderChunk_SimulatesSignalRDisconnect()
|
|
|
{
|
|
|
// Arrange
|
|
|
var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
var jsStreamReference = Mock.Of<IJSStreamReference>();
|
|
|
- var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
|
|
|
+ var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None);
|
|
|
var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
|
|
|
var chunk = new byte[5];
|
|
|
|
|
|
@@ -145,19 +175,113 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|
|
{
|
|
|
await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: i, chunk, error: null);
|
|
|
}
|
|
|
- var ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 7, chunk, error: null));
|
|
|
+ var ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 7, chunk, error: null).DefaultTimeout());
|
|
|
Assert.Equal("Out of sequence chunk received, expected 5, but received 7.", ex.Message);
|
|
|
|
|
|
// Act & Assert 2
|
|
|
using var mem = new MemoryStream();
|
|
|
- ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await remoteJSDataStream.CopyToAsync(mem));
|
|
|
+ ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await remoteJSDataStream.CopyToAsync(mem).DefaultTimeout());
|
|
|
Assert.Equal("Out of sequence chunk received, expected 5, but received 7.", ex.Message);
|
|
|
}
|
|
|
|
|
|
+ [Fact]
|
|
|
+ public async Task ReceiveData_NoDataProvidedBeforeTimeout_StreamDisposed()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var unhandledExceptionRaisedTask = new TaskCompletionSource<bool>();
|
|
|
+ var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
+ jsRuntime.UnhandledException += (_, ex) =>
|
|
|
+ {
|
|
|
+ Assert.Equal("Did not receive any data in the allotted time.", ex.Message);
|
|
|
+ unhandledExceptionRaisedTask.SetResult(ex is TimeoutException);
|
|
|
+ };
|
|
|
+
|
|
|
+ var jsStreamReference = Mock.Of<IJSStreamReference>();
|
|
|
+ var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(
|
|
|
+ jsRuntime,
|
|
|
+ jsStreamReference,
|
|
|
+ totalLength: 15,
|
|
|
+ maximumIncomingBytes: 10_000,
|
|
|
+ jsInteropDefaultCallTimeout: TimeSpan.FromSeconds(2),
|
|
|
+ pauseIncomingBytesThreshold: 50,
|
|
|
+ resumeIncomingBytesThreshold: 25,
|
|
|
+ cancellationToken: CancellationToken.None);
|
|
|
+ var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
|
|
|
+ var chunk = new byte[] { 3, 5, 7 };
|
|
|
+
|
|
|
+ // Act & Assert 1
|
|
|
+ // Trigger timeout and ensure unhandled exception raised to crush circuit
|
|
|
+ remoteJSDataStream.InvalidateLastDataReceivedTimeForTimeout();
|
|
|
+ var unhandledExceptionResult = await unhandledExceptionRaisedTask.Task.DefaultTimeout();
|
|
|
+ Assert.True(unhandledExceptionResult);
|
|
|
+
|
|
|
+ // Act & Assert 2
|
|
|
+ // Confirm exception also raised on pipe reader
|
|
|
+ using var mem = new MemoryStream();
|
|
|
+ var ex = await Assert.ThrowsAsync<TimeoutException>(async () => await remoteJSDataStream.CopyToAsync(mem).DefaultTimeout());
|
|
|
+ Assert.Equal("Did not receive any data in the allotted time.", ex.Message);
|
|
|
+
|
|
|
+ // Act & Assert 3
|
|
|
+ // Ensures stream is disposed after the timeout and any additional chunks aren't accepted
|
|
|
+ var success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null).DefaultTimeout();
|
|
|
+ Assert.False(success);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task ReceiveData_ReceivesDataThenTimesout_StreamDisposed()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var unhandledExceptionRaisedTask = new TaskCompletionSource<bool>();
|
|
|
+ var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
|
|
|
+ jsRuntime.UnhandledException += (_, ex) =>
|
|
|
+ {
|
|
|
+ Assert.Equal("Did not receive any data in the allotted time.", ex.Message);
|
|
|
+ unhandledExceptionRaisedTask.SetResult(ex is TimeoutException);
|
|
|
+ };
|
|
|
+
|
|
|
+ var jsStreamReference = Mock.Of<IJSStreamReference>();
|
|
|
+ var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(
|
|
|
+ jsRuntime,
|
|
|
+ jsStreamReference,
|
|
|
+ totalLength: 15,
|
|
|
+ maximumIncomingBytes: 10_000,
|
|
|
+ jsInteropDefaultCallTimeout: TimeSpan.FromSeconds(3),
|
|
|
+ pauseIncomingBytesThreshold: 50,
|
|
|
+ resumeIncomingBytesThreshold: 25,
|
|
|
+ cancellationToken: CancellationToken.None);
|
|
|
+ var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
|
|
|
+ var chunk = new byte[] { 3, 5, 7 };
|
|
|
+
|
|
|
+ // Act & Assert 1
|
|
|
+ var success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null).DefaultTimeout();
|
|
|
+ Assert.True(success);
|
|
|
+
|
|
|
+ // Act & Assert 2
|
|
|
+ success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 1, chunk, error: null).DefaultTimeout();
|
|
|
+ Assert.True(success);
|
|
|
+
|
|
|
+ // Act & Assert 3
|
|
|
+ // Trigger timeout and ensure unhandled exception raised to crush circuit
|
|
|
+ remoteJSDataStream.InvalidateLastDataReceivedTimeForTimeout();
|
|
|
+ var unhandledExceptionResult = await unhandledExceptionRaisedTask.Task.DefaultTimeout();
|
|
|
+ Assert.True(unhandledExceptionResult);
|
|
|
+
|
|
|
+ // Act & Assert 4
|
|
|
+ // Confirm exception also raised on pipe reader
|
|
|
+ using var mem = new MemoryStream();
|
|
|
+ var ex = await Assert.ThrowsAsync<TimeoutException>(async () => await remoteJSDataStream.CopyToAsync(mem).DefaultTimeout());
|
|
|
+ Assert.Equal("Did not receive any data in the allotted time.", ex.Message);
|
|
|
+
|
|
|
+ // Act & Assert 5
|
|
|
+ // Ensures stream is disposed after the timeout and any additional chunks aren't accepted
|
|
|
+ success = await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 2, chunk, error: null).DefaultTimeout();
|
|
|
+ Assert.False(success);
|
|
|
+ }
|
|
|
+
|
|
|
private static async Task<RemoteJSDataStream> CreateRemoteJSDataStreamAsync(TestRemoteJSRuntime jsRuntime = null)
|
|
|
{
|
|
|
var jsStreamReference = Mock.Of<IJSStreamReference>();
|
|
|
- var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime ?? _jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
|
|
|
+ var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime ?? _jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None);
|
|
|
return remoteJSDataStream;
|
|
|
}
|
|
|
|