Merge pull request #3818 from stanley-cheung/jenkins_interop_script_more_flexibility
Allow extra docker args to be passed to build_interop_image.sh
diff --git a/binding.gyp b/binding.gyp
index 39036b3..ba7b6df 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -54,6 +54,24 @@
'include_dirs': [
'.',
'include'
+ ],
+ 'conditions': [
+ ['OS != "win"', {
+ 'conditions': [
+ ['config=="gcov"', {
+ 'cflags': [
+ '-ftest-coverage',
+ '-fprofile-arcs',
+ '-O0'
+ ],
+ 'ldflags': [
+ '-ftest-coverage',
+ '-fprofile-arcs'
+ ]
+ }
+ ]
+ ]
+ }],
]
},
'targets': [
@@ -279,22 +297,6 @@
'-g'
],
"conditions": [
- ['OS != "win"', {
- 'conditions': [
- ['config=="gcov"', {
- 'cflags': [
- '-ftest-coverage',
- '-fprofile-arcs',
- '-O0'
- ],
- 'ldflags': [
- '-ftest-coverage',
- '-fprofile-arcs'
- ]
- }
- ]
- ]
- }],
['OS == "mac"', {
'xcode_settings': {
'MACOSX_DEPLOYMENT_TARGET': '10.9',
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index b63e294..fe11905 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -83,6 +83,18 @@
Callback *Call::constructor;
Persistent<FunctionTemplate> Call::fun_tpl;
+/**
+ * Helper function for throwing errors with a grpc_call_error value.
+ * Modified from the answer by Gus Goose to
+ * http://stackoverflow.com/questions/31794200.
+ */
+Local<Value> nanErrorWithCode(const char *msg, grpc_call_error code) {
+ EscapableHandleScope scope;
+ Local<Object> err = Nan::Error(msg).As<Object>();
+ Nan::Set(err, Nan::New("code").ToLocalChecked(), Nan::New<Uint32>(code));
+ return scope.Escape(err);
+}
+
bool EndsWith(const char *str, const char *substr) {
return strcmp(str+strlen(str)-strlen(substr), substr) == 0;
}
@@ -712,7 +724,11 @@
Call *call = ObjectWrap::Unwrap<Call>(info.This());
grpc_status_code code = static_cast<grpc_status_code>(
Nan::To<uint32_t>(info[0]).FromJust());
- Utf8String details(info[0]);
+ if (code == GRPC_STATUS_OK) {
+ return Nan::ThrowRangeError(
+ "cancelWithStatus cannot be called with OK status");
+ }
+ Utf8String details(info[1]);
grpc_call_cancel_with_status(call->wrapped_call, code, *details, NULL);
}
diff --git a/src/node/ext/call.h b/src/node/ext/call.h
index dd6c38e..1e3c3ba 100644
--- a/src/node/ext/call.h
+++ b/src/node/ext/call.h
@@ -53,18 +53,7 @@
typedef Nan::Persistent<v8::Value, Nan::CopyablePersistentTraits<v8::Value>> PersistentValue;
-/**
- * Helper function for throwing errors with a grpc_call_error value.
- * Modified from the answer by Gus Goose to
- * http://stackoverflow.com/questions/31794200.
- */
-inline v8::Local<v8::Value> nanErrorWithCode(const char *msg,
- grpc_call_error code) {
- Nan::EscapableHandleScope scope;
- v8::Local<v8::Object> err = Nan::Error(msg).As<v8::Object>();
- Nan::Set(err, Nan::New("code").ToLocalChecked(), Nan::New<v8::Uint32>(code));
- return scope.Escape(err);
-}
+v8::Local<v8::Value> nanErrorWithCode(const char *msg, grpc_call_error code);
v8::Local<v8::Value> ParseMetadata(const grpc_metadata_array *metadata_array);
diff --git a/src/node/ext/call_credentials.cc b/src/node/ext/call_credentials.cc
index 839bb56..ff16a1f 100644
--- a/src/node/ext/call_credentials.cc
+++ b/src/node/ext/call_credentials.cc
@@ -126,16 +126,9 @@
info.GetReturnValue().Set(info.This());
return;
} else {
- const int argc = 1;
- Local<Value> argv[argc] = {info[0]};
- MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
- argc, argv);
- if (maybe_instance.IsEmpty()) {
- // There's probably a pending exception
- return;
- } else {
- info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
- }
+ // This should never be called directly
+ return Nan::ThrowTypeError(
+ "CallCredentials can only be created with the provided functions");
}
}
diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc
index a328c01..584a0cf 100644
--- a/src/node/ext/channel.cc
+++ b/src/node/ext/channel.cc
@@ -71,6 +71,72 @@
Callback *Channel::constructor;
Persistent<FunctionTemplate> Channel::fun_tpl;
+bool ParseChannelArgs(Local<Value> args_val,
+ grpc_channel_args **channel_args_ptr) {
+ if (args_val->IsUndefined() || args_val->IsNull()) {
+ *channel_args_ptr = NULL;
+ return true;
+ }
+ if (!args_val->IsObject()) {
+ *channel_args_ptr = NULL;
+ return false;
+ }
+ grpc_channel_args *channel_args = reinterpret_cast<grpc_channel_args*>(
+ malloc(sizeof(channel_args)));
+ *channel_args_ptr = channel_args;
+ Local<Object> args_hash = Nan::To<Object>(args_val).ToLocalChecked();
+ Local<Array> keys = Nan::GetOwnPropertyNames(args_hash).ToLocalChecked();
+ channel_args->num_args = keys->Length();
+ channel_args->args = reinterpret_cast<grpc_arg *>(
+ calloc(channel_args->num_args, sizeof(grpc_arg)));
+ for (unsigned int i = 0; i < channel_args->num_args; i++) {
+ Local<Value> key = Nan::Get(keys, i).ToLocalChecked();
+ Utf8String key_str(key);
+ if (*key_str == NULL) {
+ // Key string onversion failed
+ return false;
+ }
+ Local<Value> value = Nan::Get(args_hash, key).ToLocalChecked();
+ if (value->IsInt32()) {
+ channel_args->args[i].type = GRPC_ARG_INTEGER;
+ channel_args->args[i].value.integer = Nan::To<int32_t>(value).FromJust();
+ } else if (value->IsString()) {
+ Utf8String val_str(value);
+ channel_args->args[i].type = GRPC_ARG_STRING;
+ channel_args->args[i].value.string = reinterpret_cast<char*>(
+ calloc(val_str.length() + 1,sizeof(char)));
+ memcpy(channel_args->args[i].value.string,
+ *val_str, val_str.length() + 1);
+ } else {
+ // The value does not match either of the accepted types
+ return false;
+ }
+ channel_args->args[i].key = reinterpret_cast<char*>(
+ calloc(key_str.length() + 1, sizeof(char)));
+ memcpy(channel_args->args[i].key, *key_str, key_str.length() + 1);
+ }
+ return true;
+}
+
+void DeallocateChannelArgs(grpc_channel_args *channel_args) {
+ if (channel_args == NULL) {
+ return;
+ }
+ for (size_t i = 0; i < channel_args->num_args; i++) {
+ if (channel_args->args[i].key == NULL) {
+ /* NULL key implies that this argument and all subsequent arguments failed
+ * to parse */
+ break;
+ }
+ free(channel_args->args[i].key);
+ if (channel_args->args[i].type == GRPC_ARG_STRING) {
+ free(channel_args->args[i].value.string);
+ }
+ }
+ free(channel_args->args);
+ free(channel_args);
+}
+
Channel::Channel(grpc_channel *channel) : wrapped_channel(channel) {}
Channel::~Channel() {
@@ -119,49 +185,11 @@
ChannelCredentials *creds_object = ObjectWrap::Unwrap<ChannelCredentials>(
Nan::To<Object>(info[1]).ToLocalChecked());
creds = creds_object->GetWrappedCredentials();
- grpc_channel_args *channel_args_ptr;
- if (info[2]->IsUndefined()) {
- channel_args_ptr = NULL;
- wrapped_channel = grpc_insecure_channel_create(*host, NULL, NULL);
- } else if (info[2]->IsObject()) {
- Local<Object> args_hash = Nan::To<Object>(info[2]).ToLocalChecked();
- Local<Array> keys(Nan::GetOwnPropertyNames(args_hash).ToLocalChecked());
- grpc_channel_args channel_args;
- channel_args.num_args = keys->Length();
- channel_args.args = reinterpret_cast<grpc_arg *>(
- calloc(channel_args.num_args, sizeof(grpc_arg)));
- /* These are used to keep all strings until then end of the block, then
- destroy them */
- std::vector<Nan::Utf8String *> key_strings(keys->Length());
- std::vector<Nan::Utf8String *> value_strings(keys->Length());
- for (unsigned int i = 0; i < channel_args.num_args; i++) {
- MaybeLocal<String> maybe_key = Nan::To<String>(
- Nan::Get(keys, i).ToLocalChecked());
- if (maybe_key.IsEmpty()) {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg keys must be strings");
- }
- Local<String> current_key = maybe_key.ToLocalChecked();
- Local<Value> current_value = Nan::Get(args_hash,
- current_key).ToLocalChecked();
- key_strings[i] = new Nan::Utf8String(current_key);
- channel_args.args[i].key = **key_strings[i];
- if (current_value->IsInt32()) {
- channel_args.args[i].type = GRPC_ARG_INTEGER;
- channel_args.args[i].value.integer = Nan::To<int32_t>(
- current_value).FromJust();
- } else if (current_value->IsString()) {
- channel_args.args[i].type = GRPC_ARG_STRING;
- value_strings[i] = new Nan::Utf8String(current_value);
- channel_args.args[i].value.string = **value_strings[i];
- } else {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg values must be strings");
- }
- }
- channel_args_ptr = &channel_args;
- } else {
- return Nan::ThrowTypeError("Channel expects a string and an object");
+ grpc_channel_args *channel_args_ptr = NULL;
+ if (!ParseChannelArgs(info[2], &channel_args_ptr)) {
+ DeallocateChannelArgs(channel_args_ptr);
+ return Nan::ThrowTypeError("Channel options must be an object with "
+ "string keys and integer or string values");
}
if (creds == NULL) {
wrapped_channel = grpc_insecure_channel_create(*host, channel_args_ptr,
@@ -170,9 +198,7 @@
wrapped_channel =
grpc_secure_channel_create(creds, *host, channel_args_ptr, NULL);
}
- if (channel_args_ptr != NULL) {
- free(channel_args_ptr->args);
- }
+ DeallocateChannelArgs(channel_args_ptr);
Channel *channel = new Channel(wrapped_channel);
channel->Wrap(info.This());
info.GetReturnValue().Set(info.This());
diff --git a/src/node/ext/channel.h b/src/node/ext/channel.h
index 0062fd0..9ec28e1 100644
--- a/src/node/ext/channel.h
+++ b/src/node/ext/channel.h
@@ -41,6 +41,11 @@
namespace grpc {
namespace node {
+bool ParseChannelArgs(v8::Local<v8::Value> args_val,
+ grpc_channel_args **channel_args_ptr);
+
+void DeallocateChannelArgs(grpc_channel_args *channel_args);
+
/* Wrapper class for grpc_channel structs */
class Channel : public Nan::ObjectWrap {
public:
diff --git a/src/node/ext/channel_credentials.cc b/src/node/ext/channel_credentials.cc
index 3d47ff2..7ca3b98 100644
--- a/src/node/ext/channel_credentials.cc
+++ b/src/node/ext/channel_credentials.cc
@@ -127,16 +127,9 @@
info.GetReturnValue().Set(info.This());
return;
} else {
- const int argc = 1;
- Local<Value> argv[argc] = {info[0]};
- MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
- argc, argv);
- if (maybe_instance.IsEmpty()) {
- // There's probably a pending exception
- return;
- } else {
- info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
- }
+ // This should never be called directly
+ return Nan::ThrowTypeError(
+ "ChannelCredentials can only be created with the provided functions");
}
}
diff --git a/src/node/ext/server.cc b/src/node/ext/server.cc
index 87363fc..b9e1fe9 100644
--- a/src/node/ext/server.cc
+++ b/src/node/ext/server.cc
@@ -182,49 +182,14 @@
}
grpc_server *wrapped_server;
grpc_completion_queue *queue = CompletionQueueAsyncWorker::GetQueue();
- if (info[0]->IsUndefined()) {
- wrapped_server = grpc_server_create(NULL, NULL);
- } else if (info[0]->IsObject()) {
- Local<Object> args_hash = Nan::To<Object>(info[0]).ToLocalChecked();
- Local<Array> keys = Nan::GetOwnPropertyNames(args_hash).ToLocalChecked();
- grpc_channel_args channel_args;
- channel_args.num_args = keys->Length();
- channel_args.args = reinterpret_cast<grpc_arg *>(
- calloc(channel_args.num_args, sizeof(grpc_arg)));
- /* These are used to keep all strings until then end of the block, then
- destroy them */
- std::vector<Utf8String *> key_strings(keys->Length());
- std::vector<Utf8String *> value_strings(keys->Length());
- for (unsigned int i = 0; i < channel_args.num_args; i++) {
- MaybeLocal<String> maybe_key = Nan::To<String>(
- Nan::Get(keys, i).ToLocalChecked());
- if (maybe_key.IsEmpty()) {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg keys must be strings");
- }
- Local<String> current_key = maybe_key.ToLocalChecked();
- Local<Value> current_value = Nan::Get(args_hash,
- current_key).ToLocalChecked();
- key_strings[i] = new Utf8String(current_key);
- channel_args.args[i].key = **key_strings[i];
- if (current_value->IsInt32()) {
- channel_args.args[i].type = GRPC_ARG_INTEGER;
- channel_args.args[i].value.integer = Nan::To<int32_t>(
- current_value).FromJust();
- } else if (current_value->IsString()) {
- channel_args.args[i].type = GRPC_ARG_STRING;
- value_strings[i] = new Utf8String(current_value);
- channel_args.args[i].value.string = **value_strings[i];
- } else {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg values must be strings");
- }
- }
- wrapped_server = grpc_server_create(&channel_args, NULL);
- free(channel_args.args);
- } else {
- return Nan::ThrowTypeError("Server expects an object");
+ grpc_channel_args *channel_args;
+ if (!ParseChannelArgs(info[0], &channel_args)) {
+ DeallocateChannelArgs(channel_args);
+ return Nan::ThrowTypeError("Server options must be an object with "
+ "string keys and integer or string values");
}
+ wrapped_server = grpc_server_create(channel_args, NULL);
+ DeallocateChannelArgs(channel_args);
grpc_server_register_completion_queue(wrapped_server, queue, NULL);
Server *server = new Server(wrapped_server);
server->Wrap(info.This());
diff --git a/src/node/ext/server_credentials.cc b/src/node/ext/server_credentials.cc
index 5e922bd..e6c55e2 100644
--- a/src/node/ext/server_credentials.cc
+++ b/src/node/ext/server_credentials.cc
@@ -117,7 +117,7 @@
if (info.IsConstructCall()) {
if (!info[0]->IsExternal()) {
return Nan::ThrowTypeError(
- "ServerCredentials can only be created with the provide functions");
+ "ServerCredentials can only be created with the provided functions");
}
Local<External> ext = info[0].As<External>();
grpc_server_credentials *creds_value =
@@ -126,16 +126,9 @@
credentials->Wrap(info.This());
info.GetReturnValue().Set(info.This());
} else {
- const int argc = 1;
- Local<Value> argv[argc] = {info[0]};
- MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
- argc, argv);
- if (maybe_instance.IsEmpty()) {
- // There's probably a pending exception
- return;
- } else {
- info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
- }
+ // This should never be called directly
+ return Nan::ThrowTypeError(
+ "ServerCredentials can only be created with the provided functions");
}
}
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index cb55083..b506189 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -35,7 +35,6 @@
var fs = require('fs');
var path = require('path');
-var _ = require('lodash');
var grpc = require('..');
var testProto = grpc.load({
root: __dirname + '/../../..',
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 909376e..596ea5e 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -661,6 +661,7 @@
var checkState = function(err) {
if (err) {
callback(new Error('Failed to connect before the deadline'));
+ return;
}
var new_state = client.$channel.getConnectivityState(true);
if (new_state === grpc.connectivityState.READY) {
diff --git a/src/node/src/common.js b/src/node/src/common.js
index 5551ebe..ebaaa13 100644
--- a/src/node/src/common.js
+++ b/src/node/src/common.js
@@ -87,14 +87,9 @@
return '';
}
var name = value.name;
- if (value.className === 'Service.RPCMethod') {
- name = _.capitalize(name);
- }
- if (value.hasOwnProperty('parent')) {
- var parent_name = fullyQualifiedName(value.parent);
- if (parent_name !== '') {
- name = parent_name + '.' + name;
- }
+ var parent_name = fullyQualifiedName(value.parent);
+ if (parent_name !== '') {
+ name = parent_name + '.' + name;
}
return name;
};
diff --git a/src/node/src/credentials.js b/src/node/src/credentials.js
index ddc094f..ff10a22 100644
--- a/src/node/src/credentials.js
+++ b/src/node/src/credentials.js
@@ -99,6 +99,9 @@
if (error.hasOwnProperty('code')) {
code = error.code;
}
+ if (!metadata) {
+ metadata = new Metadata();
+ }
}
callback(code, message, metadata._getCoreRepresentation());
});
diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js
index c316fe7..f1f86b3 100644
--- a/src/node/test/call_test.js
+++ b/src/node/test/call_test.js
@@ -107,6 +107,23 @@
new grpc.Call(channel, 'method', 'now');
}, TypeError);
});
+ it('should succeed without the new keyword', function() {
+ assert.doesNotThrow(function() {
+ var call = grpc.Call(channel, 'method', new Date());
+ assert(call instanceof grpc.Call);
+ });
+ });
+ });
+ describe('deadline', function() {
+ it('should time out immediately with negative deadline', function(done) {
+ var call = new grpc.Call(channel, 'method', -Infinity);
+ var batch = {};
+ batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+ call.startBatch(batch, function(err, response) {
+ assert.strictEqual(response.status.code, grpc.status.DEADLINE_EXCEEDED);
+ done();
+ });
+ });
});
describe('startBatch', function() {
it('should fail without an object and a function', function() {
@@ -192,6 +209,43 @@
});
});
});
+ describe('cancelWithStatus', function() {
+ it('should reject anything other than an integer and a string', function() {
+ assert.doesNotThrow(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus(1, 'details');
+ });
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus();
+ });
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus('');
+ });
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus(5, {});
+ });
+ });
+ it('should reject the OK status code', function() {
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus(0, 'details');
+ });
+ });
+ it('should result in the call ending with a status', function(done) {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ var batch = {};
+ batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+ call.startBatch(batch, function(err, response) {
+ assert.strictEqual(response.status.code, 5);
+ assert.strictEqual(response.status.details, 'details');
+ done();
+ });
+ call.cancelWithStatus(5, 'details');
+ });
+ });
describe('getPeer', function() {
it('should return a string', function() {
var call = new grpc.Call(channel, 'method', getDeadline(1));
diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js
index 05269f7..7163a5f 100644
--- a/src/node/test/channel_test.js
+++ b/src/node/test/channel_test.js
@@ -104,6 +104,12 @@
new grpc.Channel('hostname', insecureCreds, {'key' : new Date()});
});
});
+ it('should succeed without the new keyword', function() {
+ assert.doesNotThrow(function() {
+ var channel = grpc.Channel('hostname', insecureCreds);
+ assert(channel instanceof grpc.Channel);
+ });
+ });
});
describe('close', function() {
var channel;
@@ -155,7 +161,6 @@
deadline.setSeconds(deadline.getSeconds() + 1);
channel.watchConnectivityState(old_state, deadline, function(err, value) {
assert(err);
- console.log('Callback from watchConnectivityState');
done();
});
});
diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js
index 7fc311a..3d0b38f 100644
--- a/src/node/test/credentials_test.js
+++ b/src/node/test/credentials_test.js
@@ -60,6 +60,22 @@
};
}
+var fakeSuccessfulGoogleCredentials = {
+ getRequestMetadata: function(service_url, callback) {
+ setTimeout(function() {
+ callback(null, {Authorization: 'success'});
+ }, 0);
+ }
+};
+
+var fakeFailingGoogleCredentials = {
+ getRequestMetadata: function(service_url, callback) {
+ setTimeout(function() {
+ callback(new Error("Authorization failure"));
+ }, 0);
+ }
+};
+
describe('client credentials', function() {
var Client;
var server;
@@ -169,6 +185,52 @@
done();
});
});
+ it.skip('should propagate errors that the updater emits', function(done) {
+ var metadataUpdater = function(service_url, callback) {
+ var error = new Error('Authentication error');
+ error.code = grpc.status.UNAUTHENTICATED;
+ callback(error);
+ };
+ var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
+ var combined_creds = grpc.credentials.combineChannelCredentials(
+ client_ssl_creds, creds);
+ var client = new Client('localhost:' + port, combined_creds,
+ client_options);
+ client.unary({}, function(err, data) {
+ assert(err);
+ assert.strictEqual(err.message, 'Authentication error');
+ assert.strictEqual(err.code, grpc.status.UNAUTHENTICATED);
+ done();
+ });
+ });
+ it('should successfully wrap a Google credential', function(done) {
+ var creds = grpc.credentials.createFromGoogleCredential(
+ fakeSuccessfulGoogleCredentials);
+ var combined_creds = grpc.credentials.combineChannelCredentials(
+ client_ssl_creds, creds);
+ var client = new Client('localhost:' + port, combined_creds,
+ client_options);
+ var call = client.unary({}, function(err, data) {
+ assert.ifError(err);
+ });
+ call.on('metadata', function(metadata) {
+ assert.deepEqual(metadata.get('authorization'), ['success']);
+ done();
+ });
+ });
+ it.skip('should get an error from a Google credential', function(done) {
+ var creds = grpc.credentials.createFromGoogleCredential(
+ fakeFailingGoogleCredentials);
+ var combined_creds = grpc.credentials.combineChannelCredentials(
+ client_ssl_creds, creds);
+ var client = new Client('localhost:' + port, combined_creds,
+ client_options);
+ client.unary({}, function(err, data) {
+ assert(err);
+ assert.strictEqual(err.message, 'Authorization failure');
+ done();
+ });
+ });
describe('Per-rpc creds', function() {
var client;
var updater_creds;
diff --git a/src/node/test/health_test.js b/src/node/test/health_test.js
index a4dc24c..c93b528 100644
--- a/src/node/test/health_test.js
+++ b/src/node/test/health_test.js
@@ -45,11 +45,13 @@
'grpc.test.TestServiceNotServing': 'NOT_SERVING',
'grpc.test.TestServiceServing': 'SERVING'
};
- var healthServer = new grpc.Server();
- healthServer.addProtoService(health.service,
- new health.Implementation(statusMap));
+ var healthServer;
+ var healthImpl;
var healthClient;
before(function() {
+ healthServer = new grpc.Server();
+ healthImpl = new health.Implementation(statusMap);
+ healthServer.addProtoService(health.service, healthImpl);
var port_num = healthServer.bind('0.0.0.0:0',
grpc.ServerCredentials.createInsecure());
healthServer.start();
@@ -89,4 +91,16 @@
done();
});
});
+ it('should get a different response if the status changes', function(done) {
+ healthClient.check({service: 'transient'}, function(err, response) {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.NOT_FOUND);
+ healthImpl.setStatus('transient', 'SERVING');
+ healthClient.check({service: 'transient'}, function(err, response) {
+ assert.ifError(err);
+ assert.strictEqual(response.status, 'SERVING');
+ done();
+ });
+ });
+ });
});
diff --git a/src/node/test/interop_sanity_test.js b/src/node/test/interop_sanity_test.js
index f8c0b14..f008a87 100644
--- a/src/node/test/interop_sanity_test.js
+++ b/src/node/test/interop_sanity_test.js
@@ -71,7 +71,7 @@
interop_client.runTest(port, name_override, 'server_streaming', true, true,
done);
});
- it.only('should pass ping_pong', function(done) {
+ it('should pass ping_pong', function(done) {
interop_client.runTest(port, name_override, 'ping_pong', true, true, done);
});
it('should pass empty_stream', function(done) {
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index 395ea88..39673e4 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -92,6 +92,31 @@
});
});
});
+describe('surface Server', function() {
+ var server;
+ beforeEach(function() {
+ server = new grpc.Server();
+ });
+ afterEach(function() {
+ server.forceShutdown();
+ });
+ it('should error if started twice', function() {
+ server.start();
+ assert.throws(function() {
+ server.start();
+ });
+ });
+ it('should error if a port is bound after the server starts', function() {
+ server.start();
+ assert.throws(function() {
+ server.bind('localhost:0', grpc.ServerCredentials.createInsecure());
+ });
+ });
+ it('should successfully shutdown if tryShutdown is called', function(done) {
+ server.start();
+ server.tryShutdown(done);
+ });
+});
describe('Server.prototype.addProtoService', function() {
var server;
var dummyImpls = {
@@ -202,6 +227,16 @@
});
});
});
+ it('should time out if the server does not exist', function(done) {
+ var bad_client = new Client('nonexistent_hostname',
+ grpc.credentials.createInsecure());
+ var deadline = new Date();
+ deadline.setSeconds(deadline.getSeconds() + 1);
+ grpc.waitForClientReady(bad_client, deadline, function(error) {
+ assert(error);
+ done();
+ });
+ });
});
describe('Echo service', function() {
var server;
@@ -365,6 +400,123 @@
done();
});
});
+ it('properly handles duplicate values', function(done) {
+ var dup_metadata = metadata.clone();
+ dup_metadata.add('key', 'value2');
+ var call = client.unary({}, function(err, data) {assert.ifError(err); },
+ dup_metadata);
+ call.on('metadata', function(resp_metadata) {
+ // Two arrays are equal iff their symmetric difference is empty
+ assert.deepEqual(_.xor(dup_metadata.get('key'), resp_metadata.get('key')),
+ []);
+ done();
+ });
+ });
+});
+describe('Client malformed response handling', function() {
+ var server;
+ var client;
+ var badArg = new Buffer([0xFF]);
+ before(function() {
+ var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
+ var test_service = test_proto.lookup('TestService');
+ var malformed_test_service = {
+ unary: {
+ path: '/TestService/Unary',
+ requestStream: false,
+ responseStream: false,
+ requestDeserialize: _.identity,
+ responseSerialize: _.identity
+ },
+ clientStream: {
+ path: '/TestService/ClientStream',
+ requestStream: true,
+ responseStream: false,
+ requestDeserialize: _.identity,
+ responseSerialize: _.identity
+ },
+ serverStream: {
+ path: '/TestService/ServerStream',
+ requestStream: false,
+ responseStream: true,
+ requestDeserialize: _.identity,
+ responseSerialize: _.identity
+ },
+ bidiStream: {
+ path: '/TestService/BidiStream',
+ requestStream: true,
+ responseStream: true,
+ requestDeserialize: _.identity,
+ responseSerialize: _.identity
+ }
+ };
+ server = new grpc.Server();
+ server.addService(malformed_test_service, {
+ unary: function(call, cb) {
+ cb(null, badArg);
+ },
+ clientStream: function(stream, cb) {
+ stream.on('data', function() {/* Ignore requests */});
+ stream.on('end', function() {
+ cb(null, badArg);
+ });
+ },
+ serverStream: function(stream) {
+ stream.write(badArg);
+ stream.end();
+ },
+ bidiStream: function(stream) {
+ stream.on('data', function() {
+ // Ignore requests
+ stream.write(badArg);
+ });
+ stream.on('end', function() {
+ stream.end();
+ });
+ }
+ });
+ var port = server.bind('localhost:0', server_insecure_creds);
+ var Client = surface_client.makeProtobufClientConstructor(test_service);
+ client = new Client('localhost:' + port, grpc.credentials.createInsecure());
+ server.start();
+ });
+ after(function() {
+ server.forceShutdown();
+ });
+ it('should get an INTERNAL status with a unary call', function(done) {
+ client.unary({}, function(err, data) {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.INTERNAL);
+ done();
+ });
+ });
+ it('should get an INTERNAL status with a client stream call', function(done) {
+ var call = client.clientStream(function(err, data) {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.INTERNAL);
+ done();
+ });
+ call.write({});
+ call.end();
+ });
+ it('should get an INTERNAL status with a server stream call', function(done) {
+ var call = client.serverStream({});
+ call.on('data', function(){});
+ call.on('error', function(err) {
+ assert.strictEqual(err.code, grpc.status.INTERNAL);
+ done();
+ });
+ });
+ it('should get an INTERNAL status with a bidi stream call', function(done) {
+ var call = client.bidiStream();
+ call.on('data', function(){});
+ call.on('error', function(err) {
+ assert.strictEqual(err.code, grpc.status.INTERNAL);
+ done();
+ });
+ call.write({});
+ call.end();
+ });
});
describe('Other conditions', function() {
var test_service;
@@ -382,7 +534,8 @@
unary: function(call, cb) {
var req = call.request;
if (req.error) {
- cb(new Error('Requested error'), null, trailer_metadata);
+ cb({code: grpc.status.UNKNOWN,
+ details: 'Requested error'}, null, trailer_metadata);
} else {
cb(null, {count: 1}, trailer_metadata);
}
@@ -407,7 +560,8 @@
serverStream: function(stream) {
var req = stream.request;
if (req.error) {
- var err = new Error('Requested error');
+ var err = {code: grpc.status.UNKNOWN,
+ details: 'Requested error'};
err.metadata = trailer_metadata;
stream.emit('error', err);
} else {
@@ -447,6 +601,23 @@
assert.strictEqual(typeof grpc.getClientChannel(client).getTarget(),
'string');
});
+ it('client should be able to pause and resume a stream', function(done) {
+ var call = client.bidiStream();
+ call.on('data', function(data) {
+ assert(data.count < 3);
+ call.pause();
+ setTimeout(function() {
+ call.resume();
+ }, 10);
+ });
+ call.on('end', function() {
+ done();
+ });
+ call.write({});
+ call.write({});
+ call.write({});
+ call.end();
+ });
describe('Server recieving bad input', function() {
var misbehavingClient;
var badArg = new Buffer([0xFF]);
diff --git a/templates/binding.gyp.template b/templates/binding.gyp.template
index 50d0823..bfa6a56 100644
--- a/templates/binding.gyp.template
+++ b/templates/binding.gyp.template
@@ -56,6 +56,24 @@
'include_dirs': [
'.',
'include'
+ ],
+ 'conditions': [
+ ['OS != "win"', {
+ 'conditions': [
+ ['config=="gcov"', {
+ 'cflags': [
+ '-ftest-coverage',
+ '-fprofile-arcs',
+ '-O0'
+ ],
+ 'ldflags': [
+ '-ftest-coverage',
+ '-fprofile-arcs'
+ ]
+ }
+ ]
+ ]
+ }],
]
},
'targets': [
@@ -95,22 +113,6 @@
'-g'
],
"conditions": [
- ['OS != "win"', {
- 'conditions': [
- ['config=="gcov"', {
- 'cflags': [
- '-ftest-coverage',
- '-fprofile-arcs',
- '-O0'
- ],
- 'ldflags': [
- '-ftest-coverage',
- '-fprofile-arcs'
- ]
- }
- ]
- ]
- }],
['OS == "mac"', {
'xcode_settings': {
'MACOSX_DEPLOYMENT_TARGET': '10.9',
diff --git a/tools/run_tests/run_node.sh b/tools/run_tests/run_node.sh
index 0a11e87..85e3a38 100755
--- a/tools/run_tests/run_node.sh
+++ b/tools/run_tests/run_node.sh
@@ -45,7 +45,8 @@
gcov Release/obj.target/grpc/ext/*.o
lcov --base-directory . --directory . -c -o coverage.info
genhtml -o ../reports/node_ext_coverage --num-spaces 2 \
- -t 'Node gRPC test coverage' coverage.info
+ -t 'Node gRPC test coverage' coverage.info --rc genhtml_hi_limit=95 \
+ --rc genhtml_med_limit=80
echo '<html><head><meta http-equiv="refresh" content="0;URL=lcov-report/index.html"></head></html>' > \
../reports/node_coverage/index.html
else