Merge pull request #3815 from murgatroid99/node_increase_coverage

Bring Node coverage up to 95%
pull/3819/head
Nicolas Noble 9 years ago
commit 68638f31a4
  1. 34
      binding.gyp
  2. 18
      src/node/ext/call.cc
  3. 13
      src/node/ext/call.h
  4. 13
      src/node/ext/call_credentials.cc
  5. 118
      src/node/ext/channel.cc
  6. 5
      src/node/ext/channel.h
  7. 13
      src/node/ext/channel_credentials.cc
  8. 49
      src/node/ext/server.cc
  9. 15
      src/node/ext/server_credentials.cc
  10. 1
      src/node/interop/interop_client.js
  11. 1
      src/node/src/client.js
  12. 11
      src/node/src/common.js
  13. 3
      src/node/src/credentials.js
  14. 54
      src/node/test/call_test.js
  15. 7
      src/node/test/channel_test.js
  16. 62
      src/node/test/credentials_test.js
  17. 20
      src/node/test/health_test.js
  18. 2
      src/node/test/interop_sanity_test.js
  19. 175
      src/node/test/surface_test.js
  20. 34
      templates/binding.gyp.template
  21. 3
      tools/run_tests/run_node.sh

@ -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',

@ -83,6 +83,18 @@ using v8::Value;
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 @@ NAN_METHOD(Call::CancelWithStatus) {
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);
}

@ -53,18 +53,7 @@ using std::shared_ptr;
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);

@ -126,16 +126,9 @@ NAN_METHOD(CallCredentials::New) {
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");
}
}

@ -71,6 +71,72 @@ using v8::Value;
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 @@ NAN_METHOD(Channel::New) {
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 @@ NAN_METHOD(Channel::New) {
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());

@ -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:

@ -127,16 +127,9 @@ NAN_METHOD(ChannelCredentials::New) {
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");
}
}

@ -182,49 +182,14 @@ NAN_METHOD(Server::New) {
}
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());

@ -117,7 +117,7 @@ NAN_METHOD(ServerCredentials::New) {
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 @@ NAN_METHOD(ServerCredentials::New) {
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");
}
}

@ -35,7 +35,6 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var grpc = require('..');
var testProto = grpc.load({
root: __dirname + '/../../..',

@ -661,6 +661,7 @@ exports.waitForClientReady = function(client, deadline, callback) {
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) {

@ -87,14 +87,9 @@ exports.fullyQualifiedName = function fullyQualifiedName(value) {
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;
};

@ -99,6 +99,9 @@ exports.createFromMetadataGenerator = function(metadata_generator) {
if (error.hasOwnProperty('code')) {
code = error.code;
}
if (!metadata) {
metadata = new Metadata();
}
}
callback(code, message, metadata._getCoreRepresentation());
});

@ -107,6 +107,23 @@ describe('call', function() {
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('call', function() {
});
});
});
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));

@ -104,6 +104,12 @@ describe('channel', function() {
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 @@ describe('channel', function() {
deadline.setSeconds(deadline.getSeconds() + 1);
channel.watchConnectivityState(old_state, deadline, function(err, value) {
assert(err);
console.log('Callback from watchConnectivityState');
done();
});
});

@ -60,6 +60,22 @@ function multiDone(done, count) {
};
}
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 @@ describe('client credentials', function() {
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;

@ -45,11 +45,13 @@ describe('Health Checking', function() {
'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 @@ describe('Health Checking', function() {
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();
});
});
});
});

@ -71,7 +71,7 @@ describe('Interop tests', function() {
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) {

@ -92,6 +92,31 @@ describe('File loader', function() {
});
});
});
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 @@ describe('waitForClientReady', function() {
});
});
});
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 @@ describe('Echo metadata', function() {
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 @@ describe('Other conditions', function() {
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 @@ describe('Other conditions', function() {
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 @@ describe('Other conditions', function() {
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]);

@ -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',

@ -45,7 +45,8 @@ then
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

Loading…
Cancel
Save