Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Client.Http;
using Microsoft.AspNetCore.Sockets.Client.Tests;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

namespace Microsoft.AspNetCore.SignalR.Client.Tests
Expand All @@ -19,10 +13,9 @@ public partial class HttpConnectionTests
public class AbortAsync
{
[Fact]
public async Task AbortAsyncTriggersClosedEventWithException()
public Task AbortAsyncTriggersClosedEventWithException()
{
var connection = CreateConnection(out var closedTask);
try
return WithConnectionAsync(CreateConnection(), async (connection, closed) =>
{
// Start the connection
await connection.StartAsync().OrTimeout();
Expand All @@ -32,22 +25,15 @@ public async Task AbortAsyncTriggersClosedEventWithException()
await connection.AbortAsync(expected).OrTimeout();

// Verify that it is thrown
var actual = await Assert.ThrowsAsync<Exception>(async () => await closedTask.OrTimeout());
var actual = await Assert.ThrowsAsync<Exception>(async () => await closed.OrTimeout());
Assert.Same(expected, actual);
}
finally
{
// Dispose should be clean and exception free.
await connection.DisposeAsync().OrTimeout();
}
});
}

[Fact]
public async Task AbortAsyncWhileStoppingTriggersClosedEventWithException()
public Task AbortAsyncWhileStoppingTriggersClosedEventWithException()
{
var connection = CreateConnection(out var closedTask, stopHandler: SyncPoint.Create(2, out var syncPoints));

try
return WithConnectionAsync(CreateConnection(transport: new TestTransport(onTransportStop: SyncPoint.Create(2, out var syncPoints))), async (connection, closed) =>
{
// Start the connection
await connection.StartAsync().OrTimeout();
Expand All @@ -69,26 +55,19 @@ public async Task AbortAsyncWhileStoppingTriggersClosedEventWithException()
syncPoints[0].Continue();

// We should close with the error from Abort (because it was set by the call to Abort even though Stop triggered the close)
var actual = await Assert.ThrowsAsync<Exception>(async () => await closedTask.OrTimeout());
var actual = await Assert.ThrowsAsync<Exception>(async () => await closed.OrTimeout());
Assert.Same(expected, actual);

// Clean-up
syncPoints[1].Continue();
await Task.WhenAll(stopTask, abortTask).OrTimeout();
}
finally
{
// Dispose should be clean and exception free.
await connection.DisposeAsync().OrTimeout();
}
});
}

[Fact]
public async Task StopAsyncWhileAbortingTriggersClosedEventWithoutException()
public Task StopAsyncWhileAbortingTriggersClosedEventWithoutException()
{
var connection = CreateConnection(out var closedTask, stopHandler: SyncPoint.Create(2, out var syncPoints));

try
return WithConnectionAsync(CreateConnection(transport: new TestTransport(onTransportStop: SyncPoint.Create(2, out var syncPoints))), async (connection, closed) =>
{
// Start the connection
await connection.StartAsync().OrTimeout();
Expand All @@ -104,25 +83,18 @@ public async Task StopAsyncWhileAbortingTriggersClosedEventWithoutException()
// This should clear the exception, meaning Closed will not "throw"
syncPoints[1].Continue();
await connection.StopAsync();
await closedTask.OrTimeout();
await closed.OrTimeout();

// Clean-up
syncPoints[0].Continue();
await abortTask.OrTimeout();
}
finally
{
// Dispose should be clean and exception free.
await connection.DisposeAsync().OrTimeout();
}
});
}

