Merge pull request #3746 from murgatroid99/node_interop_compliance

Made node interop tests maximally compliant with the spec
pull/3790/head
Jan Tattermusch 9 years ago
commit abaf47c3a5
  1. 172
      src/node/interop/interop_client.js
  2. 63
      src/node/interop/interop_server.js
  3. 2
      src/node/src/server.js
  4. 2
      src/node/test/async_test.js
  5. 2
      src/node/test/channel_test.js
  6. 10
      src/node/test/credentials_test.js
  7. 10
      src/node/test/interop_sanity_test.js

@ -44,12 +44,14 @@ var GoogleAuth = require('google-auth-library');
var assert = require('assert');
var AUTH_SCOPE = 'https://www.googleapis.com/auth/xapi.zoo';
var AUTH_SCOPE_RESPONSE = 'xapi.zoo';
var AUTH_USER = ('155450119199-vefjjaekcc6cmsd5914v6lqufunmh9ue' +
'@developer.gserviceaccount.com');
var COMPUTE_ENGINE_USER = ('155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel' +
'@developer.gserviceaccount.com');
var SERVICE_ACCOUNT_EMAIL;
try {
SERVICE_ACCOUNT_EMAIL = require(
process.env.GOOGLE_APPLICATION_CREDENTIALS).client_email;
} catch (e) {
// This will cause the tests to fail if they need that string
SERVICE_ACCOUNT_EMAIL = null;
}
var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial';
var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin';
@ -345,6 +347,41 @@ function customMetadata(client, done) {
stream.end();
}
function statusCodeAndMessage(client, done) {
done = multiDone(done, 2);
var arg = {
response_status: {
code: 2,
message: 'test status message'
}
};
client.unaryCall(arg, function(err, resp) {
assert(err);
assert.strictEqual(err.code, 2);
assert.strictEqual(err.message, 'test status message');
done();
});
var duplex = client.fullDuplexCall();
duplex.on('status', function(status) {
assert(status);
assert.strictEqual(status.code, 2);
assert.strictEqual(status.details, 'test status message');
done();
});
duplex.on('error', function(){});
duplex.write(arg);
duplex.end();
}
function unimplementedMethod(client, done) {
client.unimplementedCall({}, function(err, resp) {
assert(err);
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
assert(!err.message);
done();
});
}
/**
* Run one of the authentication tests.
* @param {string} expected_user The expected username in the response
@ -369,7 +406,7 @@ function authTest(expected_user, scope, client, done) {
assert.strictEqual(resp.payload.body.length, 314159);
assert.strictEqual(resp.username, expected_user);
if (scope) {
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
assert(scope.indexOf(resp.oauth_scope) > -1);
}
if (done) {
done();
@ -377,56 +414,49 @@ function authTest(expected_user, scope, client, done) {
});
}
function oauth2Test(expected_user, scope, per_rpc, client, done) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
assert.ifError(err);
function computeEngineCreds(client, done, extra) {
authTest(extra.service_account, null, client, done);
}
function serviceAccountCreds(client, done, extra) {
authTest(SERVICE_ACCOUNT_EMAIL, extra.oauth_scope, client, done);
}
function jwtTokenCreds(client, done, extra) {
authTest(SERVICE_ACCOUNT_EMAIL, null, client, done);
}
function oauth2Test(client, done, extra) {
var arg = {
fill_username: true,
fill_oauth_scope: true
};
credential = credential.createScoped(scope);
credential.getAccessToken(function(err, token) {
assert.ifError(err);
var updateMetadata = function(authURI, metadata, callback) {
metadata.add('authorization', 'Bearer ' + token);
callback(null, metadata);
};
var makeTestCall = function(error, client_metadata) {
assert.ifError(error);
client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.username, expected_user);
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
assert.strictEqual(resp.username, SERVICE_ACCOUNT_EMAIL);
assert(extra.oauth_scope.indexOf(resp.oauth_scope) > -1);
if (done) {
done();
}
}, client_metadata);
};
if (per_rpc) {
updateMetadata('', new grpc.Metadata(), makeTestCall);
} else {
client.$updateMetadata = updateMetadata;
makeTestCall(null, new grpc.Metadata());
}
});
});
}
function perRpcAuthTest(expected_user, scope, per_rpc, client, done) {
function perRpcAuthTest(client, done, extra) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
assert.ifError(err);
var arg = {
fill_username: true,
fill_oauth_scope: true
};
var scope = extra.oauth_scope;
if (credential.createScopedRequired() && scope) {
credential = credential.createScoped(scope);
}
var creds = grpc.credentials.createFromGoogleCredential(credential);
client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.username, expected_user);
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
assert.strictEqual(resp.username, SERVICE_ACCOUNT_EMAIL);
assert(extra.oauth_scope.indexOf(resp.oauth_scope) > -1);
if (done) {
done();
}
@ -473,25 +503,44 @@ function getOauth2Creds(scope, callback) {
* Map from test case names to test functions
*/
var test_cases = {
empty_unary: {run: emptyUnary},
large_unary: {run: largeUnary},
client_streaming: {run: clientStreaming},
server_streaming: {run: serverStreaming},
ping_pong: {run: pingPong},
empty_stream: {run: emptyStream},
cancel_after_begin: {run: cancelAfterBegin},
cancel_after_first_response: {run: cancelAfterFirstResponse},
timeout_on_sleeping_server: {run: timeoutOnSleepingServer},
custom_metadata: {run: customMetadata},
compute_engine_creds: {run: _.partial(authTest, COMPUTE_ENGINE_USER, null),
getCreds: _.partial(getApplicationCreds, null)},
service_account_creds: {run: _.partial(authTest, AUTH_USER, AUTH_SCOPE),
getCreds: _.partial(getApplicationCreds, AUTH_SCOPE)},
jwt_token_creds: {run: _.partial(authTest, AUTH_USER, null),
getCreds: _.partial(getApplicationCreds, null)},
oauth2_auth_token: {run: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false),
getCreds: _.partial(getOauth2Creds, AUTH_SCOPE)},
per_rpc_creds: {run: _.partial(perRpcAuthTest, AUTH_USER, AUTH_SCOPE, true)}
empty_unary: {run: emptyUnary,
Client: testProto.TestService},
large_unary: {run: largeUnary,
Client: testProto.TestService},
client_streaming: {run: clientStreaming,
Client: testProto.TestService},
server_streaming: {run: serverStreaming,
Client: testProto.TestService},
ping_pong: {run: pingPong,
Client: testProto.TestService},
empty_stream: {run: emptyStream,
Client: testProto.TestService},
cancel_after_begin: {run: cancelAfterBegin,
Client: testProto.TestService},
cancel_after_first_response: {run: cancelAfterFirstResponse,
Client: testProto.TestService},
timeout_on_sleeping_server: {run: timeoutOnSleepingServer,
Client: testProto.TestService},
custom_metadata: {run: customMetadata,
Client: testProto.TestService},
status_code_and_message: {run: statusCodeAndMessage,
Client: testProto.TestService},
unimplemented_method: {run: unimplementedMethod,
Client: testProto.UnimplementedService},
compute_engine_creds: {run: computeEngineCreds,
Client: testProto.TestService,
getCreds: getApplicationCreds},
service_account_creds: {run: serviceAccountCreds,
Client: testProto.TestService,
getCreds: getApplicationCreds},
jwt_token_creds: {run: jwtTokenCreds,
Client: testProto.TestService,
getCreds: getApplicationCreds},
oauth2_auth_token: {run: oauth2Test,
Client: testProto.TestService,
getCreds: getOauth2Creds},
per_rpc_creds: {run: perRpcAuthTest,
Client: testProto.TestService}
};
/**
@ -504,8 +553,9 @@ var test_cases = {
* @param {bool} tls Indicates that a secure channel should be used
* @param {function} done Callback to call when the test is completed. Included
* primarily for use with mocha
* @param {object=} extra Extra options for some tests
*/
function runTest(address, host_override, test_case, tls, test_ca, done) {
function runTest(address, host_override, test_case, tls, test_ca, done, extra) {
// TODO(mlumish): enable TLS functionality
var options = {};
var creds;
@ -529,12 +579,13 @@ function runTest(address, host_override, test_case, tls, test_ca, done) {
var execute = function(err, creds) {
assert.ifError(err);
var client = new testProto.TestService(address, creds, options);
test.run(client, done);
var client = new test.Client(address, creds, options);
test.run(client, done, extra);
};
if (test.getCreds) {
test.getCreds(function(err, new_creds) {
test.getCreds(extra.oauth_scope, function(err, new_creds) {
assert.ifError(err);
execute(err, grpc.credentials.combineChannelCredentials(
creds, new_creds));
});
@ -547,13 +598,18 @@ if (require.main === module) {
var parseArgs = require('minimist');
var argv = parseArgs(process.argv, {
string: ['server_host', 'server_host_override', 'server_port', 'test_case',
'use_tls', 'use_test_ca']
'use_tls', 'use_test_ca', 'default_service_account', 'oauth_scope',
'service_account_key_file']
});
var extra_args = {
service_account: argv.default_service_account,
oauth_scope: argv.oauth_scope
};
runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override,
argv.test_case, argv.use_tls === 'true', argv.use_test_ca === 'true',
function () {
console.log('OK:', argv.test_case);
});
}, extra_args);
}
/**

@ -44,6 +44,9 @@ var testProto = grpc.load({
var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial';
var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin';
var incompressible_data = fs.readFileSync(
__dirname + '/../../../test/cpp/interop/rnd.dat');
/**
* Create a buffer filled with size zeroes
* @param {number} size The length of the buffer
@ -83,6 +86,19 @@ function getEchoTrailer(call) {
return response_trailer;
}
function getPayload(payload_type, size) {
if (payload_type === 'RANDOM') {
payload_type = ['COMPRESSABLE',
'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
}
var body;
switch (payload_type) {
case 'COMPRESSABLE': body = zeroBuffer(size); break;
case 'UNCOMPRESSABLE': incompressible_data.slice(size); break;
}
return {type: payload_type, body: body};
}
/**
* Respond to an empty parameter with an empty response.
* NOTE: this currently does not work due to issue #137
@ -104,13 +120,14 @@ function handleEmpty(call, callback) {
function handleUnary(call, callback) {
echoHeader(call);
var req = call.request;
var zeros = zeroBuffer(req.response_size);
var payload_type = req.response_type;
if (payload_type === 'RANDOM') {
payload_type = ['COMPRESSABLE',
'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
if (req.response_status) {
var status = req.response_status;
status.metadata = getEchoTrailer(call);
callback(status);
return;
}
callback(null, {payload: {type: payload_type, body: zeros}},
var payload = getPayload(req.response_type, req.response_size);
callback(null, {payload: payload},
getEchoTrailer(call));
}
@ -139,18 +156,14 @@ function handleStreamingInput(call, callback) {
function handleStreamingOutput(call) {
echoHeader(call);
var req = call.request;
var payload_type = req.response_type;
if (payload_type === 'RANDOM') {
payload_type = ['COMPRESSABLE',
'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
if (req.response_status) {
var status = req.response_status;
status.metadata = getEchoTrailer(call);
call.emit('error', status);
return;
}
_.each(req.response_parameters, function(resp_param) {
call.write({
payload: {
body: zeroBuffer(resp_param.size),
type: payload_type
}
});
call.write({payload: getPayload(req.response_type, resp_param.size)});
});
call.end(getEchoTrailer(call));
}
@ -163,18 +176,14 @@ function handleStreamingOutput(call) {
function handleFullDuplex(call) {
echoHeader(call);
call.on('data', function(value) {
var payload_type = value.response_type;
if (payload_type === 'RANDOM') {
payload_type = ['COMPRESSABLE',
'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1];
if (value.response_status) {
var status = value.response_status;
status.metadata = getEchoTrailer(call);
call.emit('error', status);
return;
}
_.each(value.response_parameters, function(resp_param) {
call.write({
payload: {
body: zeroBuffer(resp_param.size),
type: payload_type
}
});
call.write({payload: getPayload(value.response_type, resp_param.size)});
});
});
call.on('end', function() {
@ -188,7 +197,7 @@ function handleFullDuplex(call) {
* @param {Call} call Call to handle
*/
function handleHalfDuplex(call) {
throw new Error('HalfDuplexCall not yet implemented');
call.emit('error', Error('HalfDuplexCall not yet implemented'));
}
/**

@ -629,7 +629,7 @@ function Server(options) {
(new Metadata())._getCoreRepresentation();
batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
code: grpc.status.UNIMPLEMENTED,
details: 'This method is not available on this server.',
details: '',
metadata: {}
};
batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;

@ -57,7 +57,7 @@ describe('Async functionality', function() {
grpc.ServerCredentials.createInsecure());
server.start();
math_client = new math.Math('localhost:' + port_num,
grpc.Credentials.createInsecure());
grpc.credentials.createInsecure());
done();
});
after(function() {

@ -149,7 +149,7 @@ describe('channel', function() {
afterEach(function() {
channel.close();
});
it.only('should time out if called alone', function(done) {
it('should time out if called alone', function(done) {
var old_state = channel.getConnectivityState();
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 1);

@ -130,8 +130,8 @@ describe('client credentials', function() {
callback(null, metadata);
};
var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
creds);
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) {
@ -150,8 +150,8 @@ describe('client credentials', function() {
callback(null, metadata);
};
var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds,
creds);
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) {
@ -231,7 +231,7 @@ describe('client credentials', function() {
updater_creds, alt_updater_creds);
var call = client.unary({}, function(err, data) {
assert.ifError(err);
}, null, {credentials: updater_creds});
}, null, {credentials: combined_updater});
call.on('metadata', function(metadata) {
assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']);
assert.deepEqual(metadata.get('other_plugin_key'),

@ -71,7 +71,7 @@ describe('Interop tests', function() {
interop_client.runTest(port, name_override, 'server_streaming', true, true,
done);
});
it('should pass ping_pong', function(done) {
it.only('should pass ping_pong', function(done) {
interop_client.runTest(port, name_override, 'ping_pong', true, true, done);
});
it('should pass empty_stream', function(done) {
@ -94,4 +94,12 @@ describe('Interop tests', function() {
interop_client.runTest(port, name_override, 'custom_metadata',
true, true, done);
});
it('should pass status_code_and_message', function(done) {
interop_client.runTest(port, name_override, 'status_code_and_message',
true, true, done);
});
it('should pass unimplemented_method', function(done) {
interop_client.runTest(port, name_override, 'unimplemented_method',
true, true, done);
});
});

Loading…
Cancel
Save