mirror of https://github.com/grpc/grpc.git
Merge pull request #415 from jtattermusch/csharp_progress
Polishing C# math service implementation and adding inprocess testschanges/78/217578/1
commit
f2d5e409d3
26 changed files with 622 additions and 260 deletions
@ -1,74 +0,0 @@ |
||||
using System; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using System.Collections.Generic; |
||||
using System.Reactive.Linq; |
||||
|
||||
namespace math |
||||
{ |
||||
// /// <summary> |
||||
// /// Dummy local implementation of math service. |
||||
// /// </summary> |
||||
// public class DummyMathServiceClient : IMathServiceClient |
||||
// { |
||||
// public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||
// { |
||||
// // TODO: cancellation... |
||||
// return DivInternal(args); |
||||
// } |
||||
// |
||||
// public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||
// { |
||||
// return Task.Factory.StartNew(() => DivInternal(args), token); |
||||
// } |
||||
// |
||||
// public IObservable<Num> Fib(FibArgs args, CancellationToken token = default(CancellationToken)) |
||||
// { |
||||
// if (args.Limit > 0) |
||||
// { |
||||
// // TODO: cancellation |
||||
// return FibInternal(args.Limit).ToObservable(); |
||||
// } |
||||
// |
||||
// throw new NotImplementedException("Not implemented yet"); |
||||
// } |
||||
// |
||||
// public Task<Num> Sum(IObservable<Num> inputs, CancellationToken token = default(CancellationToken)) |
||||
// { |
||||
// // TODO: implement |
||||
// inputs = null; |
||||
// return Task.Factory.StartNew(() => Num.CreateBuilder().Build(), token); |
||||
// } |
||||
// |
||||
// public IObservable<DivReply> DivMany(IObservable<DivArgs> inputs, CancellationToken token = default(CancellationToken)) |
||||
// { |
||||
// // TODO: implement |
||||
// inputs = null; |
||||
// return new List<DivReply> { }.ToObservable (); |
||||
// } |
||||
// |
||||
// |
||||
// DivReply DivInternal(DivArgs args) |
||||
// { |
||||
// long quotient = args.Dividend / args.Divisor; |
||||
// long remainder = args.Dividend % args.Divisor; |
||||
// return new DivReply.Builder{ Quotient = quotient, Remainder = remainder }.Build(); |
||||
// } |
||||
// |
||||
// IEnumerable<Num> FibInternal(long n) |
||||
// { |
||||
// long a = 0; |
||||
// yield return new Num.Builder{Num_=a}.Build(); |
||||
// |
||||
// long b = 1; |
||||
// for (long i = 0; i < n - 1; i++) |
||||
// { |
||||
// long temp = a; |
||||
// a = b; |
||||
// b = temp + b; |
||||
// yield return new Num.Builder{Num_=a}.Build(); |
||||
// } |
||||
// } |
||||
// } |
||||
} |
||||
|
@ -1,26 +0,0 @@ |
||||
using System; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using System.Collections.Generic; |
||||
using System.Reactive.Linq; |
||||
using Google.GRPC.Core; |
||||
|
||||
namespace math |
||||
{ |
||||
/// <summary> |
||||
/// Hand-written stub for MathService defined in math.proto. |
||||
/// This code will be generated by gRPC codegen in the future. |
||||
/// </summary> |
||||
public interface IMathServiceClient |
||||
{ |
||||
DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)); |
||||
|
||||
Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)); |
||||
|
||||
Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken)); |
||||
|
||||
ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)); |
||||
|
||||
IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken)); |
||||
} |
||||
} |
@ -0,0 +1,124 @@ |
||||
using System; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using System.Collections.Generic; |
||||
using System.Reactive.Linq; |
||||
using Google.GRPC.Core; |
||||
|
||||
namespace math |
||||
{ |
||||
/// <summary> |
||||
/// Math service definitions (this is handwritten version of code that will normally be generated). |
||||
/// </summary> |
||||
public class MathGrpc |
||||
{ |
||||
readonly static Marshaller<DivArgs> divArgsMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), DivArgs.ParseFrom); |
||||
readonly static Marshaller<DivReply> divReplyMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), DivReply.ParseFrom); |
||||
readonly static Marshaller<Num> numMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), Num.ParseFrom); |
||||
readonly static Marshaller<FibArgs> fibArgsMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), FibArgs.ParseFrom); |
||||
|
||||
readonly static Method<DivArgs, DivReply> divMethod = new Method<DivArgs, DivReply>( |
||||
MethodType.Unary, |
||||
"/math.Math/Div", |
||||
divArgsMarshaller, |
||||
divReplyMarshaller |
||||
); |
||||
readonly static Method<FibArgs, Num> fibMethod = new Method<FibArgs, Num>( |
||||
MethodType.ServerStreaming, |
||||
"/math.Math/Fib", |
||||
fibArgsMarshaller, |
||||
numMarshaller |
||||
); |
||||
readonly static Method<Num, Num> sumMethod = new Method<Num, Num>( |
||||
MethodType.ClientStreaming, |
||||
"/math.Math/Sum", |
||||
numMarshaller, |
||||
numMarshaller |
||||
); |
||||
readonly static Method<DivArgs, DivReply> divManyMethod = new Method<DivArgs, DivReply>( |
||||
MethodType.DuplexStreaming, |
||||
"/math.Math/DivMany", |
||||
divArgsMarshaller, |
||||
divReplyMarshaller |
||||
); |
||||
|
||||
public interface IMathServiceClient |
||||
{ |
||||
DivReply Div(DivArgs request, CancellationToken token = default(CancellationToken)); |
||||
|
||||
Task<DivReply> DivAsync(DivArgs request, CancellationToken token = default(CancellationToken)); |
||||
|
||||
Task Fib(FibArgs request, IObserver<Num> responseObserver, CancellationToken token = default(CancellationToken)); |
||||
|
||||
ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)); |
||||
|
||||
IObserver<DivArgs> DivMany(IObserver<DivReply> responseObserver, CancellationToken token = default(CancellationToken)); |
||||
} |
||||
|
||||
public class MathServiceClientStub : IMathServiceClient |
||||
{ |
||||
readonly Channel channel; |
||||
|
||||
public MathServiceClientStub(Channel channel) |
||||
{ |
||||
this.channel = channel; |
||||
} |
||||
|
||||
public DivReply Div(DivArgs request, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>(divMethod, channel); |
||||
return Calls.BlockingUnaryCall(call, request, token); |
||||
} |
||||
|
||||
public Task<DivReply> DivAsync(DivArgs request, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>(divMethod, channel); |
||||
return Calls.AsyncUnaryCall(call, request, token); |
||||
} |
||||
|
||||
public Task Fib(FibArgs request, IObserver<Num> responseObserver, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<FibArgs, Num>(fibMethod, channel); |
||||
return Calls.AsyncServerStreamingCall(call, request, responseObserver, token); |
||||
} |
||||
|
||||
public ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<Num, Num>(sumMethod, channel); |
||||
return Calls.AsyncClientStreamingCall(call, token); |
||||
} |
||||
|
||||
public IObserver<DivArgs> DivMany(IObserver<DivReply> responseObserver, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>(divManyMethod, channel); |
||||
return Calls.DuplexStreamingCall(call, responseObserver, token); |
||||
} |
||||
} |
||||
|
||||
// server-side interface |
||||
public interface IMathService |
||||
{ |
||||
void Div(DivArgs request, IObserver<DivReply> responseObserver); |
||||
|
||||
void Fib(FibArgs request, IObserver<Num> responseObserver); |
||||
|
||||
IObserver<Num> Sum(IObserver<Num> responseObserver); |
||||
|
||||
IObserver<DivArgs> DivMany(IObserver<DivReply> responseObserver); |
||||
} |
||||
|
||||
public static ServerServiceDefinition BindService(IMathService serviceImpl) |
||||
{ |
||||
return ServerServiceDefinition.CreateBuilder("/math.Math/") |
||||
.AddMethod(divMethod, serviceImpl.Div) |
||||
.AddMethod(fibMethod, serviceImpl.Fib) |
||||
.AddMethod(sumMethod, serviceImpl.Sum) |
||||
.AddMethod(divManyMethod, serviceImpl.DivMany).Build(); |
||||
} |
||||
|
||||
public static IMathServiceClient NewStub(Channel channel) |
||||
{ |
||||
return new MathServiceClientStub(channel); |
||||
} |
||||
} |
||||
} |
@ -1,75 +0,0 @@ |
||||
using System; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using System.Collections.Generic; |
||||
using System.Reactive.Linq; |
||||
using Google.GRPC.Core; |
||||
|
||||
namespace math |
||||
{ |
||||
/// <summary> |
||||
/// Implementation of math service stub (this is handwritten version of code |
||||
/// that will normally be generated). |
||||
/// </summary> |
||||
public class MathServiceClientStub : IMathServiceClient |
||||
{ |
||||
readonly Channel channel; |
||||
readonly TimeSpan methodTimeout; |
||||
|
||||
public MathServiceClientStub(Channel channel, TimeSpan methodTimeout) |
||||
{ |
||||
this.channel = channel; |
||||
this.methodTimeout = methodTimeout; |
||||
} |
||||
|
||||
public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); |
||||
return Calls.BlockingUnaryCall(call, args, token); |
||||
} |
||||
|
||||
public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); |
||||
return Calls.AsyncUnaryCall(call, args, token); |
||||
} |
||||
|
||||
public Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<FibArgs, Num>("/math.Math/Fib", Serialize_FibArgs, Deserialize_Num, methodTimeout, channel); |
||||
return Calls.AsyncServerStreamingCall(call, args, outputs, token); |
||||
} |
||||
|
||||
public ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<Num, Num>("/math.Math/Sum", Serialize_Num, Deserialize_Num, methodTimeout, channel); |
||||
return Calls.AsyncClientStreamingCall(call, token); |
||||
} |
||||
|
||||
public IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken)) |
||||
{ |
||||
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/DivMany", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); |
||||
return Calls.DuplexStreamingCall(call, outputs, token); |
||||
} |
||||
|
||||
private static byte[] Serialize_DivArgs(DivArgs arg) { |
||||
return arg.ToByteArray(); |
||||
} |
||||
|
||||
private static byte[] Serialize_FibArgs(FibArgs arg) { |
||||
return arg.ToByteArray(); |
||||
} |
||||
|
||||
private static byte[] Serialize_Num(Num arg) { |
||||
return arg.ToByteArray(); |
||||
} |
||||
|
||||
private static DivReply Deserialize_DivReply(byte[] payload) { |
||||
return DivReply.CreateBuilder().MergeFrom(payload).Build(); |
||||
} |
||||
|
||||
private static Num Deserialize_Num(byte[] payload) { |
||||
return Num.CreateBuilder().MergeFrom(payload).Build(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,119 @@ |
||||
using System; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using System.Collections.Generic; |
||||
using System.Reactive.Linq; |
||||
using Google.GRPC.Core.Utils; |
||||
|
||||
namespace math |
||||
{ |
||||
/// <summary> |
||||
/// Implementation of MathService server |
||||
/// </summary> |
||||
public class MathServiceImpl : MathGrpc.IMathService |
||||
{ |
||||
public void Div(DivArgs request, IObserver<DivReply> responseObserver) |
||||
{ |
||||
var response = DivInternal(request); |
||||
responseObserver.OnNext(response); |
||||
responseObserver.OnCompleted(); |
||||
} |
||||
|
||||
public void Fib(FibArgs request, IObserver<Num> responseObserver) |
||||
{ |
||||
if (request.Limit <= 0) |
||||
{ |
||||
// TODO: support cancellation.... |
||||
throw new NotImplementedException("Not implemented yet"); |
||||
} |
||||
|
||||
if (request.Limit > 0) |
||||
{ |
||||
foreach (var num in FibInternal(request.Limit)) |
||||
{ |
||||
responseObserver.OnNext(num); |
||||
} |
||||
responseObserver.OnCompleted(); |
||||
} |
||||
} |
||||
|
||||
public IObserver<Num> Sum(IObserver<Num> responseObserver) |
||||
{ |
||||
var recorder = new RecordingObserver<Num>(); |
||||
Task.Factory.StartNew(() => { |
||||
|
||||
List<Num> inputs = recorder.ToList().Result; |
||||
|
||||
long sum = 0; |
||||
foreach (Num num in inputs) |
||||
{ |
||||
sum += num.Num_; |
||||
} |
||||
|
||||
responseObserver.OnNext(Num.CreateBuilder().SetNum_(sum).Build()); |
||||
responseObserver.OnCompleted(); |
||||
}); |
||||
return recorder; |
||||
} |
||||
|
||||
public IObserver<DivArgs> DivMany(IObserver<DivReply> responseObserver) |
||||
{ |
||||
return new DivObserver(responseObserver); |
||||
} |
||||
|
||||
static DivReply DivInternal(DivArgs args) |
||||
{ |
||||
long quotient = args.Dividend / args.Divisor; |
||||
long remainder = args.Dividend % args.Divisor; |
||||
return new DivReply.Builder { Quotient = quotient, Remainder = remainder }.Build(); |
||||
} |
||||
|
||||
static IEnumerable<Num> FibInternal(long n) |
||||
{ |
||||
long a = 1; |
||||
yield return new Num.Builder { Num_=a }.Build(); |
||||
|
||||
long b = 1; |
||||
for (long i = 0; i < n - 1; i++) |
||||
{ |
||||
long temp = a; |
||||
a = b; |
||||
b = temp + b; |
||||
yield return new Num.Builder { Num_=a }.Build(); |
||||
} |
||||
} |
||||
|
||||
private class DivObserver : IObserver<DivArgs> { |
||||
|
||||
readonly IObserver<DivReply> responseObserver; |
||||
|
||||
public DivObserver(IObserver<DivReply> responseObserver) |
||||
{ |
||||
this.responseObserver = responseObserver; |
||||
} |
||||
|
||||
public void OnCompleted() |
||||
{ |
||||
Task.Factory.StartNew(() => |
||||
responseObserver.OnCompleted()); |
||||
} |
||||
|
||||
public void OnError(Exception error) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
public void OnNext(DivArgs value) |
||||
{ |
||||
// TODO: currently we need this indirection because |
||||
// responseObserver waits for write to finish, this |
||||
// callback is called from grpc threadpool which |
||||
// currently only has one thread. |
||||
// Same story for OnCompleted(). |
||||
Task.Factory.StartNew(() => |
||||
responseObserver.OnNext(DivInternal(value))); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,2 @@ |
||||
test-results |
||||
bin |
@ -0,0 +1,56 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<PropertyGroup> |
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
||||
<ProductVersion>10.0.0</ProductVersion> |
||||
<SchemaVersion>2.0</SchemaVersion> |
||||
<ProjectGuid>{143B1C29-C442-4BE0-BF3F-A8F92288AC9F}</ProjectGuid> |
||||
<OutputType>Library</OutputType> |
||||
<RootNamespace>GrpcApiTests</RootNamespace> |
||||
<AssemblyName>GrpcApiTests</AssemblyName> |
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
||||
<DebugSymbols>true</DebugSymbols> |
||||
<DebugType>full</DebugType> |
||||
<Optimize>false</Optimize> |
||||
<OutputPath>bin\Debug</OutputPath> |
||||
<DefineConstants>DEBUG;</DefineConstants> |
||||
<ErrorReport>prompt</ErrorReport> |
||||
<WarningLevel>4</WarningLevel> |
||||
<ConsolePause>false</ConsolePause> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
||||
<DebugType>full</DebugType> |
||||
<Optimize>true</Optimize> |
||||
<OutputPath>bin\Release</OutputPath> |
||||
<ErrorReport>prompt</ErrorReport> |
||||
<WarningLevel>4</WarningLevel> |
||||
<ConsolePause>false</ConsolePause> |
||||
</PropertyGroup> |
||||
<ItemGroup> |
||||
<Reference Include="System" /> |
||||
<Reference Include="nunit.framework, Version=2.6.0.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77"> |
||||
<Private>False</Private> |
||||
</Reference> |
||||
<Reference Include="Google.ProtocolBuffers"> |
||||
<HintPath>..\lib\Google.ProtocolBuffers.dll</HintPath> |
||||
</Reference> |
||||
</ItemGroup> |
||||
<ItemGroup> |
||||
<Compile Include="Properties\AssemblyInfo.cs" /> |
||||
<Compile Include="MathClientServerTests.cs" /> |
||||
</ItemGroup> |
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> |
||||
<ItemGroup> |
||||
<ProjectReference Include="..\GrpcApi\GrpcApi.csproj"> |
||||
<Project>{7DC1433E-3225-42C7-B7EA-546D56E27A4B}</Project> |
||||
<Name>GrpcApi</Name> |
||||
</ProjectReference> |
||||
<ProjectReference Include="..\GrpcCore\GrpcCore.csproj"> |
||||
<Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project> |
||||
<Name>GrpcCore</Name> |
||||
</ProjectReference> |
||||
</ItemGroup> |
||||
</Project> |
@ -0,0 +1,115 @@ |
||||
using System; |
||||
using NUnit.Framework; |
||||
using Google.GRPC.Core; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Google.GRPC.Core.Utils; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace math.Tests |
||||
{ |
||||
/// <summary> |
||||
/// Math client talks to local math server. |
||||
/// </summary> |
||||
public class MathClientServerTest |
||||
{ |
||||
string serverAddr = "localhost:" + PortPicker.PickUnusedPort(); |
||||
Server server; |
||||
Channel channel; |
||||
MathGrpc.IMathServiceClient client; |
||||
|
||||
[TestFixtureSetUp] |
||||
public void Init() |
||||
{ |
||||
server = new Server(); |
||||
server.AddServiceDefinition(MathGrpc.BindService(new MathServiceImpl())); |
||||
server.AddPort(serverAddr); |
||||
server.Start(); |
||||
channel = new Channel(serverAddr); |
||||
client = MathGrpc.NewStub(channel); |
||||
} |
||||
|
||||
[Test] |
||||
public void Div1() |
||||
{ |
||||
DivReply response = client.Div(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build()); |
||||
Assert.AreEqual(3, response.Quotient); |
||||
Assert.AreEqual(1, response.Remainder); |
||||
} |
||||
|
||||
[Test] |
||||
public void Div2() |
||||
{ |
||||
DivReply response = client.Div(new DivArgs.Builder { Dividend = 0, Divisor = 1 }.Build()); |
||||
Assert.AreEqual(0, response.Quotient); |
||||
Assert.AreEqual(0, response.Remainder); |
||||
} |
||||
|
||||
// TODO: test division by zero |
||||
|
||||
[Test] |
||||
public void DivAsync() |
||||
{ |
||||
DivReply response = client.DivAsync(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build()).Result; |
||||
Assert.AreEqual(3, response.Quotient); |
||||
Assert.AreEqual(1, response.Remainder); |
||||
} |
||||
|
||||
[Test] |
||||
public void Fib() |
||||
{ |
||||
var recorder = new RecordingObserver<Num>(); |
||||
client.Fib(new FibArgs.Builder { Limit = 6 }.Build(), recorder); |
||||
|
||||
CollectionAssert.AreEqual(new List<long>{1, 1, 2, 3, 5, 8}, |
||||
recorder.ToList().Result.ConvertAll((n) => n.Num_)); |
||||
} |
||||
|
||||
// TODO: test Fib with limit=0 and cancellation |
||||
[Test] |
||||
public void Sum() |
||||
{ |
||||
var res = client.Sum(); |
||||
foreach (var num in new long[] { 10, 20, 30 }) { |
||||
res.Inputs.OnNext(Num.CreateBuilder().SetNum_(num).Build()); |
||||
} |
||||
res.Inputs.OnCompleted(); |
||||
|
||||
Assert.AreEqual(60, res.Task.Result.Num_); |
||||
} |
||||
|
||||
[Test] |
||||
public void DivMany() |
||||
{ |
||||
List<DivArgs> divArgsList = new List<DivArgs>{ |
||||
new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build(), |
||||
new DivArgs.Builder { Dividend = 100, Divisor = 21 }.Build(), |
||||
new DivArgs.Builder { Dividend = 7, Divisor = 2 }.Build() |
||||
}; |
||||
|
||||
var recorder = new RecordingObserver<DivReply>(); |
||||
var requestObserver = client.DivMany(recorder); |
||||
|
||||
foreach (var arg in divArgsList) |
||||
{ |
||||
requestObserver.OnNext(arg); |
||||
} |
||||
requestObserver.OnCompleted(); |
||||
|
||||
var result = recorder.ToList().Result; |
||||
|
||||
CollectionAssert.AreEqual(new long[] {3, 4, 3}, result.ConvertAll((divReply) => divReply.Quotient)); |
||||
CollectionAssert.AreEqual(new long[] {1, 16, 1}, result.ConvertAll((divReply) => divReply.Remainder)); |
||||
} |
||||
|
||||
[TestFixtureTearDown] |
||||
public void Cleanup() |
||||
{ |
||||
channel.Dispose(); |
||||
|
||||
server.ShutdownAsync().Wait(); |
||||
GrpcEnvironment.Shutdown(); |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,22 @@ |
||||
using System.Reflection; |
||||
using System.Runtime.CompilerServices; |
||||
|
||||
// Information about this assembly is defined by the following attributes. |
||||
// Change them to the values specific to your project. |
||||
[assembly: AssemblyTitle("GrpcApiTests")] |
||||
[assembly: AssemblyDescription("")] |
||||
[assembly: AssemblyConfiguration("")] |
||||
[assembly: AssemblyCompany("")] |
||||
[assembly: AssemblyProduct("")] |
||||
[assembly: AssemblyCopyright("jtattermusch")] |
||||
[assembly: AssemblyTrademark("")] |
||||
[assembly: AssemblyCulture("")] |
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". |
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision, |
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision. |
||||
[assembly: AssemblyVersion("1.0.*")] |
||||
// The following attributes are used to specify the signing key for the assembly, |
||||
// if desired. See the Mono documentation for more information about signing. |
||||
//[assembly: AssemblyDelaySign(false)] |
||||
//[assembly: AssemblyKeyFile("")] |
||||
|
@ -1,31 +0,0 @@ |
||||
using System; |
||||
|
||||
namespace Google.GRPC.Core |
||||
{ |
||||
/// <summary> |
||||
/// For serializing and deserializing messages. |
||||
/// </summary> |
||||
public interface IMarshaller<T> |
||||
{ |
||||
byte[] Serialize(T value); |
||||
|
||||
T Deserialize(byte[] payload); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// UTF-8 Marshalling for string. Useful for testing. |
||||
/// </summary> |
||||
internal class StringMarshaller : IMarshaller<string> { |
||||
|
||||
public byte[] Serialize(string value) |
||||
{ |
||||
return System.Text.Encoding.UTF8.GetBytes(value); |
||||
} |
||||
|
||||
public string Deserialize(byte[] payload) |
||||
{ |
||||
return System.Text.Encoding.UTF8.GetString(payload); |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,54 @@ |
||||
using System; |
||||
|
||||
namespace Google.GRPC.Core |
||||
{ |
||||
/// <summary> |
||||
/// For serializing and deserializing messages. |
||||
/// </summary> |
||||
public struct Marshaller<T> |
||||
{ |
||||
readonly Func<T,byte[]> serializer; |
||||
readonly Func<byte[],T> deserializer; |
||||
|
||||
public Marshaller(Func<T, byte[]> serializer, Func<byte[], T> deserializer) |
||||
{ |
||||
this.serializer = serializer; |
||||
this.deserializer = deserializer; |
||||
} |
||||
|
||||
public Func<T, byte[]> Serializer |
||||
{ |
||||
get |
||||
{ |
||||
return this.serializer; |
||||
} |
||||
} |
||||
|
||||
public Func<byte[], T> Deserializer |
||||
{ |
||||
get |
||||
{ |
||||
return this.deserializer; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static class Marshallers { |
||||
|
||||
public static Marshaller<T> Create<T>(Func<T,byte[]> serializer, Func<byte[],T> deserializer) |
||||
{ |
||||
return new Marshaller<T>(serializer, deserializer); |
||||
} |
||||
|
||||
public static Marshaller<string> StringMarshaller |
||||
{ |
||||
get |
||||
{ |
||||
return new Marshaller<string>(System.Text.Encoding.UTF8.GetBytes, |
||||
System.Text.Encoding.UTF8.GetString); |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
@ -0,0 +1,65 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace Google.GRPC.Core |
||||
{ |
||||
public class ServerServiceDefinition |
||||
{ |
||||
readonly string serviceName; |
||||
// TODO: we would need an immutable dictionary here... |
||||
readonly Dictionary<string, IServerCallHandler> callHandlers; |
||||
|
||||
private ServerServiceDefinition(string serviceName, Dictionary<string, IServerCallHandler> callHandlers) |
||||
{ |
||||
this.serviceName = serviceName; |
||||
this.callHandlers = new Dictionary<string, IServerCallHandler>(callHandlers); |
||||
} |
||||
|
||||
internal Dictionary<string, IServerCallHandler> CallHandlers |
||||
{ |
||||
get |
||||
{ |
||||
return this.callHandlers; |
||||
} |
||||
} |
||||
|
||||
|
||||
public static Builder CreateBuilder(String serviceName) |
||||
{ |
||||
return new Builder(serviceName); |
||||
} |
||||
|
||||
public class Builder |
||||
{ |
||||
readonly string serviceName; |
||||
readonly Dictionary<string, IServerCallHandler> callHandlers = new Dictionary<String, IServerCallHandler>(); |
||||
|
||||
public Builder(string serviceName) |
||||
{ |
||||
this.serviceName = serviceName; |
||||
} |
||||
|
||||
public Builder AddMethod<TRequest, TResponse>( |
||||
Method<TRequest, TResponse> method, |
||||
UnaryRequestServerMethod<TRequest, TResponse> handler) |
||||
{ |
||||
callHandlers.Add(method.Name, ServerCalls.UnaryRequestCall(method, handler)); |
||||
return this; |
||||
} |
||||
|
||||
public Builder AddMethod<TRequest, TResponse>( |
||||
Method<TRequest, TResponse> method, |
||||
StreamingRequestServerMethod<TRequest, TResponse> handler) |
||||
{ |
||||
callHandlers.Add(method.Name, ServerCalls.StreamingRequestCall(method, handler)); |
||||
return this; |
||||
} |
||||
|
||||
public ServerServiceDefinition Build() |
||||
{ |
||||
return new ServerServiceDefinition(serviceName, callHandlers); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue