diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/DefaultHubDispatcherBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/DefaultHubDispatcherBenchmark.cs index fe86df6f6c..bc0e2e57df 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/DefaultHubDispatcherBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/DefaultHubDispatcherBenchmark.cs @@ -166,67 +166,67 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks [Benchmark] public Task Invocation() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "Invocation", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "Invocation", Array.Empty())); } [Benchmark] public Task InvocationAsync() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationAsync", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationAsync", Array.Empty())); } [Benchmark] public Task InvocationReturnValue() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnValue", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnValue", Array.Empty())); } [Benchmark] public Task InvocationReturnAsync() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnAsync", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnAsync", Array.Empty())); } [Benchmark] public Task InvocationValueTaskAsync() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationValueTaskAsync", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationValueTaskAsync", Array.Empty())); } [Benchmark] public Task StreamChannelReader() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReader", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReader", Array.Empty())); } [Benchmark] public Task StreamChannelReaderAsync() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderAsync", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderAsync", Array.Empty())); } [Benchmark] public Task StreamChannelReaderValueTaskAsync() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderValueTaskAsync", null, Array.Empty())); + return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderValueTaskAsync", Array.Empty())); } [Benchmark] public Task StreamChannelReaderCount_Zero() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", null, new object[] { 0 })); + return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", new object[] { 0 })); } [Benchmark] public Task StreamChannelReaderCount_One() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", null, new object[] { 1 })); + return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", new object[] { 1 })); } [Benchmark] public Task StreamChannelReaderCount_Thousand() { - return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", null, new object[] { 1000 })); + return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", new object[] { 1000 })); } } } diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubProtocolBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubProtocolBenchmark.cs index 08ad64bc70..5c089c0f7a 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubProtocolBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubProtocolBenchmark.cs @@ -37,16 +37,16 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks switch (Input) { case Message.NoArguments: - _hubMessage = new InvocationMessage("Target", null, Array.Empty()); + _hubMessage = new InvocationMessage("Target", Array.Empty()); break; case Message.FewArguments: - _hubMessage = new InvocationMessage("Target", null, new object[] { 1, "Foo", 2.0f }); + _hubMessage = new InvocationMessage("Target", new object[] { 1, "Foo", 2.0f }); break; case Message.ManyArguments: - _hubMessage = new InvocationMessage("Target", null, new object[] { 1, "string", 2.0f, true, (byte)9, new byte[] { 5, 4, 3, 2, 1 }, 'c', 123456789101112L }); + _hubMessage = new InvocationMessage("Target", new object[] { 1, "string", 2.0f, true, (byte)9, new byte[] { 5, 4, 3, 2, 1 }, 'c', 123456789101112L }); break; case Message.LargeArguments: - _hubMessage = new InvocationMessage("Target", null, new object[] { new string('F', 10240), new byte[10240] }); + _hubMessage = new InvocationMessage("Target", new object[] { new string('F', 10240), new byte[10240] }); break; } diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/ServerSentEventsBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/ServerSentEventsBenchmark.cs index 825992edac..33b9055428 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/ServerSentEventsBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/ServerSentEventsBenchmark.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Buffers; using System.IO; using System.Threading.Tasks; @@ -44,16 +44,16 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks switch (Input) { case Message.NoArguments: - hubMessage = new InvocationMessage("Target", null, Array.Empty()); + hubMessage = new InvocationMessage("Target", Array.Empty()); break; case Message.FewArguments: - hubMessage = new InvocationMessage("Target", null, new object[] { 1, "Foo", 2.0f }); + hubMessage = new InvocationMessage("Target", new object[] { 1, "Foo", 2.0f }); break; case Message.ManyArguments: - hubMessage = new InvocationMessage("Target", null, new object[] { 1, "string", 2.0f, true, (byte)9, new[] { 5, 4, 3, 2, 1 }, 'c', 123456789101112L }); + hubMessage = new InvocationMessage("Target", new object[] { 1, "string", 2.0f, true, (byte)9, new[] { 5, 4, 3, 2, 1 }, 'c', 123456789101112L }); break; case Message.LargeArguments: - hubMessage = new InvocationMessage("Target", null, new object[] { new string('F', 10240), new string('B', 10240) }); + hubMessage = new InvocationMessage("Target", new object[] { new string('F', 10240), new string('B', 10240) }); break; } diff --git a/build/repo.props b/build/repo.props index 097feaba63..38e1626110 100644 --- a/build/repo.props +++ b/build/repo.props @@ -15,6 +15,7 @@ Internal.AspNetCore.Universe.Lineup + 2.1.0-rc1-* https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json diff --git a/clients/ts/FunctionalTests/selenium/run-ci-tests.ts b/clients/ts/FunctionalTests/selenium/run-ci-tests.ts index 044cf9e2e7..7e7d1050af 100644 --- a/clients/ts/FunctionalTests/selenium/run-ci-tests.ts +++ b/clients/ts/FunctionalTests/selenium/run-ci-tests.ts @@ -80,9 +80,9 @@ if (configuration) { args.push("--configuration"); args.push(configuration); } - +if (verbose) { args.push("--verbose"); - +} let command = "npm"; diff --git a/clients/ts/signalr-protocol-msgpack/package-lock.json b/clients/ts/signalr-protocol-msgpack/package-lock.json index 064dcdcba1..84c50c6f60 100644 --- a/clients/ts/signalr-protocol-msgpack/package-lock.json +++ b/clients/ts/signalr-protocol-msgpack/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aspnet/signalr-protocol-msgpack", - "version": "1.1.0-preview1-t000", + "version": "1.0.0-rc1-t000", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/clients/ts/signalr-protocol-msgpack/package.json b/clients/ts/signalr-protocol-msgpack/package.json index a232fc89f8..0dd9d8f47d 100644 --- a/clients/ts/signalr-protocol-msgpack/package.json +++ b/clients/ts/signalr-protocol-msgpack/package.json @@ -1,6 +1,6 @@ { "name": "@aspnet/signalr-protocol-msgpack", - "version": "1.1.0-preview1-t000", + "version": "1.0.0-preview3-t000", "description": "MsgPack Protocol support for ASP.NET Core SignalR", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/clients/ts/signalr/package-lock.json b/clients/ts/signalr/package-lock.json index bded6a88f4..e7e2e130b4 100644 --- a/clients/ts/signalr/package-lock.json +++ b/clients/ts/signalr/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aspnet/signalr", - "version": "1.1.0-preview1-t000", + "version": "1.0.0-rc1-t000", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/clients/ts/signalr/package.json b/clients/ts/signalr/package.json index c70d4dde4c..4674918d1a 100644 --- a/clients/ts/signalr/package.json +++ b/clients/ts/signalr/package.json @@ -1,6 +1,6 @@ { "name": "@aspnet/signalr", - "version": "1.1.0-preview1-t000", + "version": "1.0.0-preview3-t000", "description": "ASP.NET Core SignalR Client", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/korebuild.json b/korebuild.json index 19b76654e1..9260aaf1e6 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,6 +1,6 @@ { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", - "channel": "dev", + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.1/tools/korebuild.schema.json", + "channel": "release/2.1", "toolsets": { "nodejs": { "required": true, diff --git a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LoggingHttpMessageHandler.cs b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LoggingHttpMessageHandler.cs index 7d8df64783..d8a90205fa 100644 --- a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LoggingHttpMessageHandler.cs +++ b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LoggingHttpMessageHandler.cs @@ -43,8 +43,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal private static readonly Action _sendingHttpRequest = LoggerMessage.Define(LogLevel.Trace, new EventId(1, "SendingHttpRequest"), "Sending HTTP request {RequestMethod} '{RequestUrl}'."); - private static readonly Action _unsuccessfulHttpResponse = - LoggerMessage.Define(LogLevel.Warning, new EventId(2, "UnsuccessfulHttpResponse"), "Unsuccessful HTTP response status code of {StatusCode} return from {RequestMethod} '{RequestUrl}'."); + private static readonly Action _unsuccessfulHttpResponse = + LoggerMessage.Define(LogLevel.Warning, new EventId(2, "UnsuccessfulHttpResponse"), "Unsuccessful HTTP response {StatusCode} return from {RequestMethod} '{RequestUrl}'."); public static void SendingHttpRequest(ILogger logger, HttpMethod requestMethod, Uri requestUrl) { @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal } public static void UnsuccessfulHttpResponse(ILogger logger, HttpStatusCode statusCode, HttpMethod requestMethod, Uri requestUrl) { - _unsuccessfulHttpResponse(logger, statusCode, requestMethod, requestUrl, null); + _unsuccessfulHttpResponse(logger, (int)statusCode, requestMethod, requestUrl, null); } } } diff --git a/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.Log.cs b/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.Log.cs index 13df2672c6..29067a65b5 100644 --- a/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.Log.cs +++ b/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.Log.cs @@ -50,6 +50,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports private static readonly Action _sendFailed = LoggerMessage.Define(LogLevel.Error, new EventId(13, "SendFailed"), "Socket failed to send."); + private static readonly Action _closedPrematurely = + LoggerMessage.Define(LogLevel.Debug, new EventId(14, "ClosedPrematurely"), "Socket connection closed prematurely."); + public static void SocketOpened(ILogger logger, string subProtocol) { _socketOpened(logger, subProtocol, null); @@ -115,6 +118,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports _sendFailed(logger, ex); } + public static void ClosedPrematurely(ILogger logger, Exception ex) + { + _closedPrematurely(logger, ex); + } } } } diff --git a/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.cs b/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.cs index 82e091b5b5..b3c7126834 100644 --- a/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.cs +++ b/src/Microsoft.AspNetCore.Http.Connections/Internal/Transports/WebSocketsTransport.cs @@ -182,6 +182,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports } } } + catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) + { + // Client has closed the WebSocket connection without completing the close handshake + Log.ClosedPrematurely(_logger, ex); + } catch (OperationCanceledException) { // Ignore aborts, don't treat them like transport errors diff --git a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.Log.cs b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.Log.cs index b17ae06043..5dbcf2985e 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.Log.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.Log.cs @@ -168,6 +168,9 @@ namespace Microsoft.AspNetCore.SignalR.Client private static readonly Action _processingMessage = LoggerMessage.Define(LogLevel.Debug, new EventId(56, "ProcessingMessage"), "Processing {MessageLength} byte message from server."); + private static readonly Action _argumentBindingFailure = + LoggerMessage.Define(LogLevel.Error, new EventId(57, "ArgumentBindingFailure"), "Failed to bind arguments received in invocation '{InvocationId}' of '{MethodName}'."); + public static void PreparingNonBlockingInvocation(ILogger logger, string target, int count) { _preparingNonBlockingInvocation(logger, target, count, null); @@ -444,6 +447,11 @@ namespace Microsoft.AspNetCore.SignalR.Client { _unableToSendCancellation(logger, invocationId, null); } + + public static void ArgumentBindingFailure(ILogger logger, string invocationId, string target, Exception exception) + { + _argumentBindingFailure(logger, invocationId, target, exception); + } } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs index 44d6bdb969..da41d807a2 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs @@ -300,7 +300,7 @@ namespace Microsoft.AspNetCore.SignalR.Client Log.PreparingBlockingInvocation(_logger, irq.InvocationId, methodName, irq.ResultType.FullName, args.Length); // Client invocations are always blocking - var invocationMessage = new InvocationMessage(irq.InvocationId, methodName, null, args); + var invocationMessage = new InvocationMessage(irq.InvocationId, methodName, args); Log.RegisteringInvocation(_logger, invocationMessage.InvocationId); @@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.SignalR.Client Log.PreparingStreamingInvocation(_logger, irq.InvocationId, methodName, irq.ResultType.FullName, args.Length); - var invocationMessage = new StreamInvocationMessage(irq.InvocationId, methodName, null, args); + var invocationMessage = new StreamInvocationMessage(irq.InvocationId, methodName, args); // I just want an excuse to use 'irq' as a variable name... Log.RegisteringInvocation(_logger, invocationMessage.InvocationId); @@ -375,7 +375,7 @@ namespace Microsoft.AspNetCore.SignalR.Client Log.PreparingNonBlockingInvocation(_logger, methodName, args.Length); - var invocationMessage = new InvocationMessage(null, methodName, null, args); + var invocationMessage = new InvocationMessage(null, methodName, args); await SendHubMessage(invocationMessage, cancellationToken); } @@ -390,9 +390,13 @@ namespace Microsoft.AspNetCore.SignalR.Client InvocationRequest irq; switch (message) { + case InvocationBindingFailureMessage bindingFailure: + // The server can't receive a response, so we just drop the message and log + // REVIEW: Is this the right approach? + Log.ArgumentBindingFailure(_logger, bindingFailure.InvocationId, bindingFailure.Target, bindingFailure.BindingFailure.SourceException); + break; case InvocationMessage invocation: - Log.ReceivedInvocation(_logger, invocation.InvocationId, invocation.Target, - invocation.ArgumentBindingException != null ? null : invocation.Arguments); + Log.ReceivedInvocation(_logger, invocation.InvocationId, invocation.Target, invocation.Arguments); await DispatchInvocationAsync(invocation); break; case CompletionMessage completion: diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HubMethodInvocationMessage.cs b/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HubMethodInvocationMessage.cs index f125981950..272e446d0b 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HubMethodInvocationMessage.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HubMethodInvocationMessage.cs @@ -3,33 +3,16 @@ using System; using System.Linq; -using System.Runtime.ExceptionServices; namespace Microsoft.AspNetCore.SignalR.Protocol { public abstract class HubMethodInvocationMessage : HubInvocationMessage { - private readonly ExceptionDispatchInfo _argumentBindingException; - private readonly object[] _arguments; - public string Target { get; } - public object[] Arguments - { - get - { - if (_argumentBindingException != null) - { - _argumentBindingException.Throw(); - } + public object[] Arguments { get; } - return _arguments; - } - } - - public Exception ArgumentBindingException => _argumentBindingException?.SourceException; - - protected HubMethodInvocationMessage(string invocationId, string target, ExceptionDispatchInfo argumentBindingException, object[] arguments) + protected HubMethodInvocationMessage(string invocationId, string target, object[] arguments) : base(invocationId) { if (string.IsNullOrEmpty(target)) @@ -37,26 +20,20 @@ namespace Microsoft.AspNetCore.SignalR.Protocol throw new ArgumentNullException(nameof(target)); } - if ((arguments == null && argumentBindingException == null) || (arguments?.Length > 0 && argumentBindingException != null)) - { - throw new ArgumentException($"'{nameof(argumentBindingException)}' and '{nameof(arguments)}' are mutually exclusive"); - } - Target = target; - _arguments = arguments; - _argumentBindingException = argumentBindingException; + Arguments = arguments; } } public class InvocationMessage : HubMethodInvocationMessage { - public InvocationMessage(string target, ExceptionDispatchInfo argumentBindingException, object[] arguments) - : this(null, target, argumentBindingException, arguments) + public InvocationMessage(string target, object[] arguments) + : this(null, target, arguments) { } - public InvocationMessage(string invocationId, string target, ExceptionDispatchInfo argumentBindingException, object[] arguments) - : base(invocationId, target, argumentBindingException, arguments) + public InvocationMessage(string invocationId, string target, object[] arguments) + : base(invocationId, target, arguments) { } @@ -77,8 +54,8 @@ namespace Microsoft.AspNetCore.SignalR.Protocol public class StreamInvocationMessage : HubMethodInvocationMessage { - public StreamInvocationMessage(string invocationId, string target, ExceptionDispatchInfo argumentBindingException, object[] arguments) - : base(invocationId, target, argumentBindingException, arguments) + public StreamInvocationMessage(string invocationId, string target, object[] arguments) + : base(invocationId, target, arguments) { if (string.IsNullOrEmpty(invocationId)) { diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Protocol/InvocationBindingFailureMessage.cs b/src/Microsoft.AspNetCore.SignalR.Common/Protocol/InvocationBindingFailureMessage.cs new file mode 100644 index 0000000000..74c88ddc85 --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Common/Protocol/InvocationBindingFailureMessage.cs @@ -0,0 +1,22 @@ +using System.Runtime.ExceptionServices; + +namespace Microsoft.AspNetCore.SignalR.Protocol +{ + /// + /// Represents a failure to bind arguments for an invocation. This does not represent an actual + /// message that is sent on the wire, it is returned by + /// to indicate that a binding failure occurred when parsing an invocation. The invocation ID is associated + /// so that the error can be sent back to the client, associated with the appropriate invocation ID. + /// + public class InvocationBindingFailureMessage : HubInvocationMessage + { + public ExceptionDispatchInfo BindingFailure { get; } + public string Target { get; } + + public InvocationBindingFailureMessage(string invocationId, string target, ExceptionDispatchInfo bindingFailure) : base(invocationId) + { + Target = target; + BindingFailure = bindingFailure; + } + } +} diff --git a/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs index 4a5da0f4e0..c6cb42e36c 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs @@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.SignalR private HubMessage CreateInvocationMessage(string methodName, object[] args) { - return new InvocationMessage(methodName, null, args); + return new InvocationMessage(methodName, args); } public override Task SendUserAsync(string userId, string methodName, object[] args) diff --git a/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubDispatcher.cs b/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubDispatcher.cs index 748f345459..66046ebaa5 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubDispatcher.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/Internal/DefaultHubDispatcher.cs @@ -77,6 +77,10 @@ namespace Microsoft.AspNetCore.SignalR.Internal { switch (hubMessage) { + case InvocationBindingFailureMessage bindingFailureMessage: + await ProcessBindingFailure(connection, bindingFailureMessage); + break; + case InvocationMessage invocationMessage: Log.ReceivedHubInvocation(_logger, invocationMessage); await ProcessInvocation(connection, invocationMessage, isStreamedInvocation: false); @@ -113,6 +117,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal } } + private Task ProcessBindingFailure(HubConnectionContext connection, InvocationBindingFailureMessage bindingFailureMessage) + { + Log.FailedInvokingHubMethod(_logger, bindingFailureMessage.Target, bindingFailureMessage.BindingFailure.SourceException); + var errorMessage = ErrorMessageHelper.BuildErrorMessage($"Failed to invoke '{bindingFailureMessage.Target}' due to an error on the server.", + bindingFailureMessage.BindingFailure.SourceException, _enableDetailedErrors); + return SendInvocationError(bindingFailureMessage.InvocationId, connection, errorMessage); + } + public override Type GetReturnType(string invocationId) { return typeof(object); @@ -163,7 +175,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal if (!await IsHubMethodAuthorized(scope.ServiceProvider, connection.User, descriptor.Policies)) { Log.HubMethodNotAuthorized(_logger, hubMethodInvocationMessage.Target); - await SendInvocationError(hubMethodInvocationMessage, connection, + await SendInvocationError(hubMethodInvocationMessage.InvocationId, connection, $"Failed to invoke '{hubMethodInvocationMessage.Target}' because user is unauthorized"); return; } @@ -173,15 +185,6 @@ namespace Microsoft.AspNetCore.SignalR.Internal return; } - if (hubMethodInvocationMessage.ArgumentBindingException != null) - { - Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, hubMethodInvocationMessage.ArgumentBindingException); - var errorMessage = ErrorMessageHelper.BuildErrorMessage($"Failed to invoke '{hubMethodInvocationMessage.Target}' due to an error on the server.", - hubMethodInvocationMessage.ArgumentBindingException, _enableDetailedErrors); - await SendInvocationError(hubMethodInvocationMessage, connection, errorMessage); - return; - } - var hubActivator = scope.ServiceProvider.GetRequiredService>(); var hub = hubActivator.Create(); @@ -197,7 +200,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal { Log.InvalidReturnValueFromStreamingMethod(_logger, methodExecutor.MethodInfo.Name); - await SendInvocationError(hubMethodInvocationMessage, connection, + await SendInvocationError(hubMethodInvocationMessage.InvocationId, connection, $"The value returned by the streaming method '{methodExecutor.MethodInfo.Name}' is not a ChannelReader<>."); return; } @@ -215,13 +218,13 @@ namespace Microsoft.AspNetCore.SignalR.Internal catch (TargetInvocationException ex) { Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex); - await SendInvocationError(hubMethodInvocationMessage, connection, + await SendInvocationError(hubMethodInvocationMessage.InvocationId, connection, ErrorMessageHelper.BuildErrorMessage($"An unexpected error occurred invoking '{hubMethodInvocationMessage.Target}' on the server.", ex.InnerException, _enableDetailedErrors)); } catch (Exception ex) { Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex); - await SendInvocationError(hubMethodInvocationMessage, connection, + await SendInvocationError(hubMethodInvocationMessage.InvocationId, connection, ErrorMessageHelper.BuildErrorMessage($"An unexpected error occurred invoking '{hubMethodInvocationMessage.Target}' on the server.", ex, _enableDetailedErrors)); } finally @@ -294,15 +297,15 @@ namespace Microsoft.AspNetCore.SignalR.Internal return null; } - private async Task SendInvocationError(HubMethodInvocationMessage hubMethodInvocationMessage, + private async Task SendInvocationError(string invocationId, HubConnectionContext connection, string errorMessage) { - if (string.IsNullOrEmpty(hubMethodInvocationMessage.InvocationId)) + if (string.IsNullOrEmpty(invocationId)) { return; } - await connection.WriteAsync(CompletionMessage.WithError(hubMethodInvocationMessage.InvocationId, errorMessage)); + await connection.WriteAsync(CompletionMessage.WithError(invocationId, errorMessage)); } private void InitializeHub(THub hub, HubConnectionContext connection) diff --git a/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Protocol/JsonHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Protocol/JsonHubProtocol.cs index 919d89399f..4c471386a7 100644 --- a/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Protocol/JsonHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Protocols.Json/Protocol/JsonHubProtocol.cs @@ -237,6 +237,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol { if (argumentsToken != null) { + // We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything. try { var paramTypes = binder.GetParameterTypes(target); @@ -248,13 +249,16 @@ namespace Microsoft.AspNetCore.SignalR.Protocol } } - message = BindInvocationMessage(invocationId, target, argumentBindingException, arguments, hasArguments, binder); + message = argumentBindingException != null + ? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException) + : BindInvocationMessage(invocationId, target, arguments, hasArguments, binder); } break; case HubProtocolConstants.StreamInvocationMessageType: { if (argumentsToken != null) { + // We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything. try { var paramTypes = binder.GetParameterTypes(target); @@ -266,7 +270,9 @@ namespace Microsoft.AspNetCore.SignalR.Protocol } } - message = BindStreamInvocationMessage(invocationId, target, argumentBindingException, arguments, hasArguments, binder); + message = argumentBindingException != null + ? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException) + : BindStreamInvocationMessage(invocationId, target, arguments, hasArguments, binder); } break; case HubProtocolConstants.StreamItemMessageType: @@ -539,7 +545,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol return new StreamItemMessage(invocationId, item); } - private HubMessage BindStreamInvocationMessage(string invocationId, string target, ExceptionDispatchInfo argumentBindingException, object[] arguments, bool hasArguments, IInvocationBinder binder) + private HubMessage BindStreamInvocationMessage(string invocationId, string target, object[] arguments, bool hasArguments, IInvocationBinder binder) { if (string.IsNullOrEmpty(invocationId)) { @@ -556,10 +562,10 @@ namespace Microsoft.AspNetCore.SignalR.Protocol throw new InvalidDataException($"Missing required property '{TargetPropertyName}'."); } - return new StreamInvocationMessage(invocationId, target, argumentBindingException, arguments); + return new StreamInvocationMessage(invocationId, target, arguments); } - private HubMessage BindInvocationMessage(string invocationId, string target, ExceptionDispatchInfo argumentBindingException, object[] arguments, bool hasArguments, IInvocationBinder binder) + private HubMessage BindInvocationMessage(string invocationId, string target, object[] arguments, bool hasArguments, IInvocationBinder binder) { if (string.IsNullOrEmpty(target)) { @@ -571,7 +577,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol throw new InvalidDataException($"Missing required property '{ArgumentsPropertyName}'."); } - return new InvocationMessage(invocationId, target, argumentBindingException, arguments); + return new InvocationMessage(invocationId, target, arguments); } private object[] BindArguments(JsonTextReader reader, IReadOnlyList paramTypes) diff --git a/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack/Protocol/MessagePackHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack/Protocol/MessagePackHubProtocol.cs index cc8009045a..02a8065def 100644 --- a/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack/Protocol/MessagePackHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Protocols.MessagePack/Protocol/MessagePackHubProtocol.cs @@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol } } - private static InvocationMessage CreateInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver) + private static HubMessage CreateInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver) { var headers = ReadHeaders(input, ref offset); var invocationId = ReadInvocationId(input, ref offset); @@ -147,15 +147,15 @@ namespace Microsoft.AspNetCore.SignalR.Protocol try { var arguments = BindArguments(input, ref offset, parameterTypes, resolver); - return ApplyHeaders(headers, new InvocationMessage(invocationId, target, null, arguments)); + return ApplyHeaders(headers, new InvocationMessage(invocationId, target, arguments)); } catch (Exception ex) { - return ApplyHeaders(headers, new InvocationMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex), null)); + return new InvocationBindingFailureMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex)); } } - private static StreamInvocationMessage CreateStreamInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver) + private static HubMessage CreateStreamInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver) { var headers = ReadHeaders(input, ref offset); var invocationId = ReadInvocationId(input, ref offset); @@ -165,11 +165,11 @@ namespace Microsoft.AspNetCore.SignalR.Protocol try { var arguments = BindArguments(input, ref offset, parameterTypes, resolver); - return ApplyHeaders(headers, new StreamInvocationMessage(invocationId, target, null, arguments)); + return ApplyHeaders(headers, new StreamInvocationMessage(invocationId, target, arguments)); } catch (Exception ex) { - return ApplyHeaders(headers, new StreamInvocationMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex), null)); + return new InvocationBindingFailureMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex)); } } diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs index 98dcd8955f..6d3c51659b 100644 --- a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Internal } WriteSerializedHubMessage(writer, - new SerializedHubMessage(new InvocationMessage(methodName, null, args))); + new SerializedHubMessage(new InvocationMessage(methodName, args))); return writer.ToArray(); } finally diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs index 6c471ce7f8..174abbebc5 100644 --- a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs @@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.SignalR.Redis var connection = _connections[connectionId]; if (connection != null) { - return connection.WriteAsync(new InvocationMessage(methodName, null, args)).AsTask(); + return connection.WriteAsync(new InvocationMessage(methodName, args)).AsTask(); } var message = _protocol.WriteInvocation(methodName, args); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs index 0121d2577f..85cff50b24 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -23,25 +22,18 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { - [CollectionDefinition(Name)] + // Disable running server tests in parallel so server logs can accurately be captured per test + [CollectionDefinition(Name, DisableParallelization = true)] public class HubConnectionTestsCollection : ICollectionFixture> { - public const string Name = "EndToEndTests"; + public const string Name = nameof(HubConnectionTestsCollection); } [Collection(HubConnectionTestsCollection.Name)] - public class HubConnectionTests : VerifiableLoggedTest + public class HubConnectionTests : VerifiableServerLoggedTest { - private readonly ServerFixture _serverFixture; - public HubConnectionTests(ServerFixture serverFixture, ITestOutputHelper output) - : base(output) + public HubConnectionTests(ServerFixture serverFixture, ITestOutputHelper output) : base(serverFixture, output) { - if (serverFixture == null) - { - throw new ArgumentNullException(nameof(serverFixture)); - } - - _serverFixture = serverFixture; } private HubConnection CreateHubConnection( @@ -66,7 +58,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { return async format => { - var connection = new HttpConnection(new Uri(_serverFixture.Url + path), transportType, loggerFactory); + var connection = new HttpConnection(new Uri(ServerFixture.Url + path), transportType, loggerFactory); await connection.StartAsync(format); return connection; }; @@ -81,7 +73,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { var connectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) - .WithUrl(_serverFixture.Url + path, transportType); + .WithUrl(ServerFixture.Url + path, transportType); connectionBuilder.Services.AddSingleton(protocol); var connection = connectionBuilder.Build(); @@ -436,8 +428,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ExceptionFromStreamingSentToClient(string protocolName, HttpTransportType transportType, string path) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "FailedInvokingHubMethod"; + } + var protocol = HubProtocols[protocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ExceptionFromStreamingSentToClient)}_{protocol.Name}_{transportType}_{path.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ExceptionFromStreamingSentToClient)}_{protocol.Name}_{transportType}_{path.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(path, transportType, protocol, loggerFactory); try @@ -464,8 +462,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionIfHubMethodCannotBeResolved(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "UnknownHubMethod"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfHubMethodCannotBeResolved)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfHubMethodCannotBeResolved)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -491,8 +495,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionOnHubMethodArgumentCountMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "FailedInvokingHubMethod"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnHubMethodArgumentCountMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnHubMethodArgumentCountMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -518,8 +528,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionOnHubMethodArgumentTypeMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "FailedInvokingHubMethod"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnHubMethodArgumentTypeMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnHubMethodArgumentTypeMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -545,8 +561,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionIfStreamingHubMethodCannotBeResolved(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "UnknownHubMethod"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfStreamingHubMethodCannotBeResolved)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfStreamingHubMethodCannotBeResolved)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -573,8 +595,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentCountMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "FailedInvokingHubMethod"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnStreamingHubMethodArgumentCountMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnStreamingHubMethodArgumentCountMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { loggerFactory.AddConsole(LogLevel.Trace); var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); @@ -602,8 +630,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionOnStreamingHubMethodArgumentTypeMismatch(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "FailedInvokingHubMethod"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnStreamingHubMethodArgumentTypeMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionOnStreamingHubMethodArgumentTypeMismatch)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -630,8 +664,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionIfNonStreamMethodInvokedWithStreamAsync(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "NonStreamingMethodCalledWithStream"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfNonStreamMethodInvokedWithStreamAsync)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfNonStreamMethodInvokedWithStreamAsync)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -657,8 +697,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionIfStreamMethodInvokedWithInvoke(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "StreamingMethodCalledWithInvoke"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfStreamMethodInvokedWithInvoke)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfStreamMethodInvokedWithInvoke)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -684,8 +730,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests [MemberData(nameof(HubProtocolsAndTransportsAndHubPaths))] public async Task ServerThrowsHubExceptionIfBuildingAsyncEnumeratorIsNotPossible(string hubProtocolName, HttpTransportType transportType, string hubPath) { + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "InvalidReturnValueFromStreamingMethod"; + } + var hubProtocol = HubProtocols[hubProtocolName]; - using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfBuildingAsyncEnumeratorIsNotPossible)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}")) + using (StartVerifableLog(out var loggerFactory, $"{nameof(ServerThrowsHubExceptionIfBuildingAsyncEnumeratorIsNotPossible)}_{hubProtocol.Name}_{transportType}_{hubPath.TrimStart('/')}", expectedErrorsFilter: ExpectedErrors)) { var connection = CreateHubConnection(hubPath, transportType, hubProtocol, loggerFactory); try @@ -715,14 +767,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { async Task AccessTokenProvider() { - var httpResponse = await new HttpClient().GetAsync(_serverFixture.Url + "/generateJwtToken"); + var httpResponse = await new HttpClient().GetAsync(ServerFixture.Url + "/generateJwtToken"); httpResponse.EnsureSuccessStatusCode(); return await httpResponse.Content.ReadAsStringAsync(); }; var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) - .WithUrl(_serverFixture.Url + "/authorizedhub", transportType, options => + .WithUrl(ServerFixture.Url + "/authorizedhub", transportType, options => { options.AccessTokenProvider = AccessTokenProvider; }) @@ -753,7 +805,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) - .WithUrl(_serverFixture.Url + "/default", transportType, options => + .WithUrl(ServerFixture.Url + "/default", transportType, options => { options.Headers["X-test"] = "42"; options.Headers["X-42"] = "test"; @@ -762,8 +814,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests try { await hubConnection.StartAsync().OrTimeout(); - var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "X-test", "X-42" }).OrTimeout(); - Assert.Equal(new[] { "42", "test" }, headerValues); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] {"X-test", "X-42"}).OrTimeout(); + Assert.Equal(new[] {"42", "test"}, headerValues); } catch (Exception ex) { @@ -785,11 +837,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { // System.Net has a HttpTransportType type which means we need to fully-qualify this rather than 'use' the namespace var cookieJar = new System.Net.CookieContainer(); - cookieJar.Add(new System.Net.Cookie("Foo", "Bar", "/", new Uri(_serverFixture.Url).Host)); + cookieJar.Add(new System.Net.Cookie("Foo", "Bar", "/", new Uri(ServerFixture.Url).Host)); var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) - .WithUrl(_serverFixture.Url + "/default", HttpTransportType.WebSockets, options => + .WithUrl(ServerFixture.Url + "/default", HttpTransportType.WebSockets, options => { options.WebSocketConfiguration = o => o.Cookies = cookieJar; }) @@ -820,7 +872,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests { var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) - .WithUrl(_serverFixture.Url + "/default", transportType) + .WithUrl(ServerFixture.Url + "/default", transportType) .Build(); try { @@ -857,7 +909,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests var hubConnectionBuilder = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) .AddMessagePackProtocol() - .WithUrl(_serverFixture.Url + "/default-nowebsockets"); + .WithUrl(ServerFixture.Url + "/default-nowebsockets"); var hubConnection = hubConnectionBuilder.Build(); try @@ -887,7 +939,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests PollTrackingMessageHandler pollTracker = null; var hubConnection = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) - .WithUrl(_serverFixture.Url + "/default", options => + .WithUrl(ServerFixture.Url + "/default", options => { options.Transports = HttpTransportType.LongPolling; options.HttpMessageHandlerFactory = handler => diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs index 18fea62dd5..c30209ccce 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs @@ -32,17 +32,17 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public static IDictionary ProtocolTestData => new[] { - new JsonProtocolTestData("InvocationMessage_HasInvocationId", new InvocationMessage("123", "Target", null, new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":1,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", null, new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("InvocationMessage_HasBoolArgument", new InvocationMessage(null, "Target", null, new object[] { true }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[true]}"), - new JsonProtocolTestData("InvocationMessage_HasNullArgument", new InvocationMessage(null, "Target", null, new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[null]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", null, new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", null, new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", null, new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", null, new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", null, new object[] { 1, "Foo", 2.0f })), true, NullValueHandling.Ignore, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", null, new object[] { "2016-05-10T13:51:20+12:34" }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), - new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", null, new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_HasInvocationId", new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":1,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), + new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), + new JsonProtocolTestData("InvocationMessage_HasBoolArgument", new InvocationMessage(null, "Target", new object[] { true }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[true]}"), + new JsonProtocolTestData("InvocationMessage_HasNullArgument", new InvocationMessage(null, "Target", new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[null]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, NullValueHandling.Ignore, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), + new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), new JsonProtocolTestData("StreamItemMessage_HasIntegerItem", new StreamItemMessage("123", 1), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":1}"), new JsonProtocolTestData("StreamItemMessage_HasStringItem", new StreamItemMessage("123", "Foo"), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":\"Foo\"}"), @@ -70,15 +70,15 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, NullValueHandling.Ignore, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"), - new JsonProtocolTestData("StreamInvocationMessage_HasInvocationId", new StreamInvocationMessage("123", "Target", null, new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", null, new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", null, new object[] { true }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", null, new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", null, new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", null, new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", null, new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", null, new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", null, new object[] { new CustomObject() })), true, NullValueHandling.Include, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasInvocationId", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", new object[] { true }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, NullValueHandling.Include, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("CancelInvocationMessage_HasInvocationId", new CancelInvocationMessage("123"), true, NullValueHandling.Ignore, "{\"type\":5,\"invocationId\":\"123\"}"), new JsonProtocolTestData("CancelInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new CancelInvocationMessage("123")), true, NullValueHandling.Ignore, "{\"type\":5," + SerializedHeaders + ",\"invocationId\":\"123\"}"), @@ -95,10 +95,10 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public static IDictionary OutOfOrderJsonTestData => new[] { - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgumentFirst", new InvocationMessage("Method", null, new object[] { "2016-05-10T13:51:20+12:34" }), false, NullValueHandling.Ignore, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"), - new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgumentFirst", new InvocationMessage("Method", null, new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), false, NullValueHandling.Ignore, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"), - new JsonProtocolTestData("InvocationMessage_IntegerArrayArgumentFirst", new InvocationMessage("Method", null, new object[] { 1, 2 }), false, NullValueHandling.Ignore, "{ \"arguments\": [1,2], \"type\":1, \"target\": \"Method\" }"), - new JsonProtocolTestData("StreamInvocationMessage_IntegerArrayArgumentFirst", new StreamInvocationMessage("3", "Method", null, new object[] { 1, 2 }), false, NullValueHandling.Ignore, "{ \"type\":4, \"arguments\": [1,2], \"target\": \"Method\", \"invocationId\": \"3\" }"), + new JsonProtocolTestData("InvocationMessage_StringIsoDateArgumentFirst", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), false, NullValueHandling.Ignore, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"), + new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgumentFirst", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), false, NullValueHandling.Ignore, "{ \"arguments\": [\"2016-05-10T13:51:20+12:34\"], \"type\":1, \"target\": \"Method\" }"), + new JsonProtocolTestData("InvocationMessage_IntegerArrayArgumentFirst", new InvocationMessage("Method", new object[] { 1, 2 }), false, NullValueHandling.Ignore, "{ \"arguments\": [1,2], \"type\":1, \"target\": \"Method\" }"), + new JsonProtocolTestData("StreamInvocationMessage_IntegerArrayArgumentFirst", new StreamInvocationMessage("3", "Method", new object[] { 1, 2 }), false, NullValueHandling.Ignore, "{ \"type\":4, \"arguments\": [1,2], \"target\": \"Method\", \"invocationId\": \"3\" }"), new JsonProtocolTestData("CompletionMessage_ResultFirst", new CompletionMessage("15", null, 10, hasResult: true), false, NullValueHandling.Ignore, "{ \"type\":3, \"result\": 10, \"invocationId\": \"15\" }"), new JsonProtocolTestData("StreamItemMessage_ItemFirst", new StreamItemMessage("1a", "foo"), false, NullValueHandling.Ignore, "{ \"item\": \"foo\", \"invocationId\": \"1a\", \"type\":2 }") }.ToDictionary(t => t.Name); @@ -256,8 +256,8 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol var protocol = new JsonHubProtocol(); var data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); protocol.TryParseMessage(ref data, binder, out var message); - var ex = Assert.Throws(() => ((HubMethodInvocationMessage)message).Arguments); - Assert.Equal(expectedMessage, ex.Message); + var bindingFailure = Assert.IsType(message); + Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message); } private static string Frame(string input) diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs index dcfe2b23a1..c0fbec1baf 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs @@ -57,39 +57,39 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol // Invocation messages new ProtocolTestData( name: "InvocationWithNoHeadersAndNoArgs", - message: new InvocationMessage("xyz", "method", null, Array.Empty()), + message: new InvocationMessage("xyz", "method", Array.Empty()), binary: "lQGAo3h5eqZtZXRob2SQ"), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndNoArgs", - message: new InvocationMessage("method", null, Array.Empty()), + message: new InvocationMessage("method", Array.Empty()), binary: "lQGAwKZtZXRob2SQ"), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndSingleNullArg", - message: new InvocationMessage("method", null, new object[] { null }), + message: new InvocationMessage("method", new object[] { null }), binary: "lQGAwKZtZXRob2SRwA=="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndSingleIntArg", - message: new InvocationMessage("method", null, new object[] { 42 }), + message: new InvocationMessage("method", new object[] { 42 }), binary: "lQGAwKZtZXRob2SRKg=="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdIntAndStringArgs", - message: new InvocationMessage("method", null, new object[] { 42, "string" }), + message: new InvocationMessage("method", new object[] { 42, "string" }), binary: "lQGAwKZtZXRob2SSKqZzdHJpbmc="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdIntAndEnumArgs", - message: new InvocationMessage("method", null, new object[] { 42, TestEnum.One }), + message: new InvocationMessage("method", new object[] { 42, TestEnum.One }), binary: "lQGAwKZtZXRob2SSKqNPbmU="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndCustomObjectArg", - message: new InvocationMessage("method", null, new object[] { 42, "string", new CustomObject() }), + message: new InvocationMessage("method", new object[] { 42, "string", new CustomObject() }), binary: "lQGAwKZtZXRob2STKqZzdHJpbmeGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndArrayOfCustomObjectArgs", - message: new InvocationMessage("method", null, new object[] { new CustomObject(), new CustomObject() }), + message: new InvocationMessage("method", new object[] { new CustomObject(), new CustomObject() }), binary: "lQGAwKZtZXRob2SShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"), new ProtocolTestData( name: "InvocationWithHeadersNoIdAndArrayOfCustomObjectArgs", - message: AddHeaders(TestHeaders, new InvocationMessage("method", null, new object[] { new CustomObject(), new CustomObject() })), + message: AddHeaders(TestHeaders, new InvocationMessage("method", new object[] { new CustomObject(), new CustomObject() })), binary: "lQGDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmXApm1ldGhvZJKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), // StreamItem Messages @@ -187,35 +187,35 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol // StreamInvocation Messages new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndNoArgs", - message: new StreamInvocationMessage("xyz", "method", null, Array.Empty()), + message: new StreamInvocationMessage("xyz", "method", Array.Empty()), binary: "lQSAo3h5eqZtZXRob2SQ"), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndNullArg", - message: new StreamInvocationMessage("xyz", "method", null, new object[] { null }), + message: new StreamInvocationMessage("xyz", "method", new object[] { null }), binary: "lQSAo3h5eqZtZXRob2SRwA=="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndIntArg", - message: new StreamInvocationMessage("xyz", "method", null, new object[] { 42 }), + message: new StreamInvocationMessage("xyz", "method", new object[] { 42 }), binary: "lQSAo3h5eqZtZXRob2SRKg=="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndEnumArg", - message: new StreamInvocationMessage("xyz", "method", null, new object[] { TestEnum.One }), + message: new StreamInvocationMessage("xyz", "method", new object[] { TestEnum.One }), binary: "lQSAo3h5eqZtZXRob2SRo09uZQ=="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndIntAndStringArgs", - message: new StreamInvocationMessage("xyz", "method", null, new object[] { 42, "string" }), + message: new StreamInvocationMessage("xyz", "method", new object[] { 42, "string" }), binary: "lQSAo3h5eqZtZXRob2SSKqZzdHJpbmc="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndIntStringAndCustomObjectArgs", - message: new StreamInvocationMessage("xyz", "method", null, new object[] { 42, "string", new CustomObject() }), + message: new StreamInvocationMessage("xyz", "method", new object[] { 42, "string", new CustomObject() }), binary: "lQSAo3h5eqZtZXRob2STKqZzdHJpbmeGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndCustomObjectArrayArg", - message: new StreamInvocationMessage("xyz", "method", null, new object[] { new CustomObject(), new CustomObject() }), + message: new StreamInvocationMessage("xyz", "method", new object[] { new CustomObject(), new CustomObject() }), binary: "lQSAo3h5eqZtZXRob2SShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"), new ProtocolTestData( name: "StreamInvocationWithHeadersAndCustomObjectArrayArg", - message: AddHeaders(TestHeaders, new StreamInvocationMessage("xyz", "method", null, new object[] { new CustomObject(), new CustomObject() })), + message: AddHeaders(TestHeaders, new StreamInvocationMessage("xyz", "method", new object[] { new CustomObject(), new CustomObject() })), binary: "lQSDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6pm1ldGhvZJKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), // CancelInvocation Messages @@ -256,7 +256,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [Fact] public void ParseMessageWithExtraData() { - var expectedMessage = new InvocationMessage("xyz", "method", null, Array.Empty()); + var expectedMessage = new InvocationMessage("xyz", "method", Array.Empty()); // Verify that the input binary string decodes to the expected MsgPack primitives var bytes = new byte[] { ArrayBytes(6), 1, 0x80, StringBytes(3), (byte)'x', (byte)'y', (byte)'z', StringBytes(6), (byte)'m', (byte)'e', (byte)'t', (byte)'h', (byte)'o', (byte)'d', ArrayBytes(0), StringBytes(2), (byte)'e', (byte)'x' }; @@ -422,9 +422,8 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol var binder = new TestBinder(new[] { typeof(string) }, typeof(string)); var data = new ReadOnlySequence(buffer); _hubProtocol.TryParseMessage(ref data, binder, out var message); - var exception = Assert.Throws(() => ((HubMethodInvocationMessage)message).Arguments); - - Assert.Equal(testData.ErrorMessage, exception.Message); + var bindingFailure = Assert.IsType(message); + Assert.Equal(testData.ErrorMessage, bindingFailure.BindingFailure.SourceException.Message); } [Theory] diff --git a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisEndToEnd.cs b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisEndToEnd.cs index 5f1ad5a929..3071d7ab91 100644 --- a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisEndToEnd.cs +++ b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisEndToEnd.cs @@ -17,13 +17,14 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.SignalR.Redis.Tests { - [CollectionDefinition(Name)] - public class EndToEndTestsCollection : ICollectionFixture> + // Disable running server tests in parallel so server logs can accurately be captured per test + [CollectionDefinition(Name, DisableParallelization = true)] + public class RedisEndToEndTestsCollection : ICollectionFixture> { - public const string Name = "RedisEndToEndTests"; + public const string Name = nameof(RedisEndToEndTestsCollection); } - [Collection(EndToEndTestsCollection.Name)] + [Collection(RedisEndToEndTestsCollection.Name)] public class RedisEndToEndTests : VerifiableLoggedTest { private readonly RedisServerFixture _serverFixture; diff --git a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisProtocolTests.cs index 7d1a4aa3e4..3a710f42cc 100644 --- a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisProtocolTests.cs @@ -84,12 +84,14 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests } // The actual invocation message doesn't matter - private static InvocationMessage _testMessage = new InvocationMessage("target", null, Array.Empty()); - private static Dictionary> _invocationTestData = new[] + private static InvocationMessage _testMessage = new InvocationMessage("target", Array.Empty()); + + // We use a func so we are guaranteed to get a new SerializedHubMessage for each test + private static Dictionary>> _invocationTestData = new[] { - CreateTestData( + CreateTestData>( "NoExcludedIds", - new RedisInvocation(new SerializedHubMessage(_testMessage), null), + () => new RedisInvocation(new SerializedHubMessage(_testMessage), null), 0x92, 0x90, 0x82, @@ -97,9 +99,9 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests 0xC4, 0x01, 0x2A, 0xA2, (byte)'p', (byte)'2', 0xC4, 0x01, 0x2A), - CreateTestData( + CreateTestData>( "OneExcludedId", - new RedisInvocation(new SerializedHubMessage(_testMessage), new [] { "a" }), + () => new RedisInvocation(new SerializedHubMessage(_testMessage), new [] { "a" }), 0x92, 0x91, 0xA1, (byte)'a', @@ -108,9 +110,9 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests 0xC4, 0x01, 0x2A, 0xA2, (byte)'p', (byte)'2', 0xC4, 0x01, 0x2A), - CreateTestData( + CreateTestData>( "ManyExcludedIds", - new RedisInvocation(new SerializedHubMessage(_testMessage), new [] { "a", "b", "c", "d", "e", "f" }), + () => new RedisInvocation(new SerializedHubMessage(_testMessage), new [] { "a", "b", "c", "d", "e", "f" }), 0x92, 0x96, 0xA1, (byte)'a', @@ -136,15 +138,17 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests var hubProtocols = new[] { new DummyHubProtocol("p1"), new DummyHubProtocol("p2") }; var protocol = new RedisProtocol(hubProtocols); + var expected = testData.Decoded(); + var decoded = protocol.ReadInvocation(testData.Encoded); - Assert.Equal(testData.Decoded.ExcludedConnectionIds, decoded.ExcludedConnectionIds); + Assert.Equal(expected.ExcludedConnectionIds, decoded.ExcludedConnectionIds); // Verify the deserialized object has the necessary serialized forms foreach (var hubProtocol in hubProtocols) { Assert.Equal( - testData.Decoded.Message.GetSerializedMessage(hubProtocol).ToArray(), + expected.Message.GetSerializedMessage(hubProtocol).ToArray(), decoded.Message.GetSerializedMessage(hubProtocol).ToArray()); Assert.Equal(1, hubProtocol.SerializationCount); } @@ -159,7 +163,8 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests // Actual invocation doesn't matter because we're using a dummy hub protocol. // But the dummy protocol will check that we gave it the test message to make sure everything flows through properly. - var encoded = protocol.WriteInvocation(_testMessage.Target, _testMessage.Arguments, testData.Decoded.ExcludedConnectionIds); + var expected = testData.Decoded(); + var encoded = protocol.WriteInvocation(_testMessage.Target, _testMessage.Arguments, expected.ExcludedConnectionIds); Assert.Equal(testData.Encoded, encoded); } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/ServerFixture.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/ServerFixture.cs index 462201fd75..952a29ef83 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/ServerFixture.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/ServerFixture.cs @@ -18,7 +18,18 @@ using Microsoft.Extensions.Logging.Testing; namespace Microsoft.AspNetCore.SignalR.Tests { - public class ServerFixture : IDisposable + public abstract class ServerFixture : IDisposable + { + internal abstract event Action ServerLogged; + + public abstract string WebSocketsUrl { get; } + + public abstract string Url { get; } + + public abstract void Dispose(); + } + + public class ServerFixture : ServerFixture where TStartup : class { private readonly ILoggerFactory _loggerFactory; @@ -28,10 +39,17 @@ namespace Microsoft.AspNetCore.SignalR.Tests private readonly IDisposable _logToken; private readonly LogSinkProvider _logSinkProvider; + private string _url; - public string WebSocketsUrl => Url.Replace("http", "ws"); + internal override event Action ServerLogged + { + add => _logSinkProvider.RecordLogged += value; + remove => _logSinkProvider.RecordLogged -= value; + } - public string Url { get; private set; } + public override string WebSocketsUrl => Url.Replace("http", "ws"); + + public override string Url => _url; public ServerFixture() : this(loggerFactory: null) { @@ -64,7 +82,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests _host = new WebHostBuilder() .ConfigureLogging(builder => builder - .SetMinimumLevel(LogLevel.Debug) + .SetMinimumLevel(LogLevel.Trace) .AddProvider(_logSinkProvider) .AddProvider(new ForwardingLoggerProvider(_loggerFactory))) .UseStartup(typeof(TStartup)) @@ -92,7 +110,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests _logger.LogInformation("Test Server started"); // Get the URL from the server - Url = _host.ServerFeatures.Get().Addresses.Single(); + _url = _host.ServerFeatures.Get().Addresses.Single(); _lifetime.ApplicationStopped.Register(() => { @@ -119,7 +137,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests return builder.ToString(); } - public void Dispose() + public override void Dispose() { _logger.LogInformation("Shutting down test server"); _host.Dispose(); @@ -151,6 +169,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests { private readonly ConcurrentQueue _logs = new ConcurrentQueue(); + public event Action RecordLogged; + public ILogger CreateLogger(string categoryName) { return new LogSinkLogger(categoryName, this); @@ -176,6 +196,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests Formatter = (o, e) => formatter((TState)o, e), }); _logs.Enqueue(record); + + RecordLogged?.Invoke(record); } private class LogSinkLogger : ILogger diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/ServerLogScope.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/ServerLogScope.cs new file mode 100644 index 0000000000..8ca3281e98 --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/ServerLogScope.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.SignalR.Tests +{ + public class ServerLogScope : IDisposable + { + private readonly ServerFixture _serverFixture; + private readonly ILoggerFactory _loggerFactory; + private readonly IDisposable _wrappedDisposable; + private readonly ConcurrentDictionary _serverLoggers; + private readonly ILogger _scopeLogger; + private readonly object _lock; + + public ServerLogScope(ServerFixture serverFixture, ILoggerFactory loggerFactory, IDisposable wrappedDisposable) + { + _loggerFactory = loggerFactory; + _serverFixture = serverFixture; + _wrappedDisposable = wrappedDisposable; + + _lock = new object(); + + _serverLoggers = new ConcurrentDictionary(StringComparer.Ordinal); + _scopeLogger = _loggerFactory.CreateLogger(nameof(ServerLogScope)); + + // Attach last after everything else is initialized because a logged error can happen at any time + _serverFixture.ServerLogged += ServerFixtureOnServerLogged; + + _scopeLogger.LogInformation("Server log scope started."); + } + + private void ServerFixtureOnServerLogged(LogRecord logRecord) + { + var write = logRecord.Write; + + if (write == null) + { + _scopeLogger.LogWarning("Server log has no data."); + return; + } + + ILogger logger; + + // There maybe thready safety issues in logging when creating multiple loggers at the same time + // https://github.com/aspnet/Logging/issues/810 + lock (_lock) + { + // Create (or get) a logger with the same name as the server logger + logger = _serverLoggers.GetOrAdd(write.LoggerName, loggerName => _loggerFactory.CreateLogger(loggerName)); + } + + logger.Log(write.LogLevel, write.EventId, write.State, write.Exception, write.Formatter); + } + + public void Dispose() + { + _serverFixture.ServerLogged -= ServerFixtureOnServerLogged; + + _scopeLogger.LogInformation("Server log scope stopped."); + + _wrappedDisposable?.Dispose(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs index 80f78c03bc..9ecf005342 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs @@ -164,13 +164,13 @@ namespace Microsoft.AspNetCore.SignalR.Tests public Task SendInvocationAsync(string methodName, bool nonBlocking, params object[] args) { var invocationId = nonBlocking ? null : GetInvocationId(); - return SendHubMessageAsync(new InvocationMessage(invocationId, methodName, null, args)); + return SendHubMessageAsync(new InvocationMessage(invocationId, methodName, args)); } public Task SendStreamInvocationAsync(string methodName, params object[] args) { var invocationId = GetInvocationId(); - return SendHubMessageAsync(new StreamInvocationMessage(invocationId, methodName, null, args)); + return SendHubMessageAsync(new StreamInvocationMessage(invocationId, methodName, args)); } public async Task SendHubMessageAsync(HubMessage message) diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/VerifiableLoggedTest.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/VerifiableLoggedTest.cs index 1968f3bcdd..061823836c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/VerifiableLoggedTest.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/VerifiableLoggedTest.cs @@ -17,14 +17,14 @@ namespace Microsoft.AspNetCore.SignalR.Tests { } - public IDisposable StartVerifableLog(out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null, Func expectedErrorsFilter = null) + public virtual IDisposable StartVerifableLog(out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null, Func expectedErrorsFilter = null) { var disposable = StartLog(out loggerFactory, testName); return new VerifyNoErrorsScope(loggerFactory, disposable, expectedErrorsFilter); } - public IDisposable StartVerifableLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null, Func expectedErrorsFilter = null) + public virtual IDisposable StartVerifableLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null, Func expectedErrorsFilter = null) { var disposable = StartLog(out loggerFactory, minLogLevel, testName); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/VerifiableServerLoggedTest.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/VerifiableServerLoggedTest.cs new file mode 100644 index 0000000000..1035116e6a --- /dev/null +++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/VerifiableServerLoggedTest.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.SignalR.Tests +{ + public class VerifiableServerLoggedTest : VerifiableLoggedTest + { + public ServerFixture ServerFixture { get; } + + public VerifiableServerLoggedTest(ServerFixture serverFixture, ITestOutputHelper output) : base(output) + { + ServerFixture = serverFixture; + } + + public override IDisposable StartVerifableLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null, Func expectedErrorsFilter = null) + { + var disposable = base.StartVerifableLog(out loggerFactory, minLogLevel, testName, expectedErrorsFilter); + return new ServerLogScope(ServerFixture, loggerFactory, disposable); + } + + public override IDisposable StartVerifableLog(out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null, Func expectedErrorsFilter = null) + { + var disposable = base.StartVerifableLog(out loggerFactory, testName, expectedErrorsFilter); + return new ServerLogScope(ServerFixture, loggerFactory, disposable); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs index d77ba5e2ff..62b5fedddf 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs @@ -27,25 +27,18 @@ using HttpConnectionOptions = Microsoft.AspNetCore.Http.Connections.Client.HttpC namespace Microsoft.AspNetCore.SignalR.Tests { - [CollectionDefinition(Name)] + // Disable running server tests in parallel so server logs can accurately be captured per test + [CollectionDefinition(Name, DisableParallelization = true)] public class EndToEndTestsCollection : ICollectionFixture> { - public const string Name = "EndToEndTests"; + public const string Name = nameof(EndToEndTestsCollection); } [Collection(EndToEndTestsCollection.Name)] - public class EndToEndTests : VerifiableLoggedTest + public class EndToEndTests : VerifiableServerLoggedTest { - private readonly ServerFixture _serverFixture; - - public EndToEndTests(ServerFixture serverFixture, ITestOutputHelper output) : base(output) + public EndToEndTests(ServerFixture serverFixture, ITestOutputHelper output) : base(serverFixture, output) { - if (serverFixture == null) - { - throw new ArgumentNullException(nameof(serverFixture)); - } - - _serverFixture = serverFixture; } [Fact] @@ -53,7 +46,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { using (StartVerifableLog(out var loggerFactory)) { - var url = _serverFixture.Url + "/echo"; + var url = ServerFixture.Url + "/echo"; // The test should connect to the server using WebSockets transport on Windows 8 and newer. // On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server. var connection = new HttpConnection(new Uri(url), HttpTransports.All, loggerFactory); @@ -73,7 +66,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests using (StartVerifableLog(out var loggerFactory, expectedErrorsFilter: ExpectedErrors)) { - var url = _serverFixture.Url + "/echo"; + var url = ServerFixture.Url + "/echo"; // The test should connect to the server using WebSockets transport on Windows 8 and newer. // On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server. @@ -90,7 +83,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { using (StartVerifableLog(out var loggerFactory, minLogLevel: LogLevel.Trace, testName: $"CanStartAndStopConnectionUsingGivenTransport_{transportType}")) { - var url = _serverFixture.Url + "/echo"; + var url = ServerFixture.Url + "/echo"; var connection = new HttpConnection(new Uri(url), transportType, loggerFactory); await connection.StartAsync(TransferFormat.Text).OrTimeout(); await connection.DisposeAsync().OrTimeout(); @@ -108,7 +101,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests const string message = "Hello, World!"; using (var ws = new ClientWebSocket()) { - var socketUrl = _serverFixture.WebSocketsUrl + "/echo"; + var socketUrl = ServerFixture.WebSocketsUrl + "/echo"; logger.LogInformation("Connecting WebSocket to {socketUrl}", socketUrl); await ws.ConnectAsync(new Uri(socketUrl), CancellationToken.None).OrTimeout(); @@ -145,7 +138,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests const string message = "Hello, World!"; using (var ws = new ClientWebSocket()) { - var socketUrl = _serverFixture.WebSocketsUrl + "/echo"; + var socketUrl = ServerFixture.WebSocketsUrl + "/echo"; logger.LogInformation("Connecting WebSocket to {socketUrl}", socketUrl); await ws.ConnectAsync(new Uri(socketUrl), CancellationToken.None).OrTimeout(); @@ -179,7 +172,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests using (StartVerifableLog(out var loggerFactory)) { var logger = loggerFactory.CreateLogger(); - var url = _serverFixture.Url + "/echo"; + var url = ServerFixture.Url + "/echo"; var mockHttpHandler = new Mock(); mockHttpHandler.Protected() @@ -230,7 +223,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests const string message = "Major Key"; - var url = _serverFixture.Url + "/echo"; + var url = ServerFixture.Url + "/echo"; var connection = new HttpConnection(new Uri(url), transportType, loggerFactory); try { @@ -292,7 +285,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { var logger = loggerFactory.CreateLogger(); - var url = _serverFixture.Url + "/echo"; + var url = ServerFixture.Url + "/echo"; var connection = new HttpConnection(new Uri(url), HttpTransportType.WebSockets, loggerFactory); try @@ -340,7 +333,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { var logger = loggerFactory.CreateLogger(); - var url = _serverFixture.Url + "/auth"; + var url = ServerFixture.Url + "/auth"; var connection = new HttpConnection(new Uri(url), HttpTransportType.WebSockets, loggerFactory); try @@ -379,7 +372,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { var logger = loggerFactory.CreateLogger(); - var url = _serverFixture.Url + "/auth"; + var url = ServerFixture.Url + "/auth"; var connection = new HttpConnection(new Uri(url), transportType, loggerFactory); try @@ -436,7 +429,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { var logger = loggerFactory.CreateLogger(); - var url = _serverFixture.Url + "/uncreatable"; + var url = ServerFixture.Url + "/uncreatable"; var connection = new HubConnectionBuilder() .WithLoggerFactory(loggerFactory) .WithUrl(url, transportType) diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs index 9076587e92..3ea5d2e4f7 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs @@ -1429,7 +1429,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests await client.Connected.OrTimeout(); var invocationId = Guid.NewGuid().ToString("N"); - await client.SendHubMessageAsync(new StreamInvocationMessage(invocationId, nameof(StreamingHub.BlockingStream), null, Array.Empty())); + await client.SendHubMessageAsync(new StreamInvocationMessage(invocationId, nameof(StreamingHub.BlockingStream), Array.Empty())); // cancel the Streaming method await client.SendHubMessageAsync(new CancelInvocationMessage(invocationId)).OrTimeout(); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/HubMethodInvocationMessageTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/HubMethodInvocationMessageTests.cs deleted file mode 100644 index dedf151d0a..0000000000 --- a/test/Microsoft.AspNetCore.SignalR.Tests/HubMethodInvocationMessageTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.ExceptionServices; -using Microsoft.AspNetCore.SignalR.Protocol; -using Xunit; - -namespace Microsoft.AspNetCore.SignalR.Tests -{ - public class HubMethodInvocationMessageTests - { - [Fact] - public void InvocationMessageToStringPutsErrorInArgs() - { - var hubMessage = new InvocationMessage("echo", ExceptionDispatchInfo.Capture(new Exception("test")), null); - - Assert.Equal("InvocationMessage { InvocationId: \"\", Target: \"echo\", Arguments: [ Error: test ] }", hubMessage.ToString()); - } - - [Fact] - public void StreamInvocationMessageToStringPutsErrorInArgs() - { - var hubMessage = new StreamInvocationMessage("3", "echo", ExceptionDispatchInfo.Capture(new Exception("test")), null); - - Assert.Equal("StreamInvocation { InvocationId: \"3\", Target: \"echo\", Arguments: [ Error: test ] }", hubMessage.ToString()); - } - } -} diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs index 18c3d64437..90dc73b020 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/WebSocketsTransportTests.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.IO.Pipelines; using System.Net; using System.Net.WebSockets; using System.Reflection; @@ -14,7 +13,6 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections.Client; using Microsoft.AspNetCore.Http.Connections.Client.Internal; using Microsoft.AspNetCore.Testing.xunit; -using Microsoft.Extensions.Logging.Testing; using Moq; using Xunit; using Xunit.Abstractions; @@ -22,18 +20,10 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.SignalR.Tests { [Collection(EndToEndTestsCollection.Name)] - public class WebSocketsTransportTests : VerifiableLoggedTest + public class WebSocketsTransportTests : VerifiableServerLoggedTest { - private readonly ServerFixture _serverFixture; - - public WebSocketsTransportTests(ServerFixture serverFixture, ITestOutputHelper output) : base(output) + public WebSocketsTransportTests(ServerFixture serverFixture, ITestOutputHelper output) : base(serverFixture, output) { - if (serverFixture == null) - { - throw new ArgumentNullException(nameof(serverFixture)); - } - - _serverFixture = serverFixture; } [ConditionalFact] @@ -70,7 +60,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests using (StartVerifableLog(out var loggerFactory)) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory); - await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), + await webSocketsTransport.StartAsync(new Uri(ServerFixture.WebSocketsUrl + "/echo"), TransferFormat.Binary).OrTimeout(); await webSocketsTransport.StopAsync().OrTimeout(); await webSocketsTransport.Running.OrTimeout(); @@ -84,7 +74,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests using (StartVerifableLog(out var loggerFactory)) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory); - await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"), + await webSocketsTransport.StartAsync(new Uri(ServerFixture.WebSocketsUrl + "/httpheader"), TransferFormat.Binary).OrTimeout(); await webSocketsTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("User-Agent")); @@ -112,7 +102,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests using (StartVerifableLog(out var loggerFactory)) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory); - await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/httpheader"), + await webSocketsTransport.StartAsync(new Uri(ServerFixture.WebSocketsUrl + "/httpheader"), TransferFormat.Binary).OrTimeout(); await webSocketsTransport.Output.WriteAsync(Encoding.UTF8.GetBytes("X-Requested-With")); @@ -135,7 +125,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests using (StartVerifableLog(out var loggerFactory)) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory); - await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), + await webSocketsTransport.StartAsync(new Uri(ServerFixture.WebSocketsUrl + "/echo"), TransferFormat.Binary); webSocketsTransport.Output.Complete(); await webSocketsTransport.Running.OrTimeout(TimeSpan.FromSeconds(10)); @@ -151,7 +141,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests using (StartVerifableLog(out var loggerFactory)) { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory); - await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echoAndClose"), transferFormat); + await webSocketsTransport.StartAsync(new Uri(ServerFixture.WebSocketsUrl + "/echoAndClose"), transferFormat); await webSocketsTransport.Output.WriteAsync(new byte[] { 0x42 }); @@ -174,7 +164,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests { var webSocketsTransport = new WebSocketsTransport(httpConnectionOptions: null, loggerFactory: loggerFactory); - await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), + await webSocketsTransport.StartAsync(new Uri(ServerFixture.WebSocketsUrl + "/echo"), transferFormat).OrTimeout(); await webSocketsTransport.StopAsync().OrTimeout(); diff --git a/version.props b/version.props index 573566501b..3f759a60f9 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ - 1.1.0 - preview1 + 1.0.0 + rc1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final t000