diff --git a/.github/workflows/build-natives.yml b/.github/workflows/build-natives.yml index d3fa130..3c76d3d 100644 --- a/.github/workflows/build-natives.yml +++ b/.github/workflows/build-natives.yml @@ -86,9 +86,9 @@ jobs: steps: - uses: actions/checkout@v4 - uses: Cysharp/Actions/.github/actions/setup-dotnet@main - - run: rustup toolchain install nightly-x86_64-pc-windows-msvc - - run: rustup component add rust-src --toolchain nightly-x86_64-pc-windows-msvc - - run: rustup default nightly + - run: rustup toolchain install nightly-2024-07-10-x86_64-pc-windows-msvc + - run: rustup component add rust-src --toolchain nightly-2024-07-10-x86_64-pc-windows-msvc + - run: rustup default nightly-2024-07-10 - run: | $env:Path += ";C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\bin" cargo -Z build-std build --target aarch64-uwp-windows-msvc --profile ${{ env._RUST_BUILD_CONFIG == 'debug' && 'dev' || env._RUST_BUILD_CONFIG }} diff --git a/src/YetAnotherHttpHandler.Unity/Packages/manifest.json b/src/YetAnotherHttpHandler.Unity/Packages/manifest.json index 73f1340..045533b 100644 --- a/src/YetAnotherHttpHandler.Unity/Packages/manifest.json +++ b/src/YetAnotherHttpHandler.Unity/Packages/manifest.json @@ -2,16 +2,16 @@ "dependencies": { "com.cysharp.yetanotherhttphandler": "file:../../YetAnotherHttpHandler", "com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", - "com.unity.collab-proxy": "1.15.15", + "com.unity.collab-proxy": "2.0.1", "com.unity.feature.2d": "1.0.0", - "com.unity.ide.rider": "3.0.13", - "com.unity.ide.visualstudio": "2.0.14", + "com.unity.ide.rider": "3.0.18", + "com.unity.ide.visualstudio": "2.0.17", "com.unity.ide.vscode": "1.2.5", "com.unity.test-framework": "1.3.8", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.6.4", "com.unity.ugui": "1.0.0", - "com.unity.visualscripting": "1.7.6", + "com.unity.visualscripting": "1.8.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/src/YetAnotherHttpHandler.Unity/Packages/packages-lock.json b/src/YetAnotherHttpHandler.Unity/Packages/packages-lock.json new file mode 100644 index 0000000..1eb4938 --- /dev/null +++ b/src/YetAnotherHttpHandler.Unity/Packages/packages-lock.json @@ -0,0 +1,476 @@ +{ + "dependencies": { + "com.cysharp.yetanotherhttphandler": { + "version": "file:../../YetAnotherHttpHandler", + "depth": 0, + "source": "local", + "dependencies": {} + }, + "com.github-glitchenzo.nugetforunity": { + "version": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", + "depth": 0, + "source": "git", + "dependencies": {}, + "hash": "b93741e2b5fe9073e169853ade0039c7cf2595f0" + }, + "com.unity.2d.animation": { + "version": "7.0.10", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.2d.common": "6.0.6", + "com.unity.2d.sprite": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.uielements": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.2d.common": { + "version": "6.0.6", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.2d.sprite": "1.0.0", + "com.unity.mathematics": "1.1.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.burst": "1.5.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.2d.path": { + "version": "5.0.2", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.2d.pixel-perfect": { + "version": "5.0.3", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.2d.psdimporter": { + "version": "6.0.7", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.2d.animation": "7.0.9", + "com.unity.2d.common": "6.0.6", + "com.unity.2d.sprite": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.2d.sprite": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.2d.spriteshape": { + "version": "7.0.7", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.1.0", + "com.unity.2d.common": "6.0.6", + "com.unity.2d.path": "5.0.2", + "com.unity.modules.physics2d": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.2d.tilemap": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.2d.tilemap.extras": { + "version": "2.2.5", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.modules.tilemap": "1.0.0", + "com.unity.2d.tilemap": "1.0.0", + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.burst": { + "version": "1.6.6", + "depth": 3, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collab-proxy": { + "version": "2.0.1", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "2.0.3", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.feature.2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.2d.animation": "7.0.10", + "com.unity.2d.pixel-perfect": "5.0.3", + "com.unity.2d.psdimporter": "6.0.7", + "com.unity.2d.sprite": "1.0.0", + "com.unity.2d.spriteshape": "7.0.7", + "com.unity.2d.tilemap": "1.0.0", + "com.unity.2d.tilemap.extras": "2.2.5" + } + }, + "com.unity.ide.rider": { + "version": "3.0.18", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.17", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.mathematics": { + "version": "1.2.6", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.test-framework": { + "version": "1.3.8", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "2.0.3", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.textmeshpro": { + "version": "3.0.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.6.4", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.visualscripting": { + "version": "1.8.0", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.uielementsnative": "1.0.0" + } + }, + "com.unity.modules.uielementsnative": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} diff --git a/src/YetAnotherHttpHandler/ResponseContext.cs b/src/YetAnotherHttpHandler/ResponseContext.cs index 8b3eb01..d57466c 100644 --- a/src/YetAnotherHttpHandler/ResponseContext.cs +++ b/src/YetAnotherHttpHandler/ResponseContext.cs @@ -162,6 +162,14 @@ public async Task GetResponseAsync() { return await _responseTask.Task.ConfigureAwait(false); } +#if UNITY_2021_1_OR_NEWER + // NOTE: .NET's HttpClient will unwrap OperationCanceledException if HttpRequestException is thrown, including OperationCanceledException. + // However, Unity's Mono does not do this, so you will need to unwrap it manually. + catch (Exception e) when (e is TaskCanceledException or OperationCanceledException) + { + throw; + } +#endif catch (Exception e) { throw new HttpRequestException(e.Message, e); diff --git a/test/YetAnotherHttpHandler.Test/Http1Test.cs b/test/YetAnotherHttpHandler.Test/Http1Test.cs index 115abae..2f204fc 100644 --- a/test/YetAnotherHttpHandler.Test/Http1Test.cs +++ b/test/YetAnotherHttpHandler.Test/Http1Test.cs @@ -118,12 +118,28 @@ public async Task Post_Cancel() // Assert Assert.NotNull(ex); -#if UNITY_2021_1_OR_NEWER - Assert.IsAssignableFrom(ex); - Assert.IsAssignableFrom(ex.InnerException); -#else - // NOTE: .NET HttpClient throws HttpRequestException with OperationCanceledException if it contains an OperationCanceledException + // NOTE: .NET's HttpClient will unwrap OperationCanceledException if an HttpRequestException containing OperationCanceledException is thrown. + Assert.IsAssignableFrom(ex); + } + + [Fact] + public async Task Post_Timeout() + { + // Arrange + using var httpHandler = new Cysharp.Net.Http.YetAnotherHttpHandler(); + var httpClient = new HttpClient(httpHandler) { Timeout = TimeSpan.FromSeconds(2) }; + await using var server = await LaunchServerAsync(TestWebAppServerListenMode.InsecureHttp1Only); + var pipe = new Pipe(); + var content = new StreamContent(pipe.Reader.AsStream()); + var cts = new CancellationTokenSource(); + + // Act + var responseTask = httpClient.PostAsync($"{server.BaseUri}/slow-upload", content); + var ex = await Record.ExceptionAsync(async () => await responseTask); + + // Assert + Assert.NotNull(ex); + // NOTE: .NET's HttpClient will unwrap OperationCanceledException if an HttpRequestException containing OperationCanceledException is thrown. Assert.IsAssignableFrom(ex); -#endif } } \ No newline at end of file diff --git a/test/YetAnotherHttpHandler.Test/Http2TestBase.cs b/test/YetAnotherHttpHandler.Test/Http2TestBase.cs index 887ef8b..e0b329b 100644 --- a/test/YetAnotherHttpHandler.Test/Http2TestBase.cs +++ b/test/YetAnotherHttpHandler.Test/Http2TestBase.cs @@ -298,12 +298,9 @@ public async Task Cancel_Post_SendingBody() var ex = await Record.ExceptionAsync(async () => await httpClient.SendAsync(request, cts.Token).WaitAsync(TimeoutToken)); // Assert -#if UNITY_2021_1_OR_NEWER - Assert.IsAssignableFrom(ex); - Assert.IsAssignableFrom(ex.InnerException); -#else - // NOTE: .NET HttpClient throws HttpRequestException with OperationCanceledException if it contains an OperationCanceledException + // NOTE: .NET's HttpClient will unwrap OperationCanceledException if an HttpRequestException containing OperationCanceledException is thrown. var operationCanceledException = Assert.IsAssignableFrom(ex); +#if !UNITY_2021_1_OR_NEWER // NOTE: Unity's Mono HttpClient internally creates a new CancellationTokenSource. Assert.Equal(cts.Token, operationCanceledException.CancellationToken); #endif @@ -580,6 +577,59 @@ public async Task Grpc_Error_Status_Unavailable_By_IOException() Assert.Equal(StatusCode.Unavailable, ((RpcException)ex).StatusCode); } + [ConditionalFact] + public async Task Grpc_Error_TimedOut_With_HttpClientTimeout() + { + // Arrange + using var httpHandler = CreateHandler(); + var httpClient = new HttpClient(httpHandler) { Timeout = TimeSpan.FromSeconds(3) }; + await using var server = await LaunchServerAsync(); + var client = new Greeter.GreeterClient(GrpcChannel.ForAddress(server.BaseUri, new GrpcChannelOptions() { HttpClient = httpClient })); + + // Act + var ex = await Record.ExceptionAsync(async () => await client.SayHelloNeverAsync(new HelloRequest() { Name = "Alice" })); + + // Assert + Assert.IsType(ex); + Assert.Equal(StatusCode.Cancelled, ((RpcException)ex).StatusCode); + Assert.IsType(((RpcException)ex).Status.DebugException); + } + + [ConditionalFact] + public async Task Grpc_Error_TimedOut_With_Deadline() + { + // Arrange + using var httpHandler = CreateHandler(); + var httpClient = new HttpClient(httpHandler); + await using var server = await LaunchServerAsync(); + var client = new Greeter.GreeterClient(GrpcChannel.ForAddress(server.BaseUri, new GrpcChannelOptions() { HttpClient = httpClient })); + + // Act + var ex = await Record.ExceptionAsync(async () => await client.SayHelloNeverAsync(new HelloRequest() { Name = "Alice" }, deadline: DateTime.UtcNow.AddSeconds(3))); + + // Assert + Assert.IsType(ex); + Assert.Equal(StatusCode.DeadlineExceeded, ((RpcException)ex).StatusCode); + } + + [ConditionalFact] + public async Task Grpc_Error_TimedOut_With_CancellationToken() + { + // Arrange + using var httpHandler = CreateHandler(); + var httpClient = new HttpClient(httpHandler); + await using var server = await LaunchServerAsync(); + var client = new Greeter.GreeterClient(GrpcChannel.ForAddress(server.BaseUri, new GrpcChannelOptions() { HttpClient = httpClient })); + + // Act + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + var ex = await Record.ExceptionAsync(async () => await client.SayHelloNeverAsync(new HelloRequest() { Name = "Alice" }, cancellationToken: cts.Token)); + + // Assert + Assert.IsType(ex); + Assert.Equal(StatusCode.Cancelled, ((RpcException)ex).StatusCode); + } + // Content with default value of true for AllowDuplex because AllowDuplex is internal. class DuplexStreamContent : HttpContent { diff --git a/test/YetAnotherHttpHandler.Test/Protos/greet.proto b/test/YetAnotherHttpHandler.Test/Protos/greet.proto index 29e2e06..e0079d6 100644 --- a/test/YetAnotherHttpHandler.Test/Protos/greet.proto +++ b/test/YetAnotherHttpHandler.Test/Protos/greet.proto @@ -8,6 +8,8 @@ package greet; service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply); + rpc SayHelloSlow (HelloRequest) returns (HelloReply); + rpc SayHelloNever (HelloRequest) returns (HelloReply); rpc SayHelloDuplex (stream HelloRequest) returns (stream HelloReply); rpc SayHelloDuplexCompleteRandomly (stream HelloRequest) returns (stream HelloReply); rpc SayHelloDuplexAbortRandomly (stream HelloRequest) returns (stream HelloReply); diff --git a/test/YetAnotherHttpHandler.Test/TestServerForHttp1AndHttp2.cs b/test/YetAnotherHttpHandler.Test/TestServerForHttp1AndHttp2.cs index 9d87829..85ee565 100644 --- a/test/YetAnotherHttpHandler.Test/TestServerForHttp1AndHttp2.cs +++ b/test/YetAnotherHttpHandler.Test/TestServerForHttp1AndHttp2.cs @@ -136,6 +136,18 @@ public override async Task SayHello(HelloRequest request, ServerCall return new HelloReply { Message = $"Hello {request.Name}" }; } + public override async Task SayHelloSlow(HelloRequest request, ServerCallContext context) + { + await Task.Delay(TimeSpan.FromSeconds(30)); + return new HelloReply { Message = $"Hello {request.Name}" }; + } + + public override async Task SayHelloNever(HelloRequest request, ServerCallContext context) + { + await Task.Delay(-1); + throw new NotImplementedException(); + } + public override async Task SayHelloDuplex(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { await foreach (var request in requestStream.ReadAllAsync(context.CancellationToken)) diff --git a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Compat/Assert.cs b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Compat/Assert.cs index fbe7aea..b410439 100644 --- a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Compat/Assert.cs +++ b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Compat/Assert.cs @@ -32,7 +32,7 @@ public static T IsAssignableFrom(object actual) public static T IsType(object actual) { - NUnit.Framework.Assert.True(actual.GetType() == typeof(T)); + NUnit.Framework.Assert.True(actual.GetType() == typeof(T), $"Expected: {typeof(T)}\nActual: {actual.GetType()}"); return (T)actual; } } diff --git a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/GreetGrpc.cs b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/GreetGrpc.cs index 6b7fb26..b211af7 100644 --- a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/GreetGrpc.cs +++ b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/GreetGrpc.cs @@ -1,340 +1,413 @@ // // Generated by the protocol buffer compiler. DO NOT EDIT! -// source: greet.proto +// source: Protos/greet.proto // -#pragma warning disable 0414, 1591, 8981, 0612 +#pragma warning disable 0414, 1591, 8981 #region Designer generated code using grpc = global::Grpc.Core; -namespace TestWebApp { - /// - /// The greeting service definition. - /// - public static partial class Greeter - { - static readonly string __ServiceName = "greet.Greeter"; - - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static void __Helper_SerializeMessage(global::Google.Protobuf.IMessage message, grpc::SerializationContext context) +namespace TestWebApp +{ + /// + /// The greeting service definition. + /// + public static partial class Greeter { - #if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION - if (message is global::Google.Protobuf.IBufferMessage) - { - context.SetPayloadLength(message.CalculateSize()); - global::Google.Protobuf.MessageExtensions.WriteTo(message, context.GetBufferWriter()); - context.Complete(); - return; - } - #endif - context.Complete(global::Google.Protobuf.MessageExtensions.ToByteArray(message)); - } + static readonly string __ServiceName = "greet.Greeter"; - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static class __Helper_MessageCache - { - public static readonly bool IsBufferMessage = global::System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(global::Google.Protobuf.IBufferMessage)).IsAssignableFrom(typeof(T)); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static void __Helper_SerializeMessage(global::Google.Protobuf.IMessage message, grpc::SerializationContext context) + { +#if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION + if (message is global::Google.Protobuf.IBufferMessage) + { + context.SetPayloadLength(message.CalculateSize()); + global::Google.Protobuf.MessageExtensions.WriteTo(message, context.GetBufferWriter()); + context.Complete(); + return; + } +#endif + context.Complete(global::Google.Protobuf.MessageExtensions.ToByteArray(message)); + } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static T __Helper_DeserializeMessage(grpc::DeserializationContext context, global::Google.Protobuf.MessageParser parser) where T : global::Google.Protobuf.IMessage - { - #if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION - if (__Helper_MessageCache.IsBufferMessage) - { - return parser.ParseFrom(context.PayloadAsReadOnlySequence()); - } - #endif - return parser.ParseFrom(context.PayloadAsNewBuffer()); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static class __Helper_MessageCache + { + public static readonly bool IsBufferMessage = global::System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(global::Google.Protobuf.IBufferMessage)).IsAssignableFrom(typeof(T)); + } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Marshaller __Marshaller_greet_HelloRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.HelloRequest.Parser)); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Marshaller __Marshaller_greet_HelloReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.HelloReply.Parser)); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Marshaller __Marshaller_greet_ResetRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.ResetRequest.Parser)); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Marshaller __Marshaller_greet_ResetReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.ResetReply.Parser)); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Marshaller __Marshaller_greet_EchoRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.EchoRequest.Parser)); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Marshaller __Marshaller_greet_EchoReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.EchoReply.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static T __Helper_DeserializeMessage(grpc::DeserializationContext context, global::Google.Protobuf.MessageParser parser) where T : global::Google.Protobuf.IMessage + { +#if !GRPC_DISABLE_PROTOBUF_BUFFER_SERIALIZATION + if (__Helper_MessageCache.IsBufferMessage) + { + return parser.ParseFrom(context.PayloadAsReadOnlySequence()); + } +#endif + return parser.ParseFrom(context.PayloadAsNewBuffer()); + } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Method __Method_SayHello = new grpc::Method( - grpc::MethodType.Unary, - __ServiceName, - "SayHello", - __Marshaller_greet_HelloRequest, - __Marshaller_greet_HelloReply); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_greet_HelloRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.HelloRequest.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_greet_HelloReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.HelloReply.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_greet_ResetRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.ResetRequest.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_greet_ResetReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.ResetReply.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_greet_EchoRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.EchoRequest.Parser)); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Marshaller __Marshaller_greet_EchoReply = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::TestWebApp.EchoReply.Parser)); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Method __Method_SayHelloDuplex = new grpc::Method( - grpc::MethodType.DuplexStreaming, - __ServiceName, - "SayHelloDuplex", - __Marshaller_greet_HelloRequest, - __Marshaller_greet_HelloReply); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_SayHello = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "SayHello", + __Marshaller_greet_HelloRequest, + __Marshaller_greet_HelloReply); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Method __Method_SayHelloDuplexCompleteRandomly = new grpc::Method( - grpc::MethodType.DuplexStreaming, - __ServiceName, - "SayHelloDuplexCompleteRandomly", - __Marshaller_greet_HelloRequest, - __Marshaller_greet_HelloReply); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_SayHelloSlow = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "SayHelloSlow", + __Marshaller_greet_HelloRequest, + __Marshaller_greet_HelloReply); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Method __Method_SayHelloDuplexAbortRandomly = new grpc::Method( - grpc::MethodType.DuplexStreaming, - __ServiceName, - "SayHelloDuplexAbortRandomly", - __Marshaller_greet_HelloRequest, - __Marshaller_greet_HelloReply); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_SayHelloNever = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "SayHelloNever", + __Marshaller_greet_HelloRequest, + __Marshaller_greet_HelloReply); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Method __Method_ResetByServer = new grpc::Method( - grpc::MethodType.Unary, - __ServiceName, - "ResetByServer", - __Marshaller_greet_ResetRequest, - __Marshaller_greet_ResetReply); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_SayHelloDuplex = new grpc::Method( + grpc::MethodType.DuplexStreaming, + __ServiceName, + "SayHelloDuplex", + __Marshaller_greet_HelloRequest, + __Marshaller_greet_HelloReply); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - static readonly grpc::Method __Method_EchoDuplex = new grpc::Method( - grpc::MethodType.DuplexStreaming, - __ServiceName, - "EchoDuplex", - __Marshaller_greet_EchoRequest, - __Marshaller_greet_EchoReply); + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_SayHelloDuplexCompleteRandomly = new grpc::Method( + grpc::MethodType.DuplexStreaming, + __ServiceName, + "SayHelloDuplexCompleteRandomly", + __Marshaller_greet_HelloRequest, + __Marshaller_greet_HelloReply); - /// Service descriptor - public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor - { - get { return global::TestWebApp.GreetReflection.Descriptor.Services[0]; } - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_SayHelloDuplexAbortRandomly = new grpc::Method( + grpc::MethodType.DuplexStreaming, + __ServiceName, + "SayHelloDuplexAbortRandomly", + __Marshaller_greet_HelloRequest, + __Marshaller_greet_HelloReply); - /// Base class for server-side implementations of Greeter - [grpc::BindServiceMethod(typeof(Greeter), "BindService")] - public abstract partial class GreeterBase - { - /// - /// Sends a greeting - /// - /// The request received from the client. - /// The context of the server-side call handler being invoked. - /// The response to send back to the client (wrapped by a task). - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::System.Threading.Tasks.Task SayHello(global::TestWebApp.HelloRequest request, grpc::ServerCallContext context) - { - throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_ResetByServer = new grpc::Method( + grpc::MethodType.Unary, + __ServiceName, + "ResetByServer", + __Marshaller_greet_ResetRequest, + __Marshaller_greet_ResetReply); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::System.Threading.Tasks.Task SayHelloDuplex(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) - { - throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + static readonly grpc::Method __Method_EchoDuplex = new grpc::Method( + grpc::MethodType.DuplexStreaming, + __ServiceName, + "EchoDuplex", + __Marshaller_greet_EchoRequest, + __Marshaller_greet_EchoReply); - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::System.Threading.Tasks.Task SayHelloDuplexCompleteRandomly(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) - { - throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); - } + /// Service descriptor + public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor + { + get { return global::TestWebApp.GreetReflection.Descriptor.Services[0]; } + } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::System.Threading.Tasks.Task SayHelloDuplexAbortRandomly(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) - { - throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); - } + /// Base class for server-side implementations of Greeter + [grpc::BindServiceMethod(typeof(Greeter), "BindService")] + public abstract partial class GreeterBase + { + /// + /// Sends a greeting + /// + /// The request received from the client. + /// The context of the server-side call handler being invoked. + /// The response to send back to the client (wrapped by a task). + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task SayHello(global::TestWebApp.HelloRequest request, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::System.Threading.Tasks.Task ResetByServer(global::TestWebApp.ResetRequest request, grpc::ServerCallContext context) - { - throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task SayHelloSlow(global::TestWebApp.HelloRequest request, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::System.Threading.Tasks.Task EchoDuplex(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) - { - throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task SayHelloNever(global::TestWebApp.HelloRequest request, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task SayHelloDuplex(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } - /// Client for Greeter - public partial class GreeterClient : grpc::ClientBase - { - /// Creates a new client for Greeter - /// The channel to use to make remote calls. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public GreeterClient(grpc::ChannelBase channel) : base(channel) - { - } - /// Creates a new client for Greeter that uses a custom CallInvoker. - /// The callInvoker to use to make remote calls. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public GreeterClient(grpc::CallInvoker callInvoker) : base(callInvoker) - { - } - /// Protected parameterless constructor to allow creation of test doubles. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - protected GreeterClient() : base() - { - } - /// Protected constructor to allow creation of configured clients. - /// The client configuration. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - protected GreeterClient(ClientBaseConfiguration configuration) : base(configuration) - { - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task SayHelloDuplexCompleteRandomly(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } - /// - /// Sends a greeting - /// - /// The request to send to the server. - /// The initial metadata to send with the call. This parameter is optional. - /// An optional deadline for the call. The call will be cancelled if deadline is hit. - /// An optional token for canceling the call. - /// The response received from the server. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::TestWebApp.HelloReply SayHello(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return SayHello(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - } - /// - /// Sends a greeting - /// - /// The request to send to the server. - /// The options for the call. - /// The response received from the server. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::TestWebApp.HelloReply SayHello(global::TestWebApp.HelloRequest request, grpc::CallOptions options) - { - return CallInvoker.BlockingUnaryCall(__Method_SayHello, null, options, request); - } - /// - /// Sends a greeting - /// - /// The request to send to the server. - /// The initial metadata to send with the call. This parameter is optional. - /// An optional deadline for the call. The call will be cancelled if deadline is hit. - /// An optional token for canceling the call. - /// The call object. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncUnaryCall SayHelloAsync(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return SayHelloAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - } - /// - /// Sends a greeting - /// - /// The request to send to the server. - /// The options for the call. - /// The call object. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncUnaryCall SayHelloAsync(global::TestWebApp.HelloRequest request, grpc::CallOptions options) - { - return CallInvoker.AsyncUnaryCall(__Method_SayHello, null, options, request); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplex(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return SayHelloDuplex(new grpc::CallOptions(headers, deadline, cancellationToken)); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplex(grpc::CallOptions options) - { - return CallInvoker.AsyncDuplexStreamingCall(__Method_SayHelloDuplex, null, options); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexCompleteRandomly(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return SayHelloDuplexCompleteRandomly(new grpc::CallOptions(headers, deadline, cancellationToken)); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexCompleteRandomly(grpc::CallOptions options) - { - return CallInvoker.AsyncDuplexStreamingCall(__Method_SayHelloDuplexCompleteRandomly, null, options); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexAbortRandomly(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return SayHelloDuplexAbortRandomly(new grpc::CallOptions(headers, deadline, cancellationToken)); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexAbortRandomly(grpc::CallOptions options) - { - return CallInvoker.AsyncDuplexStreamingCall(__Method_SayHelloDuplexAbortRandomly, null, options); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::TestWebApp.ResetReply ResetByServer(global::TestWebApp.ResetRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return ResetByServer(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual global::TestWebApp.ResetReply ResetByServer(global::TestWebApp.ResetRequest request, grpc::CallOptions options) - { - return CallInvoker.BlockingUnaryCall(__Method_ResetByServer, null, options, request); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncUnaryCall ResetByServerAsync(global::TestWebApp.ResetRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return ResetByServerAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncUnaryCall ResetByServerAsync(global::TestWebApp.ResetRequest request, grpc::CallOptions options) - { - return CallInvoker.AsyncUnaryCall(__Method_ResetByServer, null, options, request); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall EchoDuplex(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - { - return EchoDuplex(new grpc::CallOptions(headers, deadline, cancellationToken)); - } - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public virtual grpc::AsyncDuplexStreamingCall EchoDuplex(grpc::CallOptions options) - { - return CallInvoker.AsyncDuplexStreamingCall(__Method_EchoDuplex, null, options); - } - /// Creates a new instance of client from given ClientBaseConfiguration. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - protected override GreeterClient NewInstance(ClientBaseConfiguration configuration) - { - return new GreeterClient(configuration); - } - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task SayHelloDuplexAbortRandomly(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } - /// Creates service definition that can be registered with a server - /// An object implementing the server-side handling logic. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public static grpc::ServerServiceDefinition BindService(GreeterBase serviceImpl) - { - return grpc::ServerServiceDefinition.CreateBuilder() - .AddMethod(__Method_SayHello, serviceImpl.SayHello) - .AddMethod(__Method_SayHelloDuplex, serviceImpl.SayHelloDuplex) - .AddMethod(__Method_SayHelloDuplexCompleteRandomly, serviceImpl.SayHelloDuplexCompleteRandomly) - .AddMethod(__Method_SayHelloDuplexAbortRandomly, serviceImpl.SayHelloDuplexAbortRandomly) - .AddMethod(__Method_ResetByServer, serviceImpl.ResetByServer) - .AddMethod(__Method_EchoDuplex, serviceImpl.EchoDuplex).Build(); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task ResetByServer(global::TestWebApp.ResetRequest request, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } - /// Register service method with a service binder with or without implementation. Useful when customizing the service binding logic. - /// Note: this method is part of an experimental API that can change or be removed without any prior notice. - /// Service methods will be bound by calling AddMethod on this object. - /// An object implementing the server-side handling logic. - [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] - public static void BindService(grpc::ServiceBinderBase serviceBinder, GreeterBase serviceImpl) - { - serviceBinder.AddMethod(__Method_SayHello, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.SayHello)); - serviceBinder.AddMethod(__Method_SayHelloDuplex, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.SayHelloDuplex)); - serviceBinder.AddMethod(__Method_SayHelloDuplexCompleteRandomly, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.SayHelloDuplexCompleteRandomly)); - serviceBinder.AddMethod(__Method_SayHelloDuplexAbortRandomly, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.SayHelloDuplexAbortRandomly)); - serviceBinder.AddMethod(__Method_ResetByServer, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.ResetByServer)); - serviceBinder.AddMethod(__Method_EchoDuplex, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.EchoDuplex)); - } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::System.Threading.Tasks.Task EchoDuplex(grpc::IAsyncStreamReader requestStream, grpc::IServerStreamWriter responseStream, grpc::ServerCallContext context) + { + throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); + } + + } - } + /// Client for Greeter + public partial class GreeterClient : grpc::ClientBase + { + /// Creates a new client for Greeter + /// The channel to use to make remote calls. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public GreeterClient(grpc::ChannelBase channel) : base(channel) + { + } + /// Creates a new client for Greeter that uses a custom CallInvoker. + /// The callInvoker to use to make remote calls. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public GreeterClient(grpc::CallInvoker callInvoker) : base(callInvoker) + { + } + /// Protected parameterless constructor to allow creation of test doubles. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + protected GreeterClient() : base() + { + } + /// Protected constructor to allow creation of configured clients. + /// The client configuration. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + protected GreeterClient(ClientBaseConfiguration configuration) : base(configuration) + { + } + + /// + /// Sends a greeting + /// + /// The request to send to the server. + /// The initial metadata to send with the call. This parameter is optional. + /// An optional deadline for the call. The call will be cancelled if deadline is hit. + /// An optional token for canceling the call. + /// The response received from the server. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.HelloReply SayHello(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHello(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + /// + /// Sends a greeting + /// + /// The request to send to the server. + /// The options for the call. + /// The response received from the server. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.HelloReply SayHello(global::TestWebApp.HelloRequest request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__Method_SayHello, null, options, request); + } + /// + /// Sends a greeting + /// + /// The request to send to the server. + /// The initial metadata to send with the call. This parameter is optional. + /// An optional deadline for the call. The call will be cancelled if deadline is hit. + /// An optional token for canceling the call. + /// The call object. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall SayHelloAsync(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + /// + /// Sends a greeting + /// + /// The request to send to the server. + /// The options for the call. + /// The call object. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall SayHelloAsync(global::TestWebApp.HelloRequest request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__Method_SayHello, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.HelloReply SayHelloSlow(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloSlow(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.HelloReply SayHelloSlow(global::TestWebApp.HelloRequest request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__Method_SayHelloSlow, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall SayHelloSlowAsync(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloSlowAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall SayHelloSlowAsync(global::TestWebApp.HelloRequest request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__Method_SayHelloSlow, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.HelloReply SayHelloNever(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloNever(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.HelloReply SayHelloNever(global::TestWebApp.HelloRequest request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__Method_SayHelloNever, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall SayHelloNeverAsync(global::TestWebApp.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloNeverAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall SayHelloNeverAsync(global::TestWebApp.HelloRequest request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__Method_SayHelloNever, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplex(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloDuplex(new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplex(grpc::CallOptions options) + { + return CallInvoker.AsyncDuplexStreamingCall(__Method_SayHelloDuplex, null, options); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexCompleteRandomly(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloDuplexCompleteRandomly(new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexCompleteRandomly(grpc::CallOptions options) + { + return CallInvoker.AsyncDuplexStreamingCall(__Method_SayHelloDuplexCompleteRandomly, null, options); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexAbortRandomly(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return SayHelloDuplexAbortRandomly(new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall SayHelloDuplexAbortRandomly(grpc::CallOptions options) + { + return CallInvoker.AsyncDuplexStreamingCall(__Method_SayHelloDuplexAbortRandomly, null, options); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.ResetReply ResetByServer(global::TestWebApp.ResetRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return ResetByServer(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual global::TestWebApp.ResetReply ResetByServer(global::TestWebApp.ResetRequest request, grpc::CallOptions options) + { + return CallInvoker.BlockingUnaryCall(__Method_ResetByServer, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall ResetByServerAsync(global::TestWebApp.ResetRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return ResetByServerAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncUnaryCall ResetByServerAsync(global::TestWebApp.ResetRequest request, grpc::CallOptions options) + { + return CallInvoker.AsyncUnaryCall(__Method_ResetByServer, null, options, request); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall EchoDuplex(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + return EchoDuplex(new grpc::CallOptions(headers, deadline, cancellationToken)); + } + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public virtual grpc::AsyncDuplexStreamingCall EchoDuplex(grpc::CallOptions options) + { + return CallInvoker.AsyncDuplexStreamingCall(__Method_EchoDuplex, null, options); + } + /// Creates a new instance of client from given ClientBaseConfiguration. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + protected override GreeterClient NewInstance(ClientBaseConfiguration configuration) + { + return new GreeterClient(configuration); + } + } + + /// Creates service definition that can be registered with a server + /// An object implementing the server-side handling logic. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public static grpc::ServerServiceDefinition BindService(GreeterBase serviceImpl) + { + return grpc::ServerServiceDefinition.CreateBuilder() + .AddMethod(__Method_SayHello, serviceImpl.SayHello) + .AddMethod(__Method_SayHelloSlow, serviceImpl.SayHelloSlow) + .AddMethod(__Method_SayHelloNever, serviceImpl.SayHelloNever) + .AddMethod(__Method_SayHelloDuplex, serviceImpl.SayHelloDuplex) + .AddMethod(__Method_SayHelloDuplexCompleteRandomly, serviceImpl.SayHelloDuplexCompleteRandomly) + .AddMethod(__Method_SayHelloDuplexAbortRandomly, serviceImpl.SayHelloDuplexAbortRandomly) + .AddMethod(__Method_ResetByServer, serviceImpl.ResetByServer) + .AddMethod(__Method_EchoDuplex, serviceImpl.EchoDuplex).Build(); + } + + /// Register service method with a service binder with or without implementation. Useful when customizing the service binding logic. + /// Note: this method is part of an experimental API that can change or be removed without any prior notice. + /// Service methods will be bound by calling AddMethod on this object. + /// An object implementing the server-side handling logic. + [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)] + public static void BindService(grpc::ServiceBinderBase serviceBinder, GreeterBase serviceImpl) + { + serviceBinder.AddMethod(__Method_SayHello, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.SayHello)); + serviceBinder.AddMethod(__Method_SayHelloSlow, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.SayHelloSlow)); + serviceBinder.AddMethod(__Method_SayHelloNever, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.SayHelloNever)); + serviceBinder.AddMethod(__Method_SayHelloDuplex, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.SayHelloDuplex)); + serviceBinder.AddMethod(__Method_SayHelloDuplexCompleteRandomly, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.SayHelloDuplexCompleteRandomly)); + serviceBinder.AddMethod(__Method_SayHelloDuplexAbortRandomly, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.SayHelloDuplexAbortRandomly)); + serviceBinder.AddMethod(__Method_ResetByServer, serviceImpl == null ? null : new grpc::UnaryServerMethod(serviceImpl.ResetByServer)); + serviceBinder.AddMethod(__Method_EchoDuplex, serviceImpl == null ? null : new grpc::DuplexStreamingServerMethod(serviceImpl.EchoDuplex)); + } + + } } #endregion diff --git a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http1Test.cs b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http1Test.cs index 9934b0c..f591d8c 100644 --- a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http1Test.cs +++ b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http1Test.cs @@ -5,7 +5,6 @@ using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; - using Fact = NUnit.Framework.TestAttribute; public class Http1Test : YahaUnityTestBase @@ -115,12 +114,28 @@ public async Task Post_Cancel() // Assert Assert.NotNull(ex); -#if UNITY_2021_1_OR_NEWER - Assert.IsAssignableFrom(ex); - Assert.IsAssignableFrom(ex.InnerException); -#else - // NOTE: .NET HttpClient throws HttpRequestException with OperationCanceledException if it contains an OperationCanceledException + // NOTE: .NET's HttpClient will unwrap OperationCanceledException if an HttpRequestException containing OperationCanceledException is thrown. + Assert.IsAssignableFrom(ex); + } + + [Fact] + public async Task Post_Timeout() + { + // Arrange + using var httpHandler = new Cysharp.Net.Http.YetAnotherHttpHandler(); + var httpClient = new HttpClient(httpHandler) { Timeout = TimeSpan.FromSeconds(2) }; + await using var server = await LaunchServerAsync(TestWebAppServerListenMode.InsecureHttp1Only); + var pipe = new Pipe(); + var content = new StreamContent(pipe.Reader.AsStream()); + var cts = new CancellationTokenSource(); + + // Act + var responseTask = httpClient.PostAsync($"{server.BaseUri}/slow-upload", content); + var ex = await Record.ExceptionAsync(async () => await responseTask); + + // Assert + Assert.NotNull(ex); + // NOTE: .NET's HttpClient will unwrap OperationCanceledException if an HttpRequestException containing OperationCanceledException is thrown. Assert.IsAssignableFrom(ex); -#endif } } \ No newline at end of file diff --git a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http2ClearTextTest.cs b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http2ClearTextTest.cs index 4b37125..fb624a8 100644 --- a/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http2ClearTextTest.cs +++ b/test/YetAnotherHttpHandler.Unity.Test/Assets/Tests/Http2ClearTextTest.cs @@ -30,7 +30,7 @@ public async Task Get_Ok() { Version = HttpVersion.Version20, }; - var response = await httpClient.SendAsync(request);//.WaitAsync(TimeoutToken); + var response = await httpClient.SendAsync(request).WaitAsync(TimeoutToken); var responseBody = await response.Content.ReadAsStringAsync().WaitAsync(TimeoutToken); // Assert @@ -114,6 +114,33 @@ public async Task Post_NotDuplex_Receive_ResponseHeaders_Before_ResponseBody() Assert.False(responseBodyTask.IsCompleted); } + // NOTE: SocketHttpHandler waits for the completion of sending the request body before the response headers. + // https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs#L1980-L1988 + // https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs#L343-L349 + //[ConditionalFact] + //public async Task Post_NotDuplex_DoNot_Receive_ResponseHeaders_Before_RequestBodyCompleted() + //{ + // // Arrange + // using var httpHandler = CreateHandler(); + // var httpClient = new HttpClient(httpHandler); + // await using var server = await LaunchAsync(); + // + // // Act + // var pipe = new Pipe(); + // var content = new StreamContent(pipe.Reader.AsStream()); + // content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); + // var request = new HttpRequestMessage(HttpMethod.Post, $"{server.BaseUri}/post-response-headers-immediately") + // { + // Version = HttpVersion.Version20, + // Content = content, + // }; + // var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(500)); + // var ex = await Assert.ThrowsAsync(async () => await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).WaitAsync(timeout.Token)); + // + // // Assert + // Assert.Equal(timeout.Token, ex.CancellationToken); + //} + [ConditionalFact] public async Task Post_NotDuplex_Body_StreamingBody() { @@ -267,12 +294,9 @@ public async Task Cancel_Post_SendingBody() var ex = await Record.ExceptionAsync(async () => await httpClient.SendAsync(request, cts.Token).WaitAsync(TimeoutToken)); // Assert -#if UNITY_2021_1_OR_NEWER - Assert.IsAssignableFrom(ex); - Assert.IsAssignableFrom(ex.InnerException); -#else - // NOTE: .NET HttpClient throws HttpRequestException with OperationCanceledException if it contains an OperationCanceledException + // NOTE: .NET's HttpClient will unwrap OperationCanceledException if an HttpRequestException containing OperationCanceledException is thrown. var operationCanceledException = Assert.IsAssignableFrom(ex); +#if !UNITY_2021_1_OR_NEWER // NOTE: Unity's Mono HttpClient internally creates a new CancellationTokenSource. Assert.Equal(cts.Token, operationCanceledException.CancellationToken); #endif @@ -303,7 +327,7 @@ public async Task Cancel_Post_SendingBody_Duplex() // Assert var operationCanceledException = Assert.IsAssignableFrom(ex); Assert.Equal(cts.Token, operationCanceledException.CancellationToken); - } +} #endif [ConditionalFact] @@ -331,6 +355,7 @@ public async Task DisposeHttpResponseMessage_Post_SendingBody_Duplex() //TestOutputHelper.WriteLine(ex?.ToString()); // Assert + Assert.NotNull(ex); Assert.IsAssignableFrom(ex); Assert.IsAssignableFrom(ex.InnerException); } @@ -548,6 +573,63 @@ public async Task Grpc_Error_Status_Unavailable_By_IOException() Assert.Equal(StatusCode.Unavailable, ((RpcException)ex).StatusCode); } + [ConditionalFact] + public async Task Grpc_Error_TimedOut_With_HttpClientTimeout() + { + // Arrange + using var httpHandler = CreateHandler(); + var httpClient = new HttpClient(httpHandler) { Timeout = TimeSpan.FromSeconds(3) }; + await using var server = await LaunchServerAsync(); + var client = new Greeter.GreeterClient(GrpcChannel.ForAddress(server.BaseUri, new GrpcChannelOptions() { HttpClient = httpClient })); + + // Act + var ex = await Record.ExceptionAsync(async () => await client.SayHelloNeverAsync(new HelloRequest() { Name = "Alice" })); + + // Assert + Assert.IsType(ex); + Assert.Equal(StatusCode.Cancelled, ((RpcException)ex).StatusCode); +#if UNITY_2021_1_OR_NEWER + Assert.IsType(((RpcException)ex).Status.DebugException); +#else + Assert.IsType(((RpcException)ex).Status.DebugException); +#endif + } + + [ConditionalFact] + public async Task Grpc_Error_TimedOut_With_Deadline() + { + // Arrange + using var httpHandler = CreateHandler(); + var httpClient = new HttpClient(httpHandler); + await using var server = await LaunchServerAsync(); + var client = new Greeter.GreeterClient(GrpcChannel.ForAddress(server.BaseUri, new GrpcChannelOptions() { HttpClient = httpClient })); + + // Act + var ex = await Record.ExceptionAsync(async () => await client.SayHelloNeverAsync(new HelloRequest() { Name = "Alice" }, deadline: DateTime.UtcNow.AddSeconds(3))); + + // Assert + Assert.IsType(ex); + Assert.Equal(StatusCode.DeadlineExceeded, ((RpcException)ex).StatusCode); + } + + [ConditionalFact] + public async Task Grpc_Error_TimedOut_With_CancellationToken() + { + // Arrange + using var httpHandler = CreateHandler(); + var httpClient = new HttpClient(httpHandler); + await using var server = await LaunchServerAsync(); + var client = new Greeter.GreeterClient(GrpcChannel.ForAddress(server.BaseUri, new GrpcChannelOptions() { HttpClient = httpClient })); + + // Act + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + var ex = await Record.ExceptionAsync(async () => await client.SayHelloNeverAsync(new HelloRequest() { Name = "Alice" }, cancellationToken: cts.Token)); + + // Assert + Assert.IsType(ex); + Assert.Equal(StatusCode.Cancelled, ((RpcException)ex).StatusCode); + } + // Content with default value of true for AllowDuplex because AllowDuplex is internal. class DuplexStreamContent : HttpContent {