Merge pull request #209 from murgatroid99/multi_impl_interop_tests

Make interop tests use new proto loader, run them with pure js client
This commit is contained in:
Michael Lumish 2018-03-09 11:11:37 -08:00 committed by GitHub
commit 0e1054016e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 111 additions and 57 deletions

View File

@ -99,7 +99,7 @@ gulp.task('clean.all', 'Delete all files created by tasks',
'internal.test.clean.all', 'js.clean.all', 'native.clean.all', 'protobuf.clean.all']);
gulp.task('native.test.only', 'Run tests of native code without rebuilding anything',
['native.core.test', 'internal.test.test', 'health-check.test']);
['native.core.test', 'health-check.test']);
gulp.task('native.test', 'Run tests of native code', (callback) => {
runSequence('build', 'native.test.only', callback);

View File

@ -41,6 +41,7 @@ export interface ChannelOptions {
'grpc.ssl_target_name_override': string;
'grpc.primary_user_agent': string;
'grpc.secondary_user_agent': string;
'grpc.default_authority': string;
[key: string]: string | number;
}
@ -158,6 +159,7 @@ export class Http2Channel extends EventEmitter implements Channel {
connectionOptions.checkServerIdentity = (host: string, cert: PeerCertificate): Error | undefined => {
return checkServerIdentity(sslTargetNameOverride, cert);
}
connectionOptions.servername = sslTargetNameOverride;
}
subChannel = http2.connect(this.authority, connectionOptions);
}
@ -224,7 +226,14 @@ export class Http2Channel extends EventEmitter implements Channel {
Promise.all([finalMetadata, this.connect()])
.then(([metadataValue]) => {
let headers = metadataValue.toHttp2Headers();
headers[HTTP2_HEADER_AUTHORITY] = this.authority.hostname;
let host: string;
// TODO(murgatroid99): Add more centralized handling of channel options
if (this.options['grpc.default_authority']) {
host = this.options['grpc.default_authority'] as string;
} else {
host = this.authority.hostname;
}
headers[HTTP2_HEADER_AUTHORITY] = host;
headers[HTTP2_HEADER_USER_AGENT] = this.userAgent;
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
headers[HTTP2_HEADER_METHOD] = 'POST';
@ -234,6 +243,7 @@ export class Http2Channel extends EventEmitter implements Channel {
if (this.connectivityState === ConnectivityState.READY) {
const session: http2.ClientHttp2Session = this.subChannel!;
// Prevent the HTTP/2 session from keeping the process alive.
// Note: this function is only available in Node 9
session.unref();
stream.attachHttp2Stream(session.request(headers));
} else {

View File

@ -137,7 +137,7 @@ export function loadPackageDefinition(packageDef: PackageDefinition) {
const nameComponents = serviceFqn.split('.');
const serviceName = nameComponents[nameComponents.length-1];
let current = result;
for (const packageName in nameComponents.slice(0, -1)) {
for (const packageName of nameComponents.slice(0, -1)) {
if (!current[packageName]) {
current[packageName] = {};
}

View File

@ -158,7 +158,7 @@ exports.loadPackageDefinition = function loadPackageDefintion(packageDef) {
const nameComponents = serviceFqn.split('.');
const serviceName = nameComponents[nameComponents.length-1];
let current = result;
for (const packageName in nameComponents.slice(0, -1)) {
for (const packageName of nameComponents.slice(0, -1)) {
if (!current[packageName]) {
current[packageName] = {};
}

View File

@ -30,10 +30,11 @@
"build/src/*.js"
],
"dependencies": {
"@types/lodash": "^4.14.104",
"@types/node": "^9.4.6",
"clang-format": "^1.2.2",
"gts": "^0.5.3",
"lodash": "^4.17.4",
"lodash": "^4.17.5",
"protobufjs": "^6.8.5",
"typescript": "~2.7.2"
}

View File

@ -18,6 +18,7 @@
import * as Protobuf from 'protobufjs';
import * as fs from 'fs';
import * as path from 'path';
import * as _ from 'lodash';
export interface Serialize<T> {
(value: T): Buffer;
@ -35,6 +36,7 @@ export interface MethodDefinition<RequestType, ResponseType> {
responseSerialize: Serialize<ResponseType>;
requestDeserialize: Deserialize<RequestType>;
responseDeserialize: Deserialize<ResponseType>;
originalName?: string;
}
export interface ServiceDefinition {
@ -88,12 +90,14 @@ function createSerializer(cls: Protobuf.Type): Serialize<object> {
function createMethodDefinition(method: Protobuf.Method, serviceName: string, options: Options): MethodDefinition<object, object> {
return {
path: '/' + serviceName + '/' + method.name,
requestStream: !!method.requestStream,
responseStream: !!method.responseStream,
requestSerialize: createSerializer(method.resolvedRequestType as Protobuf.Type),
requestDeserialize: createDeserializer(method.resolvedRequestType as Protobuf.Type, options),
responseSerialize: createSerializer(method.resolvedResponseType as Protobuf.Type),
responseDeserialize: createDeserializer(method.resolvedResponseType as Protobuf.Type, options)
requestStream: !!method.requestStream,
responseStream: !!method.responseStream,
requestSerialize: createSerializer(method.resolvedRequestType as Protobuf.Type),
requestDeserialize: createDeserializer(method.resolvedRequestType as Protobuf.Type, options),
responseSerialize: createSerializer(method.resolvedResponseType as Protobuf.Type),
responseDeserialize: createDeserializer(method.resolvedResponseType as Protobuf.Type, options),
// TODO(murgatroid99): Find a better way to handle this
originalName: _.camelCase(method.name)
};
}
@ -113,6 +117,21 @@ function createPackageDefinition(root: Protobuf.Root, options: Options): Package
return def;
}
function addIncludePathResolver(root: Protobuf.Root, includePaths: string[]) {
root.resolvePath = (origin: string, target: string) => {
for (const directory of includePaths) {
const fullPath: string = path.join(directory, target);
try {
fs.accessSync(fullPath, fs.constants.R_OK);
return fullPath;
} catch (err) {
continue;
}
}
return null;
};
}
/**
* Load a .proto file with the specified options.
* @param filename The file path to load. Can be an absolute path or relative to
@ -143,21 +162,23 @@ export function load(filename: string, options: Options): Promise<PackageDefinit
if (!(options.include instanceof Array)) {
return Promise.reject(new Error('The include option must be an array'));
}
root.resolvePath = (origin: string, target: string) => {
for (const directory of options.include as string[]) {
const fullPath: string = path.join(directory, target);
try {
fs.accessSync(fullPath, fs.constants.R_OK);
return fullPath;
} catch (err) {
continue;
}
}
return null;
};
addIncludePathResolver(root, options.include as string[]);
}
return root.load(filename, options).then((loadedRoot) => {
loadedRoot.resolveAll();
return createPackageDefinition(root, options);
});
}
export function loadSync(filename: string, options: Options): PackageDefinition {
const root: Protobuf.Root = new Protobuf.Root();
if (!!options.include) {
if (!(options.include instanceof Array)) {
throw new Error('The include option must be an array');
}
addIncludePathResolver(root, options.include as string[]);
}
const loadedRoot = root.loadSync(filename, options);
loadedRoot.resolveAll();
return createPackageDefinition(root, options);
}

View File

@ -11,29 +11,15 @@ function getImplementation(globalField) {
'If running from the command line, please --require a fixture first.'
].join(' '));
}
console.error(globalField, global[globalField]);
const impl = global[globalField];
return {
surface: require(`../packages/grpc-${impl}`),
pjson: require(`../packages/grpc-${impl}/package.json`),
core: require(`../packages/grpc-${impl}-core`),
corePjson: require(`../packages/grpc-${impl}-core/package.json`)
};
return require(`../packages/grpc-${impl}-core`);
}
const clientImpl = getImplementation('_client_implementation');
const serverImpl = getImplementation('_server_implementation');
// We export a "merged" gRPC API by merging client and server specified
// APIs together. Any function that is unspecific to client/server defaults
// to client-side implementation.
// This object also has a test-only field from which details about the
// modules may be read.
module.exports = Object.assign({
'$implementationInfo': {
client: clientImpl,
server: serverImpl
}
}, clientImpl.surface, _.pick(serverImpl.surface, [
'Server',
'ServerCredentials'
]));
module.exports = {
client: clientImpl,
server: serverImpl
};

View File

@ -18,9 +18,8 @@
'use strict';
require('../../../test/fixtures/native_native.js');
var interop_server = require('../../../test/interop/interop_server.js');
var interop_client = require('../../../test/interop/interop_client.js');
var interop_server = require('../interop/interop_server.js');
var interop_client = require('../interop/interop_client.js');
var server;

View File

@ -1,2 +1,2 @@
global._server_implementation = 'js';
global._client_implementation = 'native';
global._server_implementation = 'native';
global._client_implementation = 'js';

View File

@ -35,4 +35,29 @@ gulp.task('install', 'Install test dependencies', () => {
gulp.task('clean.all', 'Delete all files created by tasks', () => {});
gulp.task('test', 'Run API-level tests', () => {});
gulp.task('test', 'Run API-level tests', () => {
// run mocha tests matching a glob with a pre-required fixture,
// returning the associated gulp stream
const apiTestGlob = `${apiTestDir}/*.js`;
const runTestsWithFixture = (server, client) => new Promise((resolve, reject) => {
const fixture = `${server}_${client}`;
console.log(`Running ${apiTestGlob} with ${server} server + ${client} client`);
gulp.src(apiTestGlob)
.pipe(mocha({
reporter: 'mocha-jenkins-reporter',
require: `${testDir}/fixtures/${fixture}.js`
}))
.resume() // put the stream in flowing mode
.on('end', resolve)
.on('error', reject);
});
const runTestsArgPairs = [
['native', 'native'],
['native', 'js'],
// ['js', 'native'],
// ['js', 'js']
];
return runTestsArgPairs.reduce((previousPromise, argPair) => {
return previousPromise.then(runTestsWithFixture.bind(null, argPair[0], argPair[1]));
}, Promise.resolve());
});

View File

@ -20,12 +20,18 @@
var fs = require('fs');
var path = require('path');
var grpc = require('../any_grpc')['$implementationInfo'].client.surface;
var testProto = grpc.load({
root: __dirname + '/../../packages/grpc-native-core/deps/grpc',
file: 'src/proto/grpc/testing/test.proto'}).grpc.testing;
var grpc = require('../any_grpc').client;
var protoLoader = require('../../packages/grpc-protobufjs');
var GoogleAuth = require('google-auth-library');
var protoPackage = protoLoader.loadSync(
'src/proto/grpc/testing/test.proto',
{keepCase: true,
defaults: true,
enums: String,
include: [__dirname + '/../../packages/grpc-native-core/deps/grpc']});
var testProto = grpc.loadPackageDefinition(protoPackage).grpc.testing;
var assert = require('assert');
var SERVICE_ACCOUNT_EMAIL;

View File

@ -22,10 +22,16 @@ var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var AsyncDelayQueue = require('./async_delay_queue');
var grpc = require('../any_grpc')['$implementationInfo'].server.surface;
var testProto = grpc.load({
root: __dirname + '/../../packages/grpc-native-core/deps/grpc',
file: 'src/proto/grpc/testing/test.proto'}).grpc.testing;
var grpc = require('../any_grpc').server;
// TODO(murgatroid99): do this import more cleanly
var protoLoader = require('../../packages/grpc-protobufjs');
var protoPackage = protoLoader.loadSync(
'src/proto/grpc/testing/test.proto',
{keepCase: true,
defaults: true,
enums: String,
include: [__dirname + '/../../packages/grpc-native-core/deps/grpc']});
var testProto = grpc.loadPackageDefinition(protoPackage).grpc.testing;
var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial';
var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin';