Skip to content

Commit 05146ac

Browse files
committed
Merge pull request grpc#6416 from jtattermusch/csharp_api_polishing
Add C# wrapping tests and fix some issues with the API.
2 parents b75f557 + f21f465 commit 05146ac

13 files changed

+825
-188
lines changed

src/csharp/Grpc.Core.Tests/ClientServerTest.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,37 @@ public async Task ServerStreamingCall()
166166
Assert.IsNotNull("xyz", call.GetTrailers()[0].Key);
167167
}
168168

169+
[Test]
170+
public async Task ServerStreamingCall_EndOfStreamIsIdempotent()
171+
{
172+
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
173+
{
174+
});
175+
176+
var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
177+
178+
Assert.IsFalse(await call.ResponseStream.MoveNext());
179+
Assert.IsFalse(await call.ResponseStream.MoveNext());
180+
}
181+
182+
[Test]
183+
public async Task ServerStreamingCall_ErrorCanBeAwaitedTwice()
184+
{
185+
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
186+
{
187+
context.Status = new Status(StatusCode.InvalidArgument, "");
188+
});
189+
190+
var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
191+
192+
var ex = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseStream.MoveNext());
193+
Assert.AreEqual(StatusCode.InvalidArgument, ex.Status.StatusCode);
194+
195+
// attempting MoveNext again should result in throwing the same exception.
196+
var ex2 = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseStream.MoveNext());
197+
Assert.AreEqual(StatusCode.InvalidArgument, ex2.Status.StatusCode);
198+
}
199+
169200
[Test]
170201
public async Task DuplexStreamingCall()
171202
{
@@ -208,6 +239,38 @@ public async Task ClientStreamingCall_CancelAfterBegin()
208239
Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
209240
}
210241

242+
[Test]
243+
public async Task ClientStreamingCall_ServerSideReadAfterCancelNotificationReturnsNull()
244+
{
245+
var handlerStartedBarrier = new TaskCompletionSource<object>();
246+
var cancelNotificationReceivedBarrier = new TaskCompletionSource<object>();
247+
var successTcs = new TaskCompletionSource<string>();
248+
249+
helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
250+
{
251+
handlerStartedBarrier.SetResult(null);
252+
253+
// wait for cancellation to be delivered.
254+
context.CancellationToken.Register(() => cancelNotificationReceivedBarrier.SetResult(null));
255+
await cancelNotificationReceivedBarrier.Task;
256+
257+
var moveNextResult = await requestStream.MoveNext();
258+
successTcs.SetResult(!moveNextResult ? "SUCCESS" : "FAIL");
259+
return "";
260+
});
261+
262+
var cts = new CancellationTokenSource();
263+
var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
264+
265+
await handlerStartedBarrier.Task;
266+
cts.Cancel();
267+
268+
var ex = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseAsync);
269+
Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
270+
271+
Assert.AreEqual("SUCCESS", await successTcs.Task);
272+
}
273+
211274
[Test]
212275
public async Task AsyncUnaryCall_EchoMetadata()
213276
{

src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
<Compile Include="SanityTest.cs" />
8585
<Compile Include="HalfcloseTest.cs" />
8686
<Compile Include="NUnitMain.cs" />
87+
<Compile Include="Internal\FakeNativeCall.cs" />
88+
<Compile Include="Internal\AsyncCallServerTest.cs" />
8789
</ItemGroup>
8890
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
8991
<ItemGroup>
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2015, Google Inc.
4+
// All rights reserved.
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are
8+
// met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above
13+
// copyright notice, this list of conditions and the following disclaimer
14+
// in the documentation and/or other materials provided with the
15+
// distribution.
16+
// * Neither the name of Google Inc. nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
#endregion
33+
34+
using System;
35+
using System.Collections.Generic;
36+
using System.Runtime.InteropServices;
37+
using System.Threading.Tasks;
38+
39+
using Grpc.Core.Internal;
40+
using NUnit.Framework;
41+
42+
namespace Grpc.Core.Internal.Tests
43+
{
44+
/// <summary>
45+
/// Uses fake native call to test interaction of <c>AsyncCallServer</c> wrapping code with C core in different situations.
46+
/// </summary>
47+
public class AsyncCallServerTest
48+
{
49+
Server server;
50+
FakeNativeCall fakeCall;
51+
AsyncCallServer<string, string> asyncCallServer;
52+
53+
[SetUp]
54+
public void Init()
55+
{
56+
var environment = GrpcEnvironment.AddRef();
57+
58+
// Create a fake server just so we have an instance to refer to.
59+
// The server won't actually be used at all.
60+
server = new Server()
61+
{
62+
Ports = { { "localhost", 0, ServerCredentials.Insecure } }
63+
};
64+
server.Start();
65+
66+
fakeCall = new FakeNativeCall();
67+
asyncCallServer = new AsyncCallServer<string, string>(
68+
Marshallers.StringMarshaller.Serializer, Marshallers.StringMarshaller.Deserializer,
69+
environment,
70+
server);
71+
asyncCallServer.InitializeForTesting(fakeCall);
72+
}
73+
74+
[TearDown]
75+
public void Cleanup()
76+
{
77+
server.ShutdownAsync().Wait();
78+
GrpcEnvironment.Release();
79+
}
80+
81+
[Test]
82+
public void CancelNotificationAfterStartDisposes()
83+
{
84+
var finishedTask = asyncCallServer.ServerSideCallAsync();
85+
var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
86+
87+
// Finishing requestStream is needed for dispose to happen.
88+
var moveNextTask = requestStream.MoveNext();
89+
fakeCall.ReceivedMessageHandler(true, null);
90+
Assert.IsFalse(moveNextTask.Result);
91+
92+
fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
93+
AssertFinished(asyncCallServer, fakeCall, finishedTask);
94+
}
95+
96+
[Test]
97+
public void ReadAfterCancelNotificationCanSucceed()
98+
{
99+
var finishedTask = asyncCallServer.ServerSideCallAsync();
100+
var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
101+
102+
fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
103+
104+
// Check that startin a read after cancel notification has been processed is legal.
105+
var moveNextTask = requestStream.MoveNext();
106+
fakeCall.ReceivedMessageHandler(true, null);
107+
Assert.IsFalse(moveNextTask.Result);
108+
109+
AssertFinished(asyncCallServer, fakeCall, finishedTask);
110+
}
111+
112+
[Test]
113+
public void ReadCompletionFailureClosesRequestStream()
114+
{
115+
var finishedTask = asyncCallServer.ServerSideCallAsync();
116+
var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
117+
118+
// if a read completion's success==false, the request stream will silently finish
119+
// and we rely on C core cancelling the call.
120+
var moveNextTask = requestStream.MoveNext();
121+
fakeCall.ReceivedMessageHandler(false, null);
122+
Assert.IsFalse(moveNextTask.Result);
123+
124+
fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
125+
AssertFinished(asyncCallServer, fakeCall, finishedTask);
126+
}
127+
128+
[Test]
129+
public void WriteAfterCancelNotificationFails()
130+
{
131+
var finishedTask = asyncCallServer.ServerSideCallAsync();
132+
var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
133+
var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
134+
135+
fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
136+
137+
// TODO(jtattermusch): should we throw a different exception type instead?
138+
Assert.Throws(typeof(InvalidOperationException), () => responseStream.WriteAsync("request1"));
139+
140+
// Finishing requestStream is needed for dispose to happen.
141+
var moveNextTask = requestStream.MoveNext();
142+
fakeCall.ReceivedMessageHandler(true, null);
143+
Assert.IsFalse(moveNextTask.Result);
144+
145+
AssertFinished(asyncCallServer, fakeCall, finishedTask);
146+
}
147+
148+
[Test]
149+
public void WriteCompletionFailureThrows()
150+
{
151+
var finishedTask = asyncCallServer.ServerSideCallAsync();
152+
var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
153+
var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
154+
155+
var writeTask = responseStream.WriteAsync("request1");
156+
fakeCall.SendCompletionHandler(false);
157+
// TODO(jtattermusch): should we throw a different exception type instead?
158+
Assert.ThrowsAsync(typeof(InvalidOperationException), async () => await writeTask);
159+
160+
// Finishing requestStream is needed for dispose to happen.
161+
var moveNextTask = requestStream.MoveNext();
162+
fakeCall.ReceivedMessageHandler(true, null);
163+
Assert.IsFalse(moveNextTask.Result);
164+
165+
fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
166+
167+
AssertFinished(asyncCallServer, fakeCall, finishedTask);
168+
}
169+
170+
[Test]
171+
public void WriteAndWriteStatusCanRunConcurrently()
172+
{
173+
var finishedTask = asyncCallServer.ServerSideCallAsync();
174+
var requestStream = new ServerRequestStream<string, string>(asyncCallServer);
175+
var responseStream = new ServerResponseStream<string, string>(asyncCallServer);
176+
177+
var writeTask = responseStream.WriteAsync("request1");
178+
var writeStatusTask = asyncCallServer.SendStatusFromServerAsync(Status.DefaultSuccess, new Metadata());
179+
180+
fakeCall.SendCompletionHandler(true);
181+
fakeCall.SendStatusFromServerHandler(true);
182+
183+
Assert.DoesNotThrowAsync(async () => await writeTask);
184+
Assert.DoesNotThrowAsync(async () => await writeStatusTask);
185+
186+
// Finishing requestStream is needed for dispose to happen.
187+
var moveNextTask = requestStream.MoveNext();
188+
fakeCall.ReceivedMessageHandler(true, null);
189+
Assert.IsFalse(moveNextTask.Result);
190+
191+
fakeCall.ReceivedCloseOnServerHandler(true, cancelled: true);
192+
193+
AssertFinished(asyncCallServer, fakeCall, finishedTask);
194+
}
195+
196+
static void AssertFinished(AsyncCallServer<string, string> asyncCallServer, FakeNativeCall fakeCall, Task finishedTask)
197+
{
198+
Assert.IsTrue(fakeCall.IsDisposed);
199+
Assert.IsTrue(finishedTask.IsCompleted);
200+
Assert.DoesNotThrow(() => finishedTask.Wait());
201+
}
202+
}
203+
}

0 commit comments

Comments
 (0)