Skip to content
Snippets Groups Projects
Commit 5b0b392c authored by Jan Tattermusch's avatar Jan Tattermusch
Browse files

introduced MockServiceHelper to ease testing

parent 8368b2e4
No related branches found
No related tags found
No related merge requests found
......@@ -77,6 +77,7 @@
<Compile Include="TimeoutsTest.cs" />
<Compile Include="NUnitVersionTest.cs" />
<Compile Include="ChannelTest.cs" />
<Compile Include="MockServiceHelper.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
......
#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
{
/// <summary>
/// Allows setting up a mock service in the client-server tests easily.
/// </summary>
public class MockServiceHelper
{
public const string ServiceName = "tests.Test";
public static readonly Method<string, string> UnaryMethod = new Method<string, string>(
MethodType.Unary,
ServiceName,
"Unary",
Marshallers.StringMarshaller,
Marshallers.StringMarshaller);
public static readonly Method<string, string> ClientStreamingMethod = new Method<string, string>(
MethodType.ClientStreaming,
ServiceName,
"ClientStreaming",
Marshallers.StringMarshaller,
Marshallers.StringMarshaller);
public static readonly Method<string, string> ServerStreamingMethod = new Method<string, string>(
MethodType.ServerStreaming,
ServiceName,
"ServerStreaming",
Marshallers.StringMarshaller,
Marshallers.StringMarshaller);
public static readonly Method<string, string> DuplexStreamingMethod = new Method<string, string>(
MethodType.DuplexStreaming,
ServiceName,
"DuplexStreaming",
Marshallers.StringMarshaller,
Marshallers.StringMarshaller);
readonly string host;
readonly ServerServiceDefinition serviceDefinition;
UnaryServerMethod<string, string> unaryHandler;
ClientStreamingServerMethod<string, string> clientStreamingHandler;
ServerStreamingServerMethod<string, string> serverStreamingHandler;
DuplexStreamingServerMethod<string, string> duplexStreamingHandler;
Server server;
Channel channel;
public MockServiceHelper(string host = null)
{
this.host = host ?? "localhost";
serviceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
.AddMethod(UnaryMethod, (request, context) => unaryHandler(request, context))
.AddMethod(ClientStreamingMethod, (requestStream, context) => clientStreamingHandler(requestStream, context))
.AddMethod(ServerStreamingMethod, (request, responseStream, context) => serverStreamingHandler(request, responseStream, context))
.AddMethod(DuplexStreamingMethod, (requestStream, responseStream, context) => duplexStreamingHandler(requestStream, responseStream, context))
.Build();
var defaultStatus = new Status(StatusCode.Unknown, "Default mock implementation. Please provide your own.");
unaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
context.Status = defaultStatus;
return "";
});
clientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
{
context.Status = defaultStatus;
return "";
});
serverStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
{
context.Status = defaultStatus;
});
duplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
{
context.Status = defaultStatus;
});
}
/// <summary>
/// Returns the default server for this service and creates one if not yet created.
/// </summary>
public Server GetServer()
{
if (server == null)
{
server = new Server
{
Services = { serviceDefinition },
Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
};
}
return server;
}
/// <summary>
/// Returns the default channel for this service and creates one if not yet created.
/// </summary>
public Channel GetChannel()
{
if (channel == null)
{
channel = new Channel(Host, GetServer().Ports.Single().BoundPort, Credentials.Insecure);
}
return channel;
}
public CallInvocationDetails<string, string> CreateUnaryCall(CallOptions options = null)
{
options = options ?? new CallOptions();
return new CallInvocationDetails<string, string>(channel, UnaryMethod, options);
}
public CallInvocationDetails<string, string> CreateClientStreamingCall(CallOptions options = null)
{
options = options ?? new CallOptions();
return new CallInvocationDetails<string, string>(channel, ClientStreamingMethod, options);
}
public CallInvocationDetails<string, string> CreateServerStreamingCall(CallOptions options = null)
{
options = options ?? new CallOptions();
return new CallInvocationDetails<string, string>(channel, ServerStreamingMethod, options);
}
public CallInvocationDetails<string, string> CreateDuplexStreamingCall(CallOptions options = null)
{
options = options ?? new CallOptions();
return new CallInvocationDetails<string, string>(channel, DuplexStreamingMethod, options);
}
public string Host
{
get
{
return this.host;
}
}
public ServerServiceDefinition ServiceDefinition
{
get
{
return this.serviceDefinition;
}
}
public UnaryServerMethod<string, string> UnaryHandler
{
get
{
return this.unaryHandler;
}
set
{
unaryHandler = value;
}
}
public ClientStreamingServerMethod<string, string> ClientStreamingHandler
{
get
{
return this.clientStreamingHandler;
}
set
{
clientStreamingHandler = value;
}
}
public ServerStreamingServerMethod<string, string> ServerStreamingHandler
{
get
{
return this.serverStreamingHandler;
}
set
{
serverStreamingHandler = value;
}
}
public DuplexStreamingServerMethod<string, string> DuplexStreamingHandler
{
get
{
return this.duplexStreamingHandler;
}
set
{
duplexStreamingHandler = value;
}
}
}
}
......@@ -48,38 +48,15 @@ namespace Grpc.Core.Tests
/// </summary>
public class TimeoutsTest
{
const string Host = "localhost";
const string ServiceName = "tests.Test";
static readonly Method<string, string> TestMethod = new Method<string, string>(
MethodType.Unary,
ServiceName,
"Test",
Marshallers.StringMarshaller,
Marshallers.StringMarshaller);
static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
.AddMethod(TestMethod, TestMethodHandler)
.Build();
// provides a way how to retrieve an out-of-band result value from server handler
static TaskCompletionSource<string> stringFromServerHandlerTcs;
MockServiceHelper helper = new MockServiceHelper();
Server server;
Channel channel;
[SetUp]
public void Init()
{
server = new Server
{
Services = { ServiceDefinition },
Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
};
server.Start();
channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
stringFromServerHandlerTcs = new TaskCompletionSource<string>();
server = helper.GetServer();
channel = helper.GetChannel();
}
[TearDown]
......@@ -98,40 +75,44 @@ namespace Grpc.Core.Tests
[Test]
public void InfiniteDeadline()
{
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => {
Assert.AreEqual(DateTime.MaxValue, context.Deadline);
return "PASS";
});
// no deadline specified, check server sees infinite deadline
var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions());
Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE"));
Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
// DateTime.MaxValue deadline specified, check server sees infinite deadline
var callDetails2 = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions());
Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails2, "RETURN_DEADLINE"));
Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MaxValue)), "abc"));
}
[Test]
public void DeadlineTransferredToServer()
{
var remainingTimeClient = TimeSpan.FromDays(7);
var deadline = DateTime.UtcNow + remainingTimeClient;
Thread.Sleep(1000);
var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
var serverDeadlineTicksString = Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE");
var serverDeadline = new DateTime(long.Parse(serverDeadlineTicksString), DateTimeKind.Utc);
// A fairly relaxed check that the deadline set by client and deadline seen by server
// are in agreement. C core takes care of the work with transferring deadline over the wire,
// so we don't need an exact check here.
Assert.IsTrue(Math.Abs((deadline - serverDeadline).TotalMilliseconds) < 5000);
var clientDeadline = DateTime.UtcNow + TimeSpan.FromDays(7);
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => {
// A fairly relaxed check that the deadline set by client and deadline seen by server
// are in agreement. C core takes care of the work with transferring deadline over the wire,
// so we don't need an exact check here.
Assert.IsTrue(Math.Abs((clientDeadline - context.Deadline).TotalMilliseconds) < 5000);
return "PASS";
});
Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: clientDeadline)), "abc");
}
[Test]
public void DeadlineInThePast()
{
var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: DateTime.MinValue));
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => {
await Task.Delay(60000);
return "FAIL";
});
try
{
Calls.BlockingUnaryCall(callDetails, "TIMEOUT");
Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MinValue)), "abc");
Assert.Fail();
}
catch (RpcException e)
......@@ -144,12 +125,14 @@ namespace Grpc.Core.Tests
[Test]
public void DeadlineExceededStatusOnTimeout()
{
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => {
await Task.Delay(60000);
return "FAIL";
});
try
{
Calls.BlockingUnaryCall(callDetails, "TIMEOUT");
Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc");
Assert.Fail();
}
catch (RpcException e)
......@@ -162,12 +145,20 @@ namespace Grpc.Core.Tests
[Test]
public void ServerReceivesCancellationOnTimeout()
{
var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
string receivedCancellation = "NO";
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => {
// wait until cancellation token is fired.
var tcs = new TaskCompletionSource<object>();
context.CancellationToken.Register(() => { tcs.SetResult(null); });
await tcs.Task;
receivedCancellation = "YES";
return "";
});
try
{
Calls.BlockingUnaryCall(callDetails, "CHECK_CANCELLATION_RECEIVED");
Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc");
Assert.Fail();
}
catch (RpcException e)
......@@ -175,38 +166,7 @@ namespace Grpc.Core.Tests
// We can't guarantee the status code is always DeadlineExceeded. See issue #2685.
Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
}
Assert.AreEqual("CANCELLED", stringFromServerHandlerTcs.Task.Result);
}
private static async Task<string> TestMethodHandler(string request, ServerCallContext context)
{
if (request == "TIMEOUT")
{
await Task.Delay(60000);
return "";
}
if (request == "RETURN_DEADLINE")
{
if (context.Deadline == DateTime.MaxValue)
{
return "DATETIME_MAXVALUE";
}
return context.Deadline.Ticks.ToString();
}
if (request == "CHECK_CANCELLATION_RECEIVED")
{
// wait until cancellation token is fired.
var tcs = new TaskCompletionSource<object>();
context.CancellationToken.Register(() => { tcs.SetResult(null); });
await tcs.Task;
stringFromServerHandlerTcs.SetResult("CANCELLED");
return "";
}
return "";
Assert.AreEqual("YES", receivedCancellation);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment