mirror of https://github.com/grpc/grpc-web.git
				
				
				
			Refactor interop tests to be automated
This commit is contained in:
		
							parent
							
								
									ea88b10600
								
							
						
					
					
						commit
						17fbe35265
					
				
							
								
								
									
										15
									
								
								advanced.yml
								
								
								
								
							
							
						
						
									
										15
									
								
								advanced.yml
								
								
								
								
							|  | @ -30,6 +30,15 @@ services: | |||
|     image: grpcweb/node-server | ||||
|     ports: | ||||
|       - "9090:9090" | ||||
|   node-interop-server: | ||||
|     build: | ||||
|       context: ./ | ||||
|       dockerfile: ./net/grpc/gateway/docker/node_interop_server/Dockerfile | ||||
|     depends_on: | ||||
|       - common | ||||
|     image: grpcweb/node-interop-server | ||||
|     ports: | ||||
|       - "7074:7074" | ||||
|   envoy: | ||||
|     build: | ||||
|       context: ./ | ||||
|  | @ -95,12 +104,12 @@ services: | |||
|     image: grpcweb/binary-client | ||||
|     ports: | ||||
|       - "8081:8081" | ||||
|   interop: | ||||
|   interop-client: | ||||
|     build: | ||||
|       context: ./ | ||||
|       dockerfile: ./net/grpc/gateway/docker/interop/Dockerfile | ||||
|       dockerfile: ./net/grpc/gateway/docker/interop_client/Dockerfile | ||||
|     depends_on: | ||||
|       - prereqs | ||||
|     image: grpcweb/interop | ||||
|     image: grpcweb/interop-client | ||||
|     ports: | ||||
|       - "8081:8081" | ||||
|  |  | |||
|  | @ -0,0 +1,37 @@ | |||
| # 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 grpcweb/common | ||||
| 
 | ||||
| WORKDIR /github/grpc-node | ||||
| 
 | ||||
| RUN git clone https://github.com/grpc/grpc-node . && \ | ||||
|   git submodule update --init --recursive | ||||
| 
 | ||||
| RUN cd packages/grpc-native-core && \ | ||||
|   npm install --build-from-source --unsafe-perm && \ | ||||
|   npm link | ||||
| 
 | ||||
| RUN cd packages/proto-loader && \ | ||||
|   npm install @types/mocha && \ | ||||
|   npm install --unsafe-perm | ||||
| 
 | ||||
| WORKDIR /github/grpc-node/test | ||||
| 
 | ||||
| RUN npm install node-pre-gyp && \ | ||||
|   npm install && \ | ||||
|   npm link grpc | ||||
| 
 | ||||
| EXPOSE 7074 | ||||
| CMD ["node", "--require", "./fixtures/native_native", "./interop/interop_server.js", "--port=7074"] | ||||
|  | @ -21,6 +21,14 @@ cd "${REPO_DIR}" | |||
| ./scripts/init_submodules.sh | ||||
| make clean | ||||
| 
 | ||||
