330 lines
12 KiB
C#
330 lines
12 KiB
C#
#region Copyright notice and license
|
|
|
|
// Copyright 2015, Google Inc.
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Grpc.Core;
|
|
using Grpc.Core.Internal;
|
|
using Grpc.Core.Utils;
|
|
using NUnit.Framework;
|
|
|
|
namespace Grpc.Core.Tests
|
|
{
|
|
public class ClientServerTest
|
|
{
|
|
const string Host = "localhost";
|
|
const string ServiceName = "/tests.Test";
|
|
|
|
static readonly Method<string, string> EchoMethod = new Method<string, string>(
|
|
MethodType.Unary,
|
|
"/tests.Test/Echo",
|
|
Marshallers.StringMarshaller,
|
|
Marshallers.StringMarshaller);
|
|
|
|
static readonly Method<string, string> ConcatAndEchoMethod = new Method<string, string>(
|
|
MethodType.ClientStreaming,
|
|
"/tests.Test/ConcatAndEcho",
|
|
Marshallers.StringMarshaller,
|
|
Marshallers.StringMarshaller);
|
|
|
|
static readonly Method<string, string> NonexistentMethod = new Method<string, string>(
|
|
MethodType.Unary,
|
|
"/tests.Test/NonexistentMethod",
|
|
Marshallers.StringMarshaller,
|
|
Marshallers.StringMarshaller);
|
|
|
|
static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
|
|
.AddMethod(EchoMethod, EchoHandler)
|
|
.AddMethod(ConcatAndEchoMethod, ConcatAndEchoHandler)
|
|
.Build();
|
|
|
|
Server server;
|
|
Channel channel;
|
|
|
|
[SetUp]
|
|
public void Init()
|
|
{
|
|
server = new Server();
|
|
server.AddServiceDefinition(ServiceDefinition);
|
|
int port = server.AddListeningPort(Host, Server.PickUnusedPort);
|
|
server.Start();
|
|
channel = new Channel(Host, port);
|
|
}
|
|
|
|
[TearDown]
|
|
public void Cleanup()
|
|
{
|
|
channel.Dispose();
|
|
server.ShutdownAsync().Wait();
|
|
}
|
|
|
|
[TestFixtureTearDown]
|
|
public void CleanupClass()
|
|
{
|
|
GrpcEnvironment.Shutdown();
|
|
}
|
|
|
|
[Test]
|
|
public void UnaryCall()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
Assert.AreEqual("ABC", Calls.BlockingUnaryCall(internalCall, "ABC", CancellationToken.None));
|
|
}
|
|
|
|
[Test]
|
|
public void UnaryCall_ServerHandlerThrows()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
try
|
|
{
|
|
Calls.BlockingUnaryCall(internalCall, "THROW", CancellationToken.None);
|
|
Assert.Fail();
|
|
}
|
|
catch (RpcException e)
|
|
{
|
|
Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void UnaryCall_ServerHandlerThrowsRpcException()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
try
|
|
{
|
|
Calls.BlockingUnaryCall(internalCall, "THROW_UNAUTHENTICATED", CancellationToken.None);
|
|
Assert.Fail();
|
|
}
|
|
catch (RpcException e)
|
|
{
|
|
Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void UnaryCall_ServerHandlerSetsStatus()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
try
|
|
{
|
|
Calls.BlockingUnaryCall(internalCall, "SET_UNAUTHENTICATED", CancellationToken.None);
|
|
Assert.Fail();
|
|
}
|
|
catch (RpcException e)
|
|
{
|
|
Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void AsyncUnaryCall()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
var result = Calls.AsyncUnaryCall(internalCall, "ABC", CancellationToken.None).ResponseAsync.Result;
|
|
Assert.AreEqual("ABC", result);
|
|
}
|
|
|
|
[Test]
|
|
public void AsyncUnaryCall_ServerHandlerThrows()
|
|
{
|
|
Task.Run(async () =>
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
try
|
|
{
|
|
await Calls.AsyncUnaryCall(internalCall, "THROW", CancellationToken.None);
|
|
Assert.Fail();
|
|
}
|
|
catch (RpcException e)
|
|
{
|
|
Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
|
|
}
|
|
}).Wait();
|
|
}
|
|
|
|
[Test]
|
|
public void ClientStreamingCall()
|
|
{
|
|
Task.Run(async () =>
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, ConcatAndEchoMethod, channel, Metadata.Empty);
|
|
var call = Calls.AsyncClientStreamingCall(internalCall, CancellationToken.None);
|
|
|
|
await call.RequestStream.WriteAll(new string[] { "A", "B", "C" });
|
|
Assert.AreEqual("ABC", await call.ResponseAsync);
|
|
}).Wait();
|
|
}
|
|
|
|
[Test]
|
|
public void ClientStreamingCall_CancelAfterBegin()
|
|
{
|
|
Task.Run(async () =>
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, ConcatAndEchoMethod, channel, Metadata.Empty);
|
|
|
|
var cts = new CancellationTokenSource();
|
|
var call = Calls.AsyncClientStreamingCall(internalCall, cts.Token);
|
|
|
|
// TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it.
|
|
await Task.Delay(1000);
|
|
cts.Cancel();
|
|
|
|
try
|
|
{
|
|
await call.ResponseAsync;
|
|
}
|
|
catch (RpcException e)
|
|
{
|
|
Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
|
|
}
|
|
}).Wait();
|
|
}
|
|
|
|
[Test]
|
|
public void AsyncUnaryCall_EchoMetadata()
|
|
{
|
|
var headers = new Metadata
|
|
{
|
|
new Metadata.Entry("asciiHeader", "abcdefg"),
|
|
new Metadata.Entry("binaryHeader-bin", new byte[] { 1, 2, 3, 0, 0xff }),
|
|
};
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, headers);
|
|
var call = Calls.AsyncUnaryCall(internalCall, "ABC", CancellationToken.None);
|
|
|
|
Assert.AreEqual("ABC", call.ResponseAsync.Result);
|
|
|
|
Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
|
|
|
|
var trailers = call.GetTrailers();
|
|
Assert.AreEqual(2, trailers.Count);
|
|
Assert.AreEqual(headers[0].Key, trailers[0].Key);
|
|
Assert.AreEqual(headers[0].Value, trailers[0].Value);
|
|
|
|
Assert.AreEqual(headers[1].Key, trailers[1].Key);
|
|
CollectionAssert.AreEqual(headers[1].ValueBytes, trailers[1].ValueBytes);
|
|
}
|
|
|
|
[Test]
|
|
public void UnaryCall_DisposedChannel()
|
|
{
|
|
channel.Dispose();
|
|
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(internalCall, "ABC", CancellationToken.None));
|
|
}
|
|
|
|
[Test]
|
|
public void UnaryCallPerformance()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
BenchmarkUtil.RunBenchmark(100, 100,
|
|
() => { Calls.BlockingUnaryCall(internalCall, "ABC", default(CancellationToken)); });
|
|
}
|
|
|
|
[Test]
|
|
public void UnknownMethodHandler()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, NonexistentMethod, channel, Metadata.Empty);
|
|
try
|
|
{
|
|
Calls.BlockingUnaryCall(internalCall, "ABC", default(CancellationToken));
|
|
Assert.Fail();
|
|
}
|
|
catch (RpcException e)
|
|
{
|
|
Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void UserAgentStringPresent()
|
|
{
|
|
var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty);
|
|
string userAgent = Calls.BlockingUnaryCall(internalCall, "RETURN-USER-AGENT", CancellationToken.None);
|
|
Assert.IsTrue(userAgent.StartsWith("grpc-csharp/"));
|
|
}
|
|
|
|
private static async Task<string> EchoHandler(string request, ServerCallContext context)
|
|
{
|
|
foreach (Metadata.Entry metadataEntry in context.RequestHeaders)
|
|
{
|
|
if (metadataEntry.Key != "user-agent")
|
|
{
|
|
context.ResponseTrailers.Add(metadataEntry);
|
|
}
|
|
}
|
|
|
|
if (request == "RETURN-USER-AGENT")
|
|
{
|
|
return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
|
|
}
|
|
|
|
if (request == "THROW")
|
|
{
|
|
throw new Exception("This was thrown on purpose by a test");
|
|
}
|
|
|
|
if (request == "THROW_UNAUTHENTICATED")
|
|
{
|
|
throw new RpcException(new Status(StatusCode.Unauthenticated, ""));
|
|
}
|
|
|
|
if (request == "SET_UNAUTHENTICATED")
|
|
{
|
|
context.Status = new Status(StatusCode.Unauthenticated, "");
|
|
}
|
|
|
|
return request;
|
|
}
|
|
|
|
private static async Task<string> ConcatAndEchoHandler(IAsyncStreamReader<string> requestStream, ServerCallContext context)
|
|
{
|
|
string result = "";
|
|
await requestStream.ForEach(async (request) =>
|
|
{
|
|
if (request == "THROW")
|
|
{
|
|
throw new Exception("This was thrown on purpose by a test");
|
|
}
|
|
result += request;
|
|
});
|
|
// simulate processing takes some time.
|
|
await Task.Delay(250);
|
|
return result;
|
|
}
|
|
}
|
|
}
|