[Fact]
public async Task StartAsyncCannotBeCalledWhileAbortAsyncInProgress()
public Task StartAsyncCannotBeCalledWhileAbortAsyncInProgress()
{
var connection = CreateConnection(out var closedTask, stopHandler: SyncPoint.Create(out var syncPoint));

try
return WithConnectionAsync(CreateConnection(transport: new TestTransport(onTransportStop: SyncPoint.Create(out var syncPoint))), async (connection, closed) =>
{
// Start the connection
await connection.StartAsync().OrTimeout();
Expand All @@ -141,134 +113,15 @@ public async Task StartAsyncCannotBeCalledWhileAbortAsyncInProgress()
// (it will throw the abort exception)
syncPoint.Continue();
await abortTask.OrTimeout();
var actual = await Assert.ThrowsAsync<Exception>(() => closedTask.OrTimeout());
var actual = await Assert.ThrowsAsync<Exception>(() => closed.OrTimeout());
Assert.Same(expected, actual);

// We can start now
await connection.StartAsync().OrTimeout();

// And we can stop without getting the abort exception.
await connection.StopAsync().OrTimeout();
}
finally
{
// Dispose should be clean and exception free.
await connection.DisposeAsync().OrTimeout();
}
}

private HttpConnection CreateConnection(out Task closedTask, Func<Task> stopHandler = null)
{
var httpHandler = new TestHttpMessageHandler();
var transportFactory = new TestTransportFactory(new TestTransport(stopHandler));
var connection = new HttpConnection(
new Uri("http://fakeuri.org/"),
transportFactory,
NullLoggerFactory.Instance,
new HttpOptions()
{
HttpMessageHandler = httpHandler,
});

var closedTcs = new TaskCompletionSource<object>();
connection.Closed += ex =>
{
if (ex != null)
{
closedTcs.SetException(ex);
}
else
{
closedTcs.SetResult(null);
}
};
closedTask = closedTcs.Task;

return connection;
}

private class TestTransport : ITransport
{
private Channel<byte[], SendMessage> _application;
private readonly Func<Task> _stopHandler;

public TransferMode? Mode => TransferMode.Text;

public TestTransport(Func<Task> stopHandler)
{
_stopHandler = stopHandler ?? new Func<Task>(() => Task.CompletedTask);
}

public Task StartAsync(Uri url, Channel<byte[], SendMessage> application, TransferMode requestedTransferMode, string connectionId, IConnection connection)
{
_application = application;
return Task.CompletedTask;
}

public async Task StopAsync()
{
await _stopHandler();
_application.Writer.TryComplete();
}
}

// Possibly useful as a general-purpose async testing helper?
private class SyncPoint
{
private TaskCompletionSource<object> _atSyncPoint = new TaskCompletionSource<object>();
private TaskCompletionSource<object> _continueFromSyncPoint = new TaskCompletionSource<object>();

// Used by the test code to wait and continue
public Task WaitForSyncPoint() => _atSyncPoint.Task;
public void Continue() => _continueFromSyncPoint.TrySetResult(null);

// Used by the code under test to wait for the test code to release it.
public Task WaitToContinue()
{
_atSyncPoint.TrySetResult(null);
return _continueFromSyncPoint.Task;
}

public static Func<Task> Create(out SyncPoint syncPoint)
{
var handler = Create(1, out var syncPoints);
syncPoint = syncPoints[0];
return handler;
}

/// <summary>
/// Creates a re-entrant function that waits for sync points in sequence.
/// </summary>
/// <param name="count">The number of sync points to expect</param>
/// <param name="syncPoints">The <see cref="SyncPoint"/> objects that can be used to coordinate the sync point</param>
/// <returns></returns>
public static Func<Task> Create(int count, out SyncPoint[] syncPoints)
{
// Need to use a local so the closure can capture it. You can't use out vars in a closure.
var localSyncPoints = new SyncPoint[count];
for (var i = 0; i < count; i += 1)
{
localSyncPoints[i] = new SyncPoint();
}

syncPoints = localSyncPoints;

var counter = 0;
return () =>
{
if (counter >= localSyncPoints.Length)
{
return Task.CompletedTask;
}
else
{
var syncPoint = localSyncPoints[counter];

counter += 1;
return syncPoint.WaitToContinue();
}
};
}
});
}
}
}
Expand Down
Loading