diff options
Diffstat (limited to 'src')
29 files changed, 584 insertions, 98 deletions
diff --git a/src/core/iomgr/tcp_server_posix.c b/src/core/iomgr/tcp_server_posix.c index a89ee02d34..b758702da8 100644 --- a/src/core/iomgr/tcp_server_posix.c +++ b/src/core/iomgr/tcp_server_posix.c @@ -411,7 +411,6 @@ static grpc_tcp_listener *add_socket_to_server(grpc_tcp_server *s, int fd, grpc_tcp_listener *grpc_tcp_server_add_port(grpc_tcp_server *s, const void *addr, size_t addr_len) { - int allocated_port = -1; grpc_tcp_listener *sp; grpc_tcp_listener *sp2 = NULL; int fd; @@ -464,14 +463,13 @@ grpc_tcp_listener *grpc_tcp_server_add_port(grpc_tcp_server *s, addr_len = sizeof(wild6); fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode); sp = add_socket_to_server(s, fd, addr, addr_len); - allocated_port = sp->port; if (fd >= 0 && dsmode == GRPC_DSMODE_DUALSTACK) { goto done; } /* If we didn't get a dualstack socket, also listen on 0.0.0.0. */ - if (port == 0 && allocated_port > 0) { - grpc_sockaddr_set_port((struct sockaddr *)&wild4, allocated_port); + if (port == 0 && sp != NULL) { + grpc_sockaddr_set_port((struct sockaddr *)&wild4, sp->port); sp2 = sp; } addr = (struct sockaddr *)&wild4; @@ -488,8 +486,8 @@ grpc_tcp_listener *grpc_tcp_server_add_port(grpc_tcp_server *s, addr_len = sizeof(addr4_copy); } sp = add_socket_to_server(s, fd, addr, addr_len); - sp->sibling = sp2; - if (sp2) sp2->is_sibling = 1; + if (sp != NULL) sp->sibling = sp2; + if (sp2 != NULL) sp2->is_sibling = 1; done: gpr_free(allocated_addr); diff --git a/src/core/iomgr/tcp_windows.c b/src/core/iomgr/tcp_windows.c index 5ff78231bd..6915cb2f49 100644 --- a/src/core/iomgr/tcp_windows.c +++ b/src/core/iomgr/tcp_windows.c @@ -197,7 +197,7 @@ static void win_read(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, tcp->read_slice = gpr_slice_malloc(8192); - buffer.len = GPR_SLICE_LENGTH(tcp->read_slice); + buffer.len = (ULONG)GPR_SLICE_LENGTH(tcp->read_slice); // we know slice size fits in 32bit. buffer.buf = (char *)GPR_SLICE_START_PTR(tcp->read_slice); TCP_REF(tcp, "read"); @@ -273,6 +273,7 @@ static void win_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, WSABUF local_buffers[16]; WSABUF *allocated = NULL; WSABUF *buffers = local_buffers; + size_t len; if (tcp->shutting_down) { grpc_exec_ctx_enqueue(exec_ctx, cb, 0); @@ -281,19 +282,21 @@ static void win_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, tcp->write_cb = cb; tcp->write_slices = slices; - + GPR_ASSERT(tcp->write_slices->count <= UINT_MAX); if (tcp->write_slices->count > GPR_ARRAY_SIZE(local_buffers)) { buffers = (WSABUF *)gpr_malloc(sizeof(WSABUF) * tcp->write_slices->count); allocated = buffers; } for (i = 0; i < tcp->write_slices->count; i++) { - buffers[i].len = GPR_SLICE_LENGTH(tcp->write_slices->slices[i]); + len = GPR_SLICE_LENGTH(tcp->write_slices->slices[i]); + GPR_ASSERT(len <= ULONG_MAX); + buffers[i].len = (ULONG) len; buffers[i].buf = (char *)GPR_SLICE_START_PTR(tcp->write_slices->slices[i]); } /* First, let's try a synchronous, non-blocking write. */ - status = WSASend(socket->socket, buffers, tcp->write_slices->count, + status = WSASend(socket->socket, buffers, (DWORD)tcp->write_slices->count, &bytes_sent, 0, NULL, NULL); info->wsa_error = status == 0 ? 0 : WSAGetLastError(); @@ -322,7 +325,7 @@ static void win_write(grpc_exec_ctx *exec_ctx, grpc_endpoint *ep, /* If we got a WSAEWOULDBLOCK earlier, then we need to re-do the same operation, this time asynchronously. */ memset(&socket->write_info.overlapped, 0, sizeof(OVERLAPPED)); - status = WSASend(socket->socket, buffers, tcp->write_slices->count, + status = WSASend(socket->socket, buffers, (DWORD)tcp->write_slices->count, &bytes_sent, 0, &socket->write_info.overlapped, NULL); if (allocated) gpr_free(allocated); diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h index 6d45895e77..3cd652cd57 100644 --- a/src/core/security/credentials.h +++ b/src/core/security/credentials.h @@ -93,6 +93,14 @@ typedef enum { /* It is the caller's responsibility to gpr_free the result if not NULL. */ char *grpc_get_well_known_google_credentials_file_path(void); +/* Implementation function for the different platforms. */ +char *grpc_get_well_known_google_credentials_file_path_impl(void); + +/* Override for testing only. Not thread-safe */ +typedef char *(*grpc_well_known_credentials_path_getter)(void); +void grpc_override_well_known_credentials_path_getter( + grpc_well_known_credentials_path_getter getter); + /* --- grpc_channel_credentials. --- */ typedef struct { @@ -201,6 +209,7 @@ grpc_credentials_status grpc_oauth2_token_fetcher_credentials_parse_server_response( const struct grpc_httpcli_response *response, grpc_credentials_md_store **token_md, gpr_timespec *token_lifetime); + void grpc_flush_cached_google_default_credentials(void); /* Metadata-only credentials with the specified key and value where diff --git a/src/core/security/credentials_posix.c b/src/core/security/credentials_posix.c index 20f67a7f14..0c92bd4a96 100644 --- a/src/core/security/credentials_posix.c +++ b/src/core/security/credentials_posix.c @@ -44,7 +44,7 @@ #include "src/core/support/env.h" #include "src/core/support/string.h" -char *grpc_get_well_known_google_credentials_file_path(void) { +char *grpc_get_well_known_google_credentials_file_path_impl(void) { char *result = NULL; char *home = gpr_getenv("HOME"); if (home == NULL) { diff --git a/src/core/security/credentials_win32.c b/src/core/security/credentials_win32.c index 92dfd9bdfe..8ee9f706a1 100644 --- a/src/core/security/credentials_win32.c +++ b/src/core/security/credentials_win32.c @@ -44,7 +44,7 @@ #include "src/core/support/env.h" #include "src/core/support/string.h" -char *grpc_get_well_known_google_credentials_file_path(void) { +char *grpc_get_well_known_google_credentials_file_path_impl(void) { char *result = NULL; char *appdata_path = gpr_getenv("APPDATA"); if (appdata_path == NULL) { diff --git a/src/core/security/google_default_credentials.c b/src/core/security/google_default_credentials.c index 6a54fe4e47..5385e41130 100644 --- a/src/core/security/google_default_credentials.c +++ b/src/core/security/google_default_credentials.c @@ -241,5 +241,20 @@ void grpc_flush_cached_google_default_credentials(void) { grpc_channel_credentials_unref(default_credentials); default_credentials = NULL; } + compute_engine_detection_done = 0; gpr_mu_unlock(&g_mu); } + +/* -- Well known credentials path. -- */ + +static grpc_well_known_credentials_path_getter creds_path_getter = NULL; + +char *grpc_get_well_known_google_credentials_file_path(void) { + if (creds_path_getter != NULL) return creds_path_getter(); + return grpc_get_well_known_google_credentials_file_path_impl(); +} + +void grpc_override_well_known_credentials_path_getter( + grpc_well_known_credentials_path_getter getter) { + creds_path_getter = getter; +} diff --git a/src/core/security/json_token.c b/src/core/security/json_token.c index 021912f333..92775d885d 100644 --- a/src/core/security/json_token.c +++ b/src/core/security/json_token.c @@ -215,8 +215,8 @@ static char *encoded_jwt_claim(const grpc_auth_json_key *json_key, gpr_log(GPR_INFO, "Cropping token lifetime to maximum allowed value."); expiration = gpr_time_add(now, grpc_max_auth_token_lifetime); } - gpr_ltoa(now.tv_sec, now_str); - gpr_ltoa(expiration.tv_sec, expiration_str); + gpr_int64toa(now.tv_sec, now_str); + gpr_int64toa(expiration.tv_sec, expiration_str); child = create_child(NULL, json, "iss", json_key->client_email, GRPC_JSON_STRING); diff --git a/src/core/support/string.c b/src/core/support/string.c index e0ffeb8a4a..46a7ca3d46 100644 --- a/src/core/support/string.c +++ b/src/core/support/string.c @@ -153,8 +153,8 @@ void gpr_reverse_bytes(char *str, int len) { } int gpr_ltoa(long value, char *string) { + long sign; int i = 0; - int neg = value < 0; if (value == 0) { string[0] = '0'; @@ -162,12 +162,33 @@ int gpr_ltoa(long value, char *string) { return 1; } - if (neg) value = -value; + sign = value < 0 ? -1 : 1; while (value) { - string[i++] = (char)('0' + value % 10); + string[i++] = (char)('0' + sign * (value % 10)); value /= 10; } - if (neg) string[i++] = '-'; + if (sign < 0) string[i++] = '-'; + gpr_reverse_bytes(string, i); + string[i] = 0; + return i; +} + +int gpr_int64toa(gpr_int64 value, char *string) { + gpr_int64 sign; + int i = 0; + + if (value == 0) { + string[0] = '0'; + string[1] = 0; + return 1; + } + + sign = value < 0 ? -1 : 1; + while (value) { + string[i++] = (char)('0' + sign * (value % 10)); + value /= 10; + } + if (sign < 0) string[i++] = '-'; gpr_reverse_bytes(string, i); string[i] = 0; return i; diff --git a/src/core/support/string.h b/src/core/support/string.h index a28e00fd3e..9b604ac5bf 100644 --- a/src/core/support/string.h +++ b/src/core/support/string.h @@ -70,6 +70,16 @@ int gpr_parse_bytes_to_uint32(const char *data, size_t length, output must be at least GPR_LTOA_MIN_BUFSIZE bytes long. */ int gpr_ltoa(long value, char *output); +/* Minimum buffer size for calling int64toa */ +#define GPR_INT64TOA_MIN_BUFSIZE (3 * sizeof(gpr_int64)) + +/* Convert an int64 to a string in base 10; returns the length of the +output string (or 0 on failure). +output must be at least GPR_INT64TOA_MIN_BUFSIZE bytes long. +NOTE: This function ensures sufficient bit width even on Win x64, +where long is 32bit is size.*/ +int gpr_int64toa(gpr_int64 value, char *output); + /* Reverse a run of bytes */ void gpr_reverse_bytes(char *str, int len); diff --git a/src/core/surface/init.c b/src/core/surface/init.c index e0106d8f2b..82027af651 100644 --- a/src/core/surface/init.c +++ b/src/core/surface/init.c @@ -147,6 +147,7 @@ void grpc_shutdown(void) { gpr_timers_global_destroy(); grpc_tracer_shutdown(); grpc_resolver_registry_shutdown(); + grpc_lb_policy_registry_shutdown(); for (i = 0; i < g_number_of_plugins; i++) { if (g_all_of_the_plugins[i].destroy != NULL) { g_all_of_the_plugins[i].destroy(); diff --git a/src/core/transport/chttp2/timeout_encoding.c b/src/core/transport/chttp2/timeout_encoding.c index 8a9b290ecb..cf81c18a20 100644 --- a/src/core/transport/chttp2/timeout_encoding.c +++ b/src/core/transport/chttp2/timeout_encoding.c @@ -36,6 +36,7 @@ #include <stdio.h> #include <string.h> +#include <grpc/support/port_platform.h> #include "src/core/support/string.h" static int round_up(int x, int divisor) { @@ -57,13 +58,13 @@ static int round_up_to_three_sig_figs(int x) { /* encode our minimum viable timeout value */ static void enc_tiny(char *buffer) { memcpy(buffer, "1n", 3); } -static void enc_ext(char *buffer, long value, char ext) { - int n = gpr_ltoa(value, buffer); +static void enc_ext(char *buffer, gpr_int64 value, char ext) { + int n = gpr_int64toa(value, buffer); buffer[n] = ext; buffer[n + 1] = 0; } -static void enc_seconds(char *buffer, long sec) { +static void enc_seconds(char *buffer, gpr_int64 sec) { if (sec % 3600 == 0) { enc_ext(buffer, sec / 3600, 'H'); } else if (sec % 60 == 0) { diff --git a/src/core/transport/connectivity_state.c b/src/core/transport/connectivity_state.c index 09b298c131..b001af7e35 100644 --- a/src/core/transport/connectivity_state.c +++ b/src/core/transport/connectivity_state.c @@ -54,8 +54,7 @@ const char *grpc_connectivity_state_name(grpc_connectivity_state state) { case GRPC_CHANNEL_FATAL_FAILURE: return "FATAL_FAILURE"; } - abort(); - return "UNKNOWN"; + GPR_UNREACHABLE_CODE(return "UNKNOWN"); } void grpc_connectivity_state_init(grpc_connectivity_state_tracker *tracker, diff --git a/src/core/transport/connectivity_state.h b/src/core/transport/connectivity_state.h index 119b1c1554..af2734c016 100644 --- a/src/core/transport/connectivity_state.h +++ b/src/core/transport/connectivity_state.h @@ -57,6 +57,8 @@ typedef struct { extern int grpc_connectivity_state_trace; +const char *grpc_connectivity_state_name(grpc_connectivity_state state); + void grpc_connectivity_state_init(grpc_connectivity_state_tracker *tracker, grpc_connectivity_state init_state, const char *name); diff --git a/src/cpp/proto/proto_utils.cc b/src/cpp/proto/proto_utils.cc index b1330fde7f..898a1d4f58 100644 --- a/src/cpp/proto/proto_utils.cc +++ b/src/cpp/proto/proto_utils.cc @@ -33,6 +33,8 @@ #include <grpc++/impl/proto_utils.h> +#include <climits> + #include <grpc/grpc.h> #include <grpc/byte_buffer.h> #include <grpc/byte_buffer_reader.h> @@ -70,7 +72,9 @@ class GrpcBufferWriter GRPC_FINAL slice_ = gpr_slice_malloc(block_size_); } *data = GPR_SLICE_START_PTR(slice_); - byte_count_ += * size = GPR_SLICE_LENGTH(slice_); + // On win x64, int is only 32bit + GPR_ASSERT(GPR_SLICE_LENGTH(slice_) <= INT_MAX); + byte_count_ += * size = (int)GPR_SLICE_LENGTH(slice_); gpr_slice_buffer_add(slice_buffer_, slice_); return true; } @@ -124,7 +128,9 @@ class GrpcBufferReader GRPC_FINAL } gpr_slice_unref(slice_); *data = GPR_SLICE_START_PTR(slice_); - byte_count_ += * size = GPR_SLICE_LENGTH(slice_); + // On win x64, int is only 32bit + GPR_ASSERT(GPR_SLICE_LENGTH(slice_) <= INT_MAX); + byte_count_ += * size = (int)GPR_SLICE_LENGTH(slice_); return true; } 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..b683751bc0 100644 --- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs @@ -201,7 +201,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,18 +219,6 @@ namespace Grpc.Core.Tests } [Test] - public void UserAgentStringPresent() - { - helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => - { - return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value; - }); - - string userAgent = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"); - Assert.IsTrue(userAgent.StartsWith("grpc-csharp/")); - } - - [Test] public void PeerInfoPresent() { helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index e5ffa31989..70b83f7fb1 100644 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -64,6 +64,7 @@ <Link>Version.cs</Link> </Compile> <Compile Include="CallCredentialsTest.cs" /> + <Compile Include="UserAgentStringTest.cs" /> <Compile Include="FakeCredentials.cs" /> <Compile Include="MarshallingErrorsTest.cs" /> <Compile Include="ChannelCredentialsTest.cs" /> 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/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/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index ec60354639..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) { @@ -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/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs index 0ade701a53..de66759b94 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs @@ -284,7 +284,7 @@ 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).ConfigureAwait(false); + await responseStream.WriteStatusAsync(new Status(StatusCode.Unimplemented, ""), Metadata.Empty).ConfigureAwait(false); await finishedTask.ConfigureAwait(false); } } 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..5facb87971 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs @@ -128,9 +128,27 @@ 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] + public async Task StatusCodeAndMessage() + { + await InteropClient.RunStatusCodeAndMessageAsync(client); + } + + [Test] + public void UnimplementedMethod() + { + InteropClient.RunUnimplementedMethod(UnimplementedService.NewClient(channel)); + } } } 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/node/ext/channel_credentials.cc b/src/node/ext/channel_credentials.cc index b77ff80af2..059bc8a890 100644 --- a/src/node/ext/channel_credentials.cc +++ b/src/node/ext/channel_credentials.cc @@ -154,6 +154,12 @@ NAN_METHOD(ChannelCredentials::CreateSsl) { return Nan::ThrowTypeError( "createSSl's third argument must be a Buffer if provided"); } + if ((key_cert_pair.private_key == NULL) != + (key_cert_pair.cert_chain == NULL)) { + return Nan::ThrowError( + "createSsl's second and third arguments must be" + " provided or omitted together"); + } grpc_channel_credentials *creds = grpc_ssl_credentials_create( root_certs, key_cert_pair.private_key == NULL ? NULL : &key_cert_pair, NULL); diff --git a/src/node/ext/server_credentials.cc b/src/node/ext/server_credentials.cc index e6c55e263c..5285d53df4 100644 --- a/src/node/ext/server_credentials.cc +++ b/src/node/ext/server_credentials.cc @@ -167,30 +167,18 @@ NAN_METHOD(ServerCredentials::CreateSsl) { return Nan::ThrowTypeError("Key/cert pairs must be objects"); } Local<Object> pair_obj = Nan::To<Object>(pair_val).ToLocalChecked(); - MaybeLocal<Value> maybe_key = Nan::Get(pair_obj, key_key); - if (maybe_key.IsEmpty()) { - delete key_cert_pairs; - return Nan::ThrowTypeError( - "Key/cert pairs must have a private_key and a cert_chain"); - } - MaybeLocal<Value> maybe_cert = Nan::Get(pair_obj, cert_key); - if (maybe_cert.IsEmpty()) { - delete key_cert_pairs; - return Nan::ThrowTypeError( - "Key/cert pairs must have a private_key and a cert_chain"); - } - if (!::node::Buffer::HasInstance(maybe_key.ToLocalChecked())) { + Local<Value> maybe_key = Nan::Get(pair_obj, key_key).ToLocalChecked(); + Local<Value> maybe_cert = Nan::Get(pair_obj, cert_key).ToLocalChecked(); + if (!::node::Buffer::HasInstance(maybe_key)) { delete key_cert_pairs; return Nan::ThrowTypeError("private_key must be a Buffer"); } - if (!::node::Buffer::HasInstance(maybe_cert.ToLocalChecked())) { + if (!::node::Buffer::HasInstance(maybe_cert)) { delete key_cert_pairs; return Nan::ThrowTypeError("cert_chain must be a Buffer"); } - key_cert_pairs[i].private_key = ::node::Buffer::Data( - maybe_key.ToLocalChecked()); - key_cert_pairs[i].cert_chain = ::node::Buffer::Data( - maybe_cert.ToLocalChecked()); + key_cert_pairs[i].private_key = ::node::Buffer::Data(maybe_key); + key_cert_pairs[i].cert_chain = ::node::Buffer::Data(maybe_cert); } grpc_server_credentials *creds = grpc_ssl_server_credentials_create( root_certs, key_cert_pairs, key_cert_pair_count, force_client_auth, NULL); diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js index 647f648ca9..294600c85a 100644 --- a/src/node/test/credentials_test.js +++ b/src/node/test/credentials_test.js @@ -76,6 +76,146 @@ var fakeFailingGoogleCredentials = { } }; +var key_data, pem_data, ca_data; + +before(function() { + var key_path = path.join(__dirname, './data/server1.key'); + var pem_path = path.join(__dirname, './data/server1.pem'); + var ca_path = path.join(__dirname, '../test/data/ca.pem'); + key_data = fs.readFileSync(key_path); + pem_data = fs.readFileSync(pem_path); + ca_data = fs.readFileSync(ca_path); +}); + +describe('channel credentials', function() { + describe('#createSsl', function() { + it('works with no arguments', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.credentials.createSsl(); + }); + assert.notEqual(creds, null); + }); + it('works with just one Buffer argument', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.credentials.createSsl(ca_data); + }); + assert.notEqual(creds, null); + }); + it('works with 3 Buffer arguments', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.credentials.createSsl(ca_data, key_data, pem_data); + }); + assert.notEqual(creds, null); + }); + it('works if the first argument is null', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.credentials.createSsl(null, key_data, pem_data); + }); + assert.notEqual(creds, null); + }); + it('fails if the first argument is a non-Buffer value', function() { + assert.throws(function() { + grpc.credentials.createSsl('test'); + }, TypeError); + }); + it('fails if the second argument is a non-Buffer value', function() { + assert.throws(function() { + grpc.credentials.createSsl(null, 'test', pem_data); + }, TypeError); + }); + it('fails if the third argument is a non-Buffer value', function() { + assert.throws(function() { + grpc.credentials.createSsl(null, key_data, 'test'); + }, TypeError); + }); + it('fails if only 1 of the last 2 arguments is provided', function() { + assert.throws(function() { + grpc.credentials.createSsl(null, key_data); + }); + assert.throws(function() { + grpc.credentials.createSsl(null, null, pem_data); + }); + }); + }); +}); + +describe('server credentials', function() { + describe('#createSsl', function() { + it('accepts a buffer and array as the first 2 arguments', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.ServerCredentials.createSsl(ca_data, []); + }); + assert.notEqual(creds, null); + }); + it('accepts a boolean as the third argument', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.ServerCredentials.createSsl(ca_data, [], true); + }); + assert.notEqual(creds, null); + }); + it('accepts an object with two buffers in the second argument', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.ServerCredentials.createSsl(null, + [{private_key: key_data, + cert_chain: pem_data}]); + }); + assert.notEqual(creds, null); + }); + it('accepts multiple objects in the second argument', function() { + var creds; + assert.doesNotThrow(function() { + creds = grpc.ServerCredentials.createSsl(null, + [{private_key: key_data, + cert_chain: pem_data}, + {private_key: key_data, + cert_chain: pem_data}]); + }); + assert.notEqual(creds, null); + }); + it('fails if the second argument is not an Array', function() { + assert.throws(function() { + grpc.ServerCredentials.createSsl(ca_data, 'test'); + }, TypeError); + }); + it('fails if the first argument is a non-Buffer value', function() { + assert.throws(function() { + grpc.ServerCredentials.createSsl('test', []); + }, TypeError); + }); + it('fails if the third argument is a non-boolean value', function() { + assert.throws(function() { + grpc.ServerCredentials.createSsl(ca_data, [], 'test'); + }, TypeError); + }); + it('fails if the array elements are not objects', function() { + assert.throws(function() { + grpc.ServerCredentials.createSsl(ca_data, 'test'); + }, TypeError); + }); + it('fails if the object does not have a Buffer private_key', function() { + assert.throws(function() { + grpc.ServerCredentials.createSsl(null, + [{private_key: 'test', + cert_chain: pem_data}]); + }, TypeError); + }); + it('fails if the object does not have a Buffer cert_chain', function() { + assert.throws(function() { + grpc.ServerCredentials.createSsl(null, + [{private_key: key_data, + cert_chain: 'test'}]); + }, TypeError); + }); + }); +}); + describe('client credentials', function() { var Client; var server; @@ -109,20 +249,13 @@ describe('client credentials', function() { }); } }); - var key_path = path.join(__dirname, './data/server1.key'); - var pem_path = path.join(__dirname, './data/server1.pem'); - var key_data = fs.readFileSync(key_path); - var pem_data = fs.readFileSync(pem_path); var creds = grpc.ServerCredentials.createSsl(null, [{private_key: key_data, cert_chain: pem_data}]); - //creds = grpc.ServerCredentials.createInsecure(); port = server.bind('localhost:0', creds); server.start(); Client = proto.TestService; - var ca_path = path.join(__dirname, '../test/data/ca.pem'); - var ca_data = fs.readFileSync(ca_path); client_ssl_creds = grpc.credentials.createSsl(ca_data); var host_override = 'foo.test.google.fr'; client_options['grpc.ssl_target_name_override'] = host_override; diff --git a/src/node/test/server_test.js b/src/node/test/server_test.js index 999a183b3c..592f47e145 100644 --- a/src/node/test/server_test.js +++ b/src/node/test/server_test.js @@ -45,9 +45,30 @@ describe('server', function() { new grpc.Server(); }); }); - it('should work with an empty list argument', function() { + it('should work with an empty object argument', function() { assert.doesNotThrow(function() { - new grpc.Server([]); + new grpc.Server({}); + }); + }); + it('should work without the new keyword', function() { + var server; + assert.doesNotThrow(function() { + server = grpc.Server(); + }); + assert(server instanceof grpc.Server); + }); + it('should only accept objects with string or int values', function() { + assert.doesNotThrow(function() { + new grpc.Server({'key' : 'value'}); + }); + assert.doesNotThrow(function() { + new grpc.Server({'key' : 5}); + }); + assert.throws(function() { + new grpc.Server({'key' : null}); + }); + assert.throws(function() { + new grpc.Server({'key' : new Date()}); }); }); }); |