Merge pull request #2954 from tbetbetbe/grpc-ruby-mv-interop-test-to-pb

Grpc ruby mv interop test to pb
pull/2956/head
Michael Lumish 10 years ago
commit b7eefcf757
  1. 2
      src/ruby/.rubocop.yml
  2. 14
      src/ruby/bin/grpc_ruby_interop_client
  3. 18
      src/ruby/bin/grpc_ruby_interop_server
  4. 8
      src/ruby/bin/interop/README.md
  5. 402
      src/ruby/bin/interop/interop_client.rb
  6. 162
      src/ruby/bin/interop/interop_server.rb
  7. 44
      src/ruby/bin/interop/test/cpp/interop/empty.rb
  8. 60
      src/ruby/bin/interop/test/cpp/interop/test_services.rb
  9. 4
      src/ruby/grpc.gemspec
  10. 15
      src/ruby/pb/README.md
  11. 453
      src/ruby/pb/test/client.rb
  12. 15
      src/ruby/pb/test/proto/empty.rb
  13. 51
      src/ruby/pb/test/proto/messages.rb
  14. 14
      src/ruby/pb/test/proto/test.rb
  15. 64
      src/ruby/pb/test/proto/test_services.rb
  16. 196
      src/ruby/pb/test/server.rb
  17. 48
      src/ruby/spec/pb/health/checker_spec.rb

@ -5,7 +5,7 @@ inherit_from: .rubocop_todo.yml
AllCops:
Exclude:
- 'bin/apis/**/*'
- 'bin/interop/test/**/*'
- 'bin/math.rb'
- 'bin/math_services.rb'
- 'pb/grpc/health/v1alpha/*'
- 'pb/test/**/*'

@ -1,3 +1,5 @@
#!/usr/bin/env ruby
# Copyright 2015, Google Inc.
# All rights reserved.
#
@ -27,13 +29,5 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'grpc/health/v1alpha/health_services'
module Grpc
# Health contains classes and modules that support providing a health check
# service.
module Health
class Checker
end
end
end
# Provides a gem binary entry point for the interop client.
require 'test/client'

@ -1,3 +1,5 @@
#!/usr/bin/env ruby
# Copyright 2015, Google Inc.
# All rights reserved.
#
@ -27,17 +29,5 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/cpp/interop/test.proto
require 'google/protobuf'
require 'test/cpp/interop/empty'
require 'test/cpp/interop/messages'
Google::Protobuf::DescriptorPool.generated_pool.build do
end
module Grpc
module Testing
end
end
# Provides a gem binary entry point for the interop server
require 'test/server'

@ -1,8 +0,0 @@
Interop test protos
===================
These ruby classes were generated with protoc v3, using grpc's ruby compiler
plugin.
- As of 2015/01 protoc v3 is available in the
[google-protobuf](https://github.com/google/protobuf) repo

@ -29,6 +29,12 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# #######################################################################
# DEPRECATED: The behaviour in this file has been moved to pb/test/client.rb
#
# This file remains to support existing tools and scripts that use it.
# ######################################################################
#
# interop_client is a testing tool that accesses a gRPC interop testing
# server and runs a test on it.
#
@ -39,397 +45,7 @@
# --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 'googleauth'
require 'google/protobuf'
require 'test/cpp/interop/test_services'
require 'test/cpp/interop/messages'
require 'test/cpp/interop/empty'
require 'signet/ssl_config'
AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR
# 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
# loads the certificates used to access the test server securely.
def load_prod_cert
fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil?
GRPC.logger.info("loading prod certs from #{ENV['SSL_CERT_FILE']}")
File.open(ENV['SSL_CERT_FILE']).read
end
# creates SSL Credentials from the test certificates.
def test_creds
certs = load_test_certs
GRPC::Core::Credentials.new(certs[0])
end
# creates SSL Credentials from the production certificates.
def prod_creds
cert_text = load_prod_cert
GRPC::Core::Credentials.new(cert_text)
end
# creates the SSL Credentials.
def ssl_creds(use_test_ca)
return test_creds if use_test_ca
prod_creds
end
# creates a test stub that accesses host:port securely.
def create_stub(opts)
address = "#{opts.host}:#{opts.port}"
if opts.secure
stub_opts = {
:creds => ssl_creds(opts.use_test_ca),
GRPC::Core::Channel::SSL_TARGET => opts.host_override
}
# Add service account creds if specified
wants_creds = %w(all compute_engine_creds service_account_creds)
if wants_creds.include?(opts.test_case)
unless opts.oauth_scope.nil?
auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
stub_opts[:update_metadata] = auth_creds.updater_proc
end
end
if opts.test_case == 'oauth2_auth_token'
auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
kw = auth_creds.updater_proc.call({}) # gives as an auth token
# use a metadata update proc that just adds the auth token.
stub_opts[:update_metadata] = proc { |md| md.merge(kw) }
end
if opts.test_case == 'jwt_token_creds' # don't use a scope
auth_creds = Google::Auth.get_application_default
stub_opts[:update_metadata] = auth_creds.updater_proc
end
GRPC.logger.info("... connecting securely to #{address}")
Grpc::Testing::TestService::Stub.new(address, **stub_opts)
else
GRPC.logger.info("... connecting insecurely to #{address}")
Grpc::Testing::TestService::Stub.new(address)
end
end
# produces a string of null chars (\0) of length l.
def nulls(l)
fail 'requires #{l} to be +ve' if l < 0
[].pack('x' * l).force_encoding('utf-8')
end
# a PingPongPlayer implements the ping pong bidi test.
class PingPongPlayer
include Minitest::Assertions
include Grpc::Testing
include Grpc::Testing::PayloadType
attr_accessor :assertions # required by Minitest::Assertions
attr_accessor :queue
attr_accessor :canceller_op
# reqs is the enumerator over the requests
def initialize(msg_sizes)
@queue = Queue.new
@msg_sizes = msg_sizes
@assertions = 0 # required by Minitest::Assertions
@canceller_op = nil # used to cancel after the first response
end
def each_item
return enum_for(:each_item) unless block_given?
req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters # short
count = 0
@msg_sizes.each do |m|
req_size, resp_size = m
req = req_cls.new(payload: Payload.new(body: nulls(req_size)),
response_type: :COMPRESSABLE,
response_parameters: [p_cls.new(size: resp_size)])
yield req
resp = @queue.pop
assert_equal(:COMPRESSABLE, resp.payload.type, 'payload type is wrong')
assert_equal(resp_size, resp.payload.body.length,
"payload body #{count} has the wrong length")
p "OK: ping_pong #{count}"
count += 1
unless @canceller_op.nil?
canceller_op.cancel
break
end
end
end
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, args)
@assertions = 0 # required by Minitest::Assertions
@stub = stub
@args = args
end
def empty_unary
resp = @stub.empty_call(Empty.new)
assert resp.is_a?(Empty), 'empty_unary: invalid response'
p 'OK: empty_unary'
end
def large_unary
perform_large_unary
p 'OK: large_unary'
end
def service_account_creds
# ignore this test if the oauth options are not set
if @args.oauth_scope.nil?
p 'NOT RUN: service_account_creds; no service_account settings'
return
end
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true)
assert_equal(wanted_email, resp.username,
'service_account_creds: incorrect username')
assert(@args.oauth_scope.include?(resp.oauth_scope),
'service_account_creds: incorrect oauth_scope')
p 'OK: service_account_creds'
end
def jwt_token_creds
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
resp = perform_large_unary(fill_username: true)
assert_equal(wanted_email, resp.username,
'service_account_creds: incorrect username')
p 'OK: jwt_token_creds'
end
def compute_engine_creds
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true)
assert_equal(@args.default_service_account, resp.username,
'compute_engine_creds: incorrect username')
p 'OK: compute_engine_creds'
end
def oauth2_auth_token
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true)
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
assert_equal(wanted_email, resp.username,
"#{__callee__}: incorrect username")
assert(@args.oauth_scope.include?(resp.oauth_scope),
"#{__callee__}: incorrect oauth_scope")
p "OK: #{__callee__}"
end
def per_rpc_creds
auth_creds = Google::Auth.get_application_default(@args.oauth_scope)
kw = auth_creds.updater_proc.call({})
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true,
**kw)
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
assert_equal(wanted_email, resp.username,
"#{__callee__}: incorrect username")
assert(@args.oauth_scope.include?(resp.oauth_scope),
"#{__callee__}: incorrect oauth_scope")
p "OK: #{__callee__}"
end
def client_streaming
msg_sizes = [27_182, 8, 1828, 45_904]
wanted_aggregate_size = 74_922
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
def server_streaming
msg_sizes = [31_415, 9, 2653, 58_979]
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
def ping_pong
msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]]
ppp = PingPongPlayer.new(msg_sizes)
resps = @stub.full_duplex_call(ppp.each_item)
resps.each { |r| ppp.queue.push(r) }
p 'OK: ping_pong'
end
def timeout_on_sleeping_server
msg_sizes = [[27_182, 31_415]]
ppp = PingPongPlayer.new(msg_sizes)
resps = @stub.full_duplex_call(ppp.each_item, timeout: 0.001)
resps.each { |r| ppp.queue.push(r) }
fail 'Should have raised GRPC::BadStatus(DEADLINE_EXCEEDED)'
rescue GRPC::BadStatus => e
assert_equal(e.code, GRPC::Core::StatusCodes::DEADLINE_EXCEEDED)
p "OK: #{__callee__}"
end
def empty_stream
ppp = PingPongPlayer.new([])
resps = @stub.full_duplex_call(ppp.each_item)
count = 0
resps.each do
|r| ppp.queue.push(r)
count += 1
end
assert_equal(0, count, 'too many responses, expect 0')
p 'OK: empty_stream'
end
def cancel_after_begin
msg_sizes = [27_182, 8, 1828, 45_904]
reqs = msg_sizes.map do |x|
req = Payload.new(body: nulls(x))
StreamingInputCallRequest.new(payload: req)
end
op = @stub.streaming_input_call(reqs, return_op: true)
op.cancel
assert_raises(GRPC::Cancelled) { op.execute }
assert(op.cancelled, 'call operation should be CANCELLED')
p 'OK: cancel_after_begin'
end
def cancel_after_first_response
msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]]
ppp = PingPongPlayer.new(msg_sizes)
op = @stub.full_duplex_call(ppp.each_item, return_op: true)
ppp.canceller_op = op # causes ppp to cancel after the 1st message
assert_raises(GRPC::Cancelled) { op.execute.each { |r| ppp.queue.push(r) } }
op.wait
assert(op.cancelled, 'call operation was not CANCELLED')
p 'OK: cancel_after_first_response'
end
def all
all_methods = NamedTests.instance_methods(false).map(&:to_s)
all_methods.each do |m|
next if m == 'all' || m.start_with?('assert')
p "TESTCASE: #{m}"
method(m).call
end
end
private
def perform_large_unary(fill_username: false, fill_oauth_scope: false, **kw)
req_size, wanted_response_size = 271_828, 314_159
payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
req = SimpleRequest.new(response_type: :COMPRESSABLE,
response_size: wanted_response_size,
payload: payload)
req.fill_username = fill_username
req.fill_oauth_scope = fill_oauth_scope
resp = @stub.unary_call(req, **kw)
assert_equal(:COMPRESSABLE, resp.payload.type,
'large_unary: payload had the wrong type')
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')
resp
end
end
# Args is used to hold the command line info.
Args = Struct.new(:default_service_account, :host, :host_override,
:oauth_scope, :port, :secure, :test_case,
:use_test_ca)
# validates the the command line options, returning them as a Hash.
def parse_args
args = Args.new
args.host_override = 'foo.test.google.fr'
OptionParser.new do |opts|
opts.on('--oauth_scope scope',
'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
args['host'] = v
end
opts.on('--default_service_account email_address',
'email address of the default service account') do |v|
args['default_service_account'] = v
end
opts.on('--server_host_override HOST_OVERRIDE',
'override host via a HTTP header') do |v|
args['host_override'] = v
end
opts.on('--server_port SERVER_PORT', 'server port') { |v| args['port'] = v }
# instance_methods(false) gives only the methods defined in that class
test_cases = NamedTests.instance_methods(false).map(&:to_s)
test_case_list = test_cases.join(',')
opts.on('--test_case CODE', test_cases, {}, 'select a test_case',
" (#{test_case_list})") { |v| args['test_case'] = v }
opts.on('-s', '--use_tls', 'require a secure connection?') do |v|
args['secure'] = v
end
opts.on('-t', '--use_test_ca',
'if secure, use the test certificate?') do |v|
args['use_test_ca'] = v
end
end.parse!
_check_args(args)
end
def _check_args(args)
%w(host port test_case).each do |a|
if args[a].nil?
fail(OptionParser::MissingArgument, "please specify --#{arg}")
end
end
args
end
def main
opts = parse_args
stub = create_stub(opts)
NamedTests.new(stub, opts).method(opts['test_case']).call
end
pb_dir = File.join(File.dirname(File.dirname(this_dir)), 'pb')
$LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir)
main
require 'test/client'

@ -29,6 +29,12 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# #######################################################################
# DEPRECATED: The behaviour in this file has been moved to pb/test/server.rb
#
# This file remains to support existing tools and scripts that use it.
# ######################################################################
#
# interop_server is a Testing app that runs a gRPC interop testing server.
#
# It helps validate interoperation b/w gRPC in different environments
@ -38,157 +44,7 @@
# 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 'test/cpp/interop/test_services'
require 'test/cpp/interop/messages'
require 'test/cpp/interop/empty'
# 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
GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2])
end
# produces a string of null chars (\0) of length l.
def nulls(l)
fail 'requires #{l} to be +ve' if l < 0
[].pack('x' * l).force_encoding('utf-8')
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)
fail 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)
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 { |s, x| s + 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
Thread.new do
begin
GRPC.logger.info('interop-server: started receiving')
reqs.each do |req|
resp_size = req.response_parameters[0].size
GRPC.logger.info("read a req, response size is #{resp_size}")
resp = cls.new(payload: Payload.new(type: req.response_type,
body: nulls(resp_size)))
q.push(resp)
end
GRPC.logger.info('interop-server: finished receiving')
q.push(self)
rescue StandardError => e
GRPC.logger.info('interop-server: failed')
GRPC.logger.warn(e)
q.push(e) # share the exception with the enumerator
end
end
q.each_item
end
def half_duplex_call(reqs)
# TODO: update with unique behaviour of the half_duplex_call if that's
# ever required by any of the tests.
full_duplex_call(reqs)
end
end
# validates the the command line options, returning them as a Hash.
def parse_options
options = {
'port' => nil,
'secure' => false
}
OptionParser.new do |opts|
opts.banner = 'Usage: --port port'
opts.on('--port PORT', 'server port') do |v|
options['port'] = v
end
opts.on('-s', '--use_tls', 'require a secure connection?') do |v|
options['secure'] = v
end
end.parse!
if options['port'].nil?
fail(OptionParser::MissingArgument, 'please specify --port')
end
options
end
def main
opts = parse_options
host = "0.0.0.0:#{opts['port']}"
s = GRPC::RpcServer.new
if opts['secure']
s.add_http2_port(host, test_server_creds)
GRPC.logger.info("... running securely on #{host}")
else
s.add_http2_port(host)
GRPC.logger.info("... running insecurely on #{host}")
end
s.handle(TestTarget)
s.run_till_terminated
end
pb_dir = File.join(File.dirname(File.dirname(this_dir)), 'pb')
$LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir)
main
require 'test/server'

@ -1,44 +0,0 @@
# 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.
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/cpp/interop/empty.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.Empty" do
end
end
module Grpc
module Testing
Empty = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Empty").msgclass
end
end

@ -1,60 +0,0 @@
# 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.
# Generated by the protocol buffer compiler. DO NOT EDIT!
# Source: test/cpp/interop/test.proto for package 'grpc.testing'
require 'grpc'
require 'test/cpp/interop/test'
module Grpc
module Testing
module TestService
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.testing.TestService'
rpc :EmptyCall, Empty, 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

@ -24,13 +24,13 @@ Gem::Specification.new do |s|
%w(math noproto).each do |b|
s.executables += ["#{b}_client.rb", "#{b}_server.rb"]
end
s.executables += %w(grpc_ruby_interop_client grpc_ruby_interop_server)
s.require_paths = %w( bin lib pb )
s.platform = Gem::Platform::RUBY
s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1'
s.add_dependency 'googleauth', '~> 0.4' # reqd for interop tests
s.add_dependency 'googleauth', '~> 0.4'
s.add_dependency 'logging', '~> 2.0'
s.add_dependency 'minitest', '~> 5.4' # reqd for interop tests
s.add_development_dependency 'simplecov', '~> 0.9'
s.add_development_dependency 'bundler', '~> 1.9'

@ -25,3 +25,18 @@ $ protoc -I . grpc/health/v1alpha/health.proto \
--ruby_out=. \
--plugin=protoc-gen-grpc=`which grpc_ruby_plugin`
```
test
----
This package defines the surface of the gRPC interop test service and client
To re-generate the surface, it's necessary to have checked-out versions of
the grpc interop test proto, e.g, by having the full gRPC repository. E.g,
```bash
$ # (from this directory within the grpc repo)
$ protoc -I../../.. ../../../test/proto/{messages,test,empty}.proto \
--grpc_out=. \
--ruby_out=. \
--plugin=protoc-gen-grpc=`which grpc_ruby_plugin`
```

@ -0,0 +1,453 @@
#!/usr/bin/env ruby
# 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.
# 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/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')
pb_dir = File.dirname(File.dirname(this_dir))
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
$LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir)
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
require 'optparse'
require 'grpc'
require 'googleauth'
require 'google/protobuf'
require 'test/proto/empty'
require 'test/proto/messages'
require 'test/proto/test_services'
require 'signet/ssl_config'
AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR
# AssertionError is use to indicate interop test failures.
class AssertionError < RuntimeError; end
# Fails with AssertionError if the block does evaluate to true
def assert(msg = 'unknown cause')
fail 'No assertion block provided' unless block_given?
fail AssertionError, msg unless yield
end
# 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
# loads the certificates used to access the test server securely.
def load_prod_cert
fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil?
GRPC.logger.info("loading prod certs from #{ENV['SSL_CERT_FILE']}")
File.open(ENV['SSL_CERT_FILE']).read
end
# creates SSL Credentials from the test certificates.
def test_creds
certs = load_test_certs
GRPC::Core::Credentials.new(certs[0])
end
# creates SSL Credentials from the production certificates.
def prod_creds
cert_text = load_prod_cert
GRPC::Core::Credentials.new(cert_text)
end
# creates the SSL Credentials.
def ssl_creds(use_test_ca)
return test_creds if use_test_ca
prod_creds
end
# creates a test stub that accesses host:port securely.
def create_stub(opts)
address = "#{opts.host}:#{opts.port}"
if opts.secure
stub_opts = {
:creds => ssl_creds(opts.use_test_ca),
GRPC::Core::Channel::SSL_TARGET => opts.host_override
}
# Add service account creds if specified
wants_creds = %w(all compute_engine_creds service_account_creds)
if wants_creds.include?(opts.test_case)
unless opts.oauth_scope.nil?
auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
stub_opts[:update_metadata] = auth_creds.updater_proc
end
end
if opts.test_case == 'oauth2_auth_token'
auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
kw = auth_creds.updater_proc.call({}) # gives as an auth token
# use a metadata update proc that just adds the auth token.
stub_opts[:update_metadata] = proc { |md| md.merge(kw) }
end
if opts.test_case == 'jwt_token_creds' # don't use a scope
auth_creds = Google::Auth.get_application_default
stub_opts[:update_metadata] = auth_creds.updater_proc
end
GRPC.logger.info("... connecting securely to #{address}")
Grpc::Testing::TestService::Stub.new(address, **stub_opts)
else
GRPC.logger.info("... connecting insecurely to #{address}")
Grpc::Testing::TestService::Stub.new(address)
end
end
# produces a string of null chars (\0) of length l.
def nulls(l)
fail 'requires #{l} to be +ve' if l < 0
[].pack('x' * l).force_encoding('utf-8')
end
# a PingPongPlayer implements the ping pong bidi test.
class PingPongPlayer
include Grpc::Testing
include Grpc::Testing::PayloadType
attr_accessor :queue
attr_accessor :canceller_op
# reqs is the enumerator over the requests
def initialize(msg_sizes)
@queue = Queue.new
@msg_sizes = msg_sizes
@canceller_op = nil # used to cancel after the first response
end
def each_item
return enum_for(:each_item) unless block_given?
req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters # short
count = 0
@msg_sizes.each do |m|
req_size, resp_size = m
req = req_cls.new(payload: Payload.new(body: nulls(req_size)),
response_type: :COMPRESSABLE,
response_parameters: [p_cls.new(size: resp_size)])
yield req
resp = @queue.pop
assert('payload type is wrong') { :COMPRESSABLE == resp.payload.type }
assert("payload body #{count} has the wrong length") do
resp_size == resp.payload.body.length
end
p "OK: ping_pong #{count}"
count += 1
unless @canceller_op.nil?
canceller_op.cancel
break
end
end
end
end
# defines methods corresponding to each interop test case.
class NamedTests
include Grpc::Testing
include Grpc::Testing::PayloadType
def initialize(stub, args)
@stub = stub
@args = args
end
def empty_unary
resp = @stub.empty_call(Empty.new)
assert('empty_unary: invalid response') { resp.is_a?(Empty) }
p 'OK: empty_unary'
end
def large_unary
perform_large_unary
p 'OK: large_unary'
end
def service_account_creds
# ignore this test if the oauth options are not set
if @args.oauth_scope.nil?
p 'NOT RUN: service_account_creds; no service_account settings'
return
end
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true)
assert("#{__callee__}: bad username") { wanted_email == resp.username }
assert("#{__callee__}: bad oauth scope") do
@args.oauth_scope.include?(resp.oauth_scope)
end
p "OK: #{__callee__}"
end
def jwt_token_creds
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
resp = perform_large_unary(fill_username: true)
assert("#{__callee__}: bad username") { wanted_email == resp.username }
p "OK: #{__callee__}"
end
def compute_engine_creds
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true)
assert("#{__callee__}: bad username") do
@args.default_service_account == resp.username
end
p "OK: #{__callee__}"
end
def oauth2_auth_token
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true)
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
assert("#{__callee__}: bad username") { wanted_email == resp.username }
assert("#{__callee__}: bad oauth scope") do
@args.oauth_scope.include?(resp.oauth_scope)
end
p "OK: #{__callee__}"
end
def per_rpc_creds
auth_creds = Google::Auth.get_application_default(@args.oauth_scope)
kw = auth_creds.updater_proc.call({})
resp = perform_large_unary(fill_username: true,
fill_oauth_scope: true,
**kw)
json_key = File.read(ENV[AUTH_ENV])
wanted_email = MultiJson.load(json_key)['client_email']
assert("#{__callee__}: bad username") { wanted_email == resp.username }
assert("#{__callee__}: bad oauth scope") do
@args.oauth_scope.include?(resp.oauth_scope)
end
p "OK: #{__callee__}"
end
def client_streaming
msg_sizes = [27_182, 8, 1828, 45_904]
wanted_aggregate_size = 74_922
reqs = msg_sizes.map do |x|
req = Payload.new(body: nulls(x))
StreamingInputCallRequest.new(payload: req)
end
resp = @stub.streaming_input_call(reqs)
assert("#{__callee__}: aggregate payload size is incorrect") do
wanted_aggregate_size == resp.aggregated_payload_size
end
p "OK: #{__callee__}"
end
def server_streaming
msg_sizes = [31_415, 9, 2653, 58_979]
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("#{__callee__}: too many responses") { i < msg_sizes.length }
assert("#{__callee__}: payload body #{i} has the wrong length") do
msg_sizes[i] == r.payload.body.length
end
assert("#{__callee__}: payload type is wrong") do
:COMPRESSABLE == r.payload.type
end
end
p "OK: #{__callee__}"
end
def ping_pong
msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]]
ppp = PingPongPlayer.new(msg_sizes)
resps = @stub.full_duplex_call(ppp.each_item)
resps.each { |r| ppp.queue.push(r) }
p "OK: #{__callee__}"
end
def timeout_on_sleeping_server
msg_sizes = [[27_182, 31_415]]
ppp = PingPongPlayer.new(msg_sizes)
resps = @stub.full_duplex_call(ppp.each_item, timeout: 0.001)
resps.each { |r| ppp.queue.push(r) }
fail 'Should have raised GRPC::BadStatus(DEADLINE_EXCEEDED)'
rescue GRPC::BadStatus => e
assert("#{__callee__}: status was wrong") do
e.code == GRPC::Core::StatusCodes::DEADLINE_EXCEEDED
end
p "OK: #{__callee__}"
end
def empty_stream
ppp = PingPongPlayer.new([])
resps = @stub.full_duplex_call(ppp.each_item)
count = 0
resps.each do |r|
ppp.queue.push(r)
count += 1
end
assert("#{__callee__}: too many responses expected 0") do
count == 0
end
p "OK: #{__callee__}"
end
def cancel_after_begin
msg_sizes = [27_182, 8, 1828, 45_904]
reqs = msg_sizes.map do |x|
req = Payload.new(body: nulls(x))
StreamingInputCallRequest.new(payload: req)
end
op = @stub.streaming_input_call(reqs, return_op: true)
op.cancel
op.execute
fail 'Should have raised GRPC:Cancelled'
rescue GRPC::Cancelled
assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled }
p "OK: #{__callee__}"
end
def cancel_after_first_response
msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]]
ppp = PingPongPlayer.new(msg_sizes)
op = @stub.full_duplex_call(ppp.each_item, return_op: true)
ppp.canceller_op = op # causes ppp to cancel after the 1st message
op.execute.each { |r| ppp.queue.push(r) }
fail 'Should have raised GRPC:Cancelled'
rescue GRPC::Cancelled
assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled }
op.wait
p "OK: #{__callee__}"
end
def all
all_methods = NamedTests.instance_methods(false).map(&:to_s)
all_methods.each do |m|
next if m == 'all' || m.start_with?('assert')
p "TESTCASE: #{m}"
method(m).call
end
end
private
def perform_large_unary(fill_username: false, fill_oauth_scope: false, **kw)
req_size, wanted_response_size = 271_828, 314_159
payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
req = SimpleRequest.new(response_type: :COMPRESSABLE,
response_size: wanted_response_size,
payload: payload)
req.fill_username = fill_username
req.fill_oauth_scope = fill_oauth_scope
resp = @stub.unary_call(req, **kw)
assert('payload type is wrong') do
:COMPRESSABLE == resp.payload.type
end
assert('payload body has the wrong length') do
wanted_response_size == resp.payload.body.length
end
assert('payload body is invalid') do
nulls(wanted_response_size) == resp.payload.body
end
resp
end
end
# Args is used to hold the command line info.
Args = Struct.new(:default_service_account, :host, :host_override,
:oauth_scope, :port, :secure, :test_case,
:use_test_ca)
# validates the the command line options, returning them as a Hash.
def parse_args
args = Args.new
args.host_override = 'foo.test.google.fr'
OptionParser.new do |opts|
opts.on('--oauth_scope scope',
'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
args['host'] = v
end
opts.on('--default_service_account email_address',
'email address of the default service account') do |v|
args['default_service_account'] = v
end
opts.on('--server_host_override HOST_OVERRIDE',
'override host via a HTTP header') do |v|
args['host_override'] = v
end
opts.on('--server_port SERVER_PORT', 'server port') { |v| args['port'] = v }
# instance_methods(false) gives only the methods defined in that class
test_cases = NamedTests.instance_methods(false).map(&:to_s)
test_case_list = test_cases.join(',')
opts.on('--test_case CODE', test_cases, {}, 'select a test_case',
" (#{test_case_list})") { |v| args['test_case'] = v }
opts.on('-s', '--use_tls', 'require a secure connection?') do |v|
args['secure'] = v
end
opts.on('-t', '--use_test_ca',
'if secure, use the test certificate?') do |v|
args['use_test_ca'] = v
end
end.parse!
_check_args(args)
end
def _check_args(args)
%w(host port test_case).each do |a|
if args[a].nil?
fail(OptionParser::MissingArgument, "please specify --#{a}")
end
end
args
end
def main
opts = parse_args
stub = create_stub(opts)
NamedTests.new(stub, opts).method(opts['test_case']).call
end
main

@ -0,0 +1,15 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/proto/empty.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.Empty" do
end
end
module Grpc
module Testing
Empty = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Empty").msgclass
end
end

@ -1,34 +1,5 @@
# 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.
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/cpp/interop/messages.proto
# source: test/proto/messages.proto
require 'google/protobuf'
@ -37,12 +8,18 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :type, :enum, 1, "grpc.testing.PayloadType"
optional :body, :string, 2
end
add_message "grpc.testing.EchoStatus" do
optional :code, :int32, 1
optional :message, :string, 2
end
add_message "grpc.testing.SimpleRequest" do
optional :response_type, :enum, 1, "grpc.testing.PayloadType"
optional :response_size, :int32, 2
optional :payload, :message, 3, "grpc.testing.Payload"
optional :fill_username, :bool, 4
optional :fill_oauth_scope, :bool, 5
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
end
add_message "grpc.testing.SimpleResponse" do
optional :payload, :message, 1, "grpc.testing.Payload"
@ -63,20 +40,32 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :response_type, :enum, 1, "grpc.testing.PayloadType"
repeated :response_parameters, :message, 2, "grpc.testing.ResponseParameters"
optional :payload, :message, 3, "grpc.testing.Payload"
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
end
add_message "grpc.testing.StreamingOutputCallResponse" do
optional :payload, :message, 1, "grpc.testing.Payload"
end
add_message "grpc.testing.ReconnectInfo" do
optional :passed, :bool, 1
repeated :backoff_ms, :int32, 2
end
add_enum "grpc.testing.PayloadType" do
value :COMPRESSABLE, 0
value :UNCOMPRESSABLE, 1
value :RANDOM, 2
end
add_enum "grpc.testing.CompressionType" do
value :NONE, 0
value :GZIP, 1
value :DEFLATE, 2
end
end
module Grpc
module Testing
Payload = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Payload").msgclass
EchoStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.EchoStatus").msgclass
SimpleRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleRequest").msgclass
SimpleResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleResponse").msgclass
StreamingInputCallRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingInputCallRequest").msgclass
@ -84,6 +73,8 @@ module Grpc
ResponseParameters = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ResponseParameters").msgclass
StreamingOutputCallRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingOutputCallRequest").msgclass
StreamingOutputCallResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingOutputCallResponse").msgclass
ReconnectInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass
PayloadType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule
CompressionType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.CompressionType").enummodule
end
end

@ -0,0 +1,14 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/proto/test.proto
require 'google/protobuf'
require 'test/proto/empty'
require 'test/proto/messages'
Google::Protobuf::DescriptorPool.generated_pool.build do
end
module Grpc
module Testing
end
end

@ -0,0 +1,64 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# Source: test/proto/test.proto for package 'grpc.testing'
require 'grpc'
require 'test/proto/test'
module Grpc
module Testing
module TestService
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.testing.TestService'
rpc :EmptyCall, Empty, 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
module UnimplementedService
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.testing.UnimplementedService'
rpc :UnimplementedCall, Empty, Empty
end
Stub = Service.rpc_stub_class
end
module ReconnectService
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.testing.ReconnectService'
rpc :Start, Empty, Empty
rpc :Stop, Empty, ReconnectInfo
end
Stub = Service.rpc_stub_class
end
end
end

@ -0,0 +1,196 @@
#!/usr/bin/env ruby
# 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.
# 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')
pb_dir = File.dirname(File.dirname(this_dir))
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
$LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir)
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
require 'forwardable'
require 'optparse'
require 'grpc'
require 'test/proto/empty'
require 'test/proto/messages'
require 'test/proto/test_services'
# 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
GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2])
end
# produces a string of null chars (\0) of length l.
def nulls(l)
fail 'requires #{l} to be +ve' if l < 0
[].pack('x' * l).force_encoding('utf-8')
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)
fail 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)
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 { |s, x| s + 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
Thread.new do
begin
GRPC.logger.info('interop-server: started receiving')
reqs.each do |req|
resp_size = req.response_parameters[0].size
GRPC.logger.info("read a req, response size is #{resp_size}")
resp = cls.new(payload: Payload.new(type: req.response_type,
body: nulls(resp_size)))
q.push(resp)
end
GRPC.logger.info('interop-server: finished receiving')
q.push(self)
rescue StandardError => e
GRPC.logger.info('interop-server: failed')
GRPC.logger.warn(e)
q.push(e) # share the exception with the enumerator
end
end
q.each_item
end
def half_duplex_call(reqs)
# TODO: update with unique behaviour of the half_duplex_call if that's
# ever required by any of the tests.
full_duplex_call(reqs)
end
end
# validates the the command line options, returning them as a Hash.
def parse_options
options = {
'port' => nil,
'secure' => false
}
OptionParser.new do |opts|
opts.banner = 'Usage: --port port'
opts.on('--port PORT', 'server port') do |v|
options['port'] = v
end
opts.on('-s', '--use_tls', 'require a secure connection?') do |v|
options['secure'] = v
end
end.parse!
if options['port'].nil?
fail(OptionParser::MissingArgument, 'please specify --port')
end
options
end
def main
opts = parse_options
host = "0.0.0.0:#{opts['port']}"
s = GRPC::RpcServer.new
if opts['secure']
s.add_http2_port(host, test_server_creds)
GRPC.logger.info("... running securely on #{host}")
else
s.add_http2_port(host)
GRPC.logger.info("... running insecurely on #{host}")
end
s.handle(TestTarget)
s.run_till_terminated
end
main

@ -30,6 +30,54 @@
require 'grpc'
require 'grpc/health/v1alpha/health'
require 'grpc/health/checker'
require 'open3'
def can_run_codegen_check
system('which grpc_ruby_plugin') && system('which protoc')
end
describe 'Health protobuf code generation' do
context 'the health service file used by grpc/health/checker' do
if !can_run_codegen_check
skip 'protoc || grpc_ruby_plugin missing, cannot verify health code-gen'
else
it 'should already be loaded indirectly i.e, used by the other specs' do
expect(require('grpc/health/v1alpha/health_services')).to be(false)
end
it 'should have the same content as created by code generation' do
root_dir = File.dirname(
File.dirname(File.dirname(File.dirname(__FILE__))))
pb_dir = File.join(root_dir, 'pb')
# Get the current content
service_path = File.join(pb_dir, 'grpc', 'health', 'v1alpha',
'health_services.rb')
want = nil
File.open(service_path) { |f| want = f.read }
# Regenerate it
plugin, = Open3.capture2('which', 'grpc_ruby_plugin')
plugin = plugin.strip
got = nil
Dir.mktmpdir do |tmp_dir|
gen_out = File.join(tmp_dir, 'grpc', 'health', 'v1alpha',
'health_services.rb')
pid = spawn(
'protoc',
'-I.',
'grpc/health/v1alpha/health.proto',
"--grpc_out=#{tmp_dir}",
"--plugin=protoc-gen-grpc=#{plugin}",
chdir: pb_dir)
Process.wait(pid)
File.open(gen_out) { |f| got = f.read }
end
expect(got).to eq(want)
end
end
end
end
describe Grpc::Health::Checker do
StatusCodes = GRPC::Core::StatusCodes

Loading…
Cancel
Save