mirror of https://github.com/grpc/grpc.git
- Adds the service and message classes generated from beefcake and a patched proto compiler - Adds an interop client that uses these service and message classes - Adds an interop server that implement the service description TESTED interop client works with the interop server Change on 2014/12/08 by temiola <temiola@google.com> ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=81615139pull/1/merge
parent
5ef51949bc
commit
ba22e87c68
9 changed files with 578 additions and 2 deletions
@ -0,0 +1,11 @@ |
||||
Interop test protos |
||||
=================== |
||||
|
||||
These were generated by a patched version of beefcake and a patched version of |
||||
protoc. |
||||
|
||||
- set up and access of the patched versions is described in ../../README.md |
||||
|
||||
The actual test proto is found in Google3 at |
||||
|
||||
- third_party/stubby/testing/proto/test.proto |
@ -0,0 +1,229 @@ |
||||
# Copyright 2014, 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. |
||||
|
||||
#!/usr/bin/env ruby |
||||
# interop_client is a testing tool that accesses a gRPC interop testing |
||||
# server and runs a test on it. |
||||
# |
||||
# Helps validate interoperation b/w different gRPC implementations. |
||||
# |
||||
# Usage: $ path/to/interop_client.rb --server_host=<hostname> \ |
||||
# --server_port=<port> \ |
||||
# --test_case=<testcase_name> |
||||
|
||||
this_dir = File.expand_path(File.dirname(__FILE__)) |
||||
lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib') |
||||
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) |
||||
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) |
||||
|
||||
require 'optparse' |
||||
require 'minitest' |
||||
require 'minitest/assertions' |
||||
|
||||
require 'grpc' |
||||
require 'grpc/generic/client_stub' |
||||
require 'grpc/generic/service' |
||||
|
||||
require 'third_party/stubby/testing/proto/test.pb' |
||||
require 'third_party/stubby/testing/proto/messages.pb' |
||||
|
||||
# loads the certificates used to access the test server securely. |
||||
def load_test_certs |
||||
this_dir = File.expand_path(File.dirname(__FILE__)) |
||||
data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata') |
||||
files = ['ca.pem', 'server1.key', 'server1.pem'] |
||||
files.map { |f| File.open(File.join(data_dir, f)).read } |
||||
end |
||||
|
||||
# creates a Credentials from the test certificates. |
||||
def test_creds |
||||
certs = load_test_certs |
||||
creds = GRPC::Core::Credentials.new(certs[0]) |
||||
end |
||||
|
||||
# creates a test stub that accesses host:port securely. |
||||
def create_stub(host, port) |
||||
address = "#{host}:#{port}" |
||||
stub_opts = { |
||||
:creds => test_creds, |
||||
GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.com', |
||||
} |
||||
logger.info("... connecting securely to #{address}") |
||||
stub = Grpc::Testing::TestService::Stub.new(address, **stub_opts) |
||||
end |
||||
|
||||
# produces a string of null chars (\0) of length l. |
||||
def nulls(l) |
||||
raise 'requires #{l} to be +ve' if l < 0 |
||||
[].pack('x' * l) |
||||
end |
||||
|
||||
# defines methods corresponding to each interop test case. |
||||
class NamedTests |
||||
include Minitest::Assertions |
||||
include Grpc::Testing |
||||
include Grpc::Testing::PayloadType |
||||
attr_accessor :assertions # required by Minitest::Assertions |
||||
|
||||
def initialize(stub) |
||||
@assertions = 0 # required by Minitest::Assertions |
||||
@stub = stub |
||||
end |
||||
|
||||
# TESTING |
||||
# PASSED |
||||
# FAIL |
||||
# ruby server: fails beefcake throws on deserializing the 0-length message |
||||
def empty_unary |
||||
resp = @stub.empty_call(Proto2::Empty.new) |
||||
assert resp.is_a?(Proto::Empty), 'empty_unary: invalid response' |
||||
p 'OK: empty_unary' |
||||
end |
||||
|
||||
# TESTING |
||||
# PASSED |
||||
# ruby server |
||||
# FAILED |
||||
def large_unary |
||||
req_size, wanted_response_size = 271828, 314159 |
||||
payload = Payload.new(:type => COMPRESSABLE, :body => nulls(req_size)) |
||||
req = SimpleRequest.new(:response_type => COMPRESSABLE, |
||||
:response_size => wanted_response_size, |
||||
:payload => payload) |
||||
resp = @stub.unary_call(req) |
||||
assert_equal(wanted_response_size, resp.payload.body.length, |
||||
'large_unary: payload had the wrong length') |
||||
assert_equal(nulls(wanted_response_size), resp.payload.body, |
||||
'large_unary: payload content is invalid') |
||||
p 'OK: large_unary' |
||||
end |
||||
|
||||
# TESTING: |
||||
# PASSED |
||||
# ruby server |
||||
# FAILED |
||||
def client_streaming |
||||
msg_sizes = [27182, 8, 1828, 45904] |
||||
wanted_aggregate_size = 74922 |
||||
reqs = msg_sizes.map do |x| |
||||
req = Payload.new(:body => nulls(x)) |
||||
StreamingInputCallRequest.new(:payload => req) |
||||
end |
||||
resp = @stub.streaming_input_call(reqs) |
||||
assert_equal(wanted_aggregate_size, resp.aggregated_payload_size, |
||||
'client_streaming: aggregate payload size is incorrect') |
||||
p 'OK: client_streaming' |
||||
end |
||||
|
||||
# TESTING: |
||||
# PASSED |
||||
# ruby server |
||||
# FAILED |
||||
def server_streaming |
||||
msg_sizes = [31415, 9, 2653, 58979] |
||||
response_spec = msg_sizes.map { |s| ResponseParameters.new(:size => s) } |
||||
req = StreamingOutputCallRequest.new(:response_type => COMPRESSABLE, |
||||
:response_parameters => response_spec) |
||||
resps = @stub.streaming_output_call(req) |
||||
resps.each_with_index do |r, i| |
||||
assert i < msg_sizes.length, 'too many responses' |
||||
assert_equal(COMPRESSABLE, r.payload.type, 'payload type is wrong') |
||||
assert_equal(msg_sizes[i], r.payload.body.length, |
||||
'payload body #{i} has the wrong length') |
||||
end |
||||
p 'OK: server_streaming' |
||||
end |
||||
|
||||
# TESTING: |
||||
# PASSED |
||||
# ruby server |
||||
# FAILED |
||||
# |
||||
# TODO(temiola): update this test to stay consistent with the java test's |
||||
# interpretation of the test spec. |
||||
def ping_pong |
||||
req_cls, param_cls= StreamingOutputCallRequest, ResponseParameters # short |
||||
msg_sizes = [[27182, 31415], [8, 9], [1828, 2653], [45904, 58979]] |
||||
reqs = msg_sizes.map do |x| |
||||
req_size, resp_size = x |
||||
req_cls.new(:payload => Payload.new(:body => nulls(req_size)), |
||||
:response_type => COMPRESSABLE, |
||||
:response_parameters => param_cls.new(:size => resp_size)) |
||||
end |
||||
resps = @stub.full_duplex_call(reqs) |
||||
resps.each_with_index do |r, i| |
||||
assert i < msg_sizes.length, 'too many responses' |
||||
assert_equal(COMPRESSABLE, r.payload.type, 'payload type is wrong') |
||||
assert_equal(msg_sizes[i][1], r.payload.body.length, |
||||
'payload body #{i} has the wrong length') |
||||
end |
||||
p 'OK ping_pong' |
||||
end |
||||
|
||||
end |
||||
|
||||
# validates the the command line options, returning them as a Hash. |
||||
def parse_options |
||||
options = { |
||||
'server_host' => nil, |
||||
'server_port' => nil, |
||||
'test_case' => nil, |
||||
} |
||||
OptionParser.new do |opts| |
||||
opts.banner = 'Usage: --server_host <server_host> --server_port server_port' |
||||
opts.on('--server_host SERVER_HOST', 'server hostname') do |v| |
||||
options['server_host'] = v |
||||
end |
||||
opts.on('--server_port SERVER_PORT', 'server port') do |v| |
||||
options['server_port'] = v |
||||
end |
||||
# instance_methods(false) gives only the methods defined in that class |
||||
test_cases = NamedTests.instance_methods(false).map { |t| t.to_s } |
||||
test_case_list = test_cases.join(',') |
||||
opts.on("--test_case CODE", test_cases, {}, "select a test_case", |
||||
" (#{test_case_list})") do |v| |
||||
options['test_case'] = v |
||||
end |
||||
end.parse! |
||||
|
||||
['server_host', 'server_port', 'test_case'].each do |arg| |
||||
if options[arg].nil? |
||||
raise OptionParser::MissingArgument.new("please specify --#{arg}") |
||||
end |
||||
end |
||||
options |
||||
end |
||||
|
||||
def main |
||||
opts = parse_options |
||||
stub = create_stub(opts['server_host'], opts['server_port']) |
||||
NamedTests.new(stub).method(opts['test_case']).call |
||||
end |
||||
|
||||
main |
@ -0,0 +1,185 @@ |
||||
# Copyright 2014, 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. |
||||
|
||||
#!/usr/bin/env ruby |
||||
# |
||||
# interop_server is a Testing app that runs a gRPC interop testing server. |
||||
# |
||||
# It helps validate interoperation b/w gRPC in different environments |
||||
# |
||||
# Helps validate interoperation b/w different gRPC implementations. |
||||
# |
||||
# Usage: $ path/to/interop_server.rb --port |
||||
|
||||
this_dir = File.expand_path(File.dirname(__FILE__)) |
||||
lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib') |
||||
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) |
||||
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) |
||||
|
||||
require 'forwardable' |
||||
require 'optparse' |
||||
|
||||
require 'grpc' |
||||
require 'grpc/generic/service' |
||||
require 'grpc/generic/rpc_server' |
||||
|
||||
require 'third_party/stubby/testing/proto/test.pb' |
||||
require 'third_party/stubby/testing/proto/messages.pb' |
||||
|
||||
# loads the certificates by the test server. |
||||
def load_test_certs |
||||
this_dir = File.expand_path(File.dirname(__FILE__)) |
||||
data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata') |
||||
files = ['ca.pem', 'server1.key', 'server1.pem'] |
||||
files.map { |f| File.open(File.join(data_dir, f)).read } |
||||
end |
||||
|
||||
# creates a ServerCredentials from the test certificates. |
||||
def test_server_creds |
||||
certs = load_test_certs |
||||
server_creds = GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2]) |
||||
end |
||||
|
||||
# produces a string of null chars (\0) of length l. |
||||
def nulls(l) |
||||
raise 'requires #{l} to be +ve' if l < 0 |
||||
[].pack('x' * l) |
||||
end |
||||
|
||||
# A EnumeratorQueue wraps a Queue yielding the items added to it via each_item. |
||||
class EnumeratorQueue |
||||
extend Forwardable |
||||
def_delegators :@q, :push |
||||
|
||||
def initialize(sentinel) |
||||
@q = Queue.new |
||||
@sentinel = sentinel |
||||
end |
||||
|
||||
def each_item |
||||
return enum_for(:each_item) unless block_given? |
||||
loop do |
||||
r = @q.pop |
||||
break if r.equal?(@sentinel) |
||||
raise r if r.is_a?Exception |
||||
yield r |
||||
end |
||||
end |
||||
end |
||||
|
||||
# A runnable implementation of the schema-specified testing service, with each |
||||
# service method implemented as required by the interop testing spec. |
||||
class TestTarget < Grpc::Testing::TestService::Service |
||||
include Grpc::Testing |
||||
include Grpc::Testing::PayloadType |
||||
|
||||
def empty_call(empty, call) |
||||
Proto::Empty.new |
||||
end |
||||
|
||||
def unary_call(simple_req, call) |
||||
req_size = simple_req.response_size |
||||
SimpleResponse.new(:payload => Payload.new(:type => COMPRESSABLE, |
||||
:body => nulls(req_size))) |
||||
end |
||||
|
||||
def streaming_input_call(call) |
||||
sizes = call.each_remote_read.map { |x| x.payload.body.length } |
||||
sum = sizes.inject { |sum,x| sum + x } |
||||
StreamingInputCallResponse.new(:aggregated_payload_size => sum) |
||||
end |
||||
|
||||
def streaming_output_call(req, call) |
||||
cls = StreamingOutputCallResponse |
||||
req.response_parameters.map do |p| |
||||
cls.new(:payload => Payload.new(:type => req.response_type, |
||||
:body => nulls(p.size))) |
||||
end |
||||
end |
||||
|
||||
def full_duplex_call(reqs) |
||||
# reqs is a lazy Enumerator of the requests sent by the client. |
||||
q = EnumeratorQueue.new(self) |
||||
cls = StreamingOutputCallResponse |
||||
t = Thread.new do |
||||
begin |
||||
reqs.each do |req| |
||||
logger.info("read #{req.inspect}") |
||||
resp_size = req.response_parameters[0].size |
||||
resp = cls.new(:payload => Payload.new(:type => req.response_type, |
||||
:body => nulls(resp_size))) |
||||
q.push(resp) |
||||
end |
||||
logger.info('finished reads') |
||||
q.push(self) |
||||
rescue StandardError => e |
||||
q.push(e) # share the exception with the enumerator |
||||
end |
||||
end |
||||
q.each_item |
||||
end |
||||
|
||||
def half_duplex_call(reqs) |
||||
# TODO(temiola): clarify the behaviour of the half_duplex_call, it's not |
||||
# currently used in any tests |
||||
full_duplex_call(reqs) |
||||
end |
||||
|
||||
end |
||||
|
||||
# validates the the command line options, returning them as a Hash. |
||||
def parse_options |
||||
options = { |
||||
'port' => nil, |
||||
} |
||||
OptionParser.new do |opts| |
||||
opts.banner = 'Usage: --port port' |
||||
opts.on('--port PORT', 'server port') do |v| |
||||
options['port'] = v |
||||
end |
||||
end.parse! |
||||
|
||||
if options['port'].nil? |
||||
raise OptionParser::MissingArgument.new("please specify --port") |
||||
end |
||||
options |
||||
end |
||||
|
||||
def main |
||||
opts = parse_options |
||||
host = "0.0.0.0:#{opts['port']}" |
||||
s = GRPC::RpcServer.new(creds: test_server_creds) |
||||
s.add_http2_port(host, true) |
||||
logger.info("... running securely on #{host}") |
||||
|
||||
s.handle(TestTarget) |
||||
s.run |
||||
end |
||||
|
||||
main |
@ -0,0 +1,14 @@ |
||||
## Generated from net/proto2/bridge/proto/message_set.proto for proto2.bridge |
||||
require 'beefcake' |
||||
|
||||
module Proto2 |
||||
module Bridge |
||||
|
||||
class MessageSet |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class MessageSet |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,12 @@ |
||||
## Generated from net/proto2/proto/empty.proto for proto2 |
||||
require 'beefcake' |
||||
|
||||
module Proto2 |
||||
|
||||
class Empty |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class Empty |
||||
end |
||||
end |
@ -0,0 +1,94 @@ |
||||
## Generated from third_party/stubby/testing/proto/messages.proto for grpc.testing |
||||
require 'beefcake' |
||||
|
||||
require 'net/proto2/bridge/proto/message_set.pb' |
||||
|
||||
module Grpc |
||||
module Testing |
||||
|
||||
module PayloadType |
||||
COMPRESSABLE = 0 |
||||
UNCOMPRESSABLE = 1 |
||||
RANDOM = 2 |
||||
end |
||||
|
||||
class Payload |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class SimpleRequest |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class SimpleResponse |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class SimpleContext |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class StreamingInputCallRequest |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class StreamingInputCallResponse |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class ResponseParameters |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class StreamingOutputCallRequest |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class StreamingOutputCallResponse |
||||
include Beefcake::Message |
||||
end |
||||
|
||||
class Payload |
||||
optional :type, PayloadType, 1 |
||||
optional :body, :bytes, 2 |
||||
end |
||||
|
||||
class SimpleRequest |
||||
optional :response_type, PayloadType, 1 |
||||
optional :response_size, :int32, 2 |
||||
optional :payload, Payload, 3 |
||||
end |
||||
|
||||
class SimpleResponse |
||||
optional :payload, Payload, 1 |
||||
optional :effective_gaia_user_id, :int64, 2 |
||||
end |
||||
|
||||
class SimpleContext |
||||
optional :value, :string, 1 |
||||
end |
||||
|
||||
class StreamingInputCallRequest |
||||
optional :payload, Payload, 1 |
||||
end |
||||
|
||||
class StreamingInputCallResponse |
||||
optional :aggregated_payload_size, :int32, 1 |
||||
end |
||||
|
||||
class ResponseParameters |
||||
optional :size, :int32, 1 |
||||
optional :interval_us, :int32, 2 |
||||
end |
||||
|
||||
class StreamingOutputCallRequest |
||||
optional :response_type, PayloadType, 1 |
||||
repeated :response_parameters, ResponseParameters, 2 |
||||
optional :payload, Payload, 3 |
||||
end |
||||
|
||||
class StreamingOutputCallResponse |
||||
optional :payload, Payload, 1 |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,30 @@ |
||||
## Generated from third_party/stubby/testing/proto/test.proto for grpc.testing |
||||
require 'beefcake' |
||||
require 'grpc' |
||||
|
||||
require 'third_party/stubby/testing/proto/messages.pb' |
||||
require 'net/proto2/proto/empty.pb' |
||||
|
||||
module Grpc |
||||
module Testing |
||||
|
||||
module TestService |
||||
|
||||
class Service |
||||
include GRPC::GenericService |
||||
|
||||
self.marshal_class_method = :encode |
||||
self.unmarshal_class_method = :decode |
||||
|
||||
rpc :EmptyCall, Proto2::Empty, Proto2::Empty |
||||
rpc :UnaryCall, SimpleRequest, SimpleResponse |
||||
rpc :StreamingOutputCall, StreamingOutputCallRequest, stream(StreamingOutputCallResponse) |
||||
rpc :StreamingInputCall, stream(StreamingInputCallRequest), StreamingInputCallResponse |
||||
rpc :FullDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse) |
||||
rpc :HalfDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse) |
||||
end |
||||
Stub = Service.rpc_stub_class |
||||
|
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue