mirror of https://github.com/grpc/grpc.git
commit
859fb06e80
81 changed files with 2594 additions and 725 deletions
@ -0,0 +1,84 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2015-2016, 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.Threading.Tasks; |
||||
using Grpc.Core.Internal; |
||||
|
||||
namespace Grpc.Core |
||||
{ |
||||
/// <summary> |
||||
/// Abstraction of client-side RPC invocation. |
||||
/// </summary> |
||||
/// <seealso cref="Calls"/> |
||||
public abstract class CallInvoker |
||||
{ |
||||
/// <summary> |
||||
/// Invokes a simple remote call in a blocking fashion. |
||||
/// </summary> |
||||
public abstract TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
where TRequest : class
|
||||
where TResponse : class; |
||||
|
||||
/// <summary> |
||||
/// Invokes a simple remote call asynchronously. |
||||
/// </summary> |
||||
public abstract AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
where TRequest : class
|
||||
where TResponse : class; |
||||
|
||||
/// <summary> |
||||
/// Invokes a server streaming call asynchronously. |
||||
/// In server streaming scenario, client sends on request and server responds with a stream of responses. |
||||
/// </summary> |
||||
public abstract AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
where TRequest : class
|
||||
where TResponse : class; |
||||
|
||||
/// <summary> |
||||
/// Invokes a client streaming call asynchronously. |
||||
/// In client streaming scenario, client sends a stream of requests and server responds with a single response. |
||||
/// </summary> |
||||
public abstract AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
where TRequest : class
|
||||
where TResponse : class; |
||||
|
||||
/// <summary> |
||||
/// Invokes a duplex streaming call asynchronously. |
||||
/// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses. |
||||
/// The response stream is completely independent and both side can be sending messages at the same time. |
||||
/// </summary> |
||||
public abstract AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
where TRequest : class
|
||||
where TResponse : class; |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2015-2016, 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.Threading.Tasks; |
||||
using Grpc.Core.Internal; |
||||
using Grpc.Core.Utils; |
||||
|
||||
namespace Grpc.Core |
||||
{ |
||||
/// <summary> |
||||
/// Invokes client RPCs using <see cref="Calls"/>. |
||||
/// </summary> |
||||
public class DefaultCallInvoker : CallInvoker |
||||
{ |
||||
readonly Channel channel; |
||||
|
||||
/// <summary> |
||||
/// Initializes a new instance of the <see cref="Grpc.Core.DefaultCallInvoker"/> class. |
||||
/// </summary> |
||||
/// <param name="channel">Channel to use.</param> |
||||
public DefaultCallInvoker(Channel channel) |
||||
{ |
||||
this.channel = GrpcPreconditions.CheckNotNull(channel); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a simple remote call in a blocking fashion. |
||||
/// </summary> |
||||
public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
var call = CreateCall(method, host, options); |
||||
return Calls.BlockingUnaryCall(call, request); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a simple remote call asynchronously. |
||||
/// </summary> |
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
var call = CreateCall(method, host, options); |
||||
return Calls.AsyncUnaryCall(call, request); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a server streaming call asynchronously. |
||||
/// In server streaming scenario, client sends on request and server responds with a stream of responses. |
||||
/// </summary> |
||||
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
var call = CreateCall(method, host, options); |
||||
return Calls.AsyncServerStreamingCall(call, request); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a client streaming call asynchronously. |
||||
/// In client streaming scenario, client sends a stream of requests and server responds with a single response. |
||||
/// </summary> |
||||
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
{ |
||||
var call = CreateCall(method, host, options); |
||||
return Calls.AsyncClientStreamingCall(call); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a duplex streaming call asynchronously. |
||||
/// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses. |
||||
/// The response stream is completely independent and both side can be sending messages at the same time. |
||||
/// </summary> |
||||
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
{ |
||||
var call = CreateCall(method, host, options); |
||||
return Calls.AsyncDuplexStreamingCall(call); |
||||
} |
||||
|
||||
protected virtual CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
where TRequest : class
|
||||
where TResponse : class
|
||||
{ |
||||
return new CallInvocationDetails<TRequest, TResponse>(channel, method, host, options); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,134 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2015-2016, 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.Threading.Tasks; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Utils; |
||||
|
||||
namespace Grpc.Core.Internal |
||||
{ |
||||
/// <summary> |
||||
/// Decorates an underlying <c>CallInvoker</c> to intercept call invocations. |
||||
/// </summary> |
||||
internal class InterceptingCallInvoker : CallInvoker |
||||
{ |
||||
readonly CallInvoker callInvoker; |
||||
readonly Func<string, string> hostInterceptor; |
||||
readonly Func<CallOptions, CallOptions> callOptionsInterceptor; |
||||
|
||||
/// <summary> |
||||
/// Initializes a new instance of the <see cref="Grpc.Core.InterceptingCallInvoker"/> class. |
||||
/// </summary> |
||||
public InterceptingCallInvoker(CallInvoker callInvoker, |
||||
Func<string, string> hostInterceptor = null, |
||||
Func<CallOptions, CallOptions> callOptionsInterceptor = null) |
||||
{ |
||||
this.callInvoker = GrpcPreconditions.CheckNotNull(callInvoker); |
||||
this.hostInterceptor = hostInterceptor; |
||||
this.callOptionsInterceptor = callOptionsInterceptor; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Intercepts a unary call. |
||||
/// </summary> |
||||
public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
host = InterceptHost(host); |
||||
options = InterceptCallOptions(options); |
||||
return callInvoker.BlockingUnaryCall(method, host, options, request); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a simple remote call asynchronously. |
||||
/// </summary> |
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
host = InterceptHost(host); |
||||
options = InterceptCallOptions(options); |
||||
return callInvoker.AsyncUnaryCall(method, host, options, request); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a server streaming call asynchronously. |
||||
/// In server streaming scenario, client sends on request and server responds with a stream of responses. |
||||
/// </summary> |
||||
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
host = InterceptHost(host); |
||||
options = InterceptCallOptions(options); |
||||
return callInvoker.AsyncServerStreamingCall(method, host, options, request); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a client streaming call asynchronously. |
||||
/// In client streaming scenario, client sends a stream of requests and server responds with a single response. |
||||
/// </summary> |
||||
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
{ |
||||
host = InterceptHost(host); |
||||
options = InterceptCallOptions(options); |
||||
return callInvoker.AsyncClientStreamingCall(method, host, options); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Invokes a duplex streaming call asynchronously. |
||||
/// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses. |
||||
/// The response stream is completely independent and both side can be sending messages at the same time. |
||||
/// </summary> |
||||
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
{ |
||||
host = InterceptHost(host); |
||||
options = InterceptCallOptions(options); |
||||
return callInvoker.AsyncDuplexStreamingCall(method, host, options); |
||||
} |
||||
|
||||
private string InterceptHost(string host) |
||||
{ |
||||
if (hostInterceptor == null) |
||||
{ |
||||
return host; |
||||
} |
||||
return hostInterceptor(host); |
||||
} |
||||
|
||||
private CallOptions InterceptCallOptions(CallOptions options) |
||||
{ |
||||
if (callOptionsInterceptor == null) |
||||
{ |
||||
return options; |
||||
} |
||||
return callOptionsInterceptor(options); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,75 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2015-2016, 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.Threading.Tasks; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Utils; |
||||
|
||||
namespace Grpc.Core.Internal |
||||
{ |
||||
/// <summary> |
||||
/// Call invoker that throws <c>NotImplementedException</c> for all requests. |
||||
/// </summary> |
||||
internal class UnimplementedCallInvoker : CallInvoker |
||||
{ |
||||
public UnimplementedCallInvoker() |
||||
{ |
||||
} |
||||
|
||||
public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,106 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2015-2016, 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.Collections.Generic; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Utils; |
||||
using Grpc.Testing; |
||||
using Moq; |
||||
using NUnit.Framework; |
||||
|
||||
namespace Grpc.IntegrationTesting |
||||
{ |
||||
public class GeneratedClientTest |
||||
{ |
||||
TestService.TestServiceClient unimplementedClient = new UnimplementedTestServiceClient(); |
||||
|
||||
[Test] |
||||
public void ExpandedParamOverloadCanBeMocked() |
||||
{ |
||||
var expected = new SimpleResponse(); |
||||
|
||||
var mockClient = new Mock<TestService.TestServiceClient>(); |
||||
// mocking is relatively clumsy because one needs to specify value for all the optional params. |
||||
mockClient.Setup(m => m.UnaryCall(It.IsAny<SimpleRequest>(), null, null, CancellationToken.None)).Returns(expected); |
||||
|
||||
Assert.AreSame(expected, mockClient.Object.UnaryCall(new SimpleRequest())); |
||||
} |
||||
|
||||
[Test] |
||||
public void CallOptionsOverloadCanBeMocked() |
||||
{ |
||||
var expected = new SimpleResponse(); |
||||
|
||||
var mockClient = new Mock<TestService.TestServiceClient>(); |
||||
mockClient.Setup(m => m.UnaryCall(It.IsAny<SimpleRequest>(), It.IsAny<CallOptions>())).Returns(expected); |
||||
|
||||
Assert.AreSame(expected, mockClient.Object.UnaryCall(new SimpleRequest(), new CallOptions())); |
||||
} |
||||
|
||||
[Test] |
||||
public void DefaultMethodStubThrows_UnaryCall() |
||||
{ |
||||
Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.UnaryCall(new SimpleRequest())); |
||||
} |
||||
|
||||
[Test] |
||||
public void DefaultMethodStubThrows_ClientStreaming() |
||||
{ |
||||
Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.StreamingInputCall()); |
||||
} |
||||
|
||||
[Test] |
||||
public void DefaultMethodStubThrows_ServerStreaming() |
||||
{ |
||||
Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.StreamingOutputCall(new StreamingOutputCallRequest())); |
||||
} |
||||
|
||||
[Test] |
||||
public void DefaultMethodStubThrows_DuplexStreaming() |
||||
{ |
||||
Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.FullDuplexCall()); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Subclass of the generated client that doesn't override any method stubs. |
||||
/// </summary> |
||||
private class UnimplementedTestServiceClient : TestService.TestServiceClient |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,116 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2015-2016, 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.Collections.Generic; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Utils; |
||||
using Grpc.Testing; |
||||
using Moq; |
||||
using NUnit.Framework; |
||||
|
||||
namespace Grpc.IntegrationTesting |
||||
{ |
||||
public class GeneratedServiceBaseTest |
||||
{ |
||||
const string Host = "localhost"; |
||||
Server server; |
||||
Channel channel; |
||||
TestService.TestServiceClient client; |
||||
|
||||
[SetUp] |
||||
public void Init() |
||||
{ |
||||
server = new Server |
||||
{ |
||||
Services = { TestService.BindService(new UnimplementedTestServiceImpl()) }, |
||||
Ports = { { Host, ServerPort.PickUnused, SslServerCredentials.Insecure } } |
||||
}; |
||||
server.Start(); |
||||
channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure); |
||||
client = TestService.NewClient(channel); |
||||
} |
||||
|
||||
[TearDown] |
||||
public void Cleanup() |
||||
{ |
||||
channel.ShutdownAsync().Wait(); |
||||
server.ShutdownAsync().Wait(); |
||||
} |
||||
|
||||
[Test] |
||||
public void UnimplementedByDefault_Unary() |
||||
{ |
||||
var ex = Assert.Throws<RpcException>(() => client.UnaryCall(new SimpleRequest { })); |
||||
Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task UnimplementedByDefault_ClientStreaming() |
||||
{ |
||||
var call = client.StreamingInputCall(); |
||||
|
||||
var ex = Assert.Throws<RpcException>(async () => await call); |
||||
Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task UnimplementedByDefault_ServerStreamingCall() |
||||
{ |
||||
var call = client.StreamingOutputCall(new StreamingOutputCallRequest()); |
||||
|
||||
var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext()); |
||||
Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task UnimplementedByDefault_DuplexStreamingCall() |
||||
{ |
||||
var call = client.FullDuplexCall(); |
||||
|
||||
var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext()); |
||||
Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Implementation of TestService that doesn't override any methods. |
||||
/// </summary> |
||||
private class UnimplementedTestServiceImpl : TestService.TestServiceBase |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,148 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2016, 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.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Text.RegularExpressions; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Google.Protobuf; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Utils; |
||||
using Grpc.Testing; |
||||
|
||||
namespace Grpc.IntegrationTesting |
||||
{ |
||||
public interface IInterarrivalTimer |
||||
{ |
||||
void WaitForNext(); |
||||
|
||||
Task WaitForNextAsync(); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Interarrival timer that doesn't wait at all. |
||||
/// </summary> |
||||
public class ClosedLoopInterarrivalTimer : IInterarrivalTimer |
||||
{ |
||||
public ClosedLoopInterarrivalTimer() |
||||
{ |
||||
} |
||||
|
||||
public void WaitForNext() |
||||
{ |
||||
// NOP |
||||
} |
||||
|
||||
public Task WaitForNextAsync() |
||||
{ |
||||
return Task.FromResult<object>(null); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Interarrival timer that generates Poisson process load. |
||||
/// </summary> |
||||
public class PoissonInterarrivalTimer : IInterarrivalTimer |
||||
{ |
||||
readonly ExponentialDistribution exponentialDistribution; |
||||
DateTime? lastEventTime; |
||||
|
||||
public PoissonInterarrivalTimer(double offeredLoad) |
||||
{ |
||||
this.exponentialDistribution = new ExponentialDistribution(new Random(), offeredLoad); |
||||
this.lastEventTime = DateTime.UtcNow; |
||||
} |
||||
|
||||
public void WaitForNext() |
||||
{ |
||||
var waitDuration = GetNextWaitDuration(); |
||||
int millisTimeout = (int) Math.Round(waitDuration.TotalMilliseconds); |
||||
if (millisTimeout > 0) |
||||
{ |
||||
// TODO(jtattermusch): probably only works well for a relatively low interarrival rate |
||||
Thread.Sleep(millisTimeout); |
||||
} |
||||
} |
||||
|
||||
public async Task WaitForNextAsync() |
||||
{ |
||||
var waitDuration = GetNextWaitDuration(); |
||||
int millisTimeout = (int) Math.Round(waitDuration.TotalMilliseconds); |
||||
if (millisTimeout > 0) |
||||
{ |
||||
// TODO(jtattermusch): probably only works well for a relatively low interarrival rate |
||||
await Task.Delay(millisTimeout); |
||||
} |
||||
} |
||||
|
||||
private TimeSpan GetNextWaitDuration() |
||||
{ |
||||
if (!lastEventTime.HasValue) |
||||
{ |
||||
this.lastEventTime = DateTime.Now; |
||||
} |
||||
|
||||
var origLastEventTime = this.lastEventTime.Value; |
||||
this.lastEventTime = origLastEventTime + TimeSpan.FromSeconds(exponentialDistribution.Next()); |
||||
return this.lastEventTime.Value - origLastEventTime; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Exp generator. |
||||
/// </summary> |
||||
private class ExponentialDistribution |
||||
{ |
||||
readonly Random random; |
||||
readonly double lambda; |
||||
readonly double lambdaReciprocal; |
||||
|
||||
public ExponentialDistribution(Random random, double lambda) |
||||
{ |
||||
this.random = random; |
||||
this.lambda = lambda; |
||||
this.lambdaReciprocal = 1.0 / lambda; |
||||
} |
||||
|
||||
public double Next() |
||||
{ |
||||
double uniform = random.NextDouble(); |
||||
// Use 1.0-uni above to avoid NaN if uni is 0 |
||||
return lambdaReciprocal * (-Math.Log(1.0 - uniform)); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,151 @@ |
||||
/*
|
||||
* |
||||
* 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. |
||||
* |
||||
*/ |
||||
|
||||
#include "test/core/bad_client/bad_client.h" |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
|
||||
#include "src/core/lib/surface/server.h" |
||||
#include "test/core/end2end/cq_verifier.h" |
||||
|
||||
static const char prefix[] = |
||||
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" |
||||
// settings frame
|
||||
"\x00\x00\x00\x04\x00\x00\x00\x00\x00" |
||||
// stream 1 headers: generated from server_registered_method.headers in this
|
||||
// directory
|
||||
"\x00\x00\xd0\x01\x04\x00\x00\x00\x01" |
||||
"\x10\x05:path\x0f/registered/bar" |
||||
"\x10\x07:scheme\x04http" |
||||
"\x10\x07:method\x04POST" |
||||
"\x10\x0a:authority\x09localhost" |
||||
"\x10\x0c" |
||||
"content-type\x10" |
||||
"application/grpc" |
||||
"\x10\x14grpc-accept-encoding\x15identity,deflate,gzip" |
||||
"\x10\x02te\x08trailers" |
||||
"\x10\x0auser-agent\"bad-client grpc-c/0.12.0.0 (linux)" |
||||
// data frame for stream 1: advertise a 10000 byte payload (that we won't
|
||||
// fulfill)
|
||||
"\x00\x00\x05\x00\x00\x00\x00\x00\x01" |
||||
"\x01\x00\x00\x27\x10" |
||||
// stream 3 headers: generated from server_registered_method.headers in this
|
||||
// directory
|
||||
"\x00\x00\xd0\x01\x04\x00\x00\x00\x03" |
||||
"\x10\x05:path\x0f/registered/bar" |
||||
"\x10\x07:scheme\x04http" |
||||
"\x10\x07:method\x04POST" |
||||
"\x10\x0a:authority\x09localhost" |
||||
"\x10\x0c" |
||||
"content-type\x10" |
||||
"application/grpc" |
||||
"\x10\x14grpc-accept-encoding\x15identity,deflate,gzip" |
||||
"\x10\x02te\x08trailers" |
||||
"\x10\x0auser-agent\"bad-client grpc-c/0.12.0.0 (linux)" |
||||
// data frame for stream 3: advertise a 10000 byte payload (that we will
|
||||
// fulfill)
|
||||
"\x00\x00\x05\x00\x00\x00\x00\x00\x03" |
||||
"\x01\x00\x00\x27\x10" |
||||
""; |
||||
|
||||
static void *tag(intptr_t t) { return (void *)t; } |
||||
|
||||
static void verifier(grpc_server *server, grpc_completion_queue *cq, |
||||
void *registered_method) { |
||||
grpc_call_error error; |
||||
grpc_call *s; |
||||
cq_verifier *cqv = cq_verifier_create(cq); |
||||
grpc_metadata_array request_metadata_recv; |
||||
gpr_timespec deadline; |
||||
grpc_byte_buffer *payload = NULL; |
||||
|
||||
grpc_metadata_array_init(&request_metadata_recv); |
||||
|
||||
error = grpc_server_request_registered_call(server, registered_method, &s, |
||||
&deadline, &request_metadata_recv, |
||||
&payload, cq, cq, tag(101)); |
||||
GPR_ASSERT(GRPC_CALL_OK == error); |
||||
cq_expect_completion(cqv, tag(101), 1); |
||||
cq_verify(cqv); |
||||
|
||||
GPR_ASSERT(payload != NULL); |
||||
|
||||
grpc_metadata_array_destroy(&request_metadata_recv); |
||||
grpc_call_destroy(s); |
||||
grpc_byte_buffer_destroy(payload); |
||||
cq_verifier_destroy(cqv); |
||||
} |
||||
|
||||
char *g_buffer; |
||||
size_t g_cap = 0; |
||||
size_t g_count = 0; |
||||
|
||||
static void addbuf(const void *data, size_t len) { |
||||
if (g_count + len > g_cap) { |
||||
g_cap = GPR_MAX(g_count + len, g_cap * 2); |
||||
g_buffer = gpr_realloc(g_buffer, g_cap); |
||||
} |
||||
memcpy(g_buffer + g_count, data, len); |
||||
g_count += len; |
||||
} |
||||
|
||||
int main(int argc, char **argv) { |
||||
int i; |
||||
grpc_test_init(argc, argv); |
||||
|
||||
#define NUM_FRAMES 10 |
||||
#define FRAME_SIZE 1000 |
||||
|
||||
addbuf(prefix, sizeof(prefix) - 1); |
||||
for (i = 0; i < NUM_FRAMES; i++) { |
||||
uint8_t hdr[9] = {(uint8_t)(FRAME_SIZE >> 16), |
||||
(uint8_t)(FRAME_SIZE >> 8), |
||||
(uint8_t)FRAME_SIZE, |
||||
0, |
||||
0, |
||||
0, |
||||
0, |
||||
0, |
||||
3}; |
||||
uint8_t msg[FRAME_SIZE]; |
||||
memset(msg, 'a', sizeof(msg)); |
||||
addbuf(hdr, sizeof(hdr)); |
||||
addbuf(msg, FRAME_SIZE); |
||||
} |
||||
grpc_run_bad_client_test(verifier, g_buffer, g_count, 0); |
||||
gpr_free(g_buffer); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,28 @@ |
||||
# Copyright 2016, 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. |
@ -0,0 +1,153 @@ |
||||
# Copyright 2016, 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. |
||||
|
||||
# performance scenario configuration for various languages |
||||
|
||||
class CXXLanguage: |
||||
|
||||
def __init__(self): |
||||
self.safename = 'cxx' |
||||
|
||||
def worker_cmdline(self): |
||||
return ['bins/opt/qps_worker'] |
||||
|
||||
def worker_port_offset(self): |
||||
return 0 |
||||
|
||||
def scenarios(self): |
||||
# TODO(jtattermusch): add more scenarios |
||||
return { |
||||
# Scenario 1: generic async streaming ping-pong (contentionless latency) |
||||
'cpp_async_generic_streaming_ping_pong': [ |
||||
'--rpc_type=STREAMING', |
||||
'--client_type=ASYNC_CLIENT', |
||||
'--server_type=ASYNC_GENERIC_SERVER', |
||||
'--outstanding_rpcs_per_channel=1', |
||||
'--client_channels=1', |
||||
'--bbuf_req_size=0', |
||||
'--bbuf_resp_size=0', |
||||
'--async_client_threads=1', |
||||
'--async_server_threads=1', |
||||
'--secure_test=true', |
||||
'--num_servers=1', |
||||
'--num_clients=1', |
||||
'--server_core_limit=0', |
||||
'--client_core_limit=0'], |
||||
# Scenario 5: Sync unary ping-pong with protobufs |
||||
'cpp_sync_unary_ping_pong_protobuf': [ |
||||
'--rpc_type=UNARY', |
||||
'--client_type=SYNC_CLIENT', |
||||
'--server_type=SYNC_SERVER', |
||||
'--outstanding_rpcs_per_channel=1', |
||||
'--client_channels=1', |
||||
'--simple_req_size=0', |
||||
'--simple_resp_size=0', |
||||
'--secure_test=true', |
||||
'--num_servers=1', |
||||
'--num_clients=1', |
||||
'--server_core_limit=0', |
||||
'--client_core_limit=0']} |
||||
|
||||
def __str__(self): |
||||
return 'c++' |
||||
|
||||
|
||||
class CSharpLanguage: |
||||
|
||||
def __init__(self): |
||||
self.safename = str(self) |
||||
|
||||
def worker_cmdline(self): |
||||
return ['tools/run_tests/performance/run_worker_csharp.sh'] |
||||
|
||||
def worker_port_offset(self): |
||||
return 100 |
||||
|
||||
def scenarios(self): |
||||
# TODO(jtattermusch): add more scenarios |
||||
return { |
||||
# Scenario 1: generic async streaming ping-pong (contentionless latency) |
||||
'csharp_async_generic_streaming_ping_pong': [ |
||||
'--rpc_type=STREAMING', |
||||
'--client_type=ASYNC_CLIENT', |
||||
'--server_type=ASYNC_GENERIC_SERVER', |
||||
'--outstanding_rpcs_per_channel=1', |
||||
'--client_channels=1', |
||||
'--bbuf_req_size=0', |
||||
'--bbuf_resp_size=0', |
||||
'--async_client_threads=1', |
||||
'--async_server_threads=1', |
||||
'--secure_test=true', |
||||
'--num_servers=1', |
||||
'--num_clients=1', |
||||
'--server_core_limit=0', |
||||
'--client_core_limit=0']} |
||||
|
||||
def __str__(self): |
||||
return 'csharp' |
||||
|
||||
|
||||
class NodeLanguage: |
||||
|
||||
def __init__(self): |
||||
pass |
||||
self.safename = str(self) |
||||
|
||||
def worker_cmdline(self): |
||||
return ['tools/run_tests/performance/run_worker_node.sh'] |
||||
|
||||
def worker_port_offset(self): |
||||
return 200 |
||||
|
||||
def scenarios(self): |
||||
# TODO(jtattermusch): add more scenarios |
||||
return { |
||||
'node_sync_unary_ping_pong_protobuf': [ |
||||
'--rpc_type=UNARY', |
||||
'--client_type=ASYNC_CLIENT', |
||||
'--server_type=ASYNC_SERVER', |
||||
'--outstanding_rpcs_per_channel=1', |
||||
'--client_channels=1', |
||||
'--simple_req_size=0', |
||||
'--simple_resp_size=0', |
||||
'--secure_test=false', |
||||
'--num_servers=1', |
||||
'--num_clients=1', |
||||
'--server_core_limit=0', |
||||
'--client_core_limit=0']} |
||||
|
||||
def __str__(self): |
||||
return 'node' |
||||
|
||||
|
||||
LANGUAGES = { |
||||
'c++' : CXXLanguage(), |
||||
'csharp' : CSharpLanguage(), |
||||
'node' : NodeLanguage(), |
||||
} |
@ -0,0 +1,202 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\1.0.204.1.props')" /> |
||||
<ItemGroup Label="ProjectConfigurations"> |
||||
<ProjectConfiguration Include="Debug|Win32"> |
||||
<Configuration>Debug</Configuration> |
||||
<Platform>Win32</Platform> |
||||
</ProjectConfiguration> |
||||
<ProjectConfiguration Include="Debug|x64"> |
||||
<Configuration>Debug</Configuration> |
||||
<Platform>x64</Platform> |
||||
</ProjectConfiguration> |
||||
<ProjectConfiguration Include="Release|Win32"> |
||||
<Configuration>Release</Configuration> |
||||
<Platform>Win32</Platform> |
||||
</ProjectConfiguration> |
||||
<ProjectConfiguration Include="Release|x64"> |
||||
<Configuration>Release</Configuration> |
||||
<Platform>x64</Platform> |
||||
</ProjectConfiguration> |
||||
</ItemGroup> |
||||
<PropertyGroup Label="Globals"> |
||||
<ProjectGuid>{23DF0572-DBF1-08DA-8EAD-8508354C90A4}</ProjectGuid> |
||||
<IgnoreWarnIntDirInTempDetected>true</IgnoreWarnIntDirInTempDetected> |
||||
<IntDir>$(SolutionDir)IntDir\$(MSBuildProjectName)\</IntDir> |
||||
</PropertyGroup> |
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> |
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '10.0'" Label="Configuration"> |
||||
<PlatformToolset>v100</PlatformToolset> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '11.0'" Label="Configuration"> |
||||
<PlatformToolset>v110</PlatformToolset> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '12.0'" Label="Configuration"> |
||||
<PlatformToolset>v120</PlatformToolset> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '14.0'" Label="Configuration"> |
||||
<PlatformToolset>v140</PlatformToolset> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration"> |
||||
<ConfigurationType>Application</ConfigurationType> |
||||
<UseDebugLibraries>true</UseDebugLibraries> |
||||
<CharacterSet>Unicode</CharacterSet> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration"> |
||||
<ConfigurationType>Application</ConfigurationType> |
||||
<UseDebugLibraries>false</UseDebugLibraries> |
||||
<WholeProgramOptimization>true</WholeProgramOptimization> |
||||
<CharacterSet>Unicode</CharacterSet> |
||||
</PropertyGroup> |
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> |
||||
<ImportGroup Label="ExtensionSettings"> |
||||
</ImportGroup> |
||||
<ImportGroup Label="PropertySheets"> |
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\global.props" /> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\openssl.props" /> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\winsock.props" /> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\zlib.props" /> |
||||
</ImportGroup> |
||||
<PropertyGroup Label="UserMacros" /> |
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> |
||||
<TargetName>head_of_line_blocking_bad_client_test</TargetName> |
||||
<Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib> |
||||
<Configuration-grpc_dependencies_zlib>Debug</Configuration-grpc_dependencies_zlib> |
||||
<Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl> |
||||
<Configuration-grpc_dependencies_openssl>Debug</Configuration-grpc_dependencies_openssl> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'"> |
||||
<TargetName>head_of_line_blocking_bad_client_test</TargetName> |
||||
<Linkage-grpc_dependencies_zlib>static</Linkage-grpc_dependencies_zlib> |
||||
<Configuration-grpc_dependencies_zlib>Release</Configuration-grpc_dependencies_zlib> |
||||
<Linkage-grpc_dependencies_openssl>static</Linkage-grpc_dependencies_openssl> |
||||
<Configuration-grpc_dependencies_openssl>Release</Configuration-grpc_dependencies_openssl> |
||||
</PropertyGroup> |
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||||
<ClCompile> |
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<Optimization>Disabled</Optimization> |
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<SDLCheck>true</SDLCheck> |
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> |
||||
<TreatWarningAsError>true</TreatWarningAsError> |
||||
<DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat> |
||||
<MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild> |
||||
</ClCompile> |
||||
<Link> |
||||
<SubSystem>Console</SubSystem> |
||||
<GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation> |
||||
<GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation> |
||||
</Link> |
||||
</ItemDefinitionGroup> |
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
||||
<ClCompile> |
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<Optimization>Disabled</Optimization> |
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<SDLCheck>true</SDLCheck> |
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> |
||||
<TreatWarningAsError>true</TreatWarningAsError> |
||||
<DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat> |
||||
<MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild> |
||||
</ClCompile> |
||||
<Link> |
||||
<SubSystem>Console</SubSystem> |
||||
<GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation> |
||||
<GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation> |
||||
</Link> |
||||
</ItemDefinitionGroup> |
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||||
<ClCompile> |
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<Optimization>MaxSpeed</Optimization> |
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<FunctionLevelLinking>true</FunctionLevelLinking> |
||||
<IntrinsicFunctions>true</IntrinsicFunctions> |
||||
<SDLCheck>true</SDLCheck> |
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
||||
<TreatWarningAsError>true</TreatWarningAsError> |
||||
<DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat> |
||||
<MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild> |
||||
</ClCompile> |
||||
<Link> |
||||
<SubSystem>Console</SubSystem> |
||||
<GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation> |
||||
<GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation> |
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding> |
||||
<OptimizeReferences>true</OptimizeReferences> |
||||
</Link> |
||||
</ItemDefinitionGroup> |
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||||
<ClCompile> |
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<Optimization>MaxSpeed</Optimization> |
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<FunctionLevelLinking>true</FunctionLevelLinking> |
||||
<IntrinsicFunctions>true</IntrinsicFunctions> |
||||
<SDLCheck>true</SDLCheck> |
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
||||
<TreatWarningAsError>true</TreatWarningAsError> |
||||
<DebugInformationFormat Condition="$(Jenkins)">None</DebugInformationFormat> |
||||
<MinimalRebuild Condition="$(Jenkins)">false</MinimalRebuild> |
||||
</ClCompile> |
||||
<Link> |
||||
<SubSystem>Console</SubSystem> |
||||
<GenerateDebugInformation Condition="!$(Jenkins)">true</GenerateDebugInformation> |
||||
<GenerateDebugInformation Condition="$(Jenkins)">false</GenerateDebugInformation> |
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding> |
||||
<OptimizeReferences>true</OptimizeReferences> |
||||
</Link> |
||||
</ItemDefinitionGroup> |
||||
|
||||
<ItemGroup> |
||||
<ClCompile Include="$(SolutionDir)\..\test\core\bad_client\tests\head_of_line_blocking.c"> |
||||
</ClCompile> |
||||
</ItemGroup> |
||||
<ItemGroup> |
||||
<ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\test/bad_client\bad_client_test\bad_client_test.vcxproj"> |
||||
<Project>{BA67B418-B699-E41A-9CC4-0279C49481A5}</Project> |
||||
</ProjectReference> |
||||
<ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc_test_util_unsecure\grpc_test_util_unsecure.vcxproj"> |
||||
<Project>{0A7E7F92-FDEA-40F1-A9EC-3BA484F98BBF}</Project> |
||||
</ProjectReference> |
||||
<ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\grpc_unsecure\grpc_unsecure.vcxproj"> |
||||
<Project>{46CEDFFF-9692-456A-AA24-38B5D6BCF4C5}</Project> |
||||
</ProjectReference> |
||||
<ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr_test_util\gpr_test_util.vcxproj"> |
||||
<Project>{EAB0A629-17A9-44DB-B5FF-E91A721FE037}</Project> |
||||
</ProjectReference> |
||||
<ProjectReference Include="$(SolutionDir)\..\vsprojects\vcxproj\.\gpr\gpr.vcxproj"> |
||||
<Project>{B23D3D1A-9438-4EDA-BEB6-9A0A03D17792}</Project> |
||||
</ProjectReference> |
||||
</ItemGroup> |
||||
<ItemGroup> |
||||
<None Include="packages.config" /> |
||||
</ItemGroup> |
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |
||||
<ImportGroup Label="ExtensionTargets"> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" /> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies\grpc.dependencies.zlib.targets')" /> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" /> |
||||
<Import Project="$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets" Condition="Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies\grpc.dependencies.openssl.targets')" /> |
||||
</ImportGroup> |
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> |
||||
<PropertyGroup> |
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> |
||||
</PropertyGroup> |
||||
<Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\native\grpc.dependencies.zlib.redist.targets')" /> |
||||
<Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.zlib.1.2.8.10\build\native\grpc.dependencies.zlib.targets')" /> |
||||
<Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\native\grpc.dependencies.openssl.redist.targets')" /> |
||||
<Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.props')" /> |
||||
<Error Condition="!Exists('$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\..\vsprojects\packages\grpc.dependencies.openssl.1.0.204.1\build\native\grpc.dependencies.openssl.targets')" /> |
||||
</Target> |
||||
</Project> |
||||
|
@ -0,0 +1,24 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
||||
<ClCompile Include="$(SolutionDir)\..\test\core\bad_client\tests\head_of_line_blocking.c"> |
||||
<Filter>test\core\bad_client\tests</Filter> |
||||
</ClCompile> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<Filter Include="test"> |
||||
<UniqueIdentifier>{c7d7f2b5-9afd-5668-b11f-ceb3a3503569}</UniqueIdentifier> |
||||
</Filter> |
||||
<Filter Include="test\core"> |
||||
<UniqueIdentifier>{3175d310-96bd-0c78-72e3-b5985873fa82}</UniqueIdentifier> |
||||
</Filter> |
||||
<Filter Include="test\core\bad_client"> |
||||
<UniqueIdentifier>{d7e592e2-acda-4572-59b7-20845fb05bd5}</UniqueIdentifier> |
||||
</Filter> |
||||
<Filter Include="test\core\bad_client\tests"> |
||||
<UniqueIdentifier>{1fa3207b-dc88-d316-7c13-9ac70ddc850e}</UniqueIdentifier> |
||||
</Filter> |
||||
</ItemGroup> |
||||
</Project> |
||||
|
Loading…
Reference in new issue