diff --git a/src/node/examples/stock.proto b/src/node/examples/stock.proto new file mode 100644 index 00000000000..efe98d84ffb --- /dev/null +++ b/src/node/examples/stock.proto @@ -0,0 +1,62 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package examples; + +// Protocol type definitions +message StockRequest { + optional string symbol = 1; + optional int32 num_trades_to_watch = 2 [default=0]; +}; + +message StockReply { + optional float price = 1; + optional string symbol = 2; +}; + + +// Interface exported by the server +service Stock { + // Simple blocking RPC + rpc GetLastTradePrice(StockRequest) returns (StockReply) { + }; + // Bidirectional streaming RPC + rpc GetLastTradePriceMultiple(stream StockRequest) returns + (stream StockReply) { + }; + // Unidirectional server-to-client streaming RPC + rpc WatchFutureTrades(StockRequest) returns (stream StockReply) { + }; + // Unidirectional client-to-server streaming RPC + rpc GetHighestTradePrice(stream StockRequest) returns (StockReply) { + }; + +}; \ No newline at end of file diff --git a/src/node/examples/stock_client.js b/src/node/examples/stock_client.js new file mode 100644 index 00000000000..8e99090f350 --- /dev/null +++ b/src/node/examples/stock_client.js @@ -0,0 +1,43 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +var grpc = require('..'); +var examples = grpc.load(__dirname + '/stock.proto').examples; + +/** + * This exports a client constructor for the Stock service. The usage looks like + * + * var StockClient = require('stock_client.js'); + * var stockClient = new StockClient(server_address); + */ +module.exports = examples.Stock; diff --git a/src/node/examples/stock_server.js b/src/node/examples/stock_server.js new file mode 100644 index 00000000000..c188181b779 --- /dev/null +++ b/src/node/examples/stock_server.js @@ -0,0 +1,83 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +var _ = require('underscore'); +var grpc = require('..'); +var examples = grpc.load(__dirname + '/stock.proto').examples; + +var StockServer = grpc.makeServerConstructor([examples.Stock.service]); + +function getLastTradePrice(call, callback) { + callback(null, {price: 88}); +} + +function watchFutureTrades(call) { + for (var i = 0; i < call.request.num_trades_to_watch; i++) { + call.write({price: 88.00 + i * 10.00}); + } + call.end(); +} + +function getHighestTradePrice(call, callback) { + var trades = []; + call.on('data', function(data) { + trades.push({symbol: data.symbol, price: _.random(0, 100)}); + }); + call.on('end', function() { + if(_.isEmpty(trades)) { + callback(null, {}); + } else { + callback(null, _.max(trades, function(trade){return trade.price;})); + } + }); +} + +function getLastTradePriceMultiple(call) { + call.on('data', function(data) { + call.write({price: 88}); + }); + call.on('end', function() { + call.end(); + }); +} + +var stockServer = new StockServer({ + 'examples.Stock' : { + getLastTradePrice: getLastTradePrice, + getLastTradePriceMultiple: getLastTradePriceMultiple, + watchFutureTrades: watchFutureTrades, + getHighestTradePrice: getHighestTradePrice + } +}); + +exports.module = stockServer; diff --git a/src/node/ext/byte_buffer.cc b/src/node/ext/byte_buffer.cc index 142951475a1..695ecedd344 100644 --- a/src/node/ext/byte_buffer.cc +++ b/src/node/ext/byte_buffer.cc @@ -39,13 +39,17 @@ #include "grpc/grpc.h" #include "grpc/support/slice.h" +#include "byte_buffer.h" + namespace grpc { namespace node { -#include "byte_buffer.h" - using ::node::Buffer; +using v8::Context; +using v8::Function; using v8::Handle; +using v8::Object; +using v8::Number; using v8::Value; grpc_byte_buffer *BufferToByteBuffer(Handle buffer) { @@ -73,7 +77,19 @@ Handle ByteBufferToBuffer(grpc_byte_buffer *buffer) { memcpy(result + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next)); offset += GPR_SLICE_LENGTH(next); } - return NanEscapeScope(NanNewBufferHandle(result, length)); + return NanEscapeScope(MakeFastBuffer(NanNewBufferHandle(result, length))); +} + +Handle MakeFastBuffer(Handle slowBuffer) { + NanEscapableScope(); + Handle globalObj = Context::GetCurrent()->Global(); + Handle bufferConstructor = Handle::Cast( + globalObj->Get(NanNew("Buffer"))); + Handle consArgs[3] = { slowBuffer, + NanNew(Buffer::Length(slowBuffer)), + NanNew(0) }; + Handle fastBuffer = bufferConstructor->NewInstance(3, consArgs); + return NanEscapeScope(fastBuffer); } } // namespace node } // namespace grpc diff --git a/src/node/ext/byte_buffer.h b/src/node/ext/byte_buffer.h index ee2b4c0d158..5f1903a42eb 100644 --- a/src/node/ext/byte_buffer.h +++ b/src/node/ext/byte_buffer.h @@ -50,6 +50,10 @@ grpc_byte_buffer *BufferToByteBuffer(v8::Handle buffer); /* Convert a grpc_byte_buffer to a Node.js Buffer */ v8::Handle ByteBufferToBuffer(grpc_byte_buffer *buffer); +/* Convert a ::node::Buffer to a fast Buffer, as defined in the Node + Buffer documentation */ +v8::Handle MakeFastBuffer(v8::Handle slowBuffer); + } // namespace node } // namespace grpc diff --git a/src/node/ext/event.cc b/src/node/ext/event.cc index fcf046b6978..b9446062d71 100644 --- a/src/node/ext/event.cc +++ b/src/node/ext/event.cc @@ -80,7 +80,8 @@ Handle ParseMetadata(grpc_metadata *metadata_elements, size_t length) { metadata_object->Set(key_string, array); } array->Set(index_map[elem->key], - NanNewBufferHandle(elem->value, elem->value_length)); + MakeFastBuffer( + NanNewBufferHandle(elem->value, elem->value_length))); index_map[elem->key] += 1; } return NanEscapeScope(metadata_object); diff --git a/src/node/src/server.js b/src/node/src/server.js index a5d737c68d3..e4f71ff05f7 100644 --- a/src/node/src/server.js +++ b/src/node/src/server.js @@ -243,15 +243,24 @@ function Server(getMetadata, options) { var handler = undefined; var deadline = data.absolute_deadline; var cancelled = false; - if (handlers.hasOwnProperty(data.method)) { - handler = handlers[data.method]; - } call.serverAccept(function(event) { if (event.data.code === grpc.status.CANCELLED) { cancelled = true; - stream.emit('cancelled'); + if (stream) { + stream.emit('cancelled'); + } } }, 0); + if (handlers.hasOwnProperty(data.method)) { + handler = handlers[data.method]; + } else { + call.serverEndInitialMetadata(0); + call.startWriteStatus( + grpc.status.UNIMPLEMENTED, + "This method is not available on this server.", + function() {}); + return; + } if (getMetadata) { call.addMetadata(getMetadata(data.method, data.metadata)); } diff --git a/src/node/test/client_server_test.js b/src/node/test/client_server_test.js index 059dd1323af..1db9f694678 100644 --- a/src/node/test/client_server_test.js +++ b/src/node/test/client_server_test.js @@ -185,6 +185,14 @@ describe('echo client', function() { done(); }); }); + it('should get correct status for unimplemented method', function(done) { + var stream = client.makeRequest(channel, 'unimplemented_method'); + stream.end(); + stream.on('status', function(status) { + assert.equal(status.code, grpc.status.UNIMPLEMENTED); + done(); + }); + }); }); /* TODO(mlumish): explore options for reducing duplication between this test * and the insecure echo client test */ diff --git a/src/php/lib/Grpc/ActiveCall.php b/src/php/lib/Grpc/ActiveCall.php index 836a4b09e3b..e0ea43ab085 100755 --- a/src/php/lib/Grpc/ActiveCall.php +++ b/src/php/lib/Grpc/ActiveCall.php @@ -28,9 +28,9 @@ class ActiveCall { $this->flags = $flags; // Invoke the call. - $this->call->start_invoke($this->completion_queue, - CLIENT_METADATA_READ, - FINISHED, 0); + $this->call->invoke($this->completion_queue, + CLIENT_METADATA_READ, + FINISHED, 0); $metadata_event = $this->completion_queue->pluck(CLIENT_METADATA_READ, Timeval::inf_future()); $this->metadata = $metadata_event->data; diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php index e1feb1206b7..ff293c0709c 100755 --- a/src/php/lib/Grpc/BaseStub.php +++ b/src/php/lib/Grpc/BaseStub.php @@ -10,8 +10,8 @@ class BaseStub { private $channel; - public function __construct($hostname) { - $this->channel = new Channel($hostname, []); + public function __construct($hostname, $opts) { + $this->channel = new Channel($hostname, $opts); } /** @@ -33,10 +33,10 @@ class BaseStub { * @param array $metadata A metadata map to send to the server * @return SimpleSurfaceActiveCall The active call object */ - protected function _simpleRequest($method, - $argument, - callable $deserialize, - $metadata = array()) { + public function _simpleRequest($method, + $argument, + callable $deserialize, + $metadata = array()) { return new SimpleSurfaceActiveCall($this->channel, $method, $deserialize, @@ -55,10 +55,10 @@ class BaseStub { * @param array $metadata A metadata map to send to the server * @return ClientStreamingSurfaceActiveCall The active call object */ - protected function _clientStreamRequest($method, - $arguments, - callable $deserialize, - $metadata = array()) { + public function _clientStreamRequest($method, + $arguments, + callable $deserialize, + $metadata = array()) { return new ClientStreamingSurfaceActiveCall($this->channel, $method, $deserialize, @@ -76,10 +76,10 @@ class BaseStub { * @param array $metadata A metadata map to send to the server * @return ServerStreamingSurfaceActiveCall The active call object */ - protected function _serverStreamRequest($method, - $argument, - callable $deserialize, - $metadata = array()) { + public function _serverStreamRequest($method, + $argument, + callable $deserialize, + $metadata = array()) { return new ServerStreamingSurfaceActiveCall($this->channel, $method, $deserialize, @@ -95,9 +95,9 @@ class BaseStub { * @param array $metadata A metadata map to send to the server * @return BidiStreamingSurfaceActiveCall The active call object */ - protected function _bidiRequest($method, - callable $deserialize, - $metadata = array()) { + public function _bidiRequest($method, + callable $deserialize, + $metadata = array()) { return new BidiStreamingSurfaceActiveCall($this->channel, $method, $deserialize, diff --git a/src/php/tests/interop/empty.php b/src/php/tests/interop/empty.php index 0107f2530b9..22b11803b67 100755 --- a/src/php/tests/interop/empty.php +++ b/src/php/tests/interop/empty.php @@ -1,9 +1,9 @@ addField($cb(), true); @@ -23,4 +23,3 @@ namespace proto2 { } } } - diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php index 43da47fd531..2ff2be7bca0 100755 --- a/src/php/tests/interop/interop_client.php +++ b/src/php/tests/interop/interop_client.php @@ -25,7 +25,7 @@ function hardAssert($value, $error_message) { * @param $stub Stub object that has service methods */ function emptyUnary($stub) { - list($result, $status) = $stub->EmptyCall(new proto2\EmptyMessage())->wait(); + list($result, $status) = $stub->EmptyCall(new grpc\testing\EmptyMessage())->wait(); hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully'); hardAssert($result != null, 'Call completed with a null response'); } @@ -161,11 +161,12 @@ $server_address = $args['server_host'] . ':' . $args['server_port']; $credentials = Grpc\Credentials::createSsl( file_get_contents(dirname(__FILE__) . '/../data/ca.pem')); $stub = new grpc\testing\TestServiceClient( - $server_address, - [ - 'grpc.ssl_target_name_override' => 'foo.test.google.com', - 'credentials' => $credentials - ]); + new Grpc\BaseStub( + $server_address, + [ + 'grpc.ssl_target_name_override' => 'foo.test.google.com', + 'credentials' => $credentials + ])); echo "Connecting to $server_address\n"; echo "Running test case $args[test_case]\n"; diff --git a/src/php/tests/interop/messages.php b/src/php/tests/interop/messages.php index beaec7c0d87..129c96fa136 100755 --- a/src/php/tests/interop/messages.php +++ b/src/php/tests/interop/messages.php @@ -1,7 +1,7 @@ reference = '\grpc\testing\Payload'; $descriptor->addField($f); + // OPTIONAL BOOL fill_username = 4 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 4; + $f->name = "fill_username"; + $f->type = \DrSlump\Protobuf::TYPE_BOOL; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + // OPTIONAL BOOL fill_oauth_scope = 5 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 5; + $f->name = "fill_oauth_scope"; + $f->type = \DrSlump\Protobuf::TYPE_BOOL; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + foreach (self::$__extensions as $cb) { $descriptor->addField($cb(), true); } @@ -293,6 +315,80 @@ namespace grpc\testing { public function setPayload(\grpc\testing\Payload $value){ return $this->_set(3, $value); } + + /** + * Check if has a value + * + * @return boolean + */ + public function hasFillUsername(){ + return $this->_has(4); + } + + /** + * Clear value + * + * @return \grpc\testing\SimpleRequest + */ + public function clearFillUsername(){ + return $this->_clear(4); + } + + /** + * Get value + * + * @return boolean + */ + public function getFillUsername(){ + return $this->_get(4); + } + + /** + * Set value + * + * @param boolean $value + * @return \grpc\testing\SimpleRequest + */ + public function setFillUsername( $value){ + return $this->_set(4, $value); + } + + /** + * Check if has a value + * + * @return boolean + */ + public function hasFillOauthScope(){ + return $this->_has(5); + } + + /** + * Clear value + * + * @return \grpc\testing\SimpleRequest + */ + public function clearFillOauthScope(){ + return $this->_clear(5); + } + + /** + * Get value + * + * @return boolean + */ + public function getFillOauthScope(){ + return $this->_get(5); + } + + /** + * Set value + * + * @param boolean $value + * @return \grpc\testing\SimpleRequest + */ + public function setFillOauthScope( $value){ + return $this->_set(5, $value); + } } } @@ -303,8 +399,11 @@ namespace grpc\testing { /** @var \grpc\testing\Payload */ public $payload = null; - /** @var int */ - public $effective_gaia_user_id = null; + /** @var string */ + public $username = null; + + /** @var string */ + public $oauth_scope = null; /** @var \Closure[] */ @@ -323,11 +422,19 @@ namespace grpc\testing { $f->reference = '\grpc\testing\Payload'; $descriptor->addField($f); - // OPTIONAL INT64 effective_gaia_user_id = 2 + // OPTIONAL STRING username = 2 $f = new \DrSlump\Protobuf\Field(); $f->number = 2; - $f->name = "effective_gaia_user_id"; - $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->name = "username"; + $f->type = \DrSlump\Protobuf::TYPE_STRING; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + // OPTIONAL STRING oauth_scope = 3 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 3; + $f->name = "oauth_scope"; + $f->type = \DrSlump\Protobuf::TYPE_STRING; $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; $descriptor->addField($f); @@ -376,109 +483,77 @@ namespace grpc\testing { } /** - * Check if has a value + * Check if has a value * * @return boolean */ - public function hasEffectiveGaiaUserId(){ + public function hasUsername(){ return $this->_has(2); } /** - * Clear value + * Clear value * * @return \grpc\testing\SimpleResponse */ - public function clearEffectiveGaiaUserId(){ + public function clearUsername(){ return $this->_clear(2); } /** - * Get value + * Get value * - * @return int + * @return string */ - public function getEffectiveGaiaUserId(){ + public function getUsername(){ return $this->_get(2); } /** - * Set value + * Set value * - * @param int $value + * @param string $value * @return \grpc\testing\SimpleResponse */ - public function setEffectiveGaiaUserId( $value){ + public function setUsername( $value){ return $this->_set(2, $value); } - } -} - -namespace grpc\testing { - - class SimpleContext extends \DrSlump\Protobuf\Message { - - /** @var string */ - public $value = null; - - /** @var \Closure[] */ - protected static $__extensions = array(); - - public static function descriptor() - { - $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.SimpleContext'); - - // OPTIONAL STRING value = 1 - $f = new \DrSlump\Protobuf\Field(); - $f->number = 1; - $f->name = "value"; - $f->type = \DrSlump\Protobuf::TYPE_STRING; - $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; - $descriptor->addField($f); - - foreach (self::$__extensions as $cb) { - $descriptor->addField($cb(), true); - } - - return $descriptor; - } - /** - * Check if has a value + * Check if has a value * * @return boolean */ - public function hasValue(){ - return $this->_has(1); + public function hasOauthScope(){ + return $this->_has(3); } /** - * Clear value + * Clear value * - * @return \grpc\testing\SimpleContext + * @return \grpc\testing\SimpleResponse */ - public function clearValue(){ - return $this->_clear(1); + public function clearOauthScope(){ + return $this->_clear(3); } /** - * Get value + * Get value * * @return string */ - public function getValue(){ - return $this->_get(1); + public function getOauthScope(){ + return $this->_get(3); } /** - * Set value + * Set value * * @param string $value - * @return \grpc\testing\SimpleContext + * @return \grpc\testing\SimpleResponse */ - public function setValue( $value){ - return $this->_set(1, $value); + public function setOauthScope( $value){ + return $this->_set(3, $value); } } } @@ -997,15 +1072,3 @@ namespace grpc\testing { } } -namespace { - \proto2\bridge\MessageSet::extension(function(){ - // OPTIONAL MESSAGE grpc\testing\SimpleContext\message_set_extension = 71139615 - $f = new \DrSlump\Protobuf\Field(); - $f->number = 71139615; - $f->name = "grpc\testing\SimpleContext\message_set_extension"; - $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; - $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; - $f->reference = '\grpc\testing\SimpleContext'; - return $f; - }); -} \ No newline at end of file diff --git a/src/php/tests/interop/test.php b/src/php/tests/interop/test.php index fe6d0fb6c45..014bbc9517a 100755 --- a/src/php/tests/interop/test.php +++ b/src/php/tests/interop/test.php @@ -1,52 +1,52 @@ rpc_impl = $rpc_impl; + } /** - * @param proto2\EmptyMessage $input - * @return proto2\EmptyMessage + * @param grpc\testing\EmptyMessage $input */ - public function EmptyCall(\proto2\EmptyMessage $argument, $metadata = array()) { - return $this->_simpleRequest('/TestService/EmptyCall', $argument, '\proto2\EmptyMessage::deserialize', $metadata); + public function EmptyCall(\grpc\testing\EmptyMessage $argument, $metadata = array()) { + return $this->rpc_impl->_simpleRequest('/grpc.testing.TestService/EmptyCall', $argument, '\grpc\testing\EmptyMessage::deserialize', $metadata); } /** * @param grpc\testing\SimpleRequest $input - * @return grpc\testing\SimpleResponse */ public function UnaryCall(\grpc\testing\SimpleRequest $argument, $metadata = array()) { - return $this->_simpleRequest('/TestService/UnaryCall', $argument, '\grpc\testing\SimpleResponse::deserialize', $metadata); + return $this->rpc_impl->_simpleRequest('/grpc.testing.TestService/UnaryCall', $argument, '\grpc\testing\SimpleResponse::deserialize', $metadata); } /** * @param grpc\testing\StreamingOutputCallRequest $input - * @return grpc\testing\StreamingOutputCallResponse */ public function StreamingOutputCall($argument, $metadata = array()) { - return $this->_serverStreamRequest('/TestService/StreamingOutputCall', $argument, '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); + return $this->rpc_impl->_serverStreamRequest('/grpc.testing.TestService/StreamingOutputCall', $argument, '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); } /** * @param grpc\testing\StreamingInputCallRequest $input - * @return grpc\testing\StreamingInputCallResponse */ public function StreamingInputCall($arguments, $metadata = array()) { - return $this->_clientStreamRequest('/TestService/StreamingInputCall', $arguments, '\grpc\testing\StreamingInputCallResponse::deserialize', $metadata); + return $this->rpc_impl->_clientStreamRequest('/grpc.testing.TestService/StreamingInputCall', $arguments, '\grpc\testing\StreamingInputCallResponse::deserialize', $metadata); } /** * @param grpc\testing\StreamingOutputCallRequest $input - * @return grpc\testing\StreamingOutputCallResponse */ public function FullDuplexCall($metadata = array()) { - return $this->_bidiRequest('/TestService/FullDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); + return $this->rpc_impl->_bidiRequest('/grpc.testing.TestService/FullDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); } /** * @param grpc\testing\StreamingOutputCallRequest $input - * @return grpc\testing\StreamingOutputCallResponse */ public function HalfDuplexCall($metadata = array()) { - return $this->_bidiRequest('/TestService/HalfDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); + return $this->rpc_impl->_bidiRequest('/grpc.testing.TestService/HalfDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); } } } diff --git a/tools/run_tests/jobset.py b/tools/run_tests/jobset.py index 8f16a4ff2c2..19ae52ef3b8 100755 --- a/tools/run_tests/jobset.py +++ b/tools/run_tests/jobset.py @@ -86,19 +86,49 @@ def which(filename): raise Exception('%s not found' % filename) +class JobSpec(object): + """Specifies what to run for a job.""" + + def __init__(self, cmdline, shortname=None, environ={}, hash_targets=[]): + """ + Arguments: + cmdline: a list of arguments to pass as the command line + environ: a dictionary of environment variables to set in the child process + hash_targets: which files to include in the hash representing the jobs version + (or empty, indicating the job should not be hashed) + """ + self.cmdline = cmdline + self.environ = environ + self.shortname = cmdline[0] if shortname is None else shortname + self.hash_targets = hash_targets or [] + + def identity(self): + return '%r %r %r' % (self.cmdline, self.environ, self.hash_targets) + + def __hash__(self): + return hash(self.identity()) + + def __cmp__(self, other): + return self.identity() == other.identity() + + class Job(object): """Manages one job.""" - def __init__(self, cmdline, bin_hash, newline_on_success): - self._cmdline = cmdline + def __init__(self, spec, bin_hash, newline_on_success): + self._spec = spec self._bin_hash = bin_hash self._tempfile = tempfile.TemporaryFile() - self._process = subprocess.Popen(args=cmdline, + env = os.environ.copy() + for k, v in spec.environ.iteritems(): + env[k] = v + self._process = subprocess.Popen(args=spec.cmdline, stderr=subprocess.STDOUT, - stdout=self._tempfile) + stdout=self._tempfile, + env=env) self._state = _RUNNING self._newline_on_success = newline_on_success - message('START', ' '.join(self._cmdline)) + message('START', spec.shortname) def state(self, update_cache): """Poll current state of the job. Prints messages at completion.""" @@ -108,12 +138,13 @@ class Job(object): self._tempfile.seek(0) stdout = self._tempfile.read() message('FAILED', '%s [ret=%d]' % ( - ' '.join(self._cmdline), self._process.returncode), stdout) + self._spec.shortname, self._process.returncode), stdout) else: self._state = _SUCCESS - message('PASSED', '%s' % ' '.join(self._cmdline), + message('PASSED', self._spec.shortname, do_newline=self._newline_on_success) - update_cache.finished(self._cmdline, self._bin_hash) + if self._bin_hash: + update_cache.finished(self._spec.identity(), self._bin_hash) return self._state def kill(self): @@ -135,16 +166,26 @@ class Jobset(object): self._newline_on_success = newline_on_success self._cache = cache - def start(self, cmdline): + def start(self, spec): """Start a job. Return True on success, False on failure.""" while len(self._running) >= self._maxjobs: if self.cancelled(): return False self.reap() if self.cancelled(): return False - with open(which(cmdline[0])) as f: - bin_hash = hashlib.sha1(f.read()).hexdigest() - if self._cache.should_run(cmdline, bin_hash): - self._running.add(Job(cmdline, bin_hash, self._newline_on_success)) + if spec.hash_targets: + bin_hash = hashlib.sha1() + for fn in spec.hash_targets: + with open(which(fn)) as f: + bin_hash.update(f.read()) + bin_hash = bin_hash.hexdigest() + should_run = self._cache.should_run(spec.identity(), bin_hash) + else: + bin_hash = None + should_run = True + if should_run: + self._running.add(Job(spec, + bin_hash, + self._newline_on_success)) return True def reap(self): diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py index a699399c276..8cc029e3ccf 100755 --- a/tools/run_tests/run_tests.py +++ b/tools/run_tests/run_tests.py @@ -17,13 +17,17 @@ import watch_dirs # SimpleConfig: just compile with CONFIG=config, and run the binary to test class SimpleConfig(object): - def __init__(self, config): + def __init__(self, config, environ={}): self.build_config = config self.maxjobs = 2 * multiprocessing.cpu_count() self.allow_hashing = (config != 'gcov') + self.environ = environ - def run_command(self, binary): - return [binary] + def job_spec(self, binary, hash_targets): + return jobset.JobSpec(cmdline=[binary], + environ=self.environ, + hash_targets=hash_targets + if self.allow_hashing else None) # ValgrindConfig: compile with some CONFIG=config, but use valgrind to run @@ -35,14 +39,14 @@ class ValgrindConfig(object): self.maxjobs = 2 * multiprocessing.cpu_count() self.allow_hashing = False - def run_command(self, binary): - return ['valgrind', binary, '--tool=%s' % self.tool] + def job_spec(self, binary, hash_targets): + return JobSpec(cmdline=['valgrind', '--tool=%s' % self.tool, binary], + hash_targets=None) class CLanguage(object): def __init__(self, make_target, test_lang): - self.allow_hashing = True self.make_target = make_target with open('tools/run_tests/tests.json') as f: js = json.load(f) @@ -50,8 +54,12 @@ class CLanguage(object): for tgt in js if tgt['language'] == test_lang] - def test_binaries(self, config): - return ['bins/%s/%s' % (config, binary) for binary in self.binaries] + def test_specs(self, config): + out = [] + for name in self.binaries: + binary = 'bins/%s/%s' % (config.build_config, name) + out.append(config.job_spec(binary, [binary])) + return out def make_targets(self): return ['buildtests_%s' % self.make_target] @@ -59,13 +67,11 @@ class CLanguage(object): def build_steps(self): return [] -class NodeLanguage(object): - def __init__(self): - self.allow_hashing = False +class NodeLanguage(object): - def test_binaries(self, config): - return ['tools/run_tests/run_node.sh'] + def test_specs(self, config): + return [config.job_spec('tools/run_tests/run_node.sh', None)] def make_targets(self): return ['static_c'] @@ -73,13 +79,11 @@ class NodeLanguage(object): def build_steps(self): return [['tools/run_tests/build_node.sh']] -class PhpLanguage(object): - def __init__(self): - self.allow_hashing = False +class PhpLanguage(object): - def test_binaries(self, config): - return ['src/php/bin/run_tests.sh'] + def test_specs(self, config): + return [config.job_spec('src/php/bin/run_tests.sh', None)] def make_targets(self): return ['static_c'] @@ -90,11 +94,8 @@ class PhpLanguage(object): class PythonLanguage(object): - def __init__(self): - self.allow_hashing = False - - def test_binaries(self, config): - return ['tools/run_tests/run_python.sh'] + def test_specs(self, config): + return [config.job_spec('tools/run_tests/run_python.sh', None)] def make_targets(self): return[] @@ -109,7 +110,8 @@ _CONFIGS = { 'opt': SimpleConfig('opt'), 'tsan': SimpleConfig('tsan'), 'msan': SimpleConfig('msan'), - 'asan': SimpleConfig('asan'), + 'asan': SimpleConfig('asan', environ={ + 'ASAN_OPTIONS': 'detect_leaks=1:color=always'}), 'gcov': SimpleConfig('gcov'), 'memcheck': ValgrindConfig('valgrind', 'memcheck'), 'helgrind': ValgrindConfig('dbg', 'helgrind') @@ -123,7 +125,7 @@ _LANGUAGES = { 'node': NodeLanguage(), 'php': PhpLanguage(), 'python': PythonLanguage(), -} + } # parse command line argp = argparse.ArgumentParser(description='Run grpc tests.') @@ -155,14 +157,20 @@ build_configs = set(cfg.build_config for cfg in run_configs) make_targets = [] languages = set(_LANGUAGES[l] for l in args.language) -build_steps = [['make', - '-j', '%d' % (multiprocessing.cpu_count() + 1), - 'CONFIG=%s' % cfg] + list(set( - itertools.chain.from_iterable(l.make_targets() - for l in languages))) - for cfg in build_configs] + list( - itertools.chain.from_iterable(l.build_steps() - for l in languages)) +build_steps = [jobset.JobSpec(['make', + '-j', '%d' % (multiprocessing.cpu_count() + 1), + 'CONFIG=%s' % cfg] + list(set( + itertools.chain.from_iterable( + l.make_targets() for l in languages)))) + for cfg in build_configs] + list(set( + jobset.JobSpec(cmdline) + for l in languages + for cmdline in l.build_steps())) +one_run = set( + spec + for config in run_configs + for language in args.language + for spec in _LANGUAGES[language].test_specs(config)) runs_per_test = args.runs_per_test forever = args.forever @@ -175,7 +183,6 @@ class TestCache(object): self._last_successful_run = {} def should_run(self, cmdline, bin_hash): - cmdline = ' '.join(cmdline) if cmdline not in self._last_successful_run: return True if self._last_successful_run[cmdline] != bin_hash: @@ -183,7 +190,7 @@ class TestCache(object): return False def finished(self, cmdline, bin_hash): - self._last_successful_run[' '.join(cmdline)] = bin_hash + self._last_successful_run[cmdline] = bin_hash def dump(self): return [{'cmdline': k, 'hash': v} @@ -209,12 +216,6 @@ def _build_and_run(check_cancelled, newline_on_success, cache): return 1 # run all the tests - one_run = dict( - (' '.join(config.run_command(x)), config.run_command(x)) - for config in run_configs - for language in args.language - for x in _LANGUAGES[language].test_binaries(config.build_config) - ).values() all_runs = itertools.chain.from_iterable( itertools.repeat(one_run, runs_per_test)) if not jobset.run(all_runs, check_cancelled, @@ -226,12 +227,8 @@ def _build_and_run(check_cancelled, newline_on_success, cache): return 0 -test_cache = (None - if not all(x.allow_hashing - for x in itertools.chain(languages, run_configs)) - else TestCache()) -if test_cache: - test_cache.maybe_load() +test_cache = TestCache() +test_cache.maybe_load() if forever: success = True @@ -248,7 +245,7 @@ if forever: 'All tests are now passing properly', do_newline=True) jobset.message('IDLE', 'No change detected') - if test_cache: test_cache.save() + test_cache.save() while not have_files_changed(): time.sleep(1) else: @@ -259,5 +256,5 @@ else: jobset.message('SUCCESS', 'All tests passed', do_newline=True) else: jobset.message('FAILED', 'Some tests failed', do_newline=True) - if test_cache: test_cache.save() + test_cache.save() sys.exit(result)