aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/csharp
diff options
context:
space:
mode:
authorGravatar David Garcia Quintas <dgq@google.com>2015-12-16 17:36:04 -0800
committerGravatar David Garcia Quintas <dgq@google.com>2015-12-16 17:36:04 -0800
commit7052ac25e60e137514d9a201a86eeb9b29b03d24 (patch)
tree2ce8f32319129e346a27d3b29a9b8d6b440cdd6c /src/csharp
parent886b7d19bafbb61e84141e66a040da8c27781c44 (diff)
parent788767a18f918131268ca88985b3547a8257e973 (diff)
Merge branch 'master' of github.com:grpc/grpc into grpclb_api
Diffstat (limited to 'src/csharp')
-rw-r--r--src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs6
-rw-r--r--src/csharp/Grpc.Core.Tests/CallOptionsTest.cs88
-rw-r--r--src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs2
-rw-r--r--src/csharp/Grpc.Core.Tests/ChannelTest.cs11
-rw-r--r--src/csharp/Grpc.Core.Tests/ClientServerTest.cs60
-rw-r--r--src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs32
-rw-r--r--src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj3
-rw-r--r--src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs105
-rw-r--r--src/csharp/Grpc.Core.Tests/MetadataTest.cs113
-rw-r--r--src/csharp/Grpc.Core.Tests/MockServiceHelper.cs7
-rw-r--r--src/csharp/Grpc.Core.Tests/PInvokeTest.cs4
-rw-r--r--src/csharp/Grpc.Core.Tests/PerformanceTest.cs8
-rw-r--r--src/csharp/Grpc.Core.Tests/SanityTest.cs125
-rw-r--r--src/csharp/Grpc.Core.Tests/ShutdownTest.cs11
-rw-r--r--src/csharp/Grpc.Core.Tests/UserAgentStringTest.cs101
-rw-r--r--src/csharp/Grpc.Core/AsyncAuthInterceptor.cs84
-rw-r--r--src/csharp/Grpc.Core/CallCredentials.cs8
-rw-r--r--src/csharp/Grpc.Core/CallOptions.cs1
-rw-r--r--src/csharp/Grpc.Core/Channel.cs46
-rw-r--r--src/csharp/Grpc.Core/ChannelOptions.cs7
-rw-r--r--src/csharp/Grpc.Core/Grpc.Core.csproj7
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCallBase.cs14
-rw-r--r--src/csharp/Grpc.Core/Internal/ClientResponseStream.cs4
-rw-r--r--src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs13
-rw-r--r--src/csharp/Grpc.Core/Internal/ServerCallHandler.cs40
-rw-r--r--src/csharp/Grpc.Core/Internal/ServerRequestStream.cs2
-rw-r--r--src/csharp/Grpc.Core/Internal/Timespec.cs23
-rw-r--r--src/csharp/Grpc.Core/Server.cs12
-rw-r--r--src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs12
-rw-r--r--src/csharp/Grpc.Examples/Grpc.Examples.csproj3
-rw-r--r--src/csharp/Grpc.Examples/proto/math.proto80
-rw-r--r--src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj3
-rw-r--r--src/csharp/Grpc.HealthCheck/proto/health.proto52
-rw-r--r--src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj4
-rw-r--r--src/csharp/Grpc.IntegrationTesting/HeaderInterceptorTest.cs113
-rw-r--r--src/csharp/Grpc.IntegrationTesting/InteropClient.cs119
-rw-r--r--src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs22
-rw-r--r--src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs54
-rw-r--r--src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs7
-rw-r--r--src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs39
-rw-r--r--src/csharp/Grpc.IntegrationTesting/packages.config1
-rw-r--r--src/csharp/ext/grpc_csharp_ext.c7
-rwxr-xr-xsrc/csharp/generate_proto_csharp.sh22
-rw-r--r--src/csharp/tests.json45
44 files changed, 1140 insertions, 380 deletions
diff --git a/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs b/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs
index 1c14c5bb5b..f77e9c6573 100644
--- a/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs
+++ b/src/csharp/Grpc.Auth/GoogleAuthInterceptors.cs
@@ -57,9 +57,9 @@ namespace Grpc.Auth
/// <returns>The interceptor.</returns>
public static AsyncAuthInterceptor FromCredential(ITokenAccess credential)
{
- return new AsyncAuthInterceptor(async (authUri, metadata) =>
+ return new AsyncAuthInterceptor(async (context, metadata) =>
{
- var accessToken = await credential.GetAccessTokenForRequestAsync(authUri, CancellationToken.None).ConfigureAwait(false);
+ var accessToken = await credential.GetAccessTokenForRequestAsync(context.ServiceUrl, CancellationToken.None).ConfigureAwait(false);
metadata.Add(CreateBearerTokenHeader(accessToken));
});
}
@@ -72,7 +72,7 @@ namespace Grpc.Auth
public static AsyncAuthInterceptor FromAccessToken(string accessToken)
{
Preconditions.CheckNotNull(accessToken);
- return new AsyncAuthInterceptor(async (authUri, metadata) =>
+ return new AsyncAuthInterceptor(async (context, metadata) =>
{
metadata.Add(CreateBearerTokenHeader(accessToken));
});
diff --git a/src/csharp/Grpc.Core.Tests/CallOptionsTest.cs b/src/csharp/Grpc.Core.Tests/CallOptionsTest.cs
new file mode 100644
index 0000000000..a3a613be74
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/CallOptionsTest.cs
@@ -0,0 +1,88 @@
+#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.Collections.Generic;
+using System.Threading;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class CallOptionsTest
+ {
+ [Test]
+ public void WithMethods()
+ {
+ var options = new CallOptions();
+
+ var metadata = new Metadata();
+ Assert.AreSame(metadata, options.WithHeaders(metadata).Headers);
+
+ var deadline = DateTime.UtcNow;
+ Assert.AreEqual(deadline, options.WithDeadline(deadline).Deadline.Value);
+
+ var token = new CancellationTokenSource().Token;
+ Assert.AreEqual(token, options.WithCancellationToken(token).CancellationToken);
+
+ // Change original instance is unchanged.
+ Assert.IsNull(options.Headers);
+ Assert.IsNull(options.Deadline);
+ Assert.AreEqual(CancellationToken.None, options.CancellationToken);
+ Assert.IsNull(options.WriteOptions);
+ Assert.IsNull(options.PropagationToken);
+ Assert.IsNull(options.Credentials);
+ }
+
+ [Test]
+ public void Normalize()
+ {
+ Assert.AreSame(Metadata.Empty, new CallOptions().Normalize().Headers);
+ Assert.AreEqual(DateTime.MaxValue, new CallOptions().Normalize().Deadline.Value);
+
+ var deadline = DateTime.UtcNow;
+ var propagationToken1 = new ContextPropagationToken(CallSafeHandle.NullInstance, deadline, CancellationToken.None,
+ new ContextPropagationOptions(propagateDeadline: true, propagateCancellation: false));
+ Assert.AreEqual(deadline, new CallOptions(propagationToken: propagationToken1).Normalize().Deadline.Value);
+ Assert.Throws(typeof(ArgumentException), () => new CallOptions(deadline: deadline, propagationToken: propagationToken1).Normalize());
+
+ var token = new CancellationTokenSource().Token;
+ var propagationToken2 = new ContextPropagationToken(CallSafeHandle.NullInstance, deadline, token,
+ new ContextPropagationOptions(propagateDeadline: false, propagateCancellation: true));
+ Assert.AreEqual(token, new CallOptions(propagationToken: propagationToken2).Normalize().CancellationToken);
+ Assert.Throws(typeof(ArgumentException), () => new CallOptions(cancellationToken: token, propagationToken: propagationToken2).Normalize());
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs
index 52be77c846..d2b5a436fd 100644
--- a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs
@@ -38,7 +38,7 @@ using Grpc.Core.Internal;
using Grpc.Core.Utils;
using NUnit.Framework;
-namespace Grpc.Core.Internal.Tests
+namespace Grpc.Core.Tests
{
public class ChannelOptionsTest
{
diff --git a/src/csharp/Grpc.Core.Tests/ChannelTest.cs b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
index f4ae9abefd..ed0ec14df5 100644
--- a/src/csharp/Grpc.Core.Tests/ChannelTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
@@ -48,6 +48,17 @@ namespace Grpc.Core.Tests
}
[Test]
+ public void Constructor_RejectsDuplicateOptions()
+ {
+ var options = new ChannelOption[]
+ {
+ new ChannelOption(ChannelOptions.PrimaryUserAgentString, "ABC"),
+ new ChannelOption(ChannelOptions.PrimaryUserAgentString, "XYZ")
+ };
+ Assert.Throws(typeof(ArgumentException), () => new Channel("127.0.0.1", ChannelCredentials.Insecure, options));
+ }
+
+ [Test]
public void State_IdleAfterCreation()
{
var channel = new Channel("localhost", ChannelCredentials.Insecure);
diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index 25a5a27c8e..77f6a63156 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -32,6 +32,7 @@
#endregion
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@@ -144,6 +145,45 @@ namespace Grpc.Core.Tests
var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
await call.RequestStream.WriteAllAsync(new string[] { "A", "B", "C" });
Assert.AreEqual("ABC", await call.ResponseAsync);
+
+ Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
+ Assert.IsNotNull(call.GetTrailers());
+ }
+
+ [Test]
+ public async Task ServerStreamingCall()
+ {
+ helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+ {
+ await responseStream.WriteAllAsync(request.Split(new []{' '}));
+ context.ResponseTrailers.Add("xyz", "");
+ });
+
+ var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "A B C");
+ CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, await call.ResponseStream.ToListAsync());
+
+ Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
+ Assert.IsNotNull("xyz", call.GetTrailers()[0].Key);
+ }
+
+ [Test]
+ public async Task DuplexStreamingCall()
+ {
+ helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
+ {
+ while (await requestStream.MoveNext())
+ {
+ await responseStream.WriteAsync(requestStream.Current);
+ }
+ context.ResponseTrailers.Add("xyz", "xyz-value");
+ });
+
+ var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall());
+ await call.RequestStream.WriteAllAsync(new string[] { "A", "B", "C" });
+ CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, await call.ResponseStream.ToListAsync());
+
+ Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
+ Assert.IsNotNull("xyz-value", call.GetTrailers()[0].Value);
}
[Test]
@@ -201,7 +241,7 @@ namespace Grpc.Core.Tests
Assert.AreEqual(headers[1].Key, trailers[1].Key);
CollectionAssert.AreEqual(headers[1].ValueBytes, trailers[1].ValueBytes);
}
-
+
[Test]
public void UnknownMethodHandler()
{
@@ -219,27 +259,27 @@ namespace Grpc.Core.Tests
}
[Test]
- public void UserAgentStringPresent()
+ public void ServerCallContext_PeerInfoPresent()
{
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
- return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
+ return context.Peer;
});
- string userAgent = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
- Assert.IsTrue(userAgent.StartsWith("grpc-csharp/"));
+ string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
+ Assert.IsTrue(peer.Contains(Host));
}
[Test]
- public void PeerInfoPresent()
+ public void ServerCallContext_HostAndMethodPresent()
{
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
- return context.Peer;
+ Assert.IsTrue(context.Host.Contains(Host));
+ Assert.AreEqual("/tests.Test/Unary", context.Method);
+ return "PASS";
});
-
- string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
- Assert.IsTrue(peer.Contains(Host));
+ Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
}
[Test]
diff --git a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
index 2db3f286f7..90c510ec61 100644
--- a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
@@ -69,11 +69,19 @@ namespace Grpc.Core.Tests
[Test]
public async Task PropagateCancellation()
{
+ var readyToCancelTcs = new TaskCompletionSource<object>();
+ var successTcs = new TaskCompletionSource<string>();
+
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
- // check that we didn't obtain the default cancellation token.
- Assert.IsTrue(context.CancellationToken.CanBeCanceled);
- return "PASS";
+ readyToCancelTcs.SetResult(null); // child call running, ready to parent call
+
+ while (!context.CancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(10);
+ }
+ successTcs.SetResult("CHILD_CALL_CANCELLED");
+ return "";
});
helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
@@ -82,13 +90,23 @@ namespace Grpc.Core.Tests
Assert.IsNotNull(propagationToken.ParentCall);
var callOptions = new CallOptions(propagationToken: propagationToken);
- return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
+ try
+ {
+ await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
+ }
+ catch(RpcException)
+ {
+ // Child call will get cancelled, eat the exception.
+ }
+ return "";
});
var cts = new CancellationTokenSource();
- var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
- await call.RequestStream.CompleteAsync();
- Assert.AreEqual("PASS", await call);
+ var parentCall = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
+ await readyToCancelTcs.Task;
+ cts.Cancel();
+ Assert.Throws(typeof(RpcException), async () => await parentCall);
+ Assert.AreEqual("CHILD_CALL_CANCELLED", await successTcs.Task);
}
[Test]
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index e5ffa31989..475c792347 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -64,6 +64,8 @@
<Link>Version.cs</Link>
</Compile>
<Compile Include="CallCredentialsTest.cs" />
+ <Compile Include="CallOptionsTest.cs" />
+ <Compile Include="UserAgentStringTest.cs" />
<Compile Include="FakeCredentials.cs" />
<Compile Include="MarshallingErrorsTest.cs" />
<Compile Include="ChannelCredentialsTest.cs" />
@@ -89,6 +91,7 @@
<Compile Include="ContextPropagationTest.cs" />
<Compile Include="MetadataTest.cs" />
<Compile Include="PerformanceTest.cs" />
+ <Compile Include="SanityTest.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
diff --git a/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
index 9be5450d81..74f7f2497a 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
@@ -82,17 +82,17 @@ namespace Grpc.Core.Internal.Tests
public void ToDateTime()
{
Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
- new Timespec(IntPtr.Zero, 0).ToDateTime());
+ new Timespec(0, 0).ToDateTime());
Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 10, DateTimeKind.Utc).AddTicks(50),
- new Timespec(new IntPtr(10), 5000).ToDateTime());
+ new Timespec(10, 5000).ToDateTime());
Assert.AreEqual(new DateTime(2015, 7, 21, 4, 21, 48, DateTimeKind.Utc),
- new Timespec(new IntPtr(1437452508), 0).ToDateTime());
+ new Timespec(1437452508, 0).ToDateTime());
// before epoch
Assert.AreEqual(new DateTime(1969, 12, 31, 23, 59, 55, DateTimeKind.Utc).AddTicks(10),
- new Timespec(new IntPtr(-5), 1000).ToDateTime());
+ new Timespec(-5, 1000).ToDateTime());
// infinity
Assert.AreEqual(DateTime.MaxValue, Timespec.InfFuture.ToDateTime());
@@ -100,80 +100,62 @@ namespace Grpc.Core.Internal.Tests
// nanos are rounded to ticks are rounded up
Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddTicks(1),
- new Timespec(IntPtr.Zero, 99).ToDateTime());
+ new Timespec(0, 99).ToDateTime());
// Illegal inputs
Assert.Throws(typeof(InvalidOperationException),
- () => new Timespec(new IntPtr(0), -2).ToDateTime());
+ () => new Timespec(0, -2).ToDateTime());
Assert.Throws(typeof(InvalidOperationException),
- () => new Timespec(new IntPtr(0), 1000 * 1000 * 1000).ToDateTime());
+ () => new Timespec(0, 1000 * 1000 * 1000).ToDateTime());
Assert.Throws(typeof(InvalidOperationException),
- () => new Timespec(new IntPtr(0), 0, GPRClockType.Monotonic).ToDateTime());
+ () => new Timespec(0, 0, GPRClockType.Monotonic).ToDateTime());
}
[Test]
public void ToDateTime_ReturnsUtc()
{
- Assert.AreEqual(DateTimeKind.Utc, new Timespec(new IntPtr(1437452508), 0).ToDateTime().Kind);
- Assert.AreNotEqual(DateTimeKind.Unspecified, new Timespec(new IntPtr(1437452508), 0).ToDateTime().Kind);
+ Assert.AreEqual(DateTimeKind.Utc, new Timespec(1437452508, 0).ToDateTime().Kind);
+ Assert.AreNotEqual(DateTimeKind.Unspecified, new Timespec(1437452508, 0).ToDateTime().Kind);
}
[Test]
public void ToDateTime_Overflow()
- {
- // we can only get overflow in ticks arithmetic on 64-bit
- if (IntPtr.Size == 8)
- {
- var timespec = new Timespec(new IntPtr(long.MaxValue - 100), 0);
- Assert.AreNotEqual(Timespec.InfFuture, timespec);
- Assert.AreEqual(DateTime.MaxValue, timespec.ToDateTime());
-
- Assert.AreEqual(DateTime.MinValue, new Timespec(new IntPtr(long.MinValue + 100), 0).ToDateTime());
- }
- else
- {
- Console.WriteLine("Test cannot be run on this platform, skipping the test.");
- }
+ {
+ var timespec = new Timespec(long.MaxValue - 100, 0);
+ Assert.AreNotEqual(Timespec.InfFuture, timespec);
+ Assert.AreEqual(DateTime.MaxValue, timespec.ToDateTime());
+
+ Assert.AreEqual(DateTime.MinValue, new Timespec(long.MinValue + 100, 0).ToDateTime());
}
[Test]
public void ToDateTime_OutOfDateTimeRange()
{
- // we can only get out of range on 64-bit, on 32 bit the max
- // timestamp is ~ Jan 19 2038, which is far within range of DateTime
- // same case for min value.
- if (IntPtr.Size == 8)
- {
- // DateTime range goes up to year 9999, 20000 years from now should
- // be out of range.
- long seconds = 20000L * 365L * 24L * 3600L;
-
- var timespec = new Timespec(new IntPtr(seconds), 0);
- Assert.AreNotEqual(Timespec.InfFuture, timespec);
- Assert.AreEqual(DateTime.MaxValue, timespec.ToDateTime());
-
- Assert.AreEqual(DateTime.MinValue, new Timespec(new IntPtr(-seconds), 0).ToDateTime());
- }
- else
- {
- Console.WriteLine("Test cannot be run on this platform, skipping the test");
- }
+ // DateTime range goes up to year 9999, 20000 years from now should
+ // be out of range.
+ long seconds = 20000L * 365L * 24L * 3600L;
+
+ var timespec = new Timespec(seconds, 0);
+ Assert.AreNotEqual(Timespec.InfFuture, timespec);
+ Assert.AreEqual(DateTime.MaxValue, timespec.ToDateTime());
+
+ Assert.AreEqual(DateTime.MinValue, new Timespec(-seconds, 0).ToDateTime());
}
[Test]
public void FromDateTime()
{
- Assert.AreEqual(new Timespec(IntPtr.Zero, 0),
+ Assert.AreEqual(new Timespec(0, 0),
Timespec.FromDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)));
- Assert.AreEqual(new Timespec(new IntPtr(10), 5000),
+ Assert.AreEqual(new Timespec(10, 5000),
Timespec.FromDateTime(new DateTime(1970, 1, 1, 0, 0, 10, DateTimeKind.Utc).AddTicks(50)));
- Assert.AreEqual(new Timespec(new IntPtr(1437452508), 0),
+ Assert.AreEqual(new Timespec(1437452508, 0),
Timespec.FromDateTime(new DateTime(2015, 7, 21, 4, 21, 48, DateTimeKind.Utc)));
// before epoch
- Assert.AreEqual(new Timespec(new IntPtr(-5), 1000),
+ Assert.AreEqual(new Timespec(-5, 1000),
Timespec.FromDateTime(new DateTime(1969, 12, 31, 23, 59, 55, DateTimeKind.Utc).AddTicks(10)));
// infinity
@@ -184,34 +166,19 @@ namespace Grpc.Core.Internal.Tests
Assert.Throws(typeof(ArgumentException),
() => Timespec.FromDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)));
}
-
- [Test]
- public void FromDateTime_OutOfTimespecRange()
- {
- // we can only get overflow in Timespec on 32-bit
- if (IntPtr.Size == 4)
- {
- Assert.AreEqual(Timespec.InfFuture, Timespec.FromDateTime(new DateTime(2040, 1, 1, 0, 0, 0, DateTimeKind.Utc)));
- Assert.AreEqual(Timespec.InfPast, Timespec.FromDateTime(new DateTime(1800, 1, 1, 0, 0, 0, DateTimeKind.Utc)));
- }
- else
- {
- Console.WriteLine("Test cannot be run on this platform, skipping the test.");
- }
- }
- // Test attribute commented out to prevent running as part of the default test suite.
- // [Test]
- // [Category("Performance")]
+ [Test]
+ [Category("Performance")]
+ [Ignore("Prevent running on Jenkins")]
public void NowBenchmark()
{
// approx Timespec.Now latency <33ns
BenchmarkUtil.RunBenchmark(10000000, 1000000000, () => { var now = Timespec.Now; });
}
-
- // Test attribute commented out to prevent running as part of the default test suite.
- // [Test]
- // [Category("Performance")]
+
+ [Test]
+ [Category("Performance")]
+ [Ignore("Prevent running on Jenkins")]
public void PreciseNowBenchmark()
{
// approx Timespec.PreciseNow latency <18ns (when compiled with GRPC_TIMERS_RDTSC)
diff --git a/src/csharp/Grpc.Core.Tests/MetadataTest.cs b/src/csharp/Grpc.Core.Tests/MetadataTest.cs
index ddeb7d0926..49e9de1174 100644
--- a/src/csharp/Grpc.Core.Tests/MetadataTest.cs
+++ b/src/csharp/Grpc.Core.Tests/MetadataTest.cs
@@ -127,5 +127,118 @@ namespace Grpc.Core.Tests
Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
}
+
+ [Test]
+ public void IndexOf()
+ {
+ var metadata = CreateMetadata();
+ Assert.AreEqual(0, metadata.IndexOf(metadata[0]));
+ Assert.AreEqual(1, metadata.IndexOf(metadata[1]));
+ }
+
+ [Test]
+ public void Insert()
+ {
+ var metadata = CreateMetadata();
+ metadata.Insert(0, new Metadata.Entry("new-key", "new-value"));
+ Assert.AreEqual(3, metadata.Count);
+ Assert.AreEqual("new-key", metadata[0].Key);
+ Assert.AreEqual("abc", metadata[1].Key);
+ }
+
+ [Test]
+ public void RemoveAt()
+ {
+ var metadata = CreateMetadata();
+ metadata.RemoveAt(0);
+ Assert.AreEqual(1, metadata.Count);
+ Assert.AreEqual("xyz", metadata[0].Key);
+ }
+
+ [Test]
+ public void Remove()
+ {
+ var metadata = CreateMetadata();
+ metadata.Remove(metadata[0]);
+ Assert.AreEqual(1, metadata.Count);
+ Assert.AreEqual("xyz", metadata[0].Key);
+ }
+
+ [Test]
+ public void Indexer_Set()
+ {
+ var metadata = CreateMetadata();
+ var entry = new Metadata.Entry("new-key", "new-value");
+
+ metadata[1] = entry;
+ Assert.AreEqual(entry, metadata[1]);
+ }
+
+ [Test]
+ public void Clear()
+ {
+ var metadata = CreateMetadata();
+ metadata.Clear();
+ Assert.AreEqual(0, metadata.Count);
+ }
+
+ [Test]
+ public void Contains()
+ {
+ var metadata = CreateMetadata();
+ Assert.IsTrue(metadata.Contains(metadata[0]));
+ Assert.IsFalse(metadata.Contains(new Metadata.Entry("new-key", "new-value")));
+ }
+
+ [Test]
+ public void CopyTo()
+ {
+ var metadata = CreateMetadata();
+ var array = new Metadata.Entry[metadata.Count + 1];
+
+ metadata.CopyTo(array, 1);
+ Assert.AreEqual(default(Metadata.Entry), array[0]);
+ Assert.AreEqual(metadata[0], array[1]);
+ }
+
+ [Test]
+ public void IEnumerableGetEnumerator()
+ {
+ var metadata = CreateMetadata();
+ var enumerator = (metadata as System.Collections.IEnumerable).GetEnumerator();
+
+ int i = 0;
+ while (enumerator.MoveNext())
+ {
+ Assert.AreEqual(metadata[i], enumerator.Current);
+ i++;
+ }
+ }
+
+ [Test]
+ public void FreezeMakesReadOnly()
+ {
+ var entry = new Metadata.Entry("new-key", "new-value");
+ var metadata = CreateMetadata().Freeze();
+
+ Assert.IsTrue(metadata.IsReadOnly);
+ Assert.Throws<InvalidOperationException>(() => metadata.Insert(0, entry));
+ Assert.Throws<InvalidOperationException>(() => metadata.RemoveAt(0));
+ Assert.Throws<InvalidOperationException>(() => metadata[0] = entry);
+ Assert.Throws<InvalidOperationException>(() => metadata.Add(entry));
+ Assert.Throws<InvalidOperationException>(() => metadata.Add("new-key", "new-value"));
+ Assert.Throws<InvalidOperationException>(() => metadata.Add("new-key-bin", new byte[] { 0xaa }));
+ Assert.Throws<InvalidOperationException>(() => metadata.Clear());
+ Assert.Throws<InvalidOperationException>(() => metadata.Remove(metadata[0]));
+ }
+
+ private Metadata CreateMetadata()
+ {
+ return new Metadata
+ {
+ { "abc", "abc-value" },
+ { "xyz", "xyz-value" },
+ };
+ }
}
}
diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
index 567e04eddc..3047314345 100644
--- a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
+++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
@@ -32,6 +32,7 @@
#endregion
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@@ -52,6 +53,7 @@ namespace Grpc.Core.Tests
readonly string host;
readonly ServerServiceDefinition serviceDefinition;
+ readonly IEnumerable<ChannelOption> channelOptions;
readonly Method<string, string> unaryMethod;
readonly Method<string, string> clientStreamingMethod;
@@ -66,9 +68,10 @@ namespace Grpc.Core.Tests
Server server;
Channel channel;
- public MockServiceHelper(string host = null, Marshaller<string> marshaller = null)
+ public MockServiceHelper(string host = null, Marshaller<string> marshaller = null, IEnumerable<ChannelOption> channelOptions = null)
{
this.host = host ?? "localhost";
+ this.channelOptions = channelOptions;
marshaller = marshaller ?? Marshallers.StringMarshaller;
unaryMethod = new Method<string, string>(
@@ -154,7 +157,7 @@ namespace Grpc.Core.Tests
{
if (channel == null)
{
- channel = new Channel(Host, GetServer().Ports.Single().BoundPort, ChannelCredentials.Insecure);
+ channel = new Channel(Host, GetServer().Ports.Single().BoundPort, ChannelCredentials.Insecure, channelOptions);
}
return channel;
}
diff --git a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
index 073c502daf..af55cb0566 100644
--- a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
+++ b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs
@@ -59,6 +59,8 @@ namespace Grpc.Core.Tests
[Test]
public void CompletionQueueCreateDestroyBenchmark()
{
+ GrpcEnvironment.AddRef(); // completion queue requires gRPC environment being initialized.
+
BenchmarkUtil.RunBenchmark(
10, 10,
() =>
@@ -66,6 +68,8 @@ namespace Grpc.Core.Tests
CompletionQueueSafeHandle cq = CompletionQueueSafeHandle.Create();
cq.Dispose();
});
+
+ GrpcEnvironment.Release();
}
/// <summary>
diff --git a/src/csharp/Grpc.Core.Tests/PerformanceTest.cs b/src/csharp/Grpc.Core.Tests/PerformanceTest.cs
index 5516cd3377..6815839992 100644
--- a/src/csharp/Grpc.Core.Tests/PerformanceTest.cs
+++ b/src/csharp/Grpc.Core.Tests/PerformanceTest.cs
@@ -67,10 +67,10 @@ namespace Grpc.Core.Tests
channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
-
- // Test attribute commented out to prevent running as part of the default test suite.
- //[Test]
- //[Category("Performance")]
+
+ [Test]
+ [Category("Performance")]
+ [Ignore("Prevent running on Jenkins")]
public void UnaryCallPerformance()
{
var profiler = new BasicProfiler();
diff --git a/src/csharp/Grpc.Core.Tests/SanityTest.cs b/src/csharp/Grpc.Core.Tests/SanityTest.cs
new file mode 100644
index 0000000000..343ab1e85a
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/SanityTest.cs
@@ -0,0 +1,125 @@
+#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.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class SanityTest
+ {
+ /// <summary>
+ /// Because we depend on a native library, sometimes when things go wrong, the
+ /// entire NUnit test process crashes. To be able to track down problems better,
+ /// the NUnit tests are run by run_tests.py script in a separate process per test class.
+ /// The list of tests to run is stored in src/csharp/tests.json.
+ /// This test checks that the tests.json file is up to date by discovering all the
+ /// existing NUnit tests in all test assemblies and comparing to contents of tests.json.
+ /// </summary>
+ [Test]
+ public void TestsJsonUpToDate()
+ {
+ var testClasses = DiscoverAllTestClasses();
+ string testsJson = GetTestsJson();
+
+ // we don't have a JSON parser at hand, but check that the test class
+ // name is contained in the file instead.
+ foreach (var className in testClasses) {
+ Assert.IsTrue(testsJson.Contains(className),
+ string.Format("Test class \"{0}\" is missing in C# tests.json file", className));
+ }
+ }
+
+ /// <summary>
+ /// Gets list of all test classes obtained by inspecting all the test assemblies.
+ /// </summary>
+ private List<string> DiscoverAllTestClasses()
+ {
+ var assemblies = GetTestAssemblies();
+
+ var testClasses = new List<string>();
+ foreach (var assembly in assemblies)
+ {
+ foreach (var t in assembly.GetTypes())
+ {
+ foreach (var m in t.GetMethods())
+ {
+ var attributes = m.GetCustomAttributes(typeof(NUnit.Framework.TestAttribute), true);
+ if (attributes.Length > 0)
+ {
+ testClasses.Add(t.FullName);
+ break;
+ }
+
+ }
+ }
+ }
+ testClasses.Sort();
+ return testClasses;
+ }
+
+ private string GetTestsJson()
+ {
+ var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ var testsJsonFile = Path.Combine(assemblyDir, "..", "..", "..", "tests.json");
+
+ return File.ReadAllText(testsJsonFile);
+ }
+
+ private List<Assembly> GetTestAssemblies()
+ {
+ var result = new List<Assembly>();
+ var executingAssembly = Assembly.GetExecutingAssembly();
+
+ result.Add(executingAssembly);
+
+ var otherAssemblies = new[] {
+ "Grpc.Examples.Tests",
+ "Grpc.HealthCheck.Tests",
+ "Grpc.IntegrationTesting"
+ };
+ foreach (var assemblyName in otherAssemblies)
+ {
+ var location = executingAssembly.Location.Replace("Grpc.Core.Tests", assemblyName);
+ result.Add(Assembly.LoadFrom(location));
+ }
+ return result;
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ShutdownTest.cs b/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
index a2be7ddd5e..10d666d109 100644
--- a/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ShutdownTest.cs
@@ -61,17 +61,20 @@ namespace Grpc.Core.Tests
}
[Test]
- public async Task AbandonedCall()
+ public async Task AbandonedCall_ServerKillAsync()
{
+ var readyToShutdown = new TaskCompletionSource<object>();
helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
{
+ readyToShutdown.SetResult(null);
await requestStream.ToListAsync();
});
- var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddMilliseconds(1))));
+ var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall());
+ await readyToShutdown.Task; // make sure handler is running
- channel.ShutdownAsync().Wait();
- server.ShutdownAsync().Wait();
+ await channel.ShutdownAsync(); // channel.ShutdownAsync() works even if there's a pending call.
+ await server.KillAsync(); // server.ShutdownAsync() would hang waiting for the call to finish.
}
}
}
diff --git a/src/csharp/Grpc.Core.Tests/UserAgentStringTest.cs b/src/csharp/Grpc.Core.Tests/UserAgentStringTest.cs
new file mode 100644
index 0000000000..cc830086a6
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/UserAgentStringTest.cs
@@ -0,0 +1,101 @@
+#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.Profiling;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class UserAgentStringTest
+ {
+ const string Host = "127.0.0.1";
+
+ MockServiceHelper helper;
+ Server server;
+ Channel channel;
+
+ [TearDown]
+ public void Cleanup()
+ {
+ channel.ShutdownAsync().Wait();
+ server.ShutdownAsync().Wait();
+ }
+
+ [Test]
+ public void DefaultUserAgentString()
+ {
+ helper = new MockServiceHelper(Host);
+ server = helper.GetServer();
+ server.Start();
+ channel = helper.GetChannel();
+
+ helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
+ {
+ var userAgentString = context.RequestHeaders.First(m => (m.Key == "user-agent")).Value;
+ var parts = userAgentString.Split(new [] {' '}, 2);
+ Assert.AreEqual(string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion), parts[0]);
+ Assert.IsTrue(parts[1].StartsWith("grpc-c/"));
+ return Task.FromResult("PASS");
+ });
+ Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), ""));
+ }
+
+ [Test]
+ public void ApplicationUserAgentString()
+ {
+ helper = new MockServiceHelper(Host,
+ channelOptions: new[] { new ChannelOption(ChannelOptions.PrimaryUserAgentString, "XYZ") });
+ server = helper.GetServer();
+ server.Start();
+ channel = helper.GetChannel();
+
+ channel = helper.GetChannel();
+ helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
+ {
+ var userAgentString = context.RequestHeaders.First(m => (m.Key == "user-agent")).Value;
+ var parts = userAgentString.Split(new[] { ' ' }, 3);
+ Assert.AreEqual("XYZ", parts[0]);
+ return Task.FromResult("PASS");
+ });
+ Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), ""));
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core/AsyncAuthInterceptor.cs b/src/csharp/Grpc.Core/AsyncAuthInterceptor.cs
new file mode 100644
index 0000000000..5c9ab04812
--- /dev/null
+++ b/src/csharp/Grpc.Core/AsyncAuthInterceptor.cs
@@ -0,0 +1,84 @@
+#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.Collections.Generic;
+using System.Threading.Tasks;
+
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+ /// <summary>
+ /// Asynchronous authentication interceptor for <see cref="CallCredentials"/>.
+ /// </summary>
+ /// <param name="context">The interceptor context.</param>
+ /// <param name="metadata">Metadata to populate with entries that will be added to outgoing call's headers.</param>
+ /// <returns></returns>
+ public delegate Task AsyncAuthInterceptor(AuthInterceptorContext context, Metadata metadata);
+
+ /// <summary>
+ /// Context for an RPC being intercepted by <see cref="AsyncAuthInterceptor"/>.
+ /// </summary>
+ public class AuthInterceptorContext
+ {
+ readonly string serviceUrl;
+ readonly string methodName;
+
+ /// <summary>
+ /// Initializes a new instance of <c>AuthInterceptorContext</c>.
+ /// </summary>
+ public AuthInterceptorContext(string serviceUrl, string methodName)
+ {
+ this.serviceUrl = Preconditions.CheckNotNull(serviceUrl);
+ this.methodName = Preconditions.CheckNotNull(methodName);
+ }
+
+ /// <summary>
+ /// The fully qualified service URL for the RPC being called.
+ /// </summary>
+ public string ServiceUrl
+ {
+ get { return serviceUrl; }
+ }
+
+ /// <summary>
+ /// The method name of the RPC being called.
+ /// </summary>
+ public string MethodName
+ {
+ get { return methodName; }
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core/CallCredentials.cs b/src/csharp/Grpc.Core/CallCredentials.cs
index 5ea179dfea..a71c8904fe 100644
--- a/src/csharp/Grpc.Core/CallCredentials.cs
+++ b/src/csharp/Grpc.Core/CallCredentials.cs
@@ -41,14 +41,6 @@ using Grpc.Core.Utils;
namespace Grpc.Core
{
/// <summary>
- /// Asynchronous authentication interceptor for <see cref="CallCredentials"/>.
- /// </summary>
- /// <param name="authUri">URL of a service to which current remote call needs to authenticate</param>
- /// <param name="metadata">Metadata to populate with entries that will be added to outgoing call's headers.</param>
- /// <returns></returns>
- public delegate Task AsyncAuthInterceptor(string authUri, Metadata metadata);
-
- /// <summary>
/// Client-side call credentials. Provide authorization with per-call granularity.
/// </summary>
public abstract class CallCredentials
diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs
index c0f94c63c2..1fda80cb90 100644
--- a/src/csharp/Grpc.Core/CallOptions.cs
+++ b/src/csharp/Grpc.Core/CallOptions.cs
@@ -184,6 +184,7 @@ namespace Grpc.Core
{
Preconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled,
"Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value.");
+ newOptions.cancellationToken = propagationToken.ParentCancellationToken;
}
}
diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs
index f5eec969f5..d8d43c7998 100644
--- a/src/csharp/Grpc.Core/Channel.cs
+++ b/src/csharp/Grpc.Core/Channel.cs
@@ -32,8 +32,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading;
using System.Threading.Tasks;
using Grpc.Core.Internal;
@@ -57,7 +55,7 @@ namespace Grpc.Core
readonly string target;
readonly GrpcEnvironment environment;
readonly ChannelSafeHandle handle;
- readonly List<ChannelOption> options;
+ readonly Dictionary<string, ChannelOption> options;
bool shutdownRequested;
@@ -71,12 +69,12 @@ namespace Grpc.Core
public Channel(string target, ChannelCredentials credentials, IEnumerable<ChannelOption> options = null)
{
this.target = Preconditions.CheckNotNull(target, "target");
+ this.options = CreateOptionsDictionary(options);
+ EnsureUserAgentChannelOption(this.options);
this.environment = GrpcEnvironment.AddRef();
- this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>();
- EnsureUserAgentChannelOption(this.options);
using (var nativeCredentials = credentials.ToNativeCredentials())
- using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options))
+ using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))
{
if (nativeCredentials != null)
{
@@ -173,7 +171,7 @@ namespace Grpc.Core
{
throw new OperationCanceledException("Channel has reached FatalFailure state.");
}
- await WaitForStateChangedAsync(currentState, deadline);
+ await WaitForStateChangedAsync(currentState, deadline).ConfigureAwait(false);
currentState = handle.CheckConnectivityState(false);
}
}
@@ -198,7 +196,7 @@ namespace Grpc.Core
handle.Dispose();
- await Task.Run(() => GrpcEnvironment.Release());
+ await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false);
}
internal ChannelSafeHandle Handle
@@ -233,18 +231,36 @@ namespace Grpc.Core
activeCallCounter.Decrement();
}
- private static void EnsureUserAgentChannelOption(List<ChannelOption> options)
+ private static void EnsureUserAgentChannelOption(Dictionary<string, ChannelOption> options)
{
- if (!options.Any((option) => option.Name == ChannelOptions.PrimaryUserAgentString))
+ var key = ChannelOptions.PrimaryUserAgentString;
+ var userAgentString = "";
+
+ ChannelOption option;
+ if (options.TryGetValue(key, out option))
{
- options.Add(new ChannelOption(ChannelOptions.PrimaryUserAgentString, GetUserAgentString()));
- }
+ // user-provided userAgentString needs to be at the beginning
+ userAgentString = option.StringValue + " ";
+ };
+
+ // TODO(jtattermusch): it would be useful to also provide .NET/mono version.
+ userAgentString += string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion);
+
+ options[ChannelOptions.PrimaryUserAgentString] = new ChannelOption(key, userAgentString);
}
- private static string GetUserAgentString()
+ private static Dictionary<string, ChannelOption> CreateOptionsDictionary(IEnumerable<ChannelOption> options)
{
- // TODO(jtattermusch): it would be useful to also provide .NET/mono version.
- return string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion);
+ var dict = new Dictionary<string, ChannelOption>();
+ if (options == null)
+ {
+ return dict;
+ }
+ foreach (var option in options)
+ {
+ dict.Add(option.Name, option);
+ }
+ return dict;
}
}
}
diff --git a/src/csharp/Grpc.Core/ChannelOptions.cs b/src/csharp/Grpc.Core/ChannelOptions.cs
index f5ef63af54..d70673cf78 100644
--- a/src/csharp/Grpc.Core/ChannelOptions.cs
+++ b/src/csharp/Grpc.Core/ChannelOptions.cs
@@ -169,7 +169,7 @@ namespace Grpc.Core
/// Creates native object for a collection of channel options.
/// </summary>
/// <returns>The native channel arguments.</returns>
- internal static ChannelArgsSafeHandle CreateChannelArgs(List<ChannelOption> options)
+ internal static ChannelArgsSafeHandle CreateChannelArgs(ICollection<ChannelOption> options)
{
if (options == null || options.Count == 0)
{
@@ -179,9 +179,9 @@ namespace Grpc.Core
try
{
nativeArgs = ChannelArgsSafeHandle.Create(options.Count);
- for (int i = 0; i < options.Count; i++)
+ int i = 0;
+ foreach (var option in options)
{
- var option = options[i];
if (option.Type == ChannelOption.OptionType.Integer)
{
nativeArgs.SetInteger(i, option.Name, option.IntValue);
@@ -194,6 +194,7 @@ namespace Grpc.Core
{
throw new InvalidOperationException("Unknown option type");
}
+ i++;
}
return nativeArgs;
}
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index 56a06f4a9b..5b3da7c6c9 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -46,6 +46,7 @@
<ItemGroup>
<Compile Include="AsyncDuplexStreamingCall.cs" />
<Compile Include="AsyncServerStreamingCall.cs" />
+ <Compile Include="AsyncAuthInterceptor.cs" />
<Compile Include="CallCredentials.cs" />
<Compile Include="IClientStreamWriter.cs" />
<Compile Include="Internal\NativeMetadataCredentialsPlugin.cs" />
@@ -148,8 +149,8 @@
<Error Condition="!Exists('..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets'))" />
<Error Condition="!Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets'))" />
</Target>
- <ItemGroup />
- <ItemGroup />
<Import Project="..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets" Condition="Exists('..\packages\grpc.dependencies.openssl.redist.1.0.204.1\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.openssl.redist.targets')" />
<Import Project="..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets" Condition="Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.10\build\portable-net45+netcore45+wpa81+wp8\grpc.dependencies.zlib.redist.targets')" />
-</Project> \ No newline at end of file
+ <ItemGroup />
+ <ItemGroup />
+</Project>
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index 953f61aa1e..92f8d77e85 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -238,20 +238,6 @@ namespace Grpc.Core.Internal
}
}
- protected Exception TrySerialize(TWrite msg, out byte[] payload)
- {
- try
- {
- payload = serializer(msg);
- return null;
- }
- catch (Exception e)
- {
- payload = null;
- return e;
- }
- }
-
protected Exception TryDeserialize(byte[] payload, out TRead msg)
{
using (Profilers.ForCurrentThread().NewScope("AsyncCallBase.TryDeserialize"))
diff --git a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
index b4a7335c7c..d6e34a0f04 100644
--- a/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
@@ -70,12 +70,12 @@ namespace Grpc.Core.Internal
}
var taskSource = new AsyncCompletionTaskSource<TResponse>();
call.StartReadMessage(taskSource.CompletionDelegate);
- var result = await taskSource.Task;
+ var result = await taskSource.Task.ConfigureAwait(false);
this.current = result;
if (result == null)
{
- await call.StreamingCallFinishedTask;
+ await call.StreamingCallFinishedTask.ConfigureAwait(false);
return false;
}
return true;
diff --git a/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
index 2a571cd6c9..8bb646d303 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMetadataCredentialsPlugin.cs
@@ -38,7 +38,7 @@ using Grpc.Core.Utils;
namespace Grpc.Core.Internal
{
- internal delegate void NativeMetadataInterceptor(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy);
+ internal delegate void NativeMetadataInterceptor(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr methodNamePtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy);
internal class NativeMetadataCredentialsPlugin
{
@@ -71,7 +71,7 @@ namespace Grpc.Core.Internal
get { return credentials; }
}
- private void NativeMetadataInterceptorHandler(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy)
+ private void NativeMetadataInterceptorHandler(IntPtr statePtr, IntPtr serviceUrlPtr, IntPtr methodNamePtr, IntPtr callbackPtr, IntPtr userDataPtr, bool isDestroy)
{
if (isDestroy)
{
@@ -81,8 +81,9 @@ namespace Grpc.Core.Internal
try
{
- string serviceUrl = Marshal.PtrToStringAnsi(serviceUrlPtr);
- StartGetMetadata(serviceUrl, callbackPtr, userDataPtr);
+ var context = new AuthInterceptorContext(Marshal.PtrToStringAnsi(serviceUrlPtr),
+ Marshal.PtrToStringAnsi(methodNamePtr));
+ StartGetMetadata(context, callbackPtr, userDataPtr);
}
catch (Exception e)
{
@@ -91,12 +92,12 @@ namespace Grpc.Core.Internal
}
}
- private async void StartGetMetadata(string serviceUrl, IntPtr callbackPtr, IntPtr userDataPtr)
+ private async void StartGetMetadata(AuthInterceptorContext context, IntPtr callbackPtr, IntPtr userDataPtr)
{
try
{
var metadata = new Metadata();
- await interceptor(serviceUrl, metadata);
+ await interceptor(context, metadata).ConfigureAwait(false);
using (var metadataArray = MetadataArraySafeHandle.Create(metadata))
{
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 59f4c5727c..de66759b94 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -78,13 +78,13 @@ namespace Grpc.Core.Internal
var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
- Preconditions.CheckArgument(await requestStream.MoveNext());
+ Preconditions.CheckArgument(await requestStream.MoveNext().ConfigureAwait(false));
var request = requestStream.Current;
// TODO(jtattermusch): we need to read the full stream so that native callhandle gets deallocated.
- Preconditions.CheckArgument(!await requestStream.MoveNext());
- var result = await handler(request, context);
+ Preconditions.CheckArgument(!await requestStream.MoveNext().ConfigureAwait(false));
+ var result = await handler(request, context).ConfigureAwait(false);
status = context.Status;
- await responseStream.WriteAsync(result);
+ await responseStream.WriteAsync(result).ConfigureAwait(false);
}
catch (Exception e)
{
@@ -93,13 +93,13 @@ namespace Grpc.Core.Internal
}
try
{
- await responseStream.WriteStatusAsync(status, context.ResponseTrailers);
+ await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Call has been already cancelled.
}
- await finishedTask;
+ await finishedTask.ConfigureAwait(false);
}
}
@@ -134,11 +134,11 @@ namespace Grpc.Core.Internal
var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
- Preconditions.CheckArgument(await requestStream.MoveNext());
+ Preconditions.CheckArgument(await requestStream.MoveNext().ConfigureAwait(false));
var request = requestStream.Current;
// TODO(jtattermusch): we need to read the full stream so that native callhandle gets deallocated.
- Preconditions.CheckArgument(!await requestStream.MoveNext());
- await handler(request, responseStream, context);
+ Preconditions.CheckArgument(!await requestStream.MoveNext().ConfigureAwait(false));
+ await handler(request, responseStream, context).ConfigureAwait(false);
status = context.Status;
}
catch (Exception e)
@@ -149,13 +149,13 @@ namespace Grpc.Core.Internal
try
{
- await responseStream.WriteStatusAsync(status, context.ResponseTrailers);
+ await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Call has been already cancelled.
}
- await finishedTask;
+ await finishedTask.ConfigureAwait(false);
}
}
@@ -190,11 +190,11 @@ namespace Grpc.Core.Internal
var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
- var result = await handler(requestStream, context);
+ var result = await handler(requestStream, context).ConfigureAwait(false);
status = context.Status;
try
{
- await responseStream.WriteAsync(result);
+ await responseStream.WriteAsync(result).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -209,13 +209,13 @@ namespace Grpc.Core.Internal
try
{
- await responseStream.WriteStatusAsync(status, context.ResponseTrailers);
+ await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Call has been already cancelled.
}
- await finishedTask;
+ await finishedTask.ConfigureAwait(false);
}
}
@@ -250,7 +250,7 @@ namespace Grpc.Core.Internal
var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
- await handler(requestStream, responseStream, context);
+ await handler(requestStream, responseStream, context).ConfigureAwait(false);
status = context.Status;
}
catch (Exception e)
@@ -260,13 +260,13 @@ namespace Grpc.Core.Internal
}
try
{
- await responseStream.WriteStatusAsync(status, context.ResponseTrailers);
+ await responseStream.WriteStatusAsync(status, context.ResponseTrailers).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Call has been already cancelled.
}
- await finishedTask;
+ await finishedTask.ConfigureAwait(false);
}
}
@@ -284,8 +284,8 @@ namespace Grpc.Core.Internal
var finishedTask = asyncCall.ServerSideCallAsync();
var responseStream = new ServerResponseStream<byte[], byte[]>(asyncCall);
- await responseStream.WriteStatusAsync(new Status(StatusCode.Unimplemented, "No such method."), Metadata.Empty);
- await finishedTask;
+ await responseStream.WriteStatusAsync(new Status(StatusCode.Unimplemented, ""), Metadata.Empty).ConfigureAwait(false);
+ await finishedTask.ConfigureAwait(false);
}
}
diff --git a/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs b/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs
index 3fccb88abb..e7be82c318 100644
--- a/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerRequestStream.cs
@@ -70,7 +70,7 @@ namespace Grpc.Core.Internal
}
var taskSource = new AsyncCompletionTaskSource<TRequest>();
call.StartReadMessage(taskSource.CompletionDelegate);
- var result = await taskSource.Task;
+ var result = await taskSource.Task.ConfigureAwait(false);
this.current = result;
return result != null;
}
diff --git a/src/csharp/Grpc.Core/Internal/Timespec.cs b/src/csharp/Grpc.Core/Internal/Timespec.cs
index 38fc067d9f..3031175c8a 100644
--- a/src/csharp/Grpc.Core/Internal/Timespec.cs
+++ b/src/csharp/Grpc.Core/Internal/Timespec.cs
@@ -63,20 +63,18 @@ namespace Grpc.Core.Internal
[DllImport("grpc_csharp_ext.dll")]
static extern int gprsharp_sizeof_timespec();
- public Timespec(IntPtr tv_sec, int tv_nsec) : this(tv_sec, tv_nsec, GPRClockType.Realtime)
+ public Timespec(long tv_sec, int tv_nsec) : this(tv_sec, tv_nsec, GPRClockType.Realtime)
{
}
- public Timespec(IntPtr tv_sec, int tv_nsec, GPRClockType clock_type)
+ public Timespec(long tv_sec, int tv_nsec, GPRClockType clock_type)
{
this.tv_sec = tv_sec;
this.tv_nsec = tv_nsec;
this.clock_type = clock_type;
}
- // NOTE: on linux 64bit sizeof(gpr_timespec) = 16, on windows 32bit sizeof(gpr_timespec) = 8
- // so IntPtr seems to have the right size to work on both.
- private System.IntPtr tv_sec;
+ private long tv_sec;
private int tv_nsec;
private GPRClockType clock_type;
@@ -116,7 +114,7 @@ namespace Grpc.Core.Internal
/// <summary>
/// Seconds since unix epoch.
/// </summary>
- public IntPtr TimevalSeconds
+ public long TimevalSeconds
{
get
{
@@ -176,18 +174,18 @@ namespace Grpc.Core.Internal
{
// convert nanos to ticks, round up to the nearest tick
long ticksFromNanos = tv_nsec / NanosPerTick + ((tv_nsec % NanosPerTick != 0) ? 1 : 0);
- long ticksTotal = checked(tv_sec.ToInt64() * TicksPerSecond + ticksFromNanos);
+ long ticksTotal = checked(tv_sec * TicksPerSecond + ticksFromNanos);
return UnixEpoch.AddTicks(ticksTotal);
}
catch (OverflowException)
{
// ticks out of long range
- return tv_sec.ToInt64() > 0 ? DateTime.MaxValue : DateTime.MinValue;
+ return tv_sec > 0 ? DateTime.MaxValue : DateTime.MinValue;
}
catch (ArgumentOutOfRangeException)
{
// resulting date time would be larger than MaxValue
- return tv_sec.ToInt64() > 0 ? DateTime.MaxValue : DateTime.MinValue;
+ return tv_sec > 0 ? DateTime.MaxValue : DateTime.MinValue;
}
}
@@ -226,12 +224,7 @@ namespace Grpc.Core.Internal
seconds--;
nanos += (int)NanosPerSecond;
}
- // new IntPtr possibly throws OverflowException
- return new Timespec(new IntPtr(seconds), nanos);
- }
- catch (OverflowException)
- {
- return dateTime > UnixEpoch ? Timespec.InfFuture : Timespec.InfPast;
+ return new Timespec(seconds, nanos);
}
catch (ArgumentOutOfRangeException)
{
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index 7c94d21561..d120f95fdf 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -148,10 +148,10 @@ namespace Grpc.Core
}
handle.ShutdownAndNotify(HandleServerShutdown, environment);
- await shutdownTcs.Task;
+ await shutdownTcs.Task.ConfigureAwait(false);
DisposeHandle();
- await Task.Run(() => GrpcEnvironment.Release());
+ await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false);
}
/// <summary>
@@ -169,8 +169,10 @@ namespace Grpc.Core
handle.ShutdownAndNotify(HandleServerShutdown, environment);
handle.CancelAllCalls();
- await shutdownTcs.Task;
+ await shutdownTcs.Task.ConfigureAwait(false);
DisposeHandle();
+
+ await Task.Run(() => GrpcEnvironment.Release()).ConfigureAwait(false);
}
internal void AddCallReference(object call)
@@ -268,7 +270,7 @@ namespace Grpc.Core
{
callHandler = NoSuchMethodCallHandler.Instance;
}
- await callHandler.HandleCall(newRpc, environment);
+ await callHandler.HandleCall(newRpc, environment).ConfigureAwait(false);
}
catch (Exception e)
{
@@ -288,7 +290,7 @@ namespace Grpc.Core
// after server shutdown, the callback returns with null call
if (!newRpc.Call.IsInvalid)
{
- Task.Run(async () => await HandleCallAsync(newRpc));
+ Task.Run(async () => await HandleCallAsync(newRpc)).ConfigureAwait(false);
}
}
diff --git a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs
index cdf1e51026..02a47568e7 100644
--- a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs
+++ b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs
@@ -48,9 +48,9 @@ namespace Grpc.Core.Utils
public static async Task ForEachAsync<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction)
where T : class
{
- while (await streamReader.MoveNext())
+ while (await streamReader.MoveNext().ConfigureAwait(false))
{
- await asyncAction(streamReader.Current);
+ await asyncAction(streamReader.Current).ConfigureAwait(false);
}
}
@@ -61,7 +61,7 @@ namespace Grpc.Core.Utils
where T : class
{
var result = new List<T>();
- while (await streamReader.MoveNext())
+ while (await streamReader.MoveNext().ConfigureAwait(false))
{
result.Add(streamReader.Current);
}
@@ -77,11 +77,11 @@ namespace Grpc.Core.Utils
{
foreach (var element in elements)
{
- await streamWriter.WriteAsync(element);
+ await streamWriter.WriteAsync(element).ConfigureAwait(false);
}
if (complete)
{
- await streamWriter.CompleteAsync();
+ await streamWriter.CompleteAsync().ConfigureAwait(false);
}
}
@@ -93,7 +93,7 @@ namespace Grpc.Core.Utils
{
foreach (var element in elements)
{
- await streamWriter.WriteAsync(element);
+ await streamWriter.WriteAsync(element).ConfigureAwait(false);
}
}
}
diff --git a/src/csharp/Grpc.Examples/Grpc.Examples.csproj b/src/csharp/Grpc.Examples/Grpc.Examples.csproj
index 55462e02fd..53b2bb78c4 100644
--- a/src/csharp/Grpc.Examples/Grpc.Examples.csproj
+++ b/src/csharp/Grpc.Examples/Grpc.Examples.csproj
@@ -66,6 +66,5 @@
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
- <None Include="proto\math.proto" />
</ItemGroup>
-</Project> \ No newline at end of file
+</Project>
diff --git a/src/csharp/Grpc.Examples/proto/math.proto b/src/csharp/Grpc.Examples/proto/math.proto
deleted file mode 100644
index 311e148c02..0000000000
--- a/src/csharp/Grpc.Examples/proto/math.proto
+++ /dev/null
@@ -1,80 +0,0 @@
-
-// 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.
-
-syntax = "proto3";
-
-package math;
-
-message DivArgs {
- int64 dividend = 1;
- int64 divisor = 2;
-}
-
-message DivReply {
- int64 quotient = 1;
- int64 remainder = 2;
-}
-
-message FibArgs {
- int64 limit = 1;
-}
-
-message Num {
- int64 num = 1;
-}
-
-message FibReply {
- int64 count = 1;
-}
-
-service Math {
- // Div divides args.dividend by args.divisor and returns the quotient and
- // remainder.
- rpc Div (DivArgs) returns (DivReply) {
- }
-
- // DivMany accepts an arbitrary number of division args from the client stream
- // and sends back the results in the reply stream. The stream continues until
- // the client closes its end; the server does the same after sending all the
- // replies. The stream ends immediately if either end aborts.
- rpc DivMany (stream DivArgs) returns (stream DivReply) {
- }
-
- // Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib
- // generates up to limit numbers; otherwise it continues until the call is
- // canceled. Unlike Fib above, Fib has no final FibReply.
- rpc Fib (FibArgs) returns (stream Num) {
- }
-
- // Sum sums a stream of numbers, returning the final result once the stream
- // is closed.
- rpc Sum (stream Num) returns (Num) {
- }
-}
diff --git a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj
index 8fce5d39aa..4e775a7a0c 100644
--- a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj
+++ b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj
@@ -65,7 +65,6 @@
<ItemGroup>
<None Include="Grpc.HealthCheck.nuspec" />
<None Include="packages.config" />
- <None Include="proto\health.proto" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Grpc.Core\Grpc.Core.csproj">
@@ -81,4 +80,4 @@
<Target Name="AfterBuild">
</Target>
-->
-</Project> \ No newline at end of file
+</Project>
diff --git a/src/csharp/Grpc.HealthCheck/proto/health.proto b/src/csharp/Grpc.HealthCheck/proto/health.proto
deleted file mode 100644
index 01aa3fcf57..0000000000
--- a/src/csharp/Grpc.HealthCheck/proto/health.proto
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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.
-
-// TODO(jtattermusch): switch to proto3 once C# supports that.
-syntax = "proto3";
-
-package grpc.health.v1alpha;
-option csharp_namespace = "Grpc.Health.V1Alpha";
-
-message HealthCheckRequest {
- string host = 1;
- string service = 2;
-}
-
-message HealthCheckResponse {
- enum ServingStatus {
- UNKNOWN = 0;
- SERVING = 1;
- NOT_SERVING = 2;
- }
- ServingStatus status = 1;
-}
-
-service Health {
- rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
-} \ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
index 012de45524..b0d920a34c 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -41,6 +41,9 @@
<Reference Include="CommandLine">
<HintPath>..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll</HintPath>
</Reference>
+ <Reference Include="Moq">
+ <HintPath>..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
+ </Reference>
<Reference Include="nunit.framework">
<HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
</Reference>
@@ -83,6 +86,7 @@
<Compile Include="..\Grpc.Core\Version.cs">
<Link>Version.cs</Link>
</Compile>
+ <Compile Include="HeaderInterceptorTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Empty.cs" />
<Compile Include="Messages.cs" />
diff --git a/src/csharp/Grpc.IntegrationTesting/HeaderInterceptorTest.cs b/src/csharp/Grpc.IntegrationTesting/HeaderInterceptorTest.cs
new file mode 100644
index 0000000000..1d758b7540
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/HeaderInterceptorTest.cs
@@ -0,0 +1,113 @@
+#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.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Utils;
+using Grpc.Testing;
+using NUnit.Framework;
+
+namespace Grpc.IntegrationTesting
+{
+ public class HeaderInterceptorTest
+ {
+ const string Host = "localhost";
+ Server server;
+ Channel channel;
+ TestService.TestServiceClient client;
+
+ [TestFixtureSetUp]
+ public void Init()
+ {
+ server = new Server
+ {
+ Services = { TestService.BindService(new TestServiceImpl()) },
+ Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
+ };
+ server.Start();
+
+ channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure);
+ client = TestService.NewClient(channel);
+ }
+
+ [TestFixtureTearDown]
+ public void Cleanup()
+ {
+ channel.ShutdownAsync().Wait();
+ server.ShutdownAsync().Wait();
+ }
+
+ [Test]
+ public async Task HeaderInterceptor_CreateMetadata()
+ {
+ var key = "x-grpc-test-echo-initial";
+ client.HeaderInterceptor = new HeaderInterceptor((method, metadata) =>
+ {
+ metadata.Add(key, "ABC");
+ });
+
+ var call = client.UnaryCallAsync(new SimpleRequest());
+ await call;
+
+ var responseHeaders = await call.ResponseHeadersAsync;
+ Assert.AreEqual("ABC", responseHeaders.First((entry) => entry.Key == key).Value);
+ }
+
+ [Test]
+ public async Task HeaderInterceptor_AppendMetadata()
+ {
+ var initialKey = "x-grpc-test-echo-initial";
+ var trailingKey = "x-grpc-test-echo-trailing-bin";
+
+ client.HeaderInterceptor = new HeaderInterceptor((method, metadata) =>
+ {
+ metadata.Add(initialKey, "ABC");
+ });
+
+ var headers = new Metadata
+ {
+ { trailingKey, new byte[] {0xaa} }
+ };
+ var call = client.UnaryCallAsync(new SimpleRequest(), headers: headers);
+ await call;
+
+ var responseHeaders = await call.ResponseHeadersAsync;
+ Assert.AreEqual("ABC", responseHeaders.First((entry) => entry.Key == initialKey).Value);
+ CollectionAssert.AreEqual(new byte[] {0xaa}, call.GetTrailers().First((entry) => entry.Key == trailingKey).ValueBytes);
+ }
+ }
+}
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index 5eec11abf7..b0e33e49f7 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -34,6 +34,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -130,8 +131,7 @@ namespace Grpc.IntegrationTesting
};
}
var channel = new Channel(options.ServerHost, options.ServerPort, credentials, channelOptions);
- TestService.TestServiceClient client = new TestService.TestServiceClient(channel);
- await RunTestCaseAsync(client, options);
+ await RunTestCaseAsync(channel, options);
await channel.ShutdownAsync();
}
@@ -159,8 +159,9 @@ namespace Grpc.IntegrationTesting
return credentials;
}
- private async Task RunTestCaseAsync(TestService.TestServiceClient client, ClientOptions options)
+ private async Task RunTestCaseAsync(Channel channel, ClientOptions options)
{
+ var client = new TestService.TestServiceClient(channel);
switch (options.TestCase)
{
case "empty_unary":
@@ -202,8 +203,14 @@ namespace Grpc.IntegrationTesting
case "timeout_on_sleeping_server":
await RunTimeoutOnSleepingServerAsync(client);
break;
- case "benchmark_empty_unary":
- RunBenchmarkEmptyUnary(client);
+ case "custom_metadata":
+ await RunCustomMetadataAsync(client);
+ break;
+ case "status_code_and_message":
+ await RunStatusCodeAndMessageAsync(client);
+ break;
+ case "unimplemented_method":
+ RunUnimplementedMethod(new UnimplementedService.UnimplementedServiceClient(channel));
break;
default:
throw new ArgumentException("Unknown test case " + options.TestCase);
@@ -227,7 +234,6 @@ namespace Grpc.IntegrationTesting
ResponseSize = 314159,
Payload = CreateZerosPayload(271828)
};
-
var response = client.UnaryCall(request);
Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
@@ -493,11 +499,95 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!");
}
- // This is not an official interop test, but it's useful.
- public static void RunBenchmarkEmptyUnary(TestService.ITestServiceClient client)
+ public static async Task RunCustomMetadataAsync(TestService.ITestServiceClient client)
{
- BenchmarkUtil.RunBenchmark(10000, 10000,
- () => { client.EmptyCall(new Empty()); });
+ Console.WriteLine("running custom_metadata");
+ {
+ // step 1: test unary call
+ var request = new SimpleRequest
+ {
+ ResponseType = PayloadType.COMPRESSABLE,
+ ResponseSize = 314159,
+ Payload = CreateZerosPayload(271828)
+ };
+
+ var call = client.UnaryCallAsync(request, headers: CreateTestMetadata());
+ await call.ResponseAsync;
+
+ var responseHeaders = await call.ResponseHeadersAsync;
+ var responseTrailers = call.GetTrailers();
+
+ Assert.AreEqual("test_initial_metadata_value", responseHeaders.First((entry) => entry.Key == "x-grpc-test-echo-initial").Value);
+ CollectionAssert.AreEqual(new byte[] { 0xab, 0xab, 0xab }, responseTrailers.First((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ValueBytes);
+ }
+
+ {
+ // step 2: test full duplex call
+ var request = new StreamingOutputCallRequest
+ {
+ ResponseType = PayloadType.COMPRESSABLE,
+ ResponseParameters = { new ResponseParameters { Size = 31415 } },
+ Payload = CreateZerosPayload(27182)
+ };
+
+ var call = client.FullDuplexCall(headers: CreateTestMetadata());
+ var responseHeaders = await call.ResponseHeadersAsync;
+
+ await call.RequestStream.WriteAsync(request);
+ await call.RequestStream.CompleteAsync();
+ await call.ResponseStream.ToListAsync();
+
+ var responseTrailers = call.GetTrailers();
+
+ Assert.AreEqual("test_initial_metadata_value", responseHeaders.First((entry) => entry.Key == "x-grpc-test-echo-initial").Value);
+ CollectionAssert.AreEqual(new byte[] { 0xab, 0xab, 0xab }, responseTrailers.First((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ValueBytes);
+ }
+
+ Console.WriteLine("Passed!");
+ }
+
+ public static async Task RunStatusCodeAndMessageAsync(TestService.ITestServiceClient client)
+ {
+ Console.WriteLine("running status_code_and_message");
+ var echoStatus = new EchoStatus
+ {
+ Code = 2,
+ Message = "test status message"
+ };
+
+ {
+ // step 1: test unary call
+ var request = new SimpleRequest { ResponseStatus = echoStatus };
+
+ var e = Assert.Throws<RpcException>(() => client.UnaryCall(request));
+ Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
+ Assert.AreEqual(echoStatus.Message, e.Status.Detail);
+ }
+
+ {
+ // step 2: test full duplex call
+ var request = new StreamingOutputCallRequest { ResponseStatus = echoStatus };
+
+ var call = client.FullDuplexCall();
+ await call.RequestStream.WriteAsync(request);
+ await call.RequestStream.CompleteAsync();
+
+ var e = Assert.Throws<RpcException>(async () => await call.ResponseStream.ToListAsync());
+ Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
+ Assert.AreEqual(echoStatus.Message, e.Status.Detail);
+ }
+
+ Console.WriteLine("Passed!");
+ }
+
+ public static void RunUnimplementedMethod(UnimplementedService.IUnimplementedServiceClient client)
+ {
+ Console.WriteLine("running unimplemented_method");
+ var e = Assert.Throws<RpcException>(() => client.UnimplementedCall(new Empty()));
+
+ Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode);
+ Assert.AreEqual("", e.Status.Detail);
+ Console.WriteLine("Passed!");
}
private static Payload CreateZerosPayload(int size)
@@ -516,5 +606,14 @@ namespace Grpc.IntegrationTesting
Assert.IsTrue(email.Length > 0); // spec requires nonempty client email.
return email;
}
+
+ private static Metadata CreateTestMetadata()
+ {
+ return new Metadata
+ {
+ {"x-grpc-test-echo-initial", "test_initial_metadata_value"},
+ {"x-grpc-test-echo-trailing-bin", new byte[] {0xab, 0xab, 0xab}}
+ };
+ }
}
}
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
index 837ae74c45..18168f9970 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
@@ -128,9 +128,29 @@ namespace Grpc.IntegrationTesting
}
[Test]
- public async Task TimeoutOnSleepingServerAsync()
+ public async Task TimeoutOnSleepingServer()
{
await InteropClient.RunTimeoutOnSleepingServerAsync(client);
}
+
+ [Test]
+ public async Task CustomMetadata()
+ {
+ await InteropClient.RunCustomMetadataAsync(client);
+ }
+
+ [Test]
+ [Ignore("TODO: see #4427")]
+ public async Task StatusCodeAndMessage()
+ {
+ await InteropClient.RunStatusCodeAndMessageAsync(client);
+ }
+
+ [Test]
+ [Ignore("TODO: see #4427")]
+ public void UnimplementedMethod()
+ {
+ InteropClient.RunUnimplementedMethod(UnimplementedService.NewClient(channel));
+ }
}
}
diff --git a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
index 3d56678b99..1c8bfed1f6 100644
--- a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
@@ -40,6 +40,7 @@ using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Utils;
using Grpc.Testing;
+using Moq;
using NUnit.Framework;
namespace Grpc.IntegrationTesting
@@ -50,37 +51,37 @@ namespace Grpc.IntegrationTesting
Server server;
Channel channel;
TestService.ITestServiceClient client;
+ List<ChannelOption> options;
+ Mock<TestService.ITestService> serviceMock;
+ AsyncAuthInterceptor asyncAuthInterceptor;
- [TestFixtureSetUp]
+ [SetUp]
public void Init()
{
- var serverCredentials = new SslServerCredentials(new[] { new KeyCertificatePair(File.ReadAllText(TestCredentials.ServerCertChainPath), File.ReadAllText(TestCredentials.ServerPrivateKeyPath)) });
+ serviceMock = new Mock<TestService.ITestService>();
+ serviceMock.Setup(m => m.UnaryCall(It.IsAny<SimpleRequest>(), It.IsAny<ServerCallContext>()))
+ .Returns(new Func<SimpleRequest, ServerCallContext, Task<SimpleResponse>>(UnaryCallHandler));
+
server = new Server
{
- Services = { TestService.BindService(new TestServiceImpl()) },
- Ports = { { Host, ServerPort.PickUnused, serverCredentials } }
+ Services = { TestService.BindService(serviceMock.Object) },
+ Ports = { { Host, ServerPort.PickUnused, TestCredentials.CreateSslServerCredentials() } }
};
server.Start();
- var options = new List<ChannelOption>
+ options = new List<ChannelOption>
{
new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
};
- var asyncAuthInterceptor = new AsyncAuthInterceptor(async (authUri, metadata) =>
+ asyncAuthInterceptor = new AsyncAuthInterceptor(async (context, metadata) =>
{
- await Task.Delay(100); // make sure the operation is asynchronous.
+ await Task.Delay(100).ConfigureAwait(false); // make sure the operation is asynchronous.
metadata.Add("authorization", "SECRET_TOKEN");
});
-
- var clientCredentials = ChannelCredentials.Create(
- new SslCredentials(File.ReadAllText(TestCredentials.ClientCertAuthorityPath)),
- CallCredentials.FromInterceptor(asyncAuthInterceptor));
- channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options);
- client = TestService.NewClient(channel);
}
- [TestFixtureTearDown]
+ [TearDown]
public void Cleanup()
{
channel.ShutdownAsync().Wait();
@@ -90,8 +91,29 @@ namespace Grpc.IntegrationTesting
[Test]
public void MetadataCredentials()
{
- var response = client.UnaryCall(new SimpleRequest { ResponseSize = 10 });
- Assert.AreEqual(10, response.Payload.Body.Length);
+ var channelCredentials = ChannelCredentials.Create(TestCredentials.CreateSslCredentials(),
+ CallCredentials.FromInterceptor(asyncAuthInterceptor));
+ channel = new Channel(Host, server.Ports.Single().BoundPort, channelCredentials, options);
+ client = TestService.NewClient(channel);
+
+ client.UnaryCall(new SimpleRequest {});
+ }
+
+ [Test]
+ public void MetadataCredentials_PerCall()
+ {
+ channel = new Channel(Host, server.Ports.Single().BoundPort, TestCredentials.CreateSslCredentials(), options);
+ client = TestService.NewClient(channel);
+
+ var callCredentials = CallCredentials.FromInterceptor(asyncAuthInterceptor);
+ client.UnaryCall(new SimpleRequest { }, new CallOptions(credentials: callCredentials));
+ }
+
+ private Task<SimpleResponse> UnaryCallHandler(SimpleRequest request, ServerCallContext context)
+ {
+ var authToken = context.RequestHeaders.First((entry) => entry.Key == "authorization").Value;
+ Assert.AreEqual("SECRET_TOKEN", authToken);
+ return Task.FromResult(new SimpleResponse());
}
}
}
diff --git a/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs
index 2b51526c88..3dd91b7948 100644
--- a/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/RunnerClientServerTest.cs
@@ -75,9 +75,10 @@ namespace Grpc.IntegrationTesting
serverRunner.StopAsync().Wait();
}
- // Test attribute commented out to prevent running as part of the default test suite.
- //[Test]
- //[Category("Performance")]
+
+ [Test]
+ [Category("Performance")]
+ [Ignore("Prevent running on Jenkins")]
public async Task ClientServerRunner()
{
var config = new ClientConfig
diff --git a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
index c5bfcf08c0..5a1b4cf319 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
@@ -33,6 +33,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
@@ -51,14 +52,20 @@ namespace Grpc.Testing
return Task.FromResult(new Empty());
}
- public Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
+ public async Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
{
+ await EnsureEchoMetadataAsync(context);
+ EnsureEchoStatus(request.ResponseStatus, context);
+
var response = new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) };
- return Task.FromResult(response);
+ return response;
}
public async Task StreamingOutputCall(StreamingOutputCallRequest request, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
{
+ await EnsureEchoMetadataAsync(context);
+ EnsureEchoStatus(request.ResponseStatus, context);
+
foreach (var responseParam in request.ResponseParameters)
{
var response = new StreamingOutputCallResponse { Payload = CreateZerosPayload(responseParam.Size) };
@@ -68,6 +75,8 @@ namespace Grpc.Testing
public async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
{
+ await EnsureEchoMetadataAsync(context);
+
int sum = 0;
await requestStream.ForEachAsync(async request =>
{
@@ -78,8 +87,11 @@ namespace Grpc.Testing
public async Task FullDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
{
+ await EnsureEchoMetadataAsync(context);
+
await requestStream.ForEachAsync(async request =>
{
+ EnsureEchoStatus(request.ResponseStatus, context);
foreach (var responseParam in request.ResponseParameters)
{
var response = new StreamingOutputCallResponse { Payload = CreateZerosPayload(responseParam.Size) };
@@ -97,5 +109,28 @@ namespace Grpc.Testing
{
return new Payload { Body = ByteString.CopyFrom(new byte[size]) };
}
+
+ private static async Task EnsureEchoMetadataAsync(ServerCallContext context)
+ {
+ var echoInitialList = context.RequestHeaders.Where((entry) => entry.Key == "x-grpc-test-echo-initial").ToList();
+ if (echoInitialList.Any()) {
+ var entry = echoInitialList.Single();
+ await context.WriteResponseHeadersAsync(new Metadata { entry });
+ }
+
+ var echoTrailingList = context.RequestHeaders.Where((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ToList();
+ if (echoTrailingList.Any()) {
+ context.ResponseTrailers.Add(echoTrailingList.Single());
+ }
+ }
+
+ private static void EnsureEchoStatus(EchoStatus responseStatus, ServerCallContext context)
+ {
+ if (responseStatus != null)
+ {
+ var statusCode = (StatusCode)responseStatus.Code;
+ context.Status = new Status(statusCode, responseStatus.Message);
+ }
+ }
}
}
diff --git a/src/csharp/Grpc.IntegrationTesting/packages.config b/src/csharp/Grpc.IntegrationTesting/packages.config
index bdb3dadf44..5c59af1b7d 100644
--- a/src/csharp/Grpc.IntegrationTesting/packages.config
+++ b/src/csharp/Grpc.IntegrationTesting/packages.config
@@ -11,6 +11,7 @@
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
+ <package id="Moq" version="4.2.1510.2205" targetFramework="net45" />
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
<package id="NUnit" version="2.6.4" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index b8705c49d3..0ef9be33a6 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -927,7 +927,8 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_metadata_credentials_notify_from_plugin(
}
typedef void(GPR_CALLTYPE *grpcsharp_metadata_interceptor_func)(
- void *state, const char *service_url, grpc_credentials_plugin_metadata_cb cb,
+ void *state, const char *service_url, const char *method_name,
+ grpc_credentials_plugin_metadata_cb cb,
void *user_data, gpr_int32 is_destroy);
static void grpcsharp_get_metadata_handler(
@@ -935,13 +936,13 @@ static void grpcsharp_get_metadata_handler(
grpc_credentials_plugin_metadata_cb cb, void *user_data) {
grpcsharp_metadata_interceptor_func interceptor =
(grpcsharp_metadata_interceptor_func)(gpr_intptr)state;
- interceptor(state, context.service_url, cb, user_data, 0);
+ interceptor(state, context.service_url, context.method_name, cb, user_data, 0);
}
static void grpcsharp_metadata_credentials_destroy_handler(void *state) {
grpcsharp_metadata_interceptor_func interceptor =
(grpcsharp_metadata_interceptor_func)(gpr_intptr)state;
- interceptor(state, NULL, NULL, NULL, 1);
+ interceptor(state, NULL, NULL, NULL, NULL, 1);
}
GPR_EXPORT grpc_call_credentials *GPR_CALLTYPE grpcsharp_metadata_credentials_create_from_plugin(
diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh
index 92348d1394..4dbd9c3308 100755
--- a/src/csharp/generate_proto_csharp.sh
+++ b/src/csharp/generate_proto_csharp.sh
@@ -30,19 +30,19 @@
# Regenerates gRPC service stubs from proto files.
set +e
-cd $(dirname $0)
+cd $(dirname $0)/../..
-PROTOC=../../bins/opt/protobuf/protoc
-PLUGIN=protoc-gen-grpc=../../bins/opt/grpc_csharp_plugin
-EXAMPLES_DIR=Grpc.Examples
-TESTING_DIR=Grpc.IntegrationTesting
-HEALTHCHECK_DIR=Grpc.HealthCheck
+PROTOC=bins/opt/protobuf/protoc
+PLUGIN=protoc-gen-grpc=bins/opt/grpc_csharp_plugin
+EXAMPLES_DIR=src/csharp/Grpc.Examples
+HEALTHCHECK_DIR=src/csharp/Grpc.HealthCheck
+TESTING_DIR=src/csharp/Grpc.IntegrationTesting
$PROTOC --plugin=$PLUGIN --csharp_out=$EXAMPLES_DIR --grpc_out=$EXAMPLES_DIR \
- -I $EXAMPLES_DIR/proto $EXAMPLES_DIR/proto/math.proto
-
-$PROTOC --plugin=$PLUGIN --csharp_out=$TESTING_DIR --grpc_out=$TESTING_DIR \
- -I ../.. ../../test/proto/*.proto ../../test/proto/benchmarks/*.proto
+ -I src/proto/math src/proto/math/math.proto
$PROTOC --plugin=$PLUGIN --csharp_out=$HEALTHCHECK_DIR --grpc_out=$HEALTHCHECK_DIR \
- -I $HEALTHCHECK_DIR/proto $HEALTHCHECK_DIR/proto/health.proto
+ -I src/proto/grpc/health/v1alpha src/proto/grpc/health/v1alpha/health.proto
+
+$PROTOC --plugin=$PLUGIN --csharp_out=$TESTING_DIR --grpc_out=$TESTING_DIR \
+ -I . test/proto/{empty,messages,test}.proto test/proto/benchmarks/*.proto
diff --git a/src/csharp/tests.json b/src/csharp/tests.json
new file mode 100644
index 0000000000..4aa93668ad
--- /dev/null
+++ b/src/csharp/tests.json
@@ -0,0 +1,45 @@
+{
+ "assemblies": [
+ "Grpc.Core.Tests",
+ "Grpc.Examples.Tests",
+ "Grpc.HealthCheck.Tests",
+ "Grpc.IntegrationTesting"
+ ],
+ "tests": [
+ "Grpc.Core.Internal.Tests.AsyncCallTest",
+ "Grpc.Core.Internal.Tests.ChannelArgsSafeHandleTest",
+ "Grpc.Core.Internal.Tests.CompletionQueueEventTest",
+ "Grpc.Core.Internal.Tests.CompletionQueueSafeHandleTest",
+ "Grpc.Core.Internal.Tests.MetadataArraySafeHandleTest",
+ "Grpc.Core.Internal.Tests.TimespecTest",
+ "Grpc.Core.Tests.CallCredentialsTest",
+ "Grpc.Core.Tests.CallOptionsTest",
+ "Grpc.Core.Tests.ChannelCredentialsTest",
+ "Grpc.Core.Tests.ChannelOptionsTest",
+ "Grpc.Core.Tests.ChannelTest",
+ "Grpc.Core.Tests.ClientServerTest",
+ "Grpc.Core.Tests.CompressionTest",
+ "Grpc.Core.Tests.ContextPropagationTest",
+ "Grpc.Core.Tests.GrpcEnvironmentTest",
+ "Grpc.Core.Tests.MarshallingErrorsTest",
+ "Grpc.Core.Tests.MetadataTest",
+ "Grpc.Core.Tests.NUnitVersionTest",
+ "Grpc.Core.Tests.PerformanceTest",
+ "Grpc.Core.Tests.PInvokeTest",
+ "Grpc.Core.Tests.ResponseHeadersTest",
+ "Grpc.Core.Tests.SanityTest",
+ "Grpc.Core.Tests.ServerTest",
+ "Grpc.Core.Tests.ShutdownTest",
+ "Grpc.Core.Tests.TimeoutsTest",
+ "Grpc.Core.Tests.UserAgentStringTest",
+ "Math.Tests.MathClientServerTest",
+ "Grpc.HealthCheck.Tests.HealthClientServerTest",
+ "Grpc.HealthCheck.Tests.HealthServiceImplTest",
+ "Grpc.IntegrationTesting.HeaderInterceptorTest",
+ "Grpc.IntegrationTesting.HistogramTest",
+ "Grpc.IntegrationTesting.InteropClientServerTest",
+ "Grpc.IntegrationTesting.MetadataCredentialsTest",
+ "Grpc.IntegrationTesting.RunnerClientServerTest",
+ "Grpc.IntegrationTesting.SslCredentialsTest"
+ ]
+} \ No newline at end of file