| # These programs need to be already installed | ||||
| progs=(docker docker-compose npm 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 | ||||
| 
 | ||||
| # Lint bazel files. | ||||
| BUILDIFIER_VERSION=1.0.0 | ||||
| BUILDIFIER_SUFFIX="" | ||||
|  | @ -33,17 +41,6 @@ chmod +x "./buildifier" | |||
| ./buildifier --mode=check --lint=warn --warnings=all -r bazel javascript net | ||||
| rm ./buildifier | ||||
| 
 | ||||
| # These programs need to be already installed | ||||
| progs=(docker docker-compose npm 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 | ||||
| 
 | ||||
| # Build all relevant docker images. They should all build successfully. | ||||
| docker-compose -f advanced.yml build | ||||
| 
 | ||||
| # Run all bazel unit tests | ||||
| BAZEL_VERSION=2.2.0 | ||||
| BAZEL_OS="linux" | ||||
|  | @ -60,12 +57,19 @@ $HOME/bin/bazel test \ | |||
|   //javascript/net/grpc/web/... \ | ||||
|   //net/grpc/gateway/examples/... | ||||
| 
 | ||||
| # Build the protoc plugin | ||||
| make plugin | ||||
| 
 | ||||
| # Build the grpc-web npm package | ||||
| cd packages/grpc-web && \ | ||||
|   npm install && \ | ||||
|   npm run build && \ | ||||
|   npm link && \ | ||||
|   cd ../.. | ||||
| 
 | ||||
| # Build all relevant docker images. They should all build successfully. | ||||
| docker-compose -f advanced.yml build | ||||
| 
 | ||||
| # 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 | ||||
|  | @ -80,3 +84,26 @@ docker-compose down | |||
| # Run unit tests from npm package | ||||
| docker run --rm grpcweb/prereqs /bin/bash \ | ||||
|   /github/grpc-web/scripts/docker-run-tests.sh | ||||
| 
 | ||||
| # Run interop tests | ||||
| pid1=$(docker run -d -v $(pwd)/test/interop/envoy.yaml:/etc/envoy/envoy.yaml:ro \ | ||||
|   --network=host -p 8080:8080 envoyproxy/envoy:v1.14.1) | ||||
| pid2=$(docker run -d --network=host -p 7074:7074 grpcweb/node-interop-server) | ||||
| 
 | ||||
| cd test/interop && \ | ||||
|   npm install && \ | ||||
|   npm link grpc-web && \ | ||||
|   protoc -I=../.. src/proto/grpc/testing/test.proto \ | ||||
|   src/proto/grpc/testing/empty.proto \ | ||||
|   src/proto/grpc/testing/messages.proto \ | ||||
|   --plugin=protoc-gen-grpc-web=$(pwd)/../../javascript/net/grpc/web/protoc-gen-grpc-web \ | ||||
|   --js_out=import_style=commonjs:. \ | ||||
|   --grpc-web_out=import_style=commonjs,mode=grpcwebtext:. && \ | ||||
|   npm test && \ | ||||
|   cd ../.. | ||||
| 
 | ||||
| # Clean up | ||||
| docker rm -f $pid1 | ||||
| docker rm -f $pid2 | ||||
| git clean -f -d -x | ||||
| echo 'Completed' | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| node_modules/ | ||||
| package-lock.json | ||||
| src/ | ||||
|  | @ -9,14 +9,20 @@ for details about gRPC interop tests in general and the list of test cases. | |||
| Run interop tests | ||||
| ----------------- | ||||
| 
 | ||||
| ### Build some docker images | ||||
| 
 | ||||
| ```sh | ||||
| $ cd grpc-web | ||||
| $ docker-compose -f advanced.yml build common prereqs node-interop-server interop-client | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ### Run the Node interop server | ||||
| 
 | ||||
| An interop server implemented in Node is hosted in the `grpc/grpc-node` repo. | ||||
| There might be a bit of set up you need to do before running the command below. | ||||
| 
 | ||||
| ```sh | ||||
| $ cd grpc-node/test | ||||
| $ node --require ./fixtures/native_native interop/interop_server.js --port=7074 | ||||
| $ docker run -d --network=host -p 7074:7074 grpcweb/node-interop-server | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
|  | @ -26,19 +32,25 @@ An `envoy.yaml` file is provided in this directory to direct traffic for these | |||
| tests. | ||||
| 
 | ||||
| ```sh | ||||
| $ cd grpc-web | ||||
| $ docker run -it --rm -v $(pwd)/test/interop/envoy.yaml:/etc/envoy/envoy.yaml:ro \ | ||||
|   --network=host -p 8080:8080 envoyproxy/envoy:latest | ||||
| $ docker run -d -v $(pwd)/test/interop/envoy.yaml:/etc/envoy/envoy.yaml:ro \ | ||||
|   --network=host -p 8080:8080 envoyproxy/envoy:v1.14.1 | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ### Run the gRPC-Web browser client | ||||
| 
 | ||||
| You can either run the interop client as `npm test`, like this: | ||||
| 
 | ||||
| ```sh | ||||
| $ cd grpc-web | ||||
| $ docker-compose -f advanced.yml build common prereqs interop | ||||
| $ docker-compose -f advanced.yml up interop | ||||
| $ cd test/interop | ||||
| $ npm install | ||||
| $ npm test | ||||
| ``` | ||||
| 
 | ||||
| Or from the browser: | ||||
| 
 | ||||
| ```sh | ||||
| $ docker-compose -f advanced.yml up interop-client | ||||
| ``` | ||||
| 
 | ||||
| Open up the browser and go to `http://localhost:8081/index.html` and open up | ||||
|  |  | |||
|  | @ -25,32 +25,32 @@ const {SimpleRequest, | |||
|          require('./src/proto/grpc/testing/messages_pb.js'); | ||||
| const {TestServiceClient} = | ||||
|   require('./src/proto/grpc/testing/test_grpc_web_pb.js'); | ||||
| var assert = require('assert'); | ||||
| const grpc = {}; | ||||
| grpc.web = require('grpc-web'); | ||||
| 
 | ||||
| var testService = new TestServiceClient( | ||||
|   'http://'+window.location.hostname+':8080', null, null); | ||||
| const SERVER_HOST = 'http://localhost:8080'; | ||||
| 
 | ||||
| function doEmptyUnary() { | ||||
|   return new Promise((resolve, reject) => { | ||||
| function multiDone(done, count) { | ||||
|   return function() { | ||||
|     count -= 1; | ||||
|     if (count <= 0) { | ||||
|       done(); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function doEmptyUnary(done) { | ||||
|   var testService = new TestServiceClient(SERVER_HOST, null, null); | ||||
|   testService.emptyCall(new Empty(), null, (err, response) => { | ||||
|       if (err) { | ||||
|         reject('EmptyUnary failed: Received unexpected error "'+ | ||||
|                err.message+'"'); | ||||
|         return; | ||||
|       } else { | ||||
|         if (!(response instanceof Empty)) { | ||||
|           reject('EmptyUnary failed: Response not of Empty type'); | ||||
|           return; | ||||
|         } | ||||
|         resolve('EmptyUnary: passed'); | ||||
|       } | ||||
|     }); | ||||
|     assert.ifError(err); | ||||
|     assert(response instanceof Empty); | ||||
|     done(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function doLargeUnary() { | ||||
|   return new Promise((resolve, reject) => { | ||||
| function doLargeUnary(done) { | ||||
|   var testService = new TestServiceClient(SERVER_HOST, null, null); | ||||
|   var req = new SimpleRequest(); | ||||
|   var size = 314159; | ||||
| 
 | ||||
|  | @ -61,23 +61,14 @@ function doLargeUnary() { | |||
|   req.setResponseSize(size); | ||||
| 
 | ||||
|   testService.unaryCall(req, null, (err, response) => { | ||||
|       if (err) { | ||||
|         reject('LargeUnary failed: Received unexpected error "'+ | ||||
|                err.message+'"'); | ||||
|         return; | ||||
|       } else { | ||||
|         if (response.getPayload().getBody().length != size) { | ||||
|           rejecet('LargeUnary failed: Received incorrect size payload'); | ||||
|           return; | ||||
|         } | ||||
|         resolve('LargeUnary: passed'); | ||||
|       } | ||||
|     }); | ||||
|     assert.ifError(err); | ||||
|     assert.equal(response.getPayload().getBody().length, size); | ||||
|     done(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function doServerStreaming() { | ||||
|   return new Promise((resolve, reject) => { | ||||
| function doServerStreaming(done) { | ||||
|   var testService = new TestServiceClient(SERVER_HOST, null, null); | ||||
|   var sizes = [31415, 9, 2653, 58979]; | ||||
| 
 | ||||
|   var responseParams = sizes.map((size, idx) => { | ||||
|  | @ -92,28 +83,19 @@ function doServerStreaming() { | |||
| 
 | ||||
|   var stream = testService.streamingOutputCall(req); | ||||
| 
 | ||||
|   done = multiDone(done, sizes.length); | ||||
|   var numCallbacks = 0; | ||||
|   stream.on('data', (response) => { | ||||
|       if (response.getPayload().getBody().length != sizes[numCallbacks]) { | ||||
|         reject('ServerStreaming failed: Received incorrect size payload'); | ||||
|         return; | ||||
|       } | ||||
|     assert.equal(response.getPayload().getBody().length, sizes[numCallbacks]); | ||||
|     numCallbacks++; | ||||
|     }); | ||||
|     // TODO(stanleycheung): is there a better way to do this?
 | ||||
|     setTimeout(() => { | ||||
|       if (numCallbacks != sizes.length) { | ||||
|         reject('ServerStreaming failed: data callback called '+ | ||||
|                numCallbacks+' times. Should be '+sizes.length); | ||||
|         return; | ||||
|       } | ||||
|       resolve('ServerStreaming: passed'); | ||||
|     }, 200); | ||||
|     done(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function doCustomMetadata() { | ||||
|   return new Promise((resolve, reject) => { | ||||
| function doCustomMetadata(done) { | ||||
|   var testService = new TestServiceClient(SERVER_HOST, null, null); | ||||
|   done = multiDone(done, 3); | ||||
| 
 | ||||
|   var req = new SimpleRequest(); | ||||
|   const size = 314159; | ||||
|   const ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial'; | ||||
|  | @ -131,55 +113,27 @@ function doCustomMetadata() { | |||
|     [ECHO_INITIAL_KEY]: ECHO_INITIAL_VALUE, | ||||
|     [ECHO_TRAILING_KEY]: ECHO_TRAILING_VALUE | ||||
|   }, (err, response) => { | ||||
|       if (err) { | ||||
|         reject('CustomMetadata failed: Received unexpected error "'+ | ||||
|                err.message+'"'); | ||||
|         return; | ||||
|       } else { | ||||
|         if (response.getPayload().getBody().length != size) { | ||||
|           rejecet('CustomMetadata failed: Received incorrect size payload'); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     assert.ifError(err); | ||||
|     assert.equal(response.getPayload().getBody().length, size); | ||||
|     done(); | ||||
|   }); | ||||
|     var metadataCallbackVerified = false; | ||||
|     var statusCallbackVerified = false; | ||||
| 
 | ||||
|   call.on('metadata', (metadata) => { | ||||
|       if (!(ECHO_INITIAL_KEY in metadata)) { | ||||
|         reject('CustomMetadata failed: initial metadata does not '+ | ||||
|                'contain '+ECHO_INITIAL_KEY); | ||||
|       } else if (metadata[ECHO_INITIAL_KEY] != ECHO_INITIAL_VALUE) { | ||||
|         reject('CustomMetadata failed: initial metadata value for '+ | ||||
|                ECHO_INITIAL_KEY+' is incorrect'); | ||||
|       } else { | ||||
|         metadataCallbackVerified = true; | ||||
|       } | ||||
|     assert(ECHO_INITIAL_KEY in metadata); | ||||
|     assert.equal(metadata[ECHO_INITIAL_KEY], ECHO_INITIAL_VALUE); | ||||
|     done(); | ||||
|   }); | ||||
| 
 | ||||
|   call.on('status', (status) => { | ||||
|       if (!('metadata' in status)) { | ||||
|         reject('CustomMetadata failed: status callback does not '+ | ||||
|                'contain metadata'); | ||||
|       } else if (!(ECHO_TRAILING_KEY in status.metadata)) { | ||||
|         reject('CustomMetadata failed: trailing metadata does not '+ | ||||
|                'contain '+ECHO_TRAILING_KEY); | ||||
|       } else if (status.metadata[ECHO_TRAILING_KEY] != ECHO_TRAILING_VALUE) { | ||||
|         reject('CustomMetadata failed: trailing metadata value for '+ | ||||
|                ECHO_TRAILING_KEY+' is incorrect'); | ||||
|       } else { | ||||
|         statusCallbackVerified = true; | ||||
|       } | ||||
|     }); | ||||
|     setTimeout(() => { | ||||
|       if (!metadataCallbackVerified || !statusCallbackVerified) { | ||||
|         reject('CustomMetadata failed: some callback failed to verify'); | ||||
|       } | ||||
|       resolve('CustomMetadta passed'); | ||||
|     }, 200); | ||||
|     assert('metadata' in status); | ||||
|     assert(ECHO_TRAILING_KEY in status.metadata); | ||||
|     assert.equal(status.metadata[ECHO_TRAILING_KEY], ECHO_TRAILING_VALUE); | ||||
|     done(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function doStatusCodeAndMessage() { | ||||
|   return new Promise((resolve, reject) => { | ||||
| function doStatusCodeAndMessage(done) { | ||||
|   var testService = new TestServiceClient(SERVER_HOST, null, null); | ||||
|   var req = new SimpleRequest(); | ||||
| 
 | ||||
|   const TEST_STATUS_MESSAGE = 'test status message'; | ||||
|  | @ -190,61 +144,66 @@ function doStatusCodeAndMessage() { | |||
|   req.setResponseStatus(echoStatus); | ||||
| 
 | ||||
|   testService.unaryCall(req, {}, (err, response) => { | ||||
|       if (err) { | ||||
|         if (!('code' in err)) { | ||||
|           reject('StatusCodeAndMessage failed: status callback does not '+ | ||||
|                  'contain code'); | ||||
|         } else if (!('message' in err)) { | ||||
|           reject('StatusCodeAndMessage failed: status callback does not '+ | ||||
|                  'contain message'); | ||||
|         } else if (err.code != 2) { | ||||
|           reject('StatusCodeAndMessage failed: status code is not 2, is '+ | ||||
|                  err.code); | ||||
|         } else if (err.message != TEST_STATUS_MESSAGE) { | ||||
|           reject('StatusCodeAndMessage failed: status mesage is not '+ | ||||
|                  TEST_STATUS_MESSAGE+', is '+err.message); | ||||
|         } else { | ||||
|           resolve('StatusCodeAndMessage passed'); | ||||
|         } | ||||
|       } else { | ||||
|         reject('StatusCodeAndMessage failed: should not have received a '+ | ||||
|                'proper response'); | ||||
|       } | ||||
|     }); | ||||
|     assert(err); | ||||
|     assert('code' in err); | ||||
|     assert('message' in err); | ||||
|     assert.equal(err.code, 2); | ||||
|     assert.equal(err.message, TEST_STATUS_MESSAGE); | ||||
|     done(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function doUnimplementedMethod() { | ||||
|   return new Promise((resolve, reject) => { | ||||
| function doUnimplementedMethod(done) { | ||||
|   var testService = new TestServiceClient(SERVER_HOST, null, null); | ||||
|   testService.unimplementedCall(new Empty(), {}, (err, response) => { | ||||
|       if (err) { | ||||
|         if (!('code' in err)) { | ||||
|           reject('UnimplementedMethod failed: status callback does not '+ | ||||
|                  'contain code'); | ||||
|         } else if (!('message' in err)) { | ||||
|           reject('UnimplementedMethod failed: status callback does not '+ | ||||
|                  'contain message'); | ||||
|         } else if (err.code != 12) { | ||||
|           reject('UnimplementedMethod failed: status code is not 12'+ | ||||
|                  '(UNIMPLEMENTED), is '+ err.code); | ||||
|         } else { | ||||
|           resolve('UnimplementedMethod passed'); | ||||
|         } | ||||
|       } else { | ||||
|         reject('UnimplementedMethod failed: should not have received a '+ | ||||
|                'proper respoonse'); | ||||
|       } | ||||
|     }); | ||||
|     assert(err); | ||||
|     assert('code' in err); | ||||
|     assert.equal(err.code, 12); | ||||
|     done(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| var testCases = [doEmptyUnary, doLargeUnary, doServerStreaming, | ||||
|                  doCustomMetadata, doStatusCodeAndMessage, | ||||
|                  doUnimplementedMethod]; | ||||
| var testCases = { | ||||
|   'empty_unary': doEmptyUnary, | ||||
|   'large_unary': doLargeUnary, | ||||
|   'server_streaming': doServerStreaming, | ||||
|   'custom_metadata': doCustomMetadata, | ||||
|   'status_code_and_message': doStatusCodeAndMessage, | ||||
|   'unimplemented_method': doUnimplementedMethod | ||||
| }; | ||||
| 
 | ||||
| testCases.reduce((promiseChain, currentTask) => { | ||||
|   return promiseChain.then(() => { | ||||
|     return currentTask().then(console.log); | ||||
|   }).catch(console.error); | ||||
| }, Promise.resolve()); | ||||
| if (typeof window === 'undefined') { | ||||
|   console.log('Running from Node...'); | ||||
| 
 | ||||
|   // Fill in XHR runtime
 | ||||
|   global.XMLHttpRequest = require("xhr2"); | ||||
| 
 | ||||
|   describe('grpc-web interop tests', function() { | ||||
|     Object.keys(testCases).forEach((testCase) => { | ||||
|       it('should pass '+testCase, testCases[testCase]); | ||||
|     }); | ||||
|   }); | ||||
| } else { | ||||
|   console.log('Running from browser...'); | ||||
| 
 | ||||
|   Object.keys(testCases).forEach((testCase) => { | ||||
|     var test = testCases[testCase]; | ||||
| 
 | ||||
|     var doneCalled = false; | ||||
|     test((err) => { | ||||
|       if (err) { | ||||
|         throw err; | ||||
|       } else { | ||||
|         doneCalled = true; | ||||
|         console.log(testCase+': passed'); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     setTimeout(() => { | ||||
|       if (!doneCalled) { | ||||
|         throw testCase+': failed. Not all done() are called'; | ||||
|       } | ||||
|     }, 500); | ||||
|   }); | ||||
| } | ||||
|  |  | |||
|  | @ -3,13 +3,17 @@ | |||
|   "version": "0.1.0", | ||||
|   "description": "gRPC-Web Interop Test Client", | ||||
|   "license": "Apache-2.0", | ||||
|   "scripts": { | ||||
|     "test": "mocha -b --timeout 500 client.js" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "google-protobuf": "^3.6.1", | ||||
|     "grpc-web": "^1.0.0" | ||||
|     "google-protobuf": "~3.11.4", | ||||
|     "grpc-web": "~1.0.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "browserify": "^16.2.2", | ||||
|     "webpack": "^4.16.5", | ||||
|     "webpack-cli": "^3.1.0" | ||||
|     "mocha": "~7.1.1", | ||||
|     "webpack": "~4.43.0", | ||||
|     "webpack-cli": "~3.3.11", | ||||
|     "xhr2": "~0.2.0" | ||||
|   } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue