diff --git a/src/Grpc.Net.Client/Internal/GrpcCall.cs b/src/Grpc.Net.Client/Internal/GrpcCall.cs index 4056f726a..54f08c45d 100644 --- a/src/Grpc.Net.Client/Internal/GrpcCall.cs +++ b/src/Grpc.Net.Client/Internal/GrpcCall.cs @@ -639,7 +639,7 @@ private async Task RunCall(HttpRequestMessage request, TimeSpan? timeout) if (_responseTcs != null) { _responseTcs.TrySetException(resolvedException); - + // Always observe cancellation-like exceptions. if (IsCancellationOrDeadlineException(ex)) { diff --git a/src/Grpc.Net.Client/Internal/HttpClientCallInvoker.cs b/src/Grpc.Net.Client/Internal/HttpClientCallInvoker.cs index f55f61196..4783ef4fc 100644 --- a/src/Grpc.Net.Client/Internal/HttpClientCallInvoker.cs +++ b/src/Grpc.Net.Client/Internal/HttpClientCallInvoker.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -43,6 +43,8 @@ public HttpClientCallInvoker(GrpcChannel channel) /// public override AsyncClientStreamingCall AsyncClientStreamingCall(Method method, string? host, CallOptions options) { + AssertMethodType(method, MethodType.ClientStreaming); + var call = CreateRootGrpcCall(Channel, method, options); call.StartClientStreaming(); @@ -67,6 +69,8 @@ public override AsyncClientStreamingCall AsyncClientStreami /// public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall(Method method, string? host, CallOptions options) { + AssertMethodType(method, MethodType.DuplexStreaming); + var call = CreateRootGrpcCall(Channel, method, options); call.StartDuplexStreaming(); @@ -90,6 +94,8 @@ public override AsyncDuplexStreamingCall AsyncDuplexStreami /// public override AsyncServerStreamingCall AsyncServerStreamingCall(Method method, string? host, CallOptions options, TRequest request) { + AssertMethodType(method, MethodType.ServerStreaming); + var call = CreateRootGrpcCall(Channel, method, options); call.StartServerStreaming(request); @@ -111,6 +117,8 @@ public override AsyncServerStreamingCall AsyncServerStreamingCall public override AsyncUnaryCall AsyncUnaryCall(Method method, string? host, CallOptions options, TRequest request) { + AssertMethodType(method, MethodType.Unary); + var call = CreateRootGrpcCall(Channel, method, options); call.StartUnary(request); @@ -127,6 +135,16 @@ public override AsyncUnaryCall AsyncUnaryCall(Me return callWrapper; } + [Conditional("ASSERT_METHOD_TYPE")] + private static void AssertMethodType(IMethod method, MethodType methodType) + { + // This can be used to assert tests are passing the right method type. + if (method.Type != methodType) + { + throw new Exception("Expected method type: " + methodType); + } + } + /// /// Invokes a simple remote call in a blocking fashion. /// diff --git a/src/Grpc.Net.Client/Internal/Retry/HedgingCall.cs b/src/Grpc.Net.Client/Internal/Retry/HedgingCall.cs index c24ff3777..c53080fee 100644 --- a/src/Grpc.Net.Client/Internal/Retry/HedgingCall.cs +++ b/src/Grpc.Net.Client/Internal/Retry/HedgingCall.cs @@ -202,10 +202,19 @@ private async Task StartCall(Action> startCallFunc { if (CommitedCallTask.IsCompletedSuccessfully() && CommitedCallTask.Result == call) { + // Ensure response task is created before waiting to the end. + // Allows cancellation exceptions to be observed in cleanup. + if (!HasResponseStream()) + { + _ = GetResponseAsync(); + } + // Wait until the commited call is finished and then clean up hedging call. // Force yield here to prevent continuation running with any locks. - await CompatibilityHelpers.AwaitWithYieldAsync(call.CallTask).ConfigureAwait(false); - Cleanup(); + var status = await CompatibilityHelpers.AwaitWithYieldAsync(call.CallTask).ConfigureAwait(false); + + var observeExceptions = status.StatusCode is StatusCode.Cancelled or StatusCode.DeadlineExceeded; + Cleanup(observeExceptions); } } } diff --git a/src/Grpc.Net.Client/Internal/Retry/RetryCall.cs b/src/Grpc.Net.Client/Internal/Retry/RetryCall.cs index d00ef20c2..dd645e3f0 100644 --- a/src/Grpc.Net.Client/Internal/Retry/RetryCall.cs +++ b/src/Grpc.Net.Client/Internal/Retry/RetryCall.cs @@ -172,7 +172,7 @@ private async Task StartRetry(Action> startCallFun // Commited so exit retry loop. return; } - else if (IsSuccessfulStreamingCall(responseStatus.Value, currentCall)) + else if (IsSuccessfulStreamingCall(responseStatus.Value)) { // Headers were returned. We're commited. CommitCall(currentCall, CommitReason.ResponseHeadersReceived); @@ -252,10 +252,19 @@ private async Task StartRetry(Action> startCallFun { if (CommitedCallTask.Result is GrpcCall call) { + // Ensure response task is created before waiting to the end. + // Allows cancellation exceptions to be observed in cleanup. + if (!HasResponseStream()) + { + _ = GetResponseAsync(); + } + // Wait until the commited call is finished and then clean up retry call. // Force yield here to prevent continuation running with any locks. - await CompatibilityHelpers.AwaitWithYieldAsync(call.CallTask).ConfigureAwait(false); - Cleanup(); + var status = await CompatibilityHelpers.AwaitWithYieldAsync(call.CallTask).ConfigureAwait(false); + + var observeExceptions = status.StatusCode is StatusCode.Cancelled or StatusCode.DeadlineExceeded; + Cleanup(observeExceptions); } } @@ -263,14 +272,14 @@ private async Task StartRetry(Action> startCallFun } } - private static bool IsSuccessfulStreamingCall(Status responseStatus, GrpcCall call) + private bool IsSuccessfulStreamingCall(Status responseStatus) { if (responseStatus.StatusCode != StatusCode.OK) { return false; } - return call.Method.Type == MethodType.ServerStreaming || call.Method.Type == MethodType.DuplexStreaming; + return HasResponseStream(); } protected override void OnCommitCall(IGrpcCall call) diff --git a/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs b/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs index 4394090b5..bab9fa974 100644 --- a/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs +++ b/src/Grpc.Net.Client/Internal/Retry/RetryCallBase.cs @@ -33,6 +33,8 @@ internal abstract partial class RetryCallBase : IGrpcCall> _commitedCallTcs; private RetryCallBaseClientStreamReader? _retryBaseClientStreamReader; private RetryCallBaseClientStreamWriter? _retryBaseClientStreamWriter; + private Task? _responseTask; + private Task? _responseHeadersTask; // Internal for unit testing. internal CancellationTokenRegistration? _ctsRegistration; @@ -111,13 +113,34 @@ protected RetryCallBase(GrpcChannel channel, Method method, } } - public async Task GetResponseAsync() + public Task GetResponseAsync() => _responseTask ??= GetResponseCoreAsync(); + + private async Task GetResponseCoreAsync() { var call = await CommitedCallTask.ConfigureAwait(false); return await call.GetResponseAsync().ConfigureAwait(false); } - public async Task GetResponseHeadersAsync() + public Task GetResponseHeadersAsync() + { + if (_responseHeadersTask == null) + { + _responseHeadersTask = GetResponseHeadersCoreAsync(); + + // ResponseHeadersAsync could be called inside a client interceptor when a call is wrapped. + // Most people won't use the headers result. Observed exception to avoid unobserved exception event. + _responseHeadersTask.ObserveException(); + + // If there was an error fetching response headers then it's likely the same error is reported + // by response TCS. The user is unlikely to observe both errors. + // Observed exception to avoid unobserved exception event. + _responseTask?.ObserveException(); + } + + return _responseHeadersTask; + } + + private async Task GetResponseHeadersCoreAsync() { var call = await CommitedCallTask.ConfigureAwait(false); return await call.GetResponseHeadersAsync().ConfigureAwait(false); @@ -369,7 +392,7 @@ protected void CommitCall(IGrpcCall call, CommitReason comm // A commited call that has already cleaned up is likely a StatusGrpcCall. if (call.Disposed) { - Cleanup(); + Cleanup(observeExceptions: false); } } } @@ -382,6 +405,11 @@ protected bool HasClientStream() return Method.Type == MethodType.ClientStreaming || Method.Type == MethodType.DuplexStreaming; } + protected bool HasResponseStream() + { + return Method.Type == MethodType.ServerStreaming || Method.Type == MethodType.DuplexStreaming; + } + protected void SetNewActiveCallUnsynchronized(IGrpcCall call) { Debug.Assert(Monitor.IsEntered(Lock), "Should be called with lock."); @@ -436,11 +464,11 @@ protected virtual void Dispose(bool disposing) CommitedCallTask.Result.Dispose(); } - Cleanup(); + Cleanup(observeExceptions: true); } } - protected void Cleanup() + protected void Cleanup(bool observeExceptions) { Channel.FinishActiveCall(this); @@ -449,6 +477,12 @@ protected void Cleanup() CancellationTokenSource.Cancel(); ClearRetryBuffer(); + + if (observeExceptions) + { + _responseTask?.ObserveException(); + _responseHeadersTask?.ObserveException(); + } } internal bool TryAddToRetryBuffer(ReadOnlyMemory message) diff --git a/src/Grpc.Net.Client/Internal/TaskExtensions.cs b/src/Grpc.Net.Client/Internal/TaskExtensions.cs index 11b7c4e89..a5d36f918 100644 --- a/src/Grpc.Net.Client/Internal/TaskExtensions.cs +++ b/src/Grpc.Net.Client/Internal/TaskExtensions.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // diff --git a/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs b/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs index c55483ecb..8f126b8e8 100644 --- a/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs +++ b/test/Grpc.Net.Client.Tests/AsyncClientStreamingCallTests.cs @@ -53,7 +53,7 @@ public async Task AsyncClientStreamingCall_Success_HttpRequestMessagePopulated() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); await call.RequestStream.CompleteAsync().DefaultTimeout(); @@ -98,7 +98,7 @@ public async Task AsyncClientStreamingCall_Success_RequestContentSent() var invoker = HttpClientCallInvokerFactory.Create(handler, "http://localhost"); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var requestContentTask = await requestContentTcs.Task.DefaultTimeout(); // Assert @@ -149,7 +149,7 @@ public async Task ClientStreamWriter_WriteWhilePendingWrite_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var writeTask1 = call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }); @@ -178,7 +178,7 @@ public async Task ClientStreamWriter_DisposeWhilePendingWrite_NoReadMessageError var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: loggerFactory); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var writeTask1 = call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }); @@ -216,7 +216,7 @@ public async Task ClientStreamWriter_CompleteWhilePendingWrite_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var writeTask1 = call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }); @@ -240,7 +240,7 @@ public async Task ClientStreamWriter_WriteWhileComplete_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); await call.RequestStream.CompleteAsync().DefaultTimeout(); var resultTask = call.ResponseAsync; @@ -273,7 +273,7 @@ public async Task ClientStreamWriter_WriteWithInvalidHttpStatus_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var writeException = await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest { Name = "1" })).DefaultTimeout(); var resultException = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -299,7 +299,7 @@ public async Task ClientStreamWriter_WriteAfterResponseHasFinished_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var ex = await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest())).DefaultTimeout(); var result = await call.ResponseAsync.DefaultTimeout(); @@ -329,7 +329,7 @@ public async Task AsyncClientStreamingCall_ErrorWhileWriting_StatusExceptionThro // Act // Client starts call - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Client starts request stream write var writeTask = call.RequestStream.WriteAsync(new HelloRequest()); @@ -422,7 +422,7 @@ public async Task ClientStreamWriter_CancelledBeforeCallStarts_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: new CancellationToken(true))); + var call = invoker.AsyncClientStreamingCall(new CallOptions(cancellationToken: new CancellationToken(true))); var ex = await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest())).DefaultTimeout(); @@ -442,7 +442,7 @@ public async Task ClientStreamWriter_CancelledBeforeCallStarts_ThrowOperationCan var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.ThrowOperationCanceledOnCancellation = true); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: new CancellationToken(true))); + var call = invoker.AsyncClientStreamingCall(new CallOptions(cancellationToken: new CancellationToken(true))); await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest())).DefaultTimeout(); @@ -461,7 +461,7 @@ public async Task ClientStreamWriter_CallThrowsException_WriteAsyncThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var writeException = await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest())).DefaultTimeout(); var resultException = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); diff --git a/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs b/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs index d15b2debc..0dfe9c663 100644 --- a/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs +++ b/test/Grpc.Net.Client.Tests/AsyncDuplexStreamingCallTests.cs @@ -50,7 +50,7 @@ public async Task AsyncDuplexStreamingCall_NoContent_NoMessagesReturned() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); var responseStream = call.ResponseStream; @@ -76,7 +76,7 @@ public async Task AsyncServerStreamingCall_MessagesReturnedTogether_MessagesRece var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); var responseStream = call.ResponseStream; @@ -109,7 +109,7 @@ public async Task AsyncDuplexStreamingCall_MessagesStreamed_MessagesReceived() var invoker = HttpClientCallInvokerFactory.Create(handler, "https://localhost"); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); var requestStream = call.RequestStream; var responseStream = call.ResponseStream; @@ -216,7 +216,7 @@ public async Task AsyncDuplexStreamingCall_CancellationDisposeRace_Success() var cts = new CancellationTokenSource(); - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token)); + var call = invoker.AsyncDuplexStreamingCall(new CallOptions(cancellationToken: cts.Token)); await call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }).DefaultTimeout(); await call.RequestStream.CompleteAsync().DefaultTimeout(); diff --git a/test/Grpc.Net.Client.Tests/AsyncServerStreamingCallTests.cs b/test/Grpc.Net.Client.Tests/AsyncServerStreamingCallTests.cs index 5e5276906..151238300 100644 --- a/test/Grpc.Net.Client.Tests/AsyncServerStreamingCallTests.cs +++ b/test/Grpc.Net.Client.Tests/AsyncServerStreamingCallTests.cs @@ -42,7 +42,7 @@ public async Task AsyncServerStreamingCall_NoContent_NoMessagesReturned() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -73,7 +73,7 @@ public async Task AsyncServerStreamingCall_MessagesReturnedTogether_MessagesRece var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -104,7 +104,7 @@ public async Task AsyncServerStreamingCall_MessagesStreamed_MessagesReceived() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -163,7 +163,7 @@ public async Task AsyncServerStreamingCall_MessagesStreamedThenError_ErrorStatus var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -206,7 +206,7 @@ public async Task AsyncServerStreamingCall_MessagesStreamedThenCancellation_Erro var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -251,7 +251,7 @@ public async Task AsyncServerStreamingCall_MessagesStreamedThenDispose_ErrorStat var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -297,7 +297,7 @@ public async Task AsyncServerStreamingCall_DisposeDuringPendingRead_NoReadMessag var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: loggerFactory); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -328,7 +328,7 @@ public async Task ClientStreamReader_WriteWithInvalidHttpStatus_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseStream.MoveNext(CancellationToken.None)).DefaultTimeout(); @@ -352,7 +352,7 @@ public async Task AsyncServerStreamingCall_TrailersOnly_TrailersReturnedWithHead var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var headers = await call.ResponseHeadersAsync.DefaultTimeout(); Assert.IsFalse(await call.ResponseStream.MoveNext(CancellationToken.None).DefaultTimeout()); @@ -382,7 +382,7 @@ public async Task AsyncServerStreamingCall_StatusInFooterAndMessageInHeader_Igno var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var headers = await call.ResponseHeadersAsync.DefaultTimeout(); await call.ResponseStream.MoveNext(CancellationToken.None).DefaultTimeout(); diff --git a/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs b/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs index 5f808a900..6f7cbf1ac 100644 --- a/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs +++ b/test/Grpc.Net.Client.Tests/AsyncUnaryCallTests.cs @@ -58,7 +58,7 @@ public async Task AsyncUnaryCall_Success_HttpRequestMessagePopulated() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var rs = await invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var rs = await invoker.AsyncUnaryCall(new HelloRequest()); // Assert Assert.AreEqual("Hello world", rs.Message); @@ -112,8 +112,7 @@ public async Task AsyncUnaryCall_HasWinHttpHandler_ContentLengthOnHttpRequestMes var invoker = HttpClientCallInvokerFactory.Create(winHttpHandler, "https://localhost"); // Act - var rs = await invoker.AsyncUnaryCall( - ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "Hello world" }).ResponseAsync.DefaultTimeout(); + var rs = await invoker.AsyncUnaryCall(new HelloRequest { Name = "Hello world" }).ResponseAsync.DefaultTimeout(); // Assert Assert.AreEqual("Hello world", rs.Message); @@ -144,8 +143,7 @@ public async Task AsyncUnaryCall_Success_RequestContentSent() var invoker = HttpClientCallInvokerFactory.Create(handler, "http://localhost"); // Act - var rs = await invoker.AsyncUnaryCall( - ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }).ResponseAsync.DefaultTimeout(); + var rs = await invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }).ResponseAsync.DefaultTimeout(); // Assert Assert.AreEqual("Hello world", rs.Message); @@ -177,7 +175,7 @@ public async Task AsyncUnaryCall_NonOkStatusTrailer_AccessResponse_ThrowRpcError var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var ex = await ExceptionAssert.ThrowsAsync(() => invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()).ResponseAsync).DefaultTimeout(); + var ex = await ExceptionAssert.ThrowsAsync(() => invoker.AsyncUnaryCall(new HelloRequest()).ResponseAsync).DefaultTimeout(); // Assert Assert.AreEqual(StatusCode.Unimplemented, ex.StatusCode); @@ -195,7 +193,7 @@ public async Task AsyncUnaryCall_NonOkStatusTrailer_AccessHeaders_ReturnHeaders( var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var headers = await invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()).ResponseHeadersAsync.DefaultTimeout(); + var headers = await invoker.AsyncUnaryCall(new HelloRequest()).ResponseHeadersAsync.DefaultTimeout(); // Assert Assert.AreEqual("true", headers.GetValue("custom")); @@ -214,7 +212,7 @@ public async Task AsyncUnaryCall_SuccessTrailersOnly_ThrowNoMessageError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var headers = await call.ResponseHeadersAsync.DefaultTimeout(); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -249,6 +247,9 @@ public enum ResponseHandleAction [TestCase(1, false, ResponseHandleAction.Nothing)] public async Task AsyncUnaryCall_CallFailed_NoUnobservedExceptions(int expectedUnobservedExceptions, bool addClientInterceptor, ResponseHandleAction action) { + // Do this before running the test to clean up any pending unobserved exceptions from other tests. + TriggerUnobservedExceptions(); + // Arrange var services = new ServiceCollection(); services.AddNUnitLogger(); @@ -282,10 +283,12 @@ public async Task AsyncUnaryCall_CallFailed_NoUnobservedExceptions(int expectedU await MakeGrpcCallAsync(logger, invoker, action); logger.LogDebug("Waiting for finalizers"); - // Provoke the garbage collector to find the unobserved exception. - GC.Collect(); - // Wait for any failed tasks to be garbage collected - GC.WaitForPendingFinalizers(); + logger.LogDebug("Waiting for finalizers"); + for (var i = 0; i < 5; i++) + { + TriggerUnobservedExceptions(); + await Task.Delay(10); + } // Assert Assert.AreEqual(expectedUnobservedExceptions, unobservedExceptions.Count); @@ -294,7 +297,7 @@ static async Task MakeGrpcCallAsync(ILogger logger, CallInvoker invoker, Respons { var runTask = Task.Run(async () => { - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); switch (action) { @@ -322,156 +325,11 @@ static async Task MakeGrpcCallAsync(ILogger logger, CallInvoker invoker, Respons } } - private class ClientLoggerInterceptor : Interceptor + private static void TriggerUnobservedExceptions() { - private readonly ILogger _logger; - - public ClientLoggerInterceptor(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public override TResponse BlockingUnaryCall( - TRequest request, - ClientInterceptorContext context, - BlockingUnaryCallContinuation continuation) - { - LogCall(context.Method); - AddCallerMetadata(ref context); - - try - { - return continuation(request, context); - } - catch (Exception ex) - { - LogError(ex); - throw; - } - } - - public override AsyncUnaryCall AsyncUnaryCall( - TRequest request, - ClientInterceptorContext context, - AsyncUnaryCallContinuation continuation) - { - LogCall(context.Method); - AddCallerMetadata(ref context); - - try - { - var call = continuation(request, context); - - return new AsyncUnaryCall(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose); - } - catch (Exception ex) - { - LogError(ex); - throw; - } - } - - private async Task HandleResponse(Task t) - { - try - { - var response = await t; - _logger.LogInformation($"Response received: {response}"); - return response; - } - catch (Exception ex) - { - LogError(ex); - throw; - } - } - - public override AsyncClientStreamingCall AsyncClientStreamingCall( - ClientInterceptorContext context, - AsyncClientStreamingCallContinuation continuation) - { - LogCall(context.Method); - AddCallerMetadata(ref context); - - try - { - return continuation(context); - } - catch (Exception ex) - { - LogError(ex); - throw; - } - } - - public override AsyncServerStreamingCall AsyncServerStreamingCall( - TRequest request, - ClientInterceptorContext context, - AsyncServerStreamingCallContinuation continuation) - { - LogCall(context.Method); - AddCallerMetadata(ref context); - - try - { - return continuation(request, context); - } - catch (Exception ex) - { - LogError(ex); - throw; - } - } - - public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall( - ClientInterceptorContext context, - AsyncDuplexStreamingCallContinuation continuation) - { - LogCall(context.Method); - AddCallerMetadata(ref context); - - try - { - return continuation(context); - } - catch (Exception ex) - { - LogError(ex); - throw; - } - } - - private void LogCall(Method method) - where TRequest : class - where TResponse : class - { - _logger.LogInformation($"Starting call. Name: {method.Name}. Type: {method.Type}. Request: {typeof(TRequest)}. Response: {typeof(TResponse)}"); - } - - private void AddCallerMetadata(ref ClientInterceptorContext context) - where TRequest : class - where TResponse : class - { - var headers = context.Options.Headers; - - // Call doesn't have a headers collection to add to. - // Need to create a new context with headers for the call. - if (headers == null) - { - headers = new Metadata(); - var options = context.Options.WithHeaders(headers); - context = new ClientInterceptorContext(context.Method, context.Host, options); - } - - // Add caller metadata to call headers - headers.Add("caller-user", Environment.UserName); - headers.Add("caller-machine", Environment.MachineName); - headers.Add("caller-os", Environment.OSVersion.ToString()); - } - - private void LogError(Exception ex) - { - _logger.LogError(ex, $"Call error: {ex.Message}"); - } + // Provoke the garbage collector to find the unobserved exception. + GC.Collect(); + // Wait for any failed tasks to be garbage collected + GC.WaitForPendingFinalizers(); } } diff --git a/test/Grpc.Net.Client.Tests/Balancer/PickFirstBalancerTests.cs b/test/Grpc.Net.Client.Tests/Balancer/PickFirstBalancerTests.cs index 3318b7d8b..f2c3bdd6b 100644 --- a/test/Grpc.Net.Client.Tests/Balancer/PickFirstBalancerTests.cs +++ b/test/Grpc.Net.Client.Tests/Balancer/PickFirstBalancerTests.cs @@ -527,7 +527,7 @@ public async Task UnaryCall_TransportConnecting_ErrorAfterTransientFailure() _ = invoker.Channel.ConnectAsync(); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); await tcs.Task.DefaultTimeout(); // Assert diff --git a/test/Grpc.Net.Client.Tests/CallCredentialTests.cs b/test/Grpc.Net.Client.Tests/CallCredentialTests.cs index eb2678c6d..a006aae90 100644 --- a/test/Grpc.Net.Client.Tests/CallCredentialTests.cs +++ b/test/Grpc.Net.Client.Tests/CallCredentialTests.cs @@ -57,7 +57,7 @@ public async Task CallCredentialsWithHttps_WhenAsyncAuthInterceptorThrow_ShouldT return Task.FromException(expectedException); }); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(credentials: callCredentials)); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -90,7 +90,7 @@ public async Task CallCredentialsWithHttps_MetadataOnRequest() // Set header. metadata.Add("authorization", "SECRET_TOKEN"); }); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(credentials: callCredentials)); var responseTask = call.ResponseAsync; await syncPoint.WaitForSyncPoint().DefaultTimeout(); @@ -133,7 +133,7 @@ public async Task CallCredentialsWithHttps_CancellationToken() unreachableAuthInterceptorSection = true; }); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(credentials: callCredentials)); var responseTask = call.ResponseAsync; call.Dispose(); @@ -170,7 +170,7 @@ public async Task CallCredentialsWithHttp_NoMetadataOnRequest() metadata.Add("authorization", "SECRET_TOKEN"); return Task.CompletedTask; }); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(credentials: callCredentials)); await call.ResponseAsync.DefaultTimeout(); // Assert @@ -204,7 +204,7 @@ public async Task CallCredentialsWithHttp_UnsafeUseInsecureChannelCallCredential await Task.Delay(50); metadata.Add("authorization", "SECRET_TOKEN"); }); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(credentials: callCredentials)); await call.ResponseAsync.DefaultTimeout(); // Assert @@ -239,7 +239,7 @@ public async Task CallCredentialsWithHttp_UnsafeUseInsecureChannelCallCredential }); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, default, new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); await call.ResponseAsync.DefaultTimeout(); // Assert @@ -385,7 +385,7 @@ public async Task CompositeCallCredentialsWithHttps_MetadataOnRequest() // Act var callCredentials = CallCredentials.Compose(first, second, third); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(credentials: callCredentials)); await call.ResponseAsync.DefaultTimeout(); // Assert @@ -422,7 +422,7 @@ public async Task CallCredentials_AuthContextPopulated(string target, string exp methodName = context.MethodName; return Task.CompletedTask; }); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(credentials: callCredentials), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(credentials: callCredentials)); await call.ResponseAsync.DefaultTimeout(); // Assert diff --git a/test/Grpc.Net.Client.Tests/CancellationTests.cs b/test/Grpc.Net.Client.Tests/CancellationTests.cs index 68bb525e3..a1bfe1009 100644 --- a/test/Grpc.Net.Client.Tests/CancellationTests.cs +++ b/test/Grpc.Net.Client.Tests/CancellationTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -38,7 +38,7 @@ public async Task AsyncClientStreamingCall_CancellationDuringSend_ResponseThrows var invoker = CreateTimedoutCallInvoker(); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(cancellationToken: cts.Token)); // Assert var responseTask = call.ResponseAsync; @@ -60,7 +60,7 @@ public async Task AsyncClientStreamingCall_CancellationDuringSend_ResponseHeader var invoker = CreateTimedoutCallInvoker(); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(cancellationToken: cts.Token)); // Assert var responseHeadersTask = call.ResponseHeadersAsync; @@ -82,7 +82,7 @@ public async Task AsyncClientStreamingCall_CancellationDuringSend_ThrowOperation var invoker = CreateTimedoutCallInvoker(configure: o => o.ThrowOperationCanceledOnCancellation = true); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(cancellationToken: cts.Token)); // Assert var responseTask = call.ResponseAsync; @@ -105,7 +105,7 @@ public async Task AsyncClientStreamingCall_CancellationDuringSend_ThrowOperation var invoker = CreateTimedoutCallInvoker(configure: o => o.ThrowOperationCanceledOnCancellation = true); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(cancellationToken: cts.Token)); // Assert var responseHeadersTask = call.ResponseHeadersAsync; @@ -126,7 +126,7 @@ public void AsyncClientStreamingCall_CancellationDuringSend_TrailersThrowsInvali var invoker = CreateTimedoutCallInvoker(); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(cancellationToken: cts.Token)); // Assert cts.Cancel(); diff --git a/test/Grpc.Net.Client.Tests/CompressionTests.cs b/test/Grpc.Net.Client.Tests/CompressionTests.cs index 610b8fc01..12fef073c 100644 --- a/test/Grpc.Net.Client.Tests/CompressionTests.cs +++ b/test/Grpc.Net.Client.Tests/CompressionTests.cs @@ -67,10 +67,10 @@ public async Task AsyncUnaryCall_UnknownCompressMetadataSentWithRequest_ThrowsEr // Act var compressionMetadata = CreateClientCompressionMetadata("not-supported"); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(headers: compressionMetadata), new HelloRequest + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "Hello" - }); + }, new CallOptions(headers: compressionMetadata)); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -127,10 +127,10 @@ public async Task AsyncUnaryCall_CompressMetadataSentWithRequest_RequestMessageC } // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, callOptions, new HelloRequest + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "Hello" - }); + }, callOptions); // Assert var response = await call.ResponseAsync; @@ -187,7 +187,7 @@ public async Task AsyncUnaryCall_CompressedResponse_ResponseMessageDecompressed( var invoker = HttpClientCallInvokerFactory.Create(handler, "http://localhost"); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "Hello" }); @@ -233,7 +233,7 @@ public async Task AsyncUnaryCall_CompressedResponseWithUnknownEncoding_ErrorThro var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "Hello" }); @@ -304,7 +304,7 @@ public async Task AsyncClientStreamingCall_CompressMetadataSentWithRequest_Reque var callOptions = new CallOptions(headers: compressionMetadata); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, callOptions); + var call = invoker.AsyncClientStreamingCall(callOptions); await call.RequestStream.WriteAsync(new HelloRequest { diff --git a/test/Grpc.Net.Client.Tests/ConnectionTests.cs b/test/Grpc.Net.Client.Tests/ConnectionTests.cs index 8cd670efc..2a76baedc 100644 --- a/test/Grpc.Net.Client.Tests/ConnectionTests.cs +++ b/test/Grpc.Net.Client.Tests/ConnectionTests.cs @@ -41,7 +41,7 @@ public async Task UnaryCall_Http1Response_ThrowError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1)), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1))); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -62,7 +62,7 @@ public async Task UnaryCall_SocketException_ThrowCorrectStatus(SocketError socke var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1)), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1))); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -81,7 +81,7 @@ public async Task UnaryCall_IOException_ThrowCorrectStatus() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1)), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1))); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); diff --git a/test/Grpc.Net.Client.Tests/DeadlineTests.cs b/test/Grpc.Net.Client.Tests/DeadlineTests.cs index 2c72ea038..91d036765 100644 --- a/test/Grpc.Net.Client.Tests/DeadlineTests.cs +++ b/test/Grpc.Net.Client.Tests/DeadlineTests.cs @@ -55,7 +55,7 @@ public async Task AsyncUnaryCall_SetSecondDeadline_RequestMessageContainsDeadlin systemClock: new TestSystemClock(new DateTime(2019, 11, 29, 1, 1, 1, DateTimeKind.Utc))); // Act - await invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1)), new HelloRequest()); + await invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1))); // Assert Assert.IsNotNull(httpRequestMessage); @@ -78,7 +78,7 @@ public async Task AsyncUnaryCall_SetMaxValueDeadline_RequestMessageHasNoDeadline var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - await invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: DateTime.MaxValue), new HelloRequest()); + await invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: DateTime.MaxValue)); // Assert Assert.IsNotNull(httpRequestMessage); @@ -108,7 +108,7 @@ public async Task AsyncUnaryCall_StartPastDeadline_RequestMessageContainsMinDead loggerFactory: testLoggerFactory); // Act - var responseTask = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(-1)), new HelloRequest()).ResponseAsync; + var responseTask = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(-1))).ResponseAsync; // Assert var ex = await ExceptionAssert.ThrowsAsync(() => responseTask).DefaultTimeout(); @@ -142,7 +142,7 @@ public async Task AsyncUnaryCall_SetVeryLargeDeadline_MaximumDeadlineTimeoutSent var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: testLoggerFactory, systemClock: testSystemClock); // Act - await invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: deadline), new HelloRequest()).ResponseAsync.DefaultTimeout(); + await invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: deadline)).ResponseAsync.DefaultTimeout(); // Assert Assert.IsNotNull(httpRequestMessage); @@ -179,7 +179,7 @@ public async Task AsyncUnaryCall_SendDeadlineHeaderAndDeadlineValue_DeadlineValu headers.Add("grpc-timeout", "1D"); // Act - var rs = await invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(headers: headers, deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1)), new HelloRequest()); + var rs = await invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(headers: headers, deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1))); // Assert Assert.AreEqual("Hello world", rs.Message); @@ -207,7 +207,7 @@ public async Task AsyncClientStreamingCall_DeadlineDuringSend_ResponseThrowsDead var deadline = testSystemClock.UtcNow.AddSeconds(0.1); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: deadline)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(deadline: deadline)); // Assert var responseTask = call.ResponseAsync; @@ -239,7 +239,7 @@ public async Task AsyncClientStreamingCall_DeadlineDuringSend_ThrowOperationCanc var deadline = testSystemClock.UtcNow.AddSeconds(0.1); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: deadline)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(deadline: deadline)); // Assert var responseTask = call.ResponseAsync; @@ -263,7 +263,7 @@ public async Task AsyncClientStreamingCall_DeadlineStatusResponse_ResponseThrows var invoker = HttpClientCallInvokerFactory.Create(httpClient, systemClock: testSystemClock, disableClientDeadline: true); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5))); + var call = invoker.AsyncClientStreamingCall(new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5))); // Assert var responseTask = call.ResponseAsync; @@ -285,7 +285,7 @@ public async Task AsyncClientStreamingCall_DeadlineStatusResponse_ThrowOperation var invoker = HttpClientCallInvokerFactory.Create(httpClient, systemClock: testSystemClock, configure: o => o.ThrowOperationCanceledOnCancellation = true, disableClientDeadline: true); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5))); + var call = invoker.AsyncClientStreamingCall(new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5))); // Assert var responseTask = call.ResponseAsync; @@ -306,7 +306,7 @@ public async Task AsyncServerStreamingCall_DeadlineStatusResponse_ResponseThrows var invoker = HttpClientCallInvokerFactory.Create(httpClient, systemClock: testSystemClock, disableClientDeadline: true); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5)), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest(), new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5))); // Assert var moveNextTask = call.ResponseStream.MoveNext(CancellationToken.None); @@ -328,7 +328,7 @@ public async Task AsyncServerStreamingCall_DeadlineStatusResponse_ThrowOperation var invoker = HttpClientCallInvokerFactory.Create(httpClient, systemClock: testSystemClock, configure: o => o.ThrowOperationCanceledOnCancellation = true, disableClientDeadline: true); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5)), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest(), new CallOptions(deadline: testSystemClock.UtcNow.AddSeconds(0.5))); // Assert var moveNextTask = call.ResponseStream.MoveNext(CancellationToken.None); @@ -344,14 +344,14 @@ public async Task AsyncClientStreamingCall_DeadlineBeforeWrite_ResponseThrowsDea var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var httpClient = ClientTestHelpers.CreateTestClient(async request => { - await tcs.Task.DefaultTimeout(); + await tcs.Task; return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); var systemClock = new TestSystemClock(DateTime.UtcNow); var invoker = HttpClientCallInvokerFactory.Create(httpClient, systemClock: systemClock); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: systemClock.UtcNow.AddMilliseconds(10))); + var call = invoker.AsyncClientStreamingCall(new CallOptions(deadline: systemClock.UtcNow.AddMilliseconds(10))); // Ensure the deadline has passed systemClock.UtcNow = systemClock.UtcNow.AddMilliseconds(200); @@ -361,6 +361,8 @@ public async Task AsyncClientStreamingCall_DeadlineBeforeWrite_ResponseThrowsDea var ex = await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest())).DefaultTimeout(); Assert.AreEqual(StatusCode.DeadlineExceeded, ex.StatusCode); Assert.AreEqual(StatusCode.DeadlineExceeded, call.GetStatus().StatusCode); + + tcs.TrySetResult(null); } [Test] @@ -378,7 +380,7 @@ public async Task AsyncClientStreamingCall_DeadlineDuringWrite_ResponseThrowsDea var deadline = systemClock.UtcNow.AddMilliseconds(0.1); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: deadline)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(deadline: deadline)); systemClock.UtcNow = deadline; @@ -401,7 +403,7 @@ public async Task AsyncServerStreamingCall_DeadlineDuringWrite_ResponseThrowsDea var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: DateTime.UtcNow.AddSeconds(0.5)), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest(), new CallOptions(deadline: DateTime.UtcNow.AddSeconds(0.5))); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseStream.MoveNext(CancellationToken.None)).DefaultTimeout(); @@ -421,7 +423,7 @@ public async Task AsyncUnaryCall_SetNonUtcDeadline_ThrowError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var ex = await ExceptionAssert.ThrowsAsync(() => invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: new DateTime(2000, DateTimeKind.Local)), new HelloRequest()).ResponseAsync).DefaultTimeout(); + var ex = await ExceptionAssert.ThrowsAsync(() => invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: new DateTime(2000, DateTimeKind.Local))).ResponseAsync).DefaultTimeout(); // Assert Assert.AreEqual("Deadline must have a kind DateTimeKind.Utc or be equal to DateTime.MaxValue or DateTime.MinValue.", ex.Message); @@ -447,7 +449,7 @@ public async Task AsyncClientStreamingCall_DeadlineLargerThanMaxTimerDueTime_Dea var deadline = testSystemClock.UtcNow.Add(timeout); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: deadline)); + var call = invoker.AsyncClientStreamingCall(new CallOptions(deadline: deadline)); // Assert var responseTask = call.ResponseAsync; @@ -486,7 +488,7 @@ public async Task AsyncUnaryCall_ServerResetsCancelCodeBeforeDeadline_CancelStat loggerFactory: serviceProvider.GetRequiredService()); // Act - var responseTask = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1)), new HelloRequest()).ResponseAsync; + var responseTask = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: invoker.Channel.Clock.UtcNow.AddSeconds(1))).ResponseAsync; // Assert var ex = await ExceptionAssert.ThrowsAsync(() => responseTask).DefaultTimeout(); @@ -516,7 +518,7 @@ public async Task AsyncUnaryCall_Http2ServerResetsCancelCodeAfterDeadline_Deadli var deadline = invoker.Channel.Clock.UtcNow.AddSeconds(1); // Act - var responseTask = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: deadline), new HelloRequest()).ResponseAsync; + var responseTask = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: deadline)).ResponseAsync; await syncPoint.WaitForSyncPoint().DefaultTimeout(); testSystemClock.UtcNow = deadline; @@ -551,7 +553,7 @@ public async Task AsyncUnaryCall_Http3ServerResetsCancelCodeAfterDeadline_Deadli var deadline = invoker.Channel.Clock.UtcNow.AddSeconds(1); // Act - var responseTask = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: deadline), new HelloRequest()).ResponseAsync; + var responseTask = invoker.AsyncUnaryCall(new HelloRequest(), new CallOptions(deadline: deadline)).ResponseAsync; await syncPoint.WaitForSyncPoint().DefaultTimeout(); testSystemClock.UtcNow = deadline; diff --git a/test/Grpc.Net.Client.Tests/DiagnosticsTests.cs b/test/Grpc.Net.Client.Tests/DiagnosticsTests.cs index 4876aee12..e83852ef9 100644 --- a/test/Grpc.Net.Client.Tests/DiagnosticsTests.cs +++ b/test/Grpc.Net.Client.Tests/DiagnosticsTests.cs @@ -51,7 +51,7 @@ public async Task Dispose_StartCallInTask_ActivityPreserved() var call = await Task.Run(() => { - var c = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var c = invoker.AsyncDuplexStreamingCall(); Assert.AreEqual("a", Activity.Current.OperationName); return c; @@ -98,7 +98,7 @@ public void DiagnosticListener_MakeCall_ActivityWritten() using (GrpcDiagnostics.DiagnosticListener.Subscribe(new ObserverToList>(result))) { - var c1 = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var c1 = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.DuplexStreaming), string.Empty, new CallOptions()); c1.Dispose(); requestMessage1 = requestMessage; @@ -166,7 +166,7 @@ public void DiagnosticListener_MakeCall_ActivityHasNameAndDuration() // Act using (GrpcDiagnostics.DiagnosticListener.Subscribe(new ActionObserver>(onDiagnosticMessage))) { - var c = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var c = invoker.AsyncDuplexStreamingCall(); c.Dispose(); } @@ -208,7 +208,7 @@ public void ActivitySource_MakeCall_ActivityHasNameAndDuration() ActivitySource.AddActivityListener(activityListener); - var c = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var c = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.DuplexStreaming), string.Empty, new CallOptions()); c.Dispose(); } diff --git a/test/Grpc.Net.Client.Tests/GetStatusTests.cs b/test/Grpc.Net.Client.Tests/GetStatusTests.cs index e55a8d0c0..2c0340241 100644 --- a/test/Grpc.Net.Client.Tests/GetStatusTests.cs +++ b/test/Grpc.Net.Client.Tests/GetStatusTests.cs @@ -52,7 +52,7 @@ public async Task AsyncUnaryCall_Non200HttpStatusCode_MappedToGrpcStatusCode(Htt var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -76,7 +76,7 @@ public async Task AsyncUnaryCall_Non200HttpStatusCodeWithGrpcStatusCode_GrpcStat var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -101,7 +101,7 @@ public async Task AsyncUnaryCall_ValidStatusReturned_ReturnsStatus() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -126,7 +126,7 @@ public async Task AsyncUnaryCall_PercentEncodedMessage_MessageDecoded() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -152,7 +152,7 @@ public async Task AsyncUnaryCall_MultipleStatusHeaders_ThrowError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -175,7 +175,7 @@ public async Task AsyncUnaryCall_MissingStatus_ThrowError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -200,7 +200,7 @@ public async Task AsyncUnaryCall_MissingStatusBrowser_ThrowError() var invoker = HttpClientCallInvokerFactory.Create(httpClient, operatingSystem: os); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -232,7 +232,7 @@ public async Task AsyncUnaryCall_InvalidStatus_ThrowError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -257,7 +257,7 @@ public async Task AsyncUnaryCall_CallInProgress_ThrowError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = Assert.Throws(() => call.GetStatus())!; // Assert diff --git a/test/Grpc.Net.Client.Tests/GetTrailersTests.cs b/test/Grpc.Net.Client.Tests/GetTrailersTests.cs index 1845f5be7..d9ebdd087 100644 --- a/test/Grpc.Net.Client.Tests/GetTrailersTests.cs +++ b/test/Grpc.Net.Client.Tests/GetTrailersTests.cs @@ -47,7 +47,7 @@ public async Task AsyncUnaryCall_MessageReturned_ReturnsTrailers() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var message = await call; var trailers1 = call.GetTrailers(); var trailers2 = call.GetTrailers(); @@ -72,7 +72,7 @@ public async Task AsyncUnaryCall_HeadersReturned_ReturnsTrailers() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var responseHeaders = await call.ResponseHeadersAsync.DefaultTimeout(); var trailers = call.GetTrailers(); @@ -84,21 +84,23 @@ public async Task AsyncUnaryCall_HeadersReturned_ReturnsTrailers() public void AsyncUnaryCall_UnfinishedCall_ThrowsError() { // Arrange - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var httpClient = ClientTestHelpers.CreateTestClient(async request => { - await tcs.Task.DefaultTimeout(); - throw new Exception("Test shouldn't reach here."); + await tcs.Task; + return ResponseUtils.CreateResponse(HttpStatusCode.NotFound); }); var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = Assert.Throws(() => call.GetTrailers())!; // Assert Assert.AreEqual("Can't get the call trailers because the call has not completed successfully.", ex.Message); + + tcs.TrySetResult(null); } [Test] @@ -114,7 +116,7 @@ public void AsyncUnaryCall_ErrorCall_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = Assert.Throws(() => call.GetTrailers())!; // Assert @@ -145,7 +147,7 @@ public async Task AsyncUnaryCall_ErrorParsingTrailers_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -174,7 +176,7 @@ public async Task AsyncUnaryCall_NoTrailers_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -198,7 +200,7 @@ public async Task AsyncUnaryCall_BadTrailersType_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -224,7 +226,7 @@ public async Task AsyncUnaryCall_NoTrailers_WinHttpHandler_ThrowsError() #pragma warning restore CS0436 // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -238,42 +240,46 @@ public async Task AsyncUnaryCall_NoTrailers_WinHttpHandler_ThrowsError() public void AsyncClientStreamingCall_UnfinishedCall_ThrowsError() { // Arrange - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var httpClient = ClientTestHelpers.CreateTestClient(async request => { - await tcs.Task.DefaultTimeout(); - throw new Exception("Test shouldn't reach here."); + await tcs.Task; + return ResponseUtils.CreateResponse(HttpStatusCode.NotFound); }); var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var ex = Assert.Throws(() => call.GetTrailers())!; // Assert Assert.AreEqual("Can't get the call trailers because the call has not completed successfully.", ex.Message); + + tcs.TrySetResult(null); } [Test] public void AsyncServerStreamingCall_UnfinishedCall_ThrowsError() { // Arrange - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var httpClient = ClientTestHelpers.CreateTestClient(async request => { - await tcs.Task.DefaultTimeout(); - throw new Exception("Test shouldn't reach here."); + await tcs.Task; + return ResponseUtils.CreateResponse(HttpStatusCode.NotFound); }); var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var ex = Assert.Throws(() => call.GetTrailers())!; // Assert Assert.AreEqual("Can't get the call trailers because the call has not completed successfully.", ex.Message); + + tcs.TrySetResult(null); } [Test] @@ -297,7 +303,7 @@ public async Task AsyncServerStreamingCall_UnfinishedReader_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; Assert.IsTrue(await responseStream.MoveNext(CancellationToken.None).DefaultTimeout()); @@ -330,7 +336,7 @@ public async Task AsyncServerStreamingCall_FinishedReader_ReturnsTrailers() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; Assert.IsTrue(await responseStream.MoveNext(CancellationToken.None).DefaultTimeout()); @@ -373,7 +379,7 @@ public async Task AsyncClientStreamingCall_CompleteWriter_ReturnsTrailers() var invoker = HttpClientCallInvokerFactory.Create(handler, "https://localhost"); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); await call.RequestStream.CompleteAsync().DefaultTimeout(); await Task.WhenAll(call.ResponseAsync, trailingHeadersWrittenTcs.Task).DefaultTimeout(); var trailers = call.GetTrailers(); @@ -396,7 +402,7 @@ public void AsyncClientStreamingCall_UncompleteWriter_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var ex = Assert.Throws(() => call.GetTrailers())!; // Assert @@ -415,7 +421,7 @@ public void AsyncClientStreamingCall_NotFoundStatus_ReturnEmpty() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var trailers = call.GetTrailers(); // Assert @@ -435,7 +441,7 @@ public void AsyncClientStreamingCall_InvalidContentType_ReturnEmpty() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var trailers = call.GetTrailers(); // Assert diff --git a/test/Grpc.Net.Client.Tests/Infrastructure/CallInvokerTestExtensions.cs b/test/Grpc.Net.Client.Tests/Infrastructure/CallInvokerTestExtensions.cs new file mode 100644 index 000000000..da13452cc --- /dev/null +++ b/test/Grpc.Net.Client.Tests/Infrastructure/CallInvokerTestExtensions.cs @@ -0,0 +1,46 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using Greet; +using Grpc.Core; +using Grpc.Tests.Shared; + +namespace Grpc.Net.Client.Tests.Infrastructure; + +internal static class CallInvokerTestExtensions +{ + public static AsyncClientStreamingCall AsyncClientStreamingCall(this CallInvoker callInvoker, CallOptions? options = null) + { + return callInvoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, options ?? new CallOptions()); + } + + public static AsyncDuplexStreamingCall AsyncDuplexStreamingCall(this CallInvoker callInvoker, CallOptions? options = null) + { + return callInvoker.AsyncDuplexStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.DuplexStreaming), string.Empty, options ?? new CallOptions()); + } + + public static AsyncServerStreamingCall AsyncServerStreamingCall(this CallInvoker callInvoker, HelloRequest request, CallOptions? options = null) + { + return callInvoker.AsyncServerStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ServerStreaming), string.Empty, options ?? new CallOptions(), request); + } + + public static AsyncUnaryCall AsyncUnaryCall(this CallInvoker callInvoker, HelloRequest request, CallOptions? options = null) + { + return callInvoker.AsyncUnaryCall(ClientTestHelpers.GetServiceMethod(MethodType.Unary), string.Empty, options ?? new CallOptions(), request); + } +} diff --git a/test/Grpc.Net.Client.Tests/Infrastructure/ClientLoggerInterceptor.cs b/test/Grpc.Net.Client.Tests/Infrastructure/ClientLoggerInterceptor.cs new file mode 100644 index 000000000..dd3425c8c --- /dev/null +++ b/test/Grpc.Net.Client.Tests/Infrastructure/ClientLoggerInterceptor.cs @@ -0,0 +1,181 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using Grpc.Core; +using Grpc.Core.Interceptors; +using Grpc.Net.Client.Internal; +using Microsoft.Extensions.Logging; + +namespace Grpc.Net.Client.Tests.Infrastructure; + +public sealed class ClientLoggerInterceptor : Interceptor +{ + private readonly ILogger _logger; + + public ClientLoggerInterceptor(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public override TResponse BlockingUnaryCall( + TRequest request, + ClientInterceptorContext context, + BlockingUnaryCallContinuation continuation) + { + LogCall(context.Method); + AddCallerMetadata(ref context); + + try + { + return continuation(request, context); + } + catch (Exception ex) + { + LogError(ex); + throw; + } + } + + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + LogCall(context.Method); + AddCallerMetadata(ref context); + + try + { + var call = continuation(request, context); + + var responseTask = HandleResponse(call.ResponseAsync); + responseTask.ObserveException(); + + return new AsyncUnaryCall(responseTask, call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose); + } + catch (Exception ex) + { + LogError(ex); + throw; + } + } + + private async Task HandleResponse(Task t) + { + try + { + var response = await t; + _logger.LogInformation($"Response received: {response}"); + return response; + } + catch (Exception ex) + { + LogError(ex); + throw; + } + } + + public override AsyncClientStreamingCall AsyncClientStreamingCall( + ClientInterceptorContext context, + AsyncClientStreamingCallContinuation continuation) + { + LogCall(context.Method); + AddCallerMetadata(ref context); + + try + { + return continuation(context); + } + catch (Exception ex) + { + LogError(ex); + throw; + } + } + + public override AsyncServerStreamingCall AsyncServerStreamingCall( + TRequest request, + ClientInterceptorContext context, + AsyncServerStreamingCallContinuation continuation) + { + LogCall(context.Method); + AddCallerMetadata(ref context); + + try + { + return continuation(request, context); + } + catch (Exception ex) + { + LogError(ex); + throw; + } + } + + public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall( + ClientInterceptorContext context, + AsyncDuplexStreamingCallContinuation continuation) + { + LogCall(context.Method); + AddCallerMetadata(ref context); + + try + { + return continuation(context); + } + catch (Exception ex) + { + LogError(ex); + throw; + } + } + + private void LogCall(Method method) + where TRequest : class + where TResponse : class + { + _logger.LogInformation($"Starting call. Name: {method.Name}. Type: {method.Type}. Request: {typeof(TRequest)}. Response: {typeof(TResponse)}"); + } + + private void AddCallerMetadata(ref ClientInterceptorContext context) + where TRequest : class + where TResponse : class + { + var headers = context.Options.Headers; + + // Call doesn't have a headers collection to add to. + // Need to create a new context with headers for the call. + if (headers == null) + { + headers = new Metadata(); + var options = context.Options.WithHeaders(headers); + context = new ClientInterceptorContext(context.Method, context.Host, options); + } + + // Add caller metadata to call headers + headers.Add("caller-user", Environment.UserName); + headers.Add("caller-machine", Environment.MachineName); + headers.Add("caller-os", Environment.OSVersion.ToString()); + } + + private void LogError(Exception ex) + { + _logger.LogError(ex, $"Call error: {ex.Message}"); + } +} + diff --git a/test/Grpc.Net.Client.Tests/InterceptorTests.cs b/test/Grpc.Net.Client.Tests/InterceptorTests.cs index 135f3db9e..63efca7dc 100644 --- a/test/Grpc.Net.Client.Tests/InterceptorTests.cs +++ b/test/Grpc.Net.Client.Tests/InterceptorTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -114,7 +114,7 @@ public void Intercept_InterceptorOrder_ExecutedInReversedOrder() public async Task Intercept_WrapClientStream_ClientStreamWrapperExecuted() { // Arrange - var serviceMethod = new Method(MethodType.Unary, "ServiceName", "Unary", Marshallers.StringMarshaller, Marshallers.StringMarshaller); + var serviceMethod = new Method(MethodType.ClientStreaming, "ServiceName", "Unary", Marshallers.StringMarshaller, Marshallers.StringMarshaller); var httpClient = ClientTestHelpers.CreateTestClient(async request => { diff --git a/test/Grpc.Net.Client.Tests/MaximumMessageSizeTests.cs b/test/Grpc.Net.Client.Tests/MaximumMessageSizeTests.cs index 69b6ef20d..a930fc8b7 100644 --- a/test/Grpc.Net.Client.Tests/MaximumMessageSizeTests.cs +++ b/test/Grpc.Net.Client.Tests/MaximumMessageSizeTests.cs @@ -172,7 +172,7 @@ public async Task AsyncDuplexStreamingCall_MessageSmallerThanSendMaxMessageSize_ var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.MaxSendMessageSize = 100); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); await call.RequestStream.WriteAsync(new HelloRequest { Name = "World" @@ -192,7 +192,7 @@ public async Task AsyncDuplexStreamingCall_MessageLargerThanSendMaxMessageSize_T var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.MaxSendMessageSize = 1); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.RequestStream.WriteAsync(new HelloRequest @@ -211,7 +211,7 @@ public async Task AsyncDuplexStreamingCall_MessageSmallerThanReceiveMaxMessageSi var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.MaxReceiveMessageSize = 100); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); await call.RequestStream.WriteAsync(new HelloRequest { Name = "World" @@ -231,7 +231,7 @@ public async Task AsyncDuplexStreamingCall_MessageLargerThanReceiveMaxMessageSiz var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.MaxReceiveMessageSize = 1); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); await call.RequestStream.WriteAsync(new HelloRequest { Name = "World" diff --git a/test/Grpc.Net.Client.Tests/ReadAllAsyncTests.cs b/test/Grpc.Net.Client.Tests/ReadAllAsyncTests.cs index d7abc83fd..1e50b55a0 100644 --- a/test/Grpc.Net.Client.Tests/ReadAllAsyncTests.cs +++ b/test/Grpc.Net.Client.Tests/ReadAllAsyncTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -51,7 +51,7 @@ public async Task ReadAllAsync_MultipleMessages_MessagesReceived() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var messages = new List(); await foreach (var item in call.ResponseStream.ReadAllAsync().DefaultTimeout()) @@ -97,7 +97,7 @@ public async Task ReadAllAsync_CancelCallViaWithCancellation_ForEachExitedOnCanc var cts = new CancellationTokenSource(); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var messages = new List(); var ex = await ExceptionAssert.ThrowsAsync(async () => @@ -138,7 +138,7 @@ public async Task ReadAllAsync_CancelCallViaArgument_ForEachExitedOnCancellation var cts = new CancellationTokenSource(); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var messages = new List(); var ex = await ExceptionAssert.ThrowsAsync(async () => @@ -178,7 +178,7 @@ public async Task MoveNextAsync_MultipleMessages_MessagesReceived() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var enumerator = call.ResponseStream.ReadAllAsync().GetAsyncEnumerator(); @@ -218,7 +218,7 @@ public async Task MoveNextAsync_CancelCall_EnumeratorThrows() var cts = new CancellationTokenSource(); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest(), new CallOptions(cancellationToken: cts.Token)); var enumerator = call.ResponseStream.ReadAllAsync().GetAsyncEnumerator(); @@ -258,7 +258,7 @@ public async Task MoveNextAsync_CancelCall_ThrowOperationCanceledOnCancellation_ var cts = new CancellationTokenSource(); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest(), new CallOptions(cancellationToken: cts.Token)); var enumerator = call.ResponseStream.ReadAllAsync().GetAsyncEnumerator(); diff --git a/test/Grpc.Net.Client.Tests/ResponseAsyncTests.cs b/test/Grpc.Net.Client.Tests/ResponseAsyncTests.cs index d4e80f726..8367796e2 100644 --- a/test/Grpc.Net.Client.Tests/ResponseAsyncTests.cs +++ b/test/Grpc.Net.Client.Tests/ResponseAsyncTests.cs @@ -126,7 +126,7 @@ public async Task AsyncUnaryCall_ErrorSendingRequest_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -147,7 +147,7 @@ public async Task AsyncUnaryCall_ErrorSendingRequest_ThrowsErrorWithInnerExcepti var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -169,7 +169,7 @@ public async Task AsyncClientStreamingCall_NotFoundStatus_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -190,7 +190,7 @@ public async Task AsyncClientStreamingCall_InvalidContentType_ThrowsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert diff --git a/test/Grpc.Net.Client.Tests/ResponseHeadersAsyncTests.cs b/test/Grpc.Net.Client.Tests/ResponseHeadersAsyncTests.cs index df721c8b4..249c39827 100644 --- a/test/Grpc.Net.Client.Tests/ResponseHeadersAsyncTests.cs +++ b/test/Grpc.Net.Client.Tests/ResponseHeadersAsyncTests.cs @@ -47,7 +47,7 @@ public async Task AsyncUnaryCall_Success_ResponseHeadersPopulated() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var responseHeaders1 = await call.ResponseHeadersAsync.DefaultTimeout(); var responseHeaders2 = await call.ResponseHeadersAsync.DefaultTimeout(); @@ -85,7 +85,7 @@ public async Task AsyncUnaryCall_AuthInterceptorSuccess_ResponseHeadersPopulated var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: options => options.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var responseHeadersTask = call.ResponseHeadersAsync; await credentialsSyncPoint.WaitForSyncPoint().DefaultTimeout(); @@ -119,7 +119,7 @@ public async Task AsyncUnaryCall_AuthInterceptorDispose_ResponseHeadersError() var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: options => options.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncUnaryCall(new HelloRequest()); var responseHeadersTask = call.ResponseHeadersAsync; await credentialsSyncPoint.WaitForSyncPoint().DefaultTimeout(); @@ -145,7 +145,7 @@ public async Task AsyncClientStreamingCall_Success_ResponseHeadersPopulated() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var responseHeaders = await call.ResponseHeadersAsync.DefaultTimeout(); // Assert @@ -166,7 +166,7 @@ public async Task AsyncDuplexStreamingCall_Success_ResponseHeadersPopulated() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); var responseHeaders = await call.ResponseHeadersAsync.DefaultTimeout(); // Assert @@ -187,7 +187,7 @@ public async Task AsyncServerStreamingCall_Success_ResponseHeadersPopulated() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseHeaders = await call.ResponseHeadersAsync.DefaultTimeout(); // Assert @@ -205,7 +205,7 @@ public async Task AsyncServerStreamingCall_ErrorSendingRequest_ReturnsError() var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseHeadersAsync).DefaultTimeout(); // Assert @@ -232,7 +232,7 @@ public async Task AsyncServerStreamingCall_DisposeBeforeHeadersReceived_ReturnsE var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); call.Dispose(); tcs.TrySetResult(true); @@ -260,7 +260,7 @@ public async Task AsyncServerStreamingCall_DisposeBeforeHeadersReceived_ThrowOpe var invoker = HttpClientCallInvokerFactory.Create(httpClient, configure: o => o.ThrowOperationCanceledOnCancellation = true); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); call.Dispose(); tcs.TrySetResult(true); @@ -282,7 +282,7 @@ public async Task AsyncClientStreamingCall_NotFoundStatus_ResponseHeadersPopulat var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var responseHeaders = await call.ResponseHeadersAsync.DefaultTimeout(); // Assert @@ -303,7 +303,7 @@ public async Task AsyncClientStreamingCall_InvalidContentType_ResponseHeadersPop var invoker = HttpClientCallInvokerFactory.Create(httpClient); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var responseHeaders = await call.ResponseHeadersAsync.DefaultTimeout(); // Assert diff --git a/test/Grpc.Net.Client.Tests/Retry/HedgingTests.cs b/test/Grpc.Net.Client.Tests/Retry/HedgingTests.cs index ec141e315..1369fa9c9 100644 --- a/test/Grpc.Net.Client.Tests/Retry/HedgingTests.cs +++ b/test/Grpc.Net.Client.Tests/Retry/HedgingTests.cs @@ -63,7 +63,7 @@ public async Task AsyncUnaryCall_OneAttempt_Success(int maxAttempts) configure: o => o.MaxRetryAttempts = maxAttempts); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert await TestHelpers.AssertIsTrueRetryAsync(() => callCount == maxAttempts, "All calls made at once."); @@ -106,7 +106,7 @@ public async Task AsyncClientStreamingCall_ManyParallelCalls_ReadDirectlyToReque configure: o => o.MaxRetryAttempts = attempts); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); var writeAsyncTask = call.RequestStream.WriteAsync(new HelloRequest { Name = "World" }); // Assert @@ -259,7 +259,7 @@ public async Task AsyncUnaryCall_ExceedAttempts_Failure() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert Assert.AreEqual(5, callCount); @@ -302,7 +302,7 @@ public async Task AsyncUnaryCall_ExceedDeadlineWithActiveCalls_Failure() var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: provider.GetRequiredService(), serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(deadline: DateTime.UtcNow.AddMilliseconds(100)), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }, new CallOptions(deadline: DateTime.UtcNow.AddMilliseconds(100))); // Assert Assert.AreEqual(1, callCount); @@ -339,7 +339,7 @@ public async Task AsyncUnaryCall_ManyAttemptsNoDelay_MarshallerCalledOnce() var method = ClientTestHelpers.GetServiceMethod(requestMarshaller: requestMarshaller); // Act - var call = invoker.AsyncUnaryCall(method, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(method, null, new CallOptions(), new HelloRequest { Name = "World" }); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -374,7 +374,7 @@ public async Task AsyncUnaryCall_ExceedAttempts_PusbackDelay_Failure() // Act stopwatch.Start(); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -412,7 +412,7 @@ public async Task AsyncUnaryCall_ExceedAttempts_NoPusbackDelay_Failure() // Act stopwatch.Start(); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -455,7 +455,7 @@ public async Task AsyncUnaryCall_PushbackDelay_PushbackDelayUpdatesNextCallDelay // Act stopwatch.Start(); - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert await TestHelpers.AssertIsTrueRetryAsync(() => callIntervals.Count == 2, "Only two calls should be made.").DefaultTimeout(); @@ -483,7 +483,7 @@ public async Task AsyncUnaryCall_FatalStatusCode_HedgeDelay_Failure() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); @@ -529,7 +529,7 @@ public async Task AsyncServerStreamingCall_SuccessAfterRetry_RequestContentSent( var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ServerStreaming), string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncServerStreamingCall(new HelloRequest { Name = "World" }); var moveNextTask = call.ResponseStream.MoveNext(CancellationToken.None); // Wait until the first call has failed and the second is on the server @@ -585,7 +585,7 @@ public async Task AsyncClientStreamingCall_SuccessAfterRetry_RequestContentSent( var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert Assert.IsNotNull(call); @@ -642,7 +642,7 @@ public async Task AsyncClientStreamingCall_CompleteAndWriteAfterResult_Error() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var responseMessage = await call.ResponseAsync.DefaultTimeout(); @@ -678,7 +678,7 @@ public async Task AsyncClientStreamingCall_WriteAfterResult_Error() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var responseMessage = await call.ResponseAsync.DefaultTimeout(); @@ -701,7 +701,7 @@ public void AsyncUnaryCall_DisposedChannel_Error() // Act & Assert invoker.Channel.Dispose(); - Assert.Throws(() => invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" })); + Assert.Throws(() => invoker.AsyncUnaryCall(new HelloRequest { Name = "World" })); } [Test] @@ -721,7 +721,7 @@ public async Task AsyncUnaryCall_ChannelDisposeDuringBackoff_CanceledStatus() var cts = new CancellationTokenSource(); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); var delayTask = Task.Delay(100); var completedTask = await Task.WhenAny(call.ResponseAsync, delayTask); diff --git a/test/Grpc.Net.Client.Tests/Retry/RetryTests.cs b/test/Grpc.Net.Client.Tests/Retry/RetryTests.cs index 18f73a7d4..105a18c89 100644 --- a/test/Grpc.Net.Client.Tests/Retry/RetryTests.cs +++ b/test/Grpc.Net.Client.Tests/Retry/RetryTests.cs @@ -16,10 +16,12 @@ #endregion +using System.Diagnostics; using System.Globalization; using System.Net; using Greet; using Grpc.Core; +using Grpc.Core.Interceptors; using Grpc.Net.Client.Internal; using Grpc.Net.Client.Internal.Http; using Grpc.Net.Client.Tests.Infrastructure; @@ -77,7 +79,7 @@ public async Task AsyncUnaryCall_SuccessAfterRetry_RequestContentSent() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert Assert.AreEqual(2, callCount); @@ -127,7 +129,7 @@ public async Task AsyncUnaryCall_AuthInteceptor_Success() var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: provider.GetRequiredService(), serviceConfig: serviceConfig, configure: options => options.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); await credentialsSyncPoint.WaitForSyncPoint().DefaultTimeout(); credentialsSyncPoint.Continue(); @@ -169,7 +171,7 @@ public async Task AsyncUnaryCall_AuthInteceptorDispose_Error() var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: provider.GetRequiredService(), serviceConfig: serviceConfig, configure: options => options.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); var responseTask = call.ResponseAsync; var responseHeadersTask = call.ResponseHeadersAsync; @@ -226,7 +228,7 @@ public async Task AsyncUnaryCall_SuccessAfterRetry_AccessResponseHeaders_Success var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); var headersTask = call.ResponseHeadersAsync; // Wait until the first call has failed and the second is on the server @@ -255,7 +257,7 @@ public async Task AsyncUnaryCall_ExceedRetryAttempts_Failure() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert Assert.AreEqual(3, callCount); @@ -280,7 +282,7 @@ public async Task AsyncUnaryCall_FailureWithLongDelay_Dispose_CallImmediatelyDis var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); var resultTask = call.ResponseAsync; // Test will timeout if dispose doesn't kill the timer. @@ -309,7 +311,7 @@ public async Task AsyncUnaryCall_PushbackStop_Failure(string header) var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert Assert.AreEqual(1, callCount); @@ -342,7 +344,7 @@ public async Task AsyncUnaryCall_PushbackExpicitDelay_DelayForSpecifiedDuration( var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Delay of 100ms will finish before second record which has a pushback delay of 200ms var completedTask = await Task.WhenAny(call.ResponseAsync, delayTask!).DefaultTimeout(); @@ -433,7 +435,7 @@ public async Task AsyncUnaryCall_PushbackExplicitDelayExceedAttempts_Failure() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert Assert.AreEqual(5, callCount); @@ -456,7 +458,7 @@ public async Task AsyncUnaryCall_UnsupportedStatusCode_Failure() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert Assert.AreEqual(1, callCount); @@ -486,7 +488,7 @@ public async Task AsyncUnaryCall_Success_RequestContentSent() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); // Assert Assert.AreEqual(1, callCount); @@ -524,7 +526,7 @@ public async Task AsyncClientStreamingCall_SuccessAfterRetry_RequestContentSent( var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert Assert.IsNotNull(call); @@ -574,7 +576,7 @@ public async Task ClientStreamWriter_WriteWhilePendingWrite_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var writeTask1 = call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }); @@ -599,7 +601,7 @@ public async Task ClientStreamWriter_WriteWhileComplete_ErrorThrown() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); await call.RequestStream.CompleteAsync().DefaultTimeout(); var resultTask = call.ResponseAsync; @@ -642,7 +644,7 @@ public async Task AsyncClientStreamingCall_CompleteAndWriteAfterResult_Error() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var responseMessage = await call.ResponseAsync.DefaultTimeout(); @@ -678,7 +680,7 @@ public async Task AsyncClientStreamingCall_WriteAfterResult_Error() var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert var responseMessage = await call.ResponseAsync.DefaultTimeout(); @@ -725,7 +727,7 @@ public async Task AsyncClientStreamingCall_OneMessageSentThenRetryThenAnotherMes var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncClientStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ClientStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncClientStreamingCall(); // Assert Assert.IsNotNull(call); @@ -780,7 +782,7 @@ public async Task AsyncUnaryCall_Success_SuccussCommitLogged() var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: provider.GetRequiredService(), serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.GetServiceMethod(MethodType.Unary), string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); await call.ResponseAsync.DefaultTimeout(); // Assert @@ -812,7 +814,7 @@ public async Task AsyncUnaryCall_NoMessagesSuccess_Failure() var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: provider.GetRequiredService(), serviceConfig: serviceConfig); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.GetServiceMethod(MethodType.Unary), string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }); var ex = await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync).DefaultTimeout(); // Assert @@ -847,7 +849,7 @@ public async Task AsyncServerStreamingCall_NoMessagesSuccess_SuccussCommitLogged var invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: provider.GetRequiredService(), serviceConfig: serviceConfig); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ServerStreaming), string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncServerStreamingCall(new HelloRequest { Name = "World" }); var moveNextTask = call.ResponseStream.MoveNext(CancellationToken.None); // Assert @@ -891,7 +893,7 @@ public async Task AsyncServerStreamingCall_SuccessAfterRetry_RequestContentSent( var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.ServerStreaming), string.Empty, new CallOptions(), new HelloRequest { Name = "World" }); + var call = invoker.AsyncServerStreamingCall(new HelloRequest { Name = "World" }); var moveNextTask = call.ResponseStream.MoveNext(CancellationToken.None); // Wait until the first call has failed and the second is on the server @@ -923,7 +925,7 @@ public async Task AsyncServerStreamingCall_FailureAfterReadingResponseMessage_Fa var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncServerStreamingCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + var call = invoker.AsyncServerStreamingCall(new HelloRequest()); var responseStream = call.ResponseStream; @@ -990,7 +992,7 @@ public async Task AsyncDuplexStreamingCall_SuccessAfterRetry_RequestContentSent( var invoker = HttpClientCallInvokerFactory.Create(httpClient, serviceConfig: serviceConfig); // Act - var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.DuplexStreaming), string.Empty, new CallOptions()); + var call = invoker.AsyncDuplexStreamingCall(); var moveNextTask = call.ResponseStream.MoveNext(CancellationToken.None); await call.RequestStream.WriteAsync(new HelloRequest { Name = "1" }).DefaultTimeout(); @@ -1027,7 +1029,7 @@ public void AsyncUnaryCall_DisposedChannel_Error() // Act & Assert invoker.Channel.Dispose(); - Assert.Throws(() => invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest { Name = "World" })); + Assert.Throws(() => invoker.AsyncUnaryCall(new HelloRequest { Name = "World" })); } [Test] @@ -1047,7 +1049,7 @@ public async Task AsyncUnaryCall_ChannelDisposeDuringBackoff_CanceledStatus() var cts = new CancellationTokenSource(); // Act - var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(cancellationToken: cts.Token), new HelloRequest { Name = "World" }); + var call = invoker.AsyncUnaryCall(new HelloRequest { Name = "World" }, new CallOptions(cancellationToken: cts.Token)); var delayTask = Task.Delay(100); var completedTask = await Task.WhenAny(call.ResponseAsync, delayTask); @@ -1062,6 +1064,295 @@ public async Task AsyncUnaryCall_ChannelDisposeDuringBackoff_CanceledStatus() Assert.AreEqual("gRPC call disposed.", ex.Status.Detail); } + public enum ResponseHandleAction + { + ResponseAsync, + ResponseHeadersAsync, + Dispose, + Nothing + } + + public static object[] NoUnobservedExceptionsCases_Unary + { + get + { + var cases = new List(); + AddCases(0, addClientInterceptor: false, throwCancellationError: false, ResponseHandleAction.ResponseAsync); + AddCases(0, addClientInterceptor: true, throwCancellationError: false, ResponseHandleAction.ResponseAsync); + AddCases(0, addClientInterceptor: false, throwCancellationError: false, ResponseHandleAction.ResponseHeadersAsync); + AddCases(0, addClientInterceptor: false, throwCancellationError: false, ResponseHandleAction.Dispose); + AddCases(0, addClientInterceptor: true, throwCancellationError: false, ResponseHandleAction.Dispose); + AddCases(1, addClientInterceptor: false, throwCancellationError: false, ResponseHandleAction.Nothing); + AddCases(0, addClientInterceptor: false, throwCancellationError: true, ResponseHandleAction.Nothing); + return cases.ToArray(); + + // Add a sync and async case for each. + void AddCases(int expectedUnobservedExceptions, bool addClientInterceptor, bool throwCancellationError, ResponseHandleAction action) + { + cases.Add(new object[] { expectedUnobservedExceptions, true, addClientInterceptor, throwCancellationError, action }); + cases.Add(new object[] { expectedUnobservedExceptions, false, addClientInterceptor, throwCancellationError, action }); + } + } + } + + [TestCaseSource(nameof(NoUnobservedExceptionsCases_Unary))] + public async Task AsyncUnaryCall_CallFailed_NoUnobservedExceptions(int expectedUnobservedExceptions, bool isAsync, bool addClientInterceptor, bool throwCancellationError, ResponseHandleAction action) + { + // Do this before running the test to clean up any pending unobserved exceptions from other tests. + TriggerUnobservedExceptions(); + + // Arrange + var services = new ServiceCollection(); + services.AddNUnitLogger(); + var loggerFactory = services.BuildServiceProvider().GetRequiredService(); + var logger = loggerFactory.CreateLogger(GetType()); + + var unobservedExceptions = new List(); + EventHandler onUnobservedTaskException = (sender, e) => + { + if (!e.Observed) + { + unobservedExceptions.Add(e.Exception!); + } + }; + + TaskScheduler.UnobservedTaskException += onUnobservedTaskException; + + try + { + var httpClient = ClientTestHelpers.CreateTestClient(async request => + { + if (isAsync) + { + await Task.Delay(50); + } + if (throwCancellationError) + { + throw new OperationCanceledException(); + } + else + { + throw new Exception("Test error"); + } + }); + var serviceConfig = ServiceConfigHelpers.CreateRetryServiceConfig(); + CallInvoker invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: loggerFactory, serviceConfig: serviceConfig); + if (addClientInterceptor) + { + invoker = invoker.Intercept(new ClientLoggerInterceptor(loggerFactory)); + } + + // Act + logger.LogDebug("Starting call"); + await MakeGrpcCallAsync(logger, invoker, action); + + logger.LogDebug("Waiting for finalizers"); + for (var i = 0; i < 5; i++) + { + TriggerUnobservedExceptions(); + await Task.Delay(10); + } + + foreach (var exception in unobservedExceptions) + { + logger.LogCritical(exception, "Unobserved task exception"); + } + + // Assert + Assert.AreEqual(expectedUnobservedExceptions, unobservedExceptions.Count); + + static async Task MakeGrpcCallAsync(ILogger logger, CallInvoker invoker, ResponseHandleAction action) + { + var runTask = Task.Run(async () => + { + var call = invoker.AsyncUnaryCall(ClientTestHelpers.ServiceMethod, string.Empty, new CallOptions(), new HelloRequest()); + + switch (action) + { + case ResponseHandleAction.ResponseAsync: + await ExceptionAssert.ThrowsAsync(() => call.ResponseAsync); + break; + case ResponseHandleAction.ResponseHeadersAsync: + await ExceptionAssert.ThrowsAsync(() => call.ResponseHeadersAsync); + break; + case ResponseHandleAction.Dispose: + await WaitForCallCompleteAsync(logger, call); + call.Dispose(); + break; + default: + // Do nothing (but wait until call is finished) + await WaitForCallCompleteAsync(logger, call); + break; + } + }); + + await runTask; + } + } + finally + { + TaskScheduler.UnobservedTaskException -= onUnobservedTaskException; + } + + static async Task WaitForCallCompleteAsync(ILogger logger, AsyncUnaryCall call) + { + await TestHelpers.AssertIsTrueRetryAsync(() => + { + try + { + call.GetStatus(); + return true; + } + catch (InvalidOperationException) + { + return false; + } + }, "Wait for call to complete.", logger); + } + } + + public static object[] NoUnobservedExceptionsCases_Streaming + { + get + { + var cases = new List(); + AddCases(0, addClientInterceptor: false, throwCancellationError: false, ResponseHandleAction.ResponseHeadersAsync); + AddCases(0, addClientInterceptor: false, throwCancellationError: false, ResponseHandleAction.Dispose); + AddCases(0, addClientInterceptor: true, throwCancellationError: false, ResponseHandleAction.Dispose); + AddCases(0, addClientInterceptor: false, throwCancellationError: false, ResponseHandleAction.Nothing); + AddCases(0, addClientInterceptor: false, throwCancellationError: true, ResponseHandleAction.Nothing); + return cases.ToArray(); + + // Add a sync and async case for each. + void AddCases(int expectedUnobservedExceptions, bool addClientInterceptor, bool throwCancellationError, ResponseHandleAction action) + { + cases.Add(new object[] { expectedUnobservedExceptions, true, addClientInterceptor, throwCancellationError, action }); + cases.Add(new object[] { expectedUnobservedExceptions, false, addClientInterceptor, throwCancellationError, action }); + } + } + } + + [TestCaseSource(nameof(NoUnobservedExceptionsCases_Streaming))] + public async Task AsyncDuplexStreamingCall_CallFailed_NoUnobservedExceptions(int expectedUnobservedExceptions, bool isAsync, bool addClientInterceptor, bool throwCancellationError, ResponseHandleAction action) + { + // Do this before running the test to clean up any pending unobserved exceptions from other tests. + TriggerUnobservedExceptions(); + + // Arrange + var services = new ServiceCollection(); + services.AddNUnitLogger(); + var loggerFactory = services.BuildServiceProvider().GetRequiredService(); + var logger = loggerFactory.CreateLogger(GetType()); + + var unobservedExceptions = new List(); + EventHandler onUnobservedTaskException = (sender, e) => + { + if (!e.Observed) + { + unobservedExceptions.Add(e.Exception!); + } + }; + + TaskScheduler.UnobservedTaskException += onUnobservedTaskException; + + try + { + var httpClient = ClientTestHelpers.CreateTestClient(async request => + { + if (isAsync) + { + await Task.Delay(50); + } + if (throwCancellationError) + { + throw new OperationCanceledException(); + } + else + { + throw new Exception("Test error"); + } + }); + var serviceConfig = ServiceConfigHelpers.CreateRetryServiceConfig(); + CallInvoker invoker = HttpClientCallInvokerFactory.Create(httpClient, loggerFactory: loggerFactory, serviceConfig: serviceConfig); + if (addClientInterceptor) + { + invoker = invoker.Intercept(new ClientLoggerInterceptor(loggerFactory)); + } + + // Act + logger.LogDebug("Starting call"); + await MakeGrpcCallAsync(logger, invoker, action); + + logger.LogDebug("Waiting for finalizers"); + for (var i = 0; i < 5; i++) + { + TriggerUnobservedExceptions(); + await Task.Delay(10); + } + + foreach (var exception in unobservedExceptions) + { + logger.LogCritical(exception, "Unobserved task exception"); + } + + // Assert + Assert.AreEqual(expectedUnobservedExceptions, unobservedExceptions.Count); + + static async Task MakeGrpcCallAsync(ILogger logger, CallInvoker invoker, ResponseHandleAction action) + { + var runTask = Task.Run(async () => + { + var call = invoker.AsyncDuplexStreamingCall(ClientTestHelpers.GetServiceMethod(MethodType.DuplexStreaming), string.Empty, new CallOptions()); + + switch (action) + { + case ResponseHandleAction.ResponseHeadersAsync: + await ExceptionAssert.ThrowsAsync(() => call.ResponseHeadersAsync); + break; + case ResponseHandleAction.Dispose: + await WaitForCallCompleteAsync(logger, call); + call.Dispose(); + break; + default: + // Do nothing (but wait until call is finished) + await WaitForCallCompleteAsync(logger, call); + break; + } + }); + + await runTask; + } + } + finally + { + TaskScheduler.UnobservedTaskException -= onUnobservedTaskException; + } + + static async Task WaitForCallCompleteAsync(ILogger logger, AsyncDuplexStreamingCall call) + { + await TestHelpers.AssertIsTrueRetryAsync(() => + { + try + { + call.GetStatus(); + return true; + } + catch (InvalidOperationException) + { + return false; + } + }, "Wait for call to complete.", logger); + } + } + + private static void TriggerUnobservedExceptions() + { + // Provoke the garbage collector to find the unobserved exception. + GC.Collect(); + // Wait for any failed tasks to be garbage collected + GC.WaitForPendingFinalizers(); + } + private static Task ReadRequestMessage(Stream requestContent) { return StreamSerializationHelper.ReadMessageAsync(