Revamp Closure JsUnit tests runtime and optimize test/build flows. (#1137)

This commit is contained in:
Eryu Xia 2021-09-21 15:27:21 -07:00 committed by GitHub
parent d9a6c7a738
commit 32fe12459b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 689 additions and 609 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
**/node_modules
packages/grpc-web/generated

View File

@ -1,16 +1,9 @@
version: '3'
services:
common:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/common/Dockerfile
image: grpcweb/common
prereqs:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/prereqs/Dockerfile
depends_on:
- common
image: grpcweb/prereqs
echo-server:
build:
@ -34,8 +27,6 @@ services:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/node_interop_server/Dockerfile
depends_on:
- common
image: grpcweb/node-interop-server
ports:
- "7074:7074"
@ -114,3 +105,8 @@ services:
depends_on:
- prereqs
image: grpcweb/protoc-plugin
jsunit-test:
build:
context: ./
dockerfile: ./packages/grpc-web/docker/jsunit-test/Dockerfile
image: grpcweb/jsunit-test

View File

@ -1,4 +1,4 @@
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library", "closure_js_test")
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
@ -239,49 +239,3 @@ closure_js_library(
],
visibility = ["//visibility:public"],
)
closure_js_test(
name = "grpcwebclientbase_test",
srcs = [
"grpcwebclientbase_test.js",
],
entry_points = [
"goog:grpc.web.GrpcWebClientBaseTest",
],
suppress = [
"visibility",
"checkTypes",
"deprecated",
"reportUnknownTypes",
"strictCheckTypes",
],
deps = [
":clientreadablestream",
":grpcwebclientbase",
":interceptor",
"@io_bazel_rules_closure//closure/library/crypt:base64",
"@io_bazel_rules_closure//closure/library/events",
"@io_bazel_rules_closure//closure/library/net:eventtype",
"@io_bazel_rules_closure//closure/library/structs:map",
"@io_bazel_rules_closure//closure/library/testing:jsunit",
"@io_bazel_rules_closure//closure/library/testing:testsuite",
],
)
closure_js_test(
name = "grpcwebstreamparser_test",
srcs = [
"grpcwebstreamparser_test.js",
],
entry_points = [
"goog:grpc.web.GrpcWebStreamParserTest",
],
suppress = [
"reportUnknownTypes",
],
deps = [
":grpcwebstreamparser",
"@io_bazel_rules_closure//closure/library/testing:jsunit",
"@io_bazel_rules_closure//closure/library/testing:testsuite",
],
)

View File

@ -239,6 +239,13 @@ class MockXhr {
}
}
/**
* @param {number} ms
*/
setTimeoutInterval(ms) {
return;
}
/**
* @param {boolean} withCredentials
*/

View File

@ -1,445 +0,0 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @fileoverview gRPC web client Readable Stream
*
* This class is being returned after a gRPC streaming call has been
* started. This class provides functionality for user to operates on
* the stream, e.g. set onData callback, etc.
*
* This wraps the underlying goog.net.streams.NodeReadableStream
*
* @author stanleycheung@google.com (Stanley Cheung)
*/
goog.module('grpc.web.StreamBodyClientReadableStream');
goog.module.declareLegacyNamespace();
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const ErrorCode = goog.require('goog.net.ErrorCode');
const EventType = goog.require('goog.net.EventType');
const GoogleRpcStatus = goog.require('proto.google.rpc.Status');
const GrpcWebError = goog.requireType('grpc.web.Error');
const Metadata = goog.requireType('grpc.web.Metadata');
const NodeReadableStream = goog.require('goog.net.streams.NodeReadableStream');
const StatusCode = goog.require('grpc.web.StatusCode');
const XhrIo = goog.require('goog.net.XhrIo');
const asserts = goog.require('goog.asserts');
const events = goog.require('goog.events');
const {GenericTransportInterface} = goog.require('grpc.web.GenericTransportInterface');
const {Status} = goog.require('grpc.web.Status');
/**
* A stream that the client can read from. Used for calls that are streaming
* from the server side.
* @template RESPONSE
* @implements {ClientReadableStream}
* @final
* @unrestricted
*/
class StreamBodyClientReadableStream {
/**
* @param {!GenericTransportInterface} genericTransportInterface
* @param {function(?): RESPONSE} responseDeserializeFn
* @param {boolean} isBinary
*/
constructor(genericTransportInterface, responseDeserializeFn, isBinary) {
/**
* Whether or not the response protobuffer format is binary.
* @private
* @const {boolean}
*/
this.isBinary_ = isBinary;
/**
* @private
* @const {?NodeReadableStream|undefined} The XHR Node Readable Stream
*/
this.xhrNodeReadableStream_ = genericTransportInterface.nodeReadableStream;
/**
* @private
* @const {function(?): RESPONSE} The deserialize function for the proto
*/
this.grpcResponseDeserializeFn_ = responseDeserializeFn;
/**
* @private
* @const {?XhrIo|undefined} The XhrIo object
*/
this.xhr_ = genericTransportInterface.xhr;
/**
* @private
* @const {!Array<function(RESPONSE)>} The list of data callback
*/
this.onDataCallbacks_ = [];
/**
* @private
* @const {!Array<function(!Metadata)>} The list of metadata callbacks
*/
this.onMetadataCallbacks_ = [];
/**
* @private
* @const {!Array<function(!Status)>} The list of status callback
*/
this.onStatusCallbacks_ = [];
/**
* @private
* @const {!Array<function(...)>} The list of stream end callback
*/
this.onEndCallbacks_ = [];
/**
* @private
* @const {!Array<function(!GrpcWebError)>} The list of error callback
*/
this.onErrorCallbacks_ = [];
if (this.xhrNodeReadableStream_) {
this.setStreamCallback_();
}
}
/**
* Set up the callback functions for unary calls.
* @param {boolean} base64Encoded True if
* 'X-Goog-Encode-Response-If-Executable' is 'base64' in request headers.
*/
setUnaryCallback(base64Encoded) {
events.listen(/** @type {!XhrIo}*/ (this.xhr_), EventType.COMPLETE, (e) => {
if (this.xhr_.isSuccess()) {
let response;
if (this.isBinary_) {
response = this.decodeBinaryResponse_(base64Encoded);
} else {
response = this.decodeJspbResponse_(base64Encoded);
}
const responseMessage = this.grpcResponseDeserializeFn_(response);
const grpcStatus = StatusCode.fromHttpStatus(this.xhr_.getStatus());
this.sendMetadataCallbacks_(this.readHeaders_());
if (grpcStatus == StatusCode.OK) {
this.sendDataCallbacks_(responseMessage);
} else {
this.sendErrorCallbacks_(
/** @type {!GrpcWebError} */ (
{code: grpcStatus, message: response}));
}
} else {
let rawResponse;
if (this.isBinary_) {
const xhrResponse = this.xhr_.getResponse();
if (xhrResponse) {
rawResponse =
new Uint8Array(/** @type {!ArrayBuffer} */ (xhrResponse));
}
} else {
rawResponse = this.xhr_.getResponseText();
}
let code;
let message;
let metadata = {};
if (rawResponse) {
const status = this.parseRpcStatus_(rawResponse);
code = status.code;
message = status.details;
metadata = status.metadata;
} else {
code = StatusCode.UNKNOWN;
message = 'Rpc failed due to xhr error. error code: ' +
this.xhr_.getLastErrorCode() +
', error: ' + this.xhr_.getLastError();
}
this.sendMetadataCallbacks_(this.readHeaders_());
this.sendErrorCallbacks_({code, message, metadata});
}
});
}
/**
* @private
*/
setStreamCallback_() {
// Add the callback to the underlying stream
this.xhrNodeReadableStream_.on('data', (data) => {
if ('1' in data) {
const response = this.grpcResponseDeserializeFn_(data['1']);
this.sendDataCallbacks_(response);
}
if ('2' in data) {
const status = this.parseRpcStatus_(data['2']);
this.sendStatusCallbacks_(status);
}
});
this.xhrNodeReadableStream_.on('end', () => {
this.sendMetadataCallbacks_(this.readHeaders_());
this.sendEndCallbacks_();
});
this.xhrNodeReadableStream_.on('error', () => {
if (this.onErrorCallbacks_.length == 0) return;
let lastErrorCode = this.xhr_.getLastErrorCode();
if (lastErrorCode === ErrorCode.NO_ERROR && !this.xhr_.isSuccess()) {
// The lastErrorCode on the XHR isn't useful in this case, but the XHR
// status is. Full details about the failure should be available in the
// status handler.
lastErrorCode = ErrorCode.HTTP_ERROR;
}
let grpcStatusCode;
switch (lastErrorCode) {
case ErrorCode.NO_ERROR:
grpcStatusCode = StatusCode.UNKNOWN;
break;
case ErrorCode.ABORT:
grpcStatusCode = StatusCode.ABORTED;
break;
case ErrorCode.TIMEOUT:
grpcStatusCode = StatusCode.DEADLINE_EXCEEDED;
break;
case ErrorCode.HTTP_ERROR:
grpcStatusCode = StatusCode.fromHttpStatus(this.xhr_.getStatus());
break;
default:
grpcStatusCode = StatusCode.UNAVAILABLE;
}
this.sendErrorCallbacks_({
code: grpcStatusCode,
// TODO(armiller): get the message from the response?
// GoogleRpcStatus.deserialize(rawResponse).getMessage()?
// Perhaps do the same status logic as in on('data') above?
message: ErrorCode.getDebugMessage(lastErrorCode)
});
});
}
/**
* @private
* @param {boolean} base64Encoded
* @return {string}
*/
decodeJspbResponse_(base64Encoded) {
// If the response is serialized as Base64 (for example if the
// X-Goog-Encode-Response-If-Executable header is in effect), decode it
// before passing it to the deserializer.
let responseText = this.xhr_.getResponseText();
if (base64Encoded &&
this.xhr_.getResponseHeader(XhrIo.CONTENT_TYPE_HEADER) ===
'text/plain') {
if (!atob) {
throw new Error('Cannot decode Base64 response');
}
responseText = atob(responseText);
}
return responseText;
}
/**
* @private
* @param {boolean} base64Encoded
* @return {*}
*/
decodeBinaryResponse_(base64Encoded) {
if (base64Encoded &&
this.xhr_.getResponseHeader('X-Goog-Safety-Encoding') == 'base64') {
// Convert the response's ArrayBuffer to a string, which should
// be a base64 encoded string.
const bytes = new Uint8Array(
/** @type {!ArrayBuffer} */ (this.xhr_.getResponse()));
let byteSource = '';
for (let i = 0; i < bytes.length; i++) {
byteSource += String.fromCharCode(bytes[i]);
}
return byteSource;
} else {
return this.xhr_.getResponse();
}
}
/**
* @private
* @return {!Metadata}
*/
readHeaders_() {
const initialMetadata = {};
const responseHeaders = this.xhr_.getResponseHeaders();
Object.keys(responseHeaders).forEach((header) => {
initialMetadata[header] = responseHeaders[header];
});
return initialMetadata;
}
/**
* @private
* @param {!Uint8Array|string} data Data returned from the underlying stream.
* @return {!Status} The Rpc Status details.
*/
parseRpcStatus_(data) {
let code;
let message;
const metadata = {};
try {
let rpcStatus;
if (this.isBinary_) {
rpcStatus = GoogleRpcStatus.deserializeBinary(data);
} else {
asserts.assertString(
data, 'RPC status must be string in gRPC-Web jspb mode.');
rpcStatus = GoogleRpcStatus.deserialize(data);
}
code = rpcStatus.getCode();
message = rpcStatus.getMessage();
if (rpcStatus.getDetailsList().length) {
metadata['grpc-web-status-details-bin'] = data;
}
} catch (e) {
// 404s may be accompanied by a GoogleRpcStatus. If they are not,
// the generic message will fail to deserialize because it is not a
// status.
if (this.xhr_ && this.xhr_.getStatus() === 404) {
code = StatusCode.NOT_FOUND;
message = 'Not Found: ' + this.xhr_.getLastUri();
} else {
code = StatusCode.UNAVAILABLE;
message = 'Unable to parse RpcStatus: ' + e;
}
}
const status = {code: code, details: message, metadata: metadata};
return status;
}
/**
* @override
* @export
*/
on(eventType, callback) {
// TODO(stanleycheung): change eventType to @enum type
if (eventType == 'data') {
this.onDataCallbacks_.push(callback);
} else if (eventType == 'metadata') {
this.onMetadataCallbacks_.push(callback);
} else if (eventType == 'status') {
this.onStatusCallbacks_.push(callback);
} else if (eventType == 'end') {
this.onEndCallbacks_.push(callback);
} else if (eventType == 'error') {
this.onErrorCallbacks_.push(callback);
}
return this;
}
/**
* @private
* @param {!Array<function(?)>} callbacks the internal list of callbacks
* @param {function(?)} callback the callback to remove
*/
removeListenerFromCallbacks_(callbacks, callback) {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
/**
* @export
* @override
*/
removeListener(eventType, callback) {
if (eventType == 'data') {
this.removeListenerFromCallbacks_(this.onDataCallbacks_, callback);
} else if (eventType == 'metadata') {
this.removeListenerFromCallbacks_(this.onMetadataCallbacks_, callback);
} else if (eventType == 'status') {
this.removeListenerFromCallbacks_(this.onStatusCallbacks_, callback);
} else if (eventType == 'end') {
this.removeListenerFromCallbacks_(this.onEndCallbacks_, callback);
} else if (eventType == 'error') {
this.removeListenerFromCallbacks_(this.onErrorCallbacks_, callback);
}
return this;
}
/**
* @override
* @export
*/
cancel() {
this.xhr_.abort();
}
/**
* @private
* @param {!RESPONSE} data The data to send back
*/
sendDataCallbacks_(data) {
for (let i = 0; i < this.onDataCallbacks_.length; i++) {
this.onDataCallbacks_[i](data);
}
}
/**
* @private
* @param {!Metadata} metadata The metadata to send back
*/
sendMetadataCallbacks_(metadata) {
for (let i = 0; i < this.onMetadataCallbacks_.length; i++) {
this.onMetadataCallbacks_[i](metadata);
}
}
/**
* @private
* @param {!Status} status The status to send back
*/
sendStatusCallbacks_(status) {
for (let i = 0; i < this.onStatusCallbacks_.length; i++) {
this.onStatusCallbacks_[i](status);
}
}
/**
* @private
* @param {!GrpcWebError} error The error to send back
*/
sendErrorCallbacks_(error) {
for (let i = 0; i < this.onErrorCallbacks_.length; i++) {
this.onErrorCallbacks_[i](error);
}
}
/**
* @private
*/
sendEndCallbacks_() {
for (let i = 0; i < this.onEndCallbacks_.length; i++) {
this.onEndCallbacks_[i]();
}
}
}
exports = StreamBodyClientReadableStream;

View File

@ -1,37 +0,0 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM node:12-stretch
ARG PROTOBUF_VERSION=3.17.3
RUN apt-get -qq update && apt-get -qq install -y \
unzip
WORKDIR /tmp
RUN curl -sSL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/\
protoc-$PROTOBUF_VERSION-linux-x86_64.zip -o protoc.zip && \
unzip -qq protoc.zip && \
cp ./bin/protoc /usr/local/bin/protoc
RUN curl -sSL https://github.com/grpc/grpc-web/releases/download/1.2.1/\
protoc-gen-grpc-web-1.2.1-linux-x86_64 -o /usr/local/bin/protoc-gen-grpc-web && \
chmod +x /usr/local/bin/protoc-gen-grpc-web
WORKDIR /var/www/html/dist
WORKDIR /github/grpc-web
RUN git clone https://github.com/grpc/grpc-web .

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM grpcweb/common
FROM node:12.22.6-stretch
WORKDIR /github/grpc-node

View File

@ -12,39 +12,54 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM grpcweb/common
ARG MAKEFLAGS=-j8
# Using multi-stage buid (See: https://docs.docker.com/develop/develop-images/multistage-build/)
######################################
# Stage 1: Fetch binaries
######################################
# node:... Docker image is based on buildpack-deps:stretch
FROM buildpack-deps:stretch AS prepare
ARG BUILDIFIER_VERSION=1.0.0
ARG BAZEL_VERSION=4.1.0
ARG PROTOBUF_VERSION=3.17.3
RUN echo "\nloglevel=error\n" >> $HOME/.npmrc
RUN apt-get -qq update && apt-get -qq install -y curl unzip
WORKDIR /github/grpc-web
WORKDIR /tmp
# Check out scripts and initialize git submodules first to take advantage of docker build cache.
COPY ./scripts ./scripts
RUN ./scripts/init_submodules.sh
COPY ./packages ./packages
RUN cd ./packages/grpc-web && \
npm install && \
npm run build && \
npm link
COPY ./Makefile ./Makefile
COPY ./WORKSPACE ./WORKSPACE
COPY ./bazel ./bazel
COPY ./javascript ./javascript
COPY ./net ./net
COPY ./src ./src
COPY ./test ./test
RUN curl -sSL https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/\
protoc-$PROTOBUF_VERSION-linux-x86_64.zip -o protoc.zip && \
unzip -qq protoc.zip && \
cp ./bin/protoc /usr/local/bin/protoc
RUN wget -nv -O buildifier \
https://github.com/bazelbuild/buildtools/releases/download/$BUILDIFIER_VERSION/buildifier && \
chmod +x ./buildifier && \
./buildifier --mode=check --lint=warn --warnings=all -r ./WORKSPACE bazel javascript net && \
rm ./buildifier
cp ./buildifier /usr/local/bin/buildifier
# Download third_party modules to be used for the next stage
WORKDIR /github/grpc-web
RUN git clone https://github.com/grpc/grpc-web .
COPY ./scripts/init_submodules.sh ./scripts/
RUN ./scripts/init_submodules.sh
######################################
# Stage 2: Copy source files and build
######################################
FROM node:12.22.6-stretch AS copy-and-build
ARG MAKEFLAGS=-j8
ARG BAZEL_VERSION=4.1.0
RUN mkdir -p /var/www/html/dist
RUN echo "\nloglevel=error\n" >> $HOME/.npmrc
COPY --from=prepare /usr/local/bin/protoc /usr/local/bin/
COPY --from=prepare /usr/local/bin/buildifier /usr/local/bin/
COPY --from=prepare /github/grpc-web/third_party /github/grpc-web/third_party
RUN wget -nv -O bazel-installer.sh \
https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/\
@ -53,6 +68,32 @@ bazel-$BAZEL_VERSION-installer-linux-x86_64.sh && \
./bazel-installer.sh && \
rm ./bazel-installer.sh
RUN bazel build javascript/net/grpc/web/... && \
WORKDIR /github/grpc-web
# Copy only files necessary to build the protoc-gen-grpc-web first as an optimization because they
# are rarely updated compared with the javascript files.
COPY ./WORKSPACE ./WORKSPACE
COPY ./bazel ./bazel
COPY ./javascript/net/grpc/web/grpc_generator.cc javascript/net/grpc/web/grpc_generator.cc
COPY ./javascript/net/grpc/web/BUILD.bazel javascript/net/grpc/web/BUILD.bazel
RUN bazel build javascript/net/grpc/web:protoc-gen-grpc-web && \
cp $(bazel info bazel-genfiles)/javascript/net/grpc/web/protoc-gen-grpc-web \
/usr/local/bin/protoc-gen-grpc-web
COPY ./javascript ./javascript
COPY ./packages ./packages
RUN cd ./packages/grpc-web && \
npm install && \
npm run build && \
npm link
COPY ./Makefile ./Makefile
COPY ./net ./net
COPY ./scripts ./scripts
COPY ./src ./src
COPY ./test ./test
RUN /usr/local/bin/buildifier \
--mode=check --lint=warn --warnings=all -r ./WORKSPACE bazel javascript net

View File

@ -28,24 +28,16 @@ all: client
client: proto-js compiled-js
compiled-js:
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/*_test.js || true
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/binary/*_test.js || true
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/commonjs/*_test.js || true
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/compatibility_tests/v3.0.0/*_test.js || true
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/compatibility_tests/v3.0.0/binary/*_test.js || true
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/compatibility_tests/v3.0.0/commonjs/*_test.js || true
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/compatibility_tests/v3.1.0/*_test.js || true
rm $(ROOT_DIR)/$(PROTOBUF_PATH)/js/compatibility_tests/v3.1.0/binary/*_test.js || true
./node_modules/.bin/google-closure-compiler \
--js=*.js \
--js=$(OUT_DIR)/*.js \
--js=$(ROOT_DIR)/javascript \
--js=$(ROOT_DIR)/$(PROTOBUF_PATH)/js \
--js='!$(ROOT_DIR)/$(PROTOBUF_PATH)/js/**/*_test.js' \
--js=$(NPM_DIR)/google-closure-library \
--entry_point=goog:proto.grpc.gateway.testing.EchoServiceClient \
--dependency_mode=PRUNE \
--js_output_file compiled.js
cd $(ROOT_DIR)/$(PROTOBUF_PATH) && git checkout .
proto-js:
mkdir -p $(OUT_DIR)

View File

@ -1,3 +1,6 @@
/index.js
/package-lock.json
/node_modules
/generated/
package-lock.json
node_modules/
__pycache__/

View File

@ -0,0 +1,28 @@
FROM selenium/standalone-chrome:93.0
# Matching the node version used in the node:12.22.6-stretch image.
ARG NODE_VERSION=12.22.6
USER root
RUN apt-get update && \
apt-get install -y nodejs npm
# Install nvm (See https://github.com/creationix/nvm#install-script) and nodejs version per
# specified in NODE_VERSION
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
ENV NVM_DIR=$HOME/.nvm
RUN . $NVM_DIR/nvm.sh \
&& nvm install $NODE_VERSION \
&& nvm alias default $NODE_VERSION \
&& nvm use default
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
WORKDIR /grpc-web
COPY ./packages ./packages
RUN cd ./packages/grpc-web && \
npm install
COPY ./javascript ./javascript
COPY ./scripts ./scripts

View File

@ -0,0 +1,10 @@
const connect = require('gulp-connect');
const gulp = require('gulp');
gulp.task('serve', () => {
connect.server({
// Serves the root of github repo so tests an access javascript files.
root: '../../',
port: 4000
});
});

View File

@ -19,19 +19,24 @@
"build": "node scripts/build.js",
"prepare": "npm run build",
"prepublishOnly": "npm run build",
"test": "mocha --timeout 60000 \"./test/**/*_test.js\""
"test": "npm run test-jsunit && npm run test-mocha",
"test-mocha": "mocha --timeout 10000 \"./test/**/*_test.js\"",
"test-jsunit": "./scripts/generate_test_files.sh && ./scripts/run_jsunit_tests.sh && rm -rf ./generated"
},
"license": "Apache-2.0",
"devDependencies": {
"@types/google-protobuf": "~3.7.0",
"command-exists": "~1.2.8",
"google-closure-compiler": "~20200224.0.0",
"google-closure-deps": "~20210601.0.0",
"google-closure-library": "~20201102.0.1",
"google-protobuf": "~3.14.0",
"gulp": "~4.0.2",
"gulp-connect": "~5.7.0",
"gulp-eval": "~1.0.0",
"mocha": "~5.2.0",
"mock-xmlhttprequest": "~2.0.0",
"protractor": "~7.0.0",
"typescript": "~3.8.0"
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Stores the configuration of Protractor. It is loaded by protractor to run
* tests.
*
* Intended to be used through ./run_jsunit_test.sh
*/
// Common configuration.
config = {
// Using jasmine to wrap Closure JSUnit tests.
framework: 'jasmine',
// The jasmine specs to run.
specs: ['protractor_spec.js'],
// Jasmine options. Increase the timeout to 5min instead of the default 30s.
jasmineNodeOpts: {
// Default time to wait in ms before a test fails.
defaultTimeoutInterval: 5 * 60 * 1000 // 5 minutes
}
};
// Configuration for headless chrome.
config.directConnect = true;
config.multiCapabilities = [{
browserName: 'chrome',
chromeOptions: {
args: [ "--headless", "--disable-gpu", "--window-size=800,600",
"--no-sandbox", "--disable-dev-shm-usage" ]
}
}];
exports.config = config;

View File

@ -0,0 +1,106 @@
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var allTests = require('./generated/all_tests');
var TEST_SERVER = 'http://localhost:4000';
describe('Run all Closure unit tests', function() {
/**
* Waits for current tests to be executed.
* @param {function(!Object)} done The function called when the test is finished.
* @param {function(!Error)} fail The function called when an unrecoverable error
* happened during the test.
*/
var waitForTest = function(done, fail) {
// executeScript runs the passed method in the "window" context of
// the current test. JSUnit exposes hooks into the test's status through
// the "G_testRunner" global object.
browser.executeScript(function() {
if (window['G_testRunner'] && window['G_testRunner']['isFinished']()) {
return {
isFinished: true,
isSuccess: window['G_testRunner']['isSuccess'](),
report: window['G_testRunner']['getReport']()
};
} else {
return {'isFinished': false};
}
}).then(function(status) {
if (status && status.isFinished) {
done(status);
} else {
waitForTest(done, fail);
}
}, function(err) {
// This can happen if the webdriver had an issue executing the script.
fail(err);
});
};
/**
* Executes the test cases for the file at the given testPath.
* @param {!string} testPath The path of the current test suite to execute.
*/
var executeTest = function(testPath) {
it('runs ' + testPath + ' with success', function(done) {
/**
* Runs the test routines for a given test path.
* @param {function()} done The function to run on completion.
*/
var runRoutine = function(done) {
browser.navigate()
.to(TEST_SERVER + '/' + testPath)
.then(function() {
waitForTest(function(status) {
expect(status).toBeSuccess();
done();
}, function(err) {
done.fail(err);
});
}, function(err) {
done.fail(err);
});
};
// Run the test routine.
runRoutine(done);
});
};
beforeEach(function() {
jasmine.addMatchers({
// This custom matcher allows for cleaner reports.
toBeSuccess: function() {
return {
// Checks that the status report is successful, otherwise displays
// the report as error message.
compare: function(status) {
return {
pass: status.isSuccess,
message: 'Some test cases failed!\n\n' + status.report
};
}
};
}
});
});
// Run all tests.
for (var i = 0; i < allTests.length; i++) {
var testPath = allTests[i];
executeTest(testPath);
}
});

View File

@ -0,0 +1,45 @@
# Copyright 2021 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common methods and constants for generating test files."""
import os
from typing import Iterator
# The directory in which test HTML files are generated.
GENERATED_TEST_BASE_PATH = "generated/test_htmls/"
def read_file(path: str) -> str:
"""Reads the content of a file."""
with open(path) as f:
return f.read()
def write_file(path: str, content: str):
"""Writes a string to file, overwriting existing content; intermediate
directories are created if not present."""
dir_name = os.path.dirname(path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
with open(path, "w") as f:
f.write(content)
def get_files_with_suffix(root_dir: str, suffix: str) -> Iterator[str]:
"""Yields file names under a directory with a given suffix."""
for dir_path, _, file_names in os.walk(root_dir):
for file_name in file_names:
if file_name.endswith(suffix):
yield os.path.join(dir_path, file_name)

View File

@ -0,0 +1,56 @@
# Copyright 2021 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Generates the all_tests.js file for consumption by Protractor.
Usage:
$ cd packages/grpc-web
$ python3 ./scripts/gen_test_htmls.py # Prerequisite
$ python3 ./scripts/gen_all_tests_js.py
"""
from string import Template
import common
ALL_TESTS_TEMPLATE_FILE = './scripts/template_all_tests_js.txt'
# The path of the generated all_tests.js file
GENERATED_ALL_TESTS_JS_PATH = './generated/all_tests.js'
# File paths needs to be prepended by the relative path of the grpc-web package
# because web server is hosting the root of github repo for tests to access the
# javascript files.
GRPC_WEB_BASE_DIR = 'packages/grpc-web'
def main():
template_data = common.read_file(ALL_TESTS_TEMPLATE_FILE)
template = Template(template_data)
test_html_paths = []
for file_name in common.get_files_with_suffix(
common.GENERATED_TEST_BASE_PATH, '_test.html'):
test_html_paths.append(" '%s/%s'," % (GRPC_WEB_BASE_DIR, file_name))
# Example output paths:
# 'packages/grpc-web/generated/test_htmls/javascript__net__grpc__web__grpcwebclientbase_test.html',
# 'packages/grpc-web/generated/test_htmls/javascript__net__grpc__web__grpcwebstreamparser_test.html',
test_html_paths_str = "\n".join(test_html_paths)
# Writes the generated output to the all_tests.js file.
common.write_file(GENERATED_ALL_TESTS_JS_PATH,
template.substitute(test_html_paths=test_html_paths_str))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,76 @@
# Copyright 2021 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Generates *_test.html files from *_test.js files.
Usage:
$ cd packages/grpc-web
$ python3 ./scripts/gen_test_htmls.py
"""
import os
import re
from string import Template
import common
# The directories containing JS tests.
DIRECTORIES_WITH_TESTS = ["../../javascript"]
TEST_HTML_TEMPLATE_FILE = './scripts/template_test_html.txt'
def main():
template_data = common.read_file(TEST_HTML_TEMPLATE_FILE)
template = Template(template_data)
for directory in DIRECTORIES_WITH_TESTS:
for js_file_path in common.get_files_with_suffix(
directory, "_test.js"):
_gen_test_html(js_file_path, template)
def _gen_test_html(js_file_path: str, template: Template):
"""Generates a Closure test wrapper HTML and saves it to the filesystem."""
# Generates the test_file_name so that:
# ../../javascript/net/grpc/web/grpcwebclientbase_test.js
# will now be named:
# javascript__net__grpc__web__grpcwebclientbase_test.html
test_file_name = js_file_path
while test_file_name.startswith('../'):
test_file_name = test_file_name[3:]
test_file_name = test_file_name.replace('/', '__')
test_file_name = os.path.splitext(test_file_name)[0] + '.html'
# Generates the test HTML using the package name of the test file
package_name = _extract_closure_package(js_file_path)
generated_html = template.substitute(package=package_name)
# Writes the test HTML files
common.write_file(common.GENERATED_TEST_BASE_PATH + test_file_name,
generated_html)
def _extract_closure_package(js_file_path) -> str:
"""Extracts the package name from goog.provide() or goog.module() in the
JS file."""
js_data = common.read_file(js_file_path)
matches = re.search(r"goog\.(provide|module)\([\n\s]*'(.+)'\);", js_data)
if matches is None:
raise ValueError("goog.provide() or goog.module() not found in file")
return matches.group(2)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,47 @@
#!/bin/bash
# Copyright 2021 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Generates the temporary files needed for tests to run, putting them in the
# generated/ directory.
#
# Usage:
# $ cd packages/grpc-web
# $ ./scripts/generate_test_files.sh
set -ex
SCRIPT_DIR=$(dirname "$0")
REPO_DIR=$(realpath "${SCRIPT_DIR}/../")
JAVASCRIPT_DIR=$(realpath "${SCRIPT_DIR}/../../../javascript")
GEN_DIR="$REPO_DIR/generated"
cd "$REPO_DIR"
mkdir -p "$GEN_DIR"
echo "Generating dependency file..."
$(npm bin)/closure-make-deps \
--closure-path="node_modules/google-closure-library/closure/goog" \
--file="node_modules/google-closure-library/closure/goog/deps.js" \
--root="$JAVASCRIPT_DIR" \
--exclude="$GEN_DIR/all_tests.js" \
--exclude="$GEN_DIR/deps.js" \
> "$GEN_DIR/deps.js"
echo "Generating test HTML files..."
python3 ./scripts/gen_test_htmls.py
python3 ./scripts/gen_all_tests_js.py
echo "Done."

View File

@ -0,0 +1,52 @@
#!/bin/bash
# Copyright 2021 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script starts a local HTTP server to serve test files, starts a Selenium Webdriver, and
# runs the unit tests using Protractor.
# Run locally with Pratractor:
#
# Usage (under ./packages/grpc-web):
# $ ./scripts/generate_test_files.sh # Required first step
# $ ./scripts/run_jsunit_tests.sh
#
# Or (preferred use):
# $ npm run test-jsunit
set -e
cd "$(dirname $(dirname "$0"))"
NPM_BIN_PATH=$(npm bin)
PROTRACTOR_BIN_PATH="./node_modules/protractor/bin"
function cleanup () {
echo "Killing HTTP Server..."
kill $serverPid
}
# Start the local webserver.
echo "Starting local HTP Server..."
$NPM_BIN_PATH/gulp serve &
serverPid=$!
echo "Local HTTP Server started with PID $serverPid."
trap cleanup EXIT
echo "Using Headless Chrome."
# Updates Selenium Webdriver.
echo "$PROTRACTOR_BIN_PATH/webdriver-manager update --gecko=false"
$PROTRACTOR_BIN_PATH/webdriver-manager update --gecko=false
# Run the tests using Protractor! (Protractor should run selenium automatically)
$PROTRACTOR_BIN_PATH/protractor protractor.conf.js

View File

@ -0,0 +1,6 @@
var allTests = [
$test_html_paths
];
if (typeof module !== 'undefined' && module.exports) {
module.exports = allTests;
}

View File

@ -0,0 +1,15 @@
<!-- Copyright (c) 2021 Google Inc. All rights reserved. -->
<!doctype html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="UTF-8" />
<script src="../../node_modules/google-closure-library/closure/goog/base.js"></script>
<script src="../deps.js"></script>
<script>
goog.require('$package');
</script>
</head>
<body>
</body>
</html>

View File

@ -83,6 +83,9 @@ describe('grpc-web generated code eval test (typescript)', function() {
it('should eval', function() {
execSync(genCodeCmd);
execSync(`tsc --strict ${genTsCodePath}`);
// --skipLibCheck is needed because some of our node_modules/ targets es6
// but our test doesn't pass with `--target es6`
// TODO: Find out how we can enable --target es6!
execSync(`tsc --strict --skipLibCheck ${genTsCodePath}`);
});
});

View File

@ -54,7 +54,11 @@ function runTscCmd(tscCmd) {
}
}
const outputDir = './test/tsc-tests/generated';
const tscCompilerOptions = `--allowJs --strict --noImplicitReturns`
// --skipLibCheck is needed because some of our node_modules/ targets es6 but
// our test doesn't pass with `--target es6`
// TODO: Find out how we can enable --target es6!
const tscCompilerOptions =
`--allowJs --strict --noImplicitReturns --skipLibCheck`;
describe('tsc test01: nested messages', function() {
before(function() {

View File

@ -1,5 +1,5 @@
#!/bin/bash
# Copyright 2018 Google LLC
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,12 +17,9 @@ set -ex
# This script is intended to be run within the base image from
# net/grpc/gateway/docker/prereqs/Dockerfile
# Ensures all Bazel targets builds
cd /github/grpc-web && \
bazel clean && \
bazel test --cache_test_results=no \
bazel build \
//javascript/net/grpc/web/... \
//net/grpc/gateway/examples/...
cd /github/grpc-web/packages/grpc-web && \
npm run prepare && \
npm test

View File

@ -0,0 +1,22 @@
#!/bin/bash
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -ex
# This script is intended to be run within the base image from
# packages/grpc-web/docker/jsunit-test/Dockerfile
cd /grpc-web/packages/grpc-web
npm run test-jsunit

View File

@ -0,0 +1,22 @@
#!/bin/bash
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -ex
# This script is intended to be run within the base image from
# net/grpc/gateway/docker/prereqs/Dockerfile
cd /github/grpc-web/packages/grpc-web
npm run prepare && \
npm run test-mocha

View File

@ -22,25 +22,34 @@ cd "${REPO_DIR}"
# These programs need to be already installed
progs=(docker docker-compose npm curl)
progs=(docker docker-compose curl)
for p in "${progs[@]}"
do
command -v "$p" > /dev/null 2>&1 || \
{ echo >&2 "$p is required but not installed. Aborting."; exit 1; }
done
##########################################################
# Step 1: Run all unit tests
##########################################################
echo -e "\n[Running] Basic test #1 - Runnning unit tests"
# Run jsunit tests
docker-compose build jsunit-test
docker run --rm grpcweb/jsunit-test /bin/bash \
/grpc-web/scripts/docker-run-jsunit-tests.sh
# Build all relevant docker images. They should all build successfully.
if [[ "$MASTER" == "1" ]]; then
# Build all for continuous_integration
docker-compose build
else
# Only build a subset of docker images for presubmit runs
docker-compose build common prereqs envoy node-server \
commonjs-client ts-client
fi
# Run (mocha) unit tests
docker-compose build prereqs
docker run --rm grpcweb/prereqs /bin/bash \
/github/grpc-web/scripts/docker-run-mocha-tests.sh
##########################################################
# Step 2: Test echo server
##########################################################
echo -e "\n[Running] Basic test #2 - Testing echo server"
docker-compose build prereqs envoy node-server
# Bring up the Echo server and the Envoy proxy (in background).
# The 'sleep' seems necessary for the docker containers to be fully up
# and listening before we test the with curl requests
@ -53,11 +62,22 @@ source ./scripts/test-proxy.sh
docker-compose down
# Run unit tests from npm package
docker run --rm grpcweb/prereqs /bin/bash \
/github/grpc-web/scripts/docker-run-tests.sh
##########################################################
# Step 3: Test all Dockerfile and Bazel targets can build!
##########################################################
echo -e "\n[Running] Basic test #3 - Testing everything buids"
if [[ "$MASTER" == "1" ]]; then
# Build all for continuous_integration
docker-compose build
else
# Only build a subset of docker images for presubmit runs
docker-compose build commonjs-client closure-client ts-client
fi
# Run build tests to ensure all Bazel targets can build.
docker run --rm grpcweb/prereqs /bin/bash \
/github/grpc-web/scripts/docker-run-build-tests.sh
# Clean up
git clean -f -d -x
echo 'Completed'
echo 'Basic tests completed successfully!'

View File

@ -34,12 +34,13 @@ do
{ echo >&2 "$p is required but not installed. Aborting."; exit 1; }
done
# Build all relevant docker images. They should all build successfully.
docker-compose build common prereqs node-interop-server interop-client java-interop-server
docker-compose build prereqs node-interop-server java-interop-server
# Run interop tests
##########################################################
# Run interop tests (against Envoy)
##########################################################
echo -e "\n[Running] Interop test #1 - against Envoy"
pid1=$(docker run -d \
-v "$(pwd)"/test/interop/envoy.yaml:/etc/envoy/envoy.yaml:ro \
--network=host envoyproxy/envoy:v1.17.0)
@ -51,9 +52,10 @@ docker rm -f "$pid1"
docker rm -f "$pid2"
#
# Run interop tests against grpc-web java connector code
#
##########################################################
# Run interop tests (against grpc-web Java connector code)
##########################################################
echo -e "\n[Running] Interop test #2 - against Java interop server"
pid3=$(docker run -d --network=host grpcweb/java-interop-server)
run_tests
docker rm -f "$pid3"

View File

@ -47,4 +47,6 @@ echo "$s1" | base64 -d | xxd
# Take the 28 bytes we cut out above, the base64-encoded string should be this
if [[ "$s1" != "AAAAAAcKBWhlbGxvgGdycGMtc3RhdHVzOjANCg==" ]]; then
exit 1;
else
echo "Envoy proxy test successful!"
fi

View File

@ -30,6 +30,7 @@ const grpc = {};
grpc.web = require('grpc-web');
const SERVER_HOST = 'http://localhost:8080';
const TIMEOUT_MS = 1000; // 1 second
function multiDone(done, count) {
return function() {
@ -189,11 +190,12 @@ if (typeof window === 'undefined') { // Running from Node
} else {
console.log('Testing grpc-web-text mode...');
}
describe('grpc-web interop tests', function() {
Object.keys(testCases).forEach((testCase) => {
if (argv.mode == 'binary' && testCases[testCase].skipBinaryMode) return;
it('should pass '+testCase, testCases[testCase].testFunc);
it('should pass '+testCase, testCases[testCase].testFunc)
.timeout(TIMEOUT_MS);
});
});
} else {
@ -216,6 +218,6 @@ if (typeof window === 'undefined') { // Running from Node
if (!doneCalled) {
throw testCase+': failed. Not all done() are called';
}
}, 500);
}, TIMEOUT_MS);
});
}

View File

@ -4,7 +4,7 @@
"description": "gRPC-Web Interop Test Client",
"license": "Apache-2.0",
"scripts": {
"test": "mocha -b --timeout 500 interop_client.js"
"test": "mocha -b interop_client.js"
},
"dependencies": {
"google-protobuf": "~3.14.0",