diff options
Diffstat (limited to 'src/ruby/spec')
-rw-r--r-- | src/ruby/spec/channel_connection_spec.rb | 38 | ||||
-rw-r--r-- | src/ruby/spec/client_auth_spec.rb | 2 | ||||
-rw-r--r-- | src/ruby/spec/client_server_spec.rb | 4 | ||||
-rw-r--r-- | src/ruby/spec/generic/active_call_spec.rb | 21 | ||||
-rw-r--r-- | src/ruby/spec/generic/client_interceptors_spec.rb | 153 | ||||
-rw-r--r-- | src/ruby/spec/generic/client_stub_spec.rb | 8 | ||||
-rw-r--r-- | src/ruby/spec/generic/interceptor_registry_spec.rb | 65 | ||||
-rw-r--r-- | src/ruby/spec/generic/rpc_server_spec.rb | 59 | ||||
-rw-r--r-- | src/ruby/spec/generic/server_interceptors_spec.rb | 218 | ||||
-rw-r--r-- | src/ruby/spec/google_rpc_status_utils_spec.rb | 80 | ||||
-rw-r--r-- | src/ruby/spec/pb/health/checker_spec.rb | 31 | ||||
-rw-r--r-- | src/ruby/spec/server_spec.rb | 18 | ||||
-rw-r--r-- | src/ruby/spec/spec_helper.rb | 4 | ||||
-rw-r--r-- | src/ruby/spec/support/helpers.rb | 107 | ||||
-rw-r--r-- | src/ruby/spec/support/services.rb | 147 |
15 files changed, 848 insertions, 107 deletions
diff --git a/src/ruby/spec/channel_connection_spec.rb b/src/ruby/spec/channel_connection_spec.rb index c76056606b..5c31f41065 100644 --- a/src/ruby/spec/channel_connection_spec.rb +++ b/src/ruby/spec/channel_connection_spec.rb @@ -11,47 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -require 'grpc' +require 'spec_helper' require 'timeout' include Timeout include GRPC::Core - -# A test message -class EchoMsg - def self.marshal(_o) - '' - end - - def self.unmarshal(_o) - EchoMsg.new - end -end - -# A test service with an echo implementation. -class EchoService - include GRPC::GenericService - rpc :an_rpc, EchoMsg, EchoMsg - attr_reader :received_md - - def initialize(**kw) - @trailing_metadata = kw - @received_md = [] - end - - def an_rpc(req, call) - GRPC.logger.info('echo service received a request') - call.output_metadata.update(@trailing_metadata) - @received_md << call.metadata unless call.metadata.nil? - req - end -end - -EchoStub = EchoService.rpc_stub_class +include GRPC::Spec::Helpers def start_server(port = 0) - @srv = GRPC::RpcServer.new(pool_size: 1) + @srv = new_rpc_server_for_testing(pool_size: 1) server_port = @srv.add_http2_port("localhost:#{port}", :this_port_is_insecure) @srv.handle(EchoService) @server_thd = Thread.new { @srv.run } diff --git a/src/ruby/spec/client_auth_spec.rb b/src/ruby/spec/client_auth_spec.rb index 79c9192aa5..b955ad231e 100644 --- a/src/ruby/spec/client_auth_spec.rb +++ b/src/ruby/spec/client_auth_spec.rb @@ -95,7 +95,7 @@ describe 'client-server auth' do server_opts = { poll_period: 1 } - @srv = RpcServer.new(**server_opts) + @srv = new_rpc_server_for_testing(**server_opts) port = @srv.add_http2_port('0.0.0.0:0', create_server_creds) @srv.handle(SslTestService) @srv_thd = Thread.new { @srv.run } diff --git a/src/ruby/spec/client_server_spec.rb b/src/ruby/spec/client_server_spec.rb index adab8c9d14..14ad369ac8 100644 --- a/src/ruby/spec/client_server_spec.rb +++ b/src/ruby/spec/client_server_spec.rb @@ -542,7 +542,7 @@ end describe 'the http client/server' do before(:example) do server_host = '0.0.0.0:0' - @server = GRPC::Core::Server.new(nil) + @server = new_core_server_for_testing(nil) server_port = @server.add_http2_port(server_host, :this_port_is_insecure) @server.start @ch = Channel.new("0.0.0.0:#{server_port}", nil, :this_channel_is_insecure) @@ -574,7 +574,7 @@ describe 'the secure http client/server' do server_host = '0.0.0.0:0' server_creds = GRPC::Core::ServerCredentials.new( nil, [{ private_key: certs[1], cert_chain: certs[2] }], false) - @server = GRPC::Core::Server.new(nil) + @server = new_core_server_for_testing(nil) server_port = @server.add_http2_port(server_host, server_creds) @server.start args = { Channel::SSL_TARGET => 'foo.test.google.fr' } diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb index a00df9236d..135d1f28bf 100644 --- a/src/ruby/spec/generic/active_call_spec.rb +++ b/src/ruby/spec/generic/active_call_spec.rb @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'grpc' +require 'spec_helper' include GRPC::Core::StatusCodes @@ -40,7 +40,7 @@ describe GRPC::ActiveCall do before(:each) do @pass_through = proc { |x| x } host = '0.0.0.0:0' - @server = GRPC::Core::Server.new(nil) + @server = new_core_server_for_testing(nil) server_port = @server.add_http2_port(host, :this_port_is_insecure) @server.start @ch = GRPC::Core::Channel.new("0.0.0.0:#{server_port}", nil, @@ -82,6 +82,16 @@ describe GRPC::ActiveCall do end end end + + describe '#interceptable' do + it 'exposes a fixed subset of the ActiveCall.methods' do + want = %w(deadline) + v = @client_call.interceptable + want.each do |w| + expect(v.methods.include?(w)) + end + end + end end describe '#remote_send' do @@ -609,9 +619,11 @@ describe GRPC::ActiveCall do msgs end + int_ctx = GRPC::InterceptionContext.new + @server_thread = Thread.new do @server_call.run_server_bidi( - fake_gen_each_reply_with_no_call_param) + fake_gen_each_reply_with_no_call_param, int_ctx) @server_call.send_status(@server_status) end end @@ -624,10 +636,11 @@ describe GRPC::ActiveCall do call_param.send_initial_metadata msgs end + int_ctx = GRPC::InterceptionContext.new @server_thread = Thread.new do @server_call.run_server_bidi( - fake_gen_each_reply_with_call_param) + fake_gen_each_reply_with_call_param, int_ctx) @server_call.send_status(@server_status) end end diff --git a/src/ruby/spec/generic/client_interceptors_spec.rb b/src/ruby/spec/generic/client_interceptors_spec.rb new file mode 100644 index 0000000000..f292715e4d --- /dev/null +++ b/src/ruby/spec/generic/client_interceptors_spec.rb @@ -0,0 +1,153 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +require 'spec_helper' + +describe 'Client Interceptors' do + let(:interceptor) { TestClientInterceptor.new } + let(:interceptors_opts) { { interceptors: [interceptor] } } + let(:request) { EchoMsg.new } + let(:service) { EchoService } + + before(:each) do + build_rpc_server + end + + context 'when a client interceptor is added' do + context 'with a request/response call' do + it 'should be called', server: true do + expect(interceptor).to receive(:request_response) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + expect_any_instance_of(GRPC::ActiveCall).to receive(:request_response) + .once.and_call_original + expect(stub.an_rpc(request)).to be_a(EchoMsg) + end + end + + it 'can modify outgoing metadata', server: true do + expect(interceptor).to receive(:request_response) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + expect_any_instance_of(GRPC::ActiveCall).to receive(:request_response) + .with(request, metadata: { 'foo' => 'bar_from_request_response' }) + .once.and_call_original + expect(stub.an_rpc(request)).to be_a(EchoMsg) + end + end + end + + context 'with a client streaming call' do + it 'should be called', server: true do + expect(interceptor).to receive(:client_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + expect_any_instance_of(GRPC::ActiveCall).to receive(:client_streamer) + .once.and_call_original + requests = [EchoMsg.new, EchoMsg.new] + expect(stub.a_client_streaming_rpc(requests)).to be_a(EchoMsg) + end + end + + it 'can modify outgoing metadata', server: true do + expect(interceptor).to receive(:client_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + requests = [EchoMsg.new, EchoMsg.new] + expect_any_instance_of(GRPC::ActiveCall).to receive(:client_streamer) + .with(requests, metadata: { 'foo' => 'bar_from_client_streamer' }) + .once.and_call_original + expect(stub.a_client_streaming_rpc(requests)).to be_a(EchoMsg) + end + end + end + + context 'with a server streaming call' do + it 'should be called', server: true do + expect(interceptor).to receive(:server_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + request = EchoMsg.new + expect_any_instance_of(GRPC::ActiveCall).to receive(:server_streamer) + .once.and_call_original + responses = stub.a_server_streaming_rpc(request) + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + end + end + + it 'can modify outgoing metadata', server: true do + expect(interceptor).to receive(:server_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + request = EchoMsg.new + expect_any_instance_of(GRPC::ActiveCall).to receive(:server_streamer) + .with(request, metadata: { 'foo' => 'bar_from_server_streamer' }) + .once.and_call_original + responses = stub.a_server_streaming_rpc(request) + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + end + end + end + + context 'with a bidi call' do + it 'should be called', server: true do + expect(interceptor).to receive(:bidi_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + expect_any_instance_of(GRPC::ActiveCall).to receive(:bidi_streamer) + .once.and_call_original + requests = [EchoMsg.new, EchoMsg.new] + responses = stub.a_bidi_rpc(requests) + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + end + end + + it 'can modify outgoing metadata', server: true do + expect(interceptor).to receive(:bidi_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub, opts: interceptors_opts) + requests = [EchoMsg.new, EchoMsg.new] + expect_any_instance_of(GRPC::ActiveCall).to receive(:bidi_streamer) + .with(requests, metadata: { 'foo' => 'bar_from_bidi_streamer' }) + .once.and_call_original + responses = stub.a_bidi_rpc(requests) + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + end + end + end + end +end diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb index 9539e56c0f..5353b534f4 100644 --- a/src/ruby/spec/generic/client_stub_spec.rb +++ b/src/ruby/spec/generic/client_stub_spec.rb @@ -228,7 +228,7 @@ describe 'ClientStub' do th.join end - it 'should receive UNAUTHENTICATED if call credentials plugin fails' do + it 'should receive UNAVAILABLE if call credentials plugin fails' do server_port = create_secure_test_server th = run_request_response(@sent_msg, @resp, @pass) @@ -252,7 +252,7 @@ describe 'ClientStub' do unauth_error_occured = false begin get_response(stub, credentials: creds) - rescue GRPC::Unauthenticated => e + rescue GRPC::Unavailable => e unauth_error_occured = true expect(e.details.include?(error_message)).to be true end @@ -888,12 +888,12 @@ describe 'ClientStub' do secure_credentials = GRPC::Core::ServerCredentials.new( nil, [{ private_key: certs[1], cert_chain: certs[2] }], false) - @server = GRPC::Core::Server.new(nil) + @server = new_core_server_for_testing(nil) @server.add_http2_port('0.0.0.0:0', secure_credentials) end def create_test_server - @server = GRPC::Core::Server.new(nil) + @server = new_core_server_for_testing(nil) @server.add_http2_port('0.0.0.0:0', :this_port_is_insecure) end diff --git a/src/ruby/spec/generic/interceptor_registry_spec.rb b/src/ruby/spec/generic/interceptor_registry_spec.rb new file mode 100644 index 0000000000..eb75d1e0b2 --- /dev/null +++ b/src/ruby/spec/generic/interceptor_registry_spec.rb @@ -0,0 +1,65 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +require 'spec_helper' + +describe GRPC::InterceptorRegistry do + let(:server) { new_rpc_server_for_testing } + let(:interceptor) { TestServerInterceptor.new } + let(:interceptors) { [interceptor] } + let(:registry) { described_class.new(interceptors) } + + describe 'initialization' do + subject { registry } + + context 'with an interceptor extending GRPC::ServerInterceptor' do + it 'should add the interceptor to the registry' do + subject + is = registry.instance_variable_get('@interceptors') + expect(is.count).to eq 1 + expect(is.first).to eq interceptor + end + end + + context 'with multiple interceptors' do + let(:interceptor2) { TestServerInterceptor.new } + let(:interceptor3) { TestServerInterceptor.new } + let(:interceptors) { [interceptor, interceptor2, interceptor3] } + + it 'should maintain order of insertion when iterated against' do + subject + is = registry.instance_variable_get('@interceptors') + expect(is.count).to eq 3 + is.each_with_index do |i, idx| + case idx + when 0 + expect(i).to eq interceptor + when 1 + expect(i).to eq interceptor2 + when 2 + expect(i).to eq interceptor3 + end + end + end + end + + context 'with an interceptor not extending GRPC::ServerInterceptor' do + let(:interceptor) { Class } + let(:err) { GRPC::InterceptorRegistry::DescendantError } + + it 'should raise an InvalidArgument exception' do + expect { subject }.to raise_error(err) + end + end + end +end diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb index b887eaaf4e..e072d0c45f 100644 --- a/src/ruby/spec/generic/rpc_server_spec.rb +++ b/src/ruby/spec/generic/rpc_server_spec.rb @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -require 'grpc' +require 'spec_helper' def load_test_certs test_root = File.join(File.dirname(File.dirname(__FILE__)), 'testdata') @@ -28,17 +27,6 @@ def check_md(wanted_md, received_md) end end -# A test message -class EchoMsg - def self.marshal(_o) - '' - end - - def self.unmarshal(_o) - EchoMsg.new - end -end - # A test service with no methods. class EmptyService include GRPC::GenericService @@ -50,27 +38,6 @@ class NoRpcImplementation rpc :an_rpc, EchoMsg, EchoMsg end -# A test service with an echo implementation. -class EchoService - include GRPC::GenericService - rpc :an_rpc, EchoMsg, EchoMsg - attr_reader :received_md - - def initialize(**kw) - @trailing_metadata = kw - @received_md = [] - end - - def an_rpc(req, call) - GRPC.logger.info('echo service received a request') - call.output_metadata.update(@trailing_metadata) - @received_md << call.metadata unless call.metadata.nil? - req - end -end - -EchoStub = EchoService.rpc_stub_class - # A test service with an implementation that fails with BadStatus class FailingService include GRPC::GenericService @@ -205,7 +172,7 @@ describe GRPC::RpcServer do it 'can be created with just some args' do opts = { server_args: { a_channel_arg: 'an_arg' } } blk = proc do - RpcServer.new(**opts) + new_rpc_server_for_testing(**opts) end expect(&blk).not_to raise_error end @@ -216,7 +183,7 @@ describe GRPC::RpcServer do server_args: { a_channel_arg: 'an_arg' }, creds: Object.new } - RpcServer.new(**opts) + new_rpc_server_for_testing(**opts) end expect(&blk).to raise_error end @@ -225,7 +192,7 @@ describe GRPC::RpcServer do describe '#stopped?' do before(:each) do opts = { server_args: { a_channel_arg: 'an_arg' }, poll_period: 1.5 } - @srv = RpcServer.new(**opts) + @srv = new_rpc_server_for_testing(**opts) @srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure) end @@ -257,7 +224,7 @@ describe GRPC::RpcServer do opts = { server_args: { a_channel_arg: 'an_arg' } } - r = RpcServer.new(**opts) + r = new_rpc_server_for_testing(**opts) expect(r.running?).to be(false) end @@ -266,7 +233,7 @@ describe GRPC::RpcServer do server_args: { a_channel_arg: 'an_arg' }, poll_period: 2 } - r = RpcServer.new(**opts) + r = new_rpc_server_for_testing(**opts) r.add_http2_port('0.0.0.0:0', :this_port_is_insecure) expect { r.run }.to raise_error(RuntimeError) end @@ -276,7 +243,7 @@ describe GRPC::RpcServer do server_args: { a_channel_arg: 'an_arg' }, poll_period: 2.5 } - r = RpcServer.new(**opts) + r = new_rpc_server_for_testing(**opts) r.add_http2_port('0.0.0.0:0', :this_port_is_insecure) r.handle(EchoService) t = Thread.new { r.run } @@ -290,7 +257,7 @@ describe GRPC::RpcServer do describe '#handle' do before(:each) do @opts = { server_args: { a_channel_arg: 'an_arg' }, poll_period: 1 } - @srv = RpcServer.new(**@opts) + @srv = new_rpc_server_for_testing(**@opts) @srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure) end @@ -336,7 +303,7 @@ describe GRPC::RpcServer do server_opts = { poll_period: 1 } - @srv = RpcServer.new(**server_opts) + @srv = new_rpc_server_for_testing(**server_opts) server_port = @srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure) @host = "localhost:#{server_port}" @ch = GRPC::Core::Channel.new(@host, nil, :this_channel_is_insecure) @@ -507,7 +474,7 @@ describe GRPC::RpcServer do poll_period: 1, max_waiting_requests: 1 } - alt_srv = RpcServer.new(**opts) + alt_srv = new_rpc_server_for_testing(**opts) alt_srv.handle(SlowService) alt_port = alt_srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure) alt_host = "0.0.0.0:#{alt_port}" @@ -571,7 +538,7 @@ describe GRPC::RpcServer do poll_period: 1, connect_md_proc: test_md_proc } - @srv = RpcServer.new(**server_opts) + @srv = new_rpc_server_for_testing(**server_opts) alt_port = @srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure) @alt_host = "0.0.0.0:#{alt_port}" end @@ -606,7 +573,7 @@ describe GRPC::RpcServer do server_opts = { poll_period: 1 } - @srv = RpcServer.new(**server_opts) + @srv = new_rpc_server_for_testing(**server_opts) alt_port = @srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure) @alt_host = "0.0.0.0:#{alt_port}" end @@ -657,7 +624,7 @@ describe GRPC::RpcServer do server_opts = { poll_period: 1 } - @srv = RpcServer.new(**server_opts) + @srv = new_rpc_server_for_testing(**server_opts) alt_port = @srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure) @alt_host = "0.0.0.0:#{alt_port}" diff --git a/src/ruby/spec/generic/server_interceptors_spec.rb b/src/ruby/spec/generic/server_interceptors_spec.rb new file mode 100644 index 0000000000..eb86686084 --- /dev/null +++ b/src/ruby/spec/generic/server_interceptors_spec.rb @@ -0,0 +1,218 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +require 'spec_helper' + +describe 'Server Interceptors' do + let(:interceptor) { TestServerInterceptor.new } + let(:request) { EchoMsg.new } + let(:trailing_metadata) { {} } + let(:service) { EchoService.new(trailing_metadata) } + let(:interceptors) { [] } + + before(:each) do + build_rpc_server(server_opts: { interceptors: interceptors }) + end + + context 'when a server interceptor is added' do + let(:interceptors) { [interceptor] } + let(:client_metadata) { { client_md: 'test' } } + let(:client_call_opts) { { metadata: client_metadata, return_op: true } } + + context 'with a request/response call' do + let(:trailing_metadata) { { server_om: 'from_request_response' } } + + it 'should be called', server: true do + expect(interceptor).to receive(:request_response) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect(stub.an_rpc(request)).to be_a(EchoMsg) + end + end + + it 'can modify trailing metadata', server: true do + expect(interceptor).to receive(:request_response) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect_any_instance_of(GRPC::ActiveCall).to( + receive(:request_response).with(request, metadata: client_metadata) + .once.and_call_original + ) + op = stub.an_rpc(request, client_call_opts) + msg = op.execute + expect(op.trailing_metadata).to eq( + 'interc' => 'from_request_response', + 'server_om' => 'from_request_response' + ) + expect(msg).to be_a(EchoMsg) + end + end + end + + context 'with a client streaming call' do + let(:trailing_metadata) { { server_om: 'from_client_streamer' } } + let(:requests) { [EchoMsg.new, EchoMsg.new] } + + it 'should be called', server: true do + expect(interceptor).to receive(:client_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect(stub.a_client_streaming_rpc(requests)).to be_a(EchoMsg) + end + end + + it 'can modify trailing metadata', server: true do + expect(interceptor).to receive(:client_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect_any_instance_of(GRPC::ActiveCall).to( + receive(:client_streamer).with(requests) + .once.and_call_original + ) + op = stub.a_client_streaming_rpc(requests, client_call_opts) + msg = op.execute + expect(op.trailing_metadata).to eq( + 'interc' => 'from_client_streamer', + 'server_om' => 'from_client_streamer' + ) + expect(msg).to be_a(EchoMsg) + end + end + end + + context 'with a server streaming call' do + let(:trailing_metadata) { { server_om: 'from_server_streamer' } } + let(:request) { EchoMsg.new } + + it 'should be called', server: true do + expect(interceptor).to receive(:server_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + responses = stub.a_server_streaming_rpc(request) + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + end + end + + it 'can modify trailing metadata', server: true do + expect(interceptor).to receive(:server_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect_any_instance_of(GRPC::ActiveCall).to( + receive(:server_streamer).with(request) + .once.and_call_original + ) + op = stub.a_server_streaming_rpc(request, client_call_opts) + responses = op.execute + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + expect(op.trailing_metadata).to eq( + 'interc' => 'from_server_streamer', + 'server_om' => 'from_server_streamer' + ) + end + end + end + + context 'with a bidi call' do + let(:trailing_metadata) { { server_om: 'from_bidi_streamer' } } + let(:requests) { [EchoMsg.new, EchoMsg.new] } + + it 'should be called', server: true do + expect(interceptor).to receive(:bidi_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + responses = stub.a_bidi_rpc(requests) + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + end + end + + it 'can modify trailing metadata', server: true do + expect(interceptor).to receive(:bidi_streamer) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect_any_instance_of(GRPC::ActiveCall).to( + receive(:bidi_streamer).with(requests) + .once.and_call_original + ) + op = stub.a_bidi_rpc(requests, client_call_opts) + responses = op.execute + responses.each do |r| + expect(r).to be_a(EchoMsg) + end + expect(op.trailing_metadata).to eq( + 'interc' => 'from_bidi_streamer', + 'server_om' => 'from_bidi_streamer' + ) + end + end + end + end + + context 'when multiple interceptors are added' do + let(:interceptor2) { TestServerInterceptor.new } + let(:interceptor3) { TestServerInterceptor.new } + let(:interceptors) do + [ + interceptor, + interceptor2, + interceptor3 + ] + end + + it 'each should be called', server: true do + expect(interceptor).to receive(:request_response) + .once.and_call_original + expect(interceptor2).to receive(:request_response) + .once.and_call_original + expect(interceptor3).to receive(:request_response) + .once.and_call_original + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect(stub.an_rpc(request)).to be_a(EchoMsg) + end + end + end + + context 'when an interceptor is not added' do + it 'should not be called', server: true do + expect(interceptor).to_not receive(:call) + + run_services_on_server(@server, services: [service]) do + stub = build_insecure_stub(EchoStub) + expect(stub.an_rpc(request)).to be_a(EchoMsg) + end + end + end +end diff --git a/src/ruby/spec/google_rpc_status_utils_spec.rb b/src/ruby/spec/google_rpc_status_utils_spec.rb index fe221c30dd..3263589b6a 100644 --- a/src/ruby/spec/google_rpc_status_utils_spec.rb +++ b/src/ruby/spec/google_rpc_status_utils_spec.rb @@ -19,6 +19,7 @@ require_relative '../pb/src/proto/grpc/testing/messages_pb' require 'google/protobuf/well_known_types' include GRPC::Core +include GRPC::Spec::Helpers describe 'conversion from a status struct to a google protobuf status' do it 'fails if the input is not a status struct' do @@ -31,12 +32,11 @@ describe 'conversion from a status struct to a google protobuf status' do expect(exception.message.include?('bad type')).to be true end - it 'fails with some error if the header key is missing' do + it 'returns nil if the header key is missing' do status = Struct::Status.new(1, 'details', key: 'val') expect(status.metadata.nil?).to be false - expect do - GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status) - end.to raise_error(StandardError) + expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( + status)).to be(nil) end it 'fails with some error if the header key fails to deserialize' do @@ -151,7 +151,7 @@ GoogleRpcStatusTestStub = GoogleRpcStatusTestService.rpc_stub_class describe 'receving a google rpc status from a remote endpoint' do def start_server(encoded_rpc_status) - @srv = GRPC::RpcServer.new(pool_size: 1) + @srv = new_rpc_server_for_testing(pool_size: 1) @server_port = @srv.add_http2_port('localhost:0', :this_port_is_insecure) @srv.handle(GoogleRpcStatusTestService.new(encoded_rpc_status)) @@ -221,3 +221,73 @@ describe 'receving a google rpc status from a remote endpoint' do status_from_exception)).to eq(rpc_status) end end + +# A test service that fails without explicitly setting the +# grpc-status-details-bin trailer. Tests assumptions about value +# of grpc-status-details-bin on the client side when the trailer wasn't +# set explicitly. +class NoStatusDetailsBinTestService + include GRPC::GenericService + rpc :an_rpc, EchoMsg, EchoMsg + + def an_rpc(_, _) + fail GRPC::Unknown + end +end + +NoStatusDetailsBinTestServiceStub = NoStatusDetailsBinTestService.rpc_stub_class + +describe 'when the endpoint doesnt send grpc-status-details-bin' do + def start_server + @srv = new_rpc_server_for_testing(pool_size: 1) + @server_port = @srv.add_http2_port('localhost:0', + :this_port_is_insecure) + @srv.handle(NoStatusDetailsBinTestService) + @server_thd = Thread.new { @srv.run } + @srv.wait_till_running + end + + def stop_server + expect(@srv.stopped?).to be(false) + @srv.stop + @server_thd.join + expect(@srv.stopped?).to be(true) + end + + before(:each) do + start_server + end + + after(:each) do + stop_server + end + + it 'should receive nil when we extract try to extract a google '\ + 'rpc status from a BadStatus exception that didnt have it' do + stub = NoStatusDetailsBinTestServiceStub.new("localhost:#{@server_port}", + :this_channel_is_insecure) + begin + stub.an_rpc(EchoMsg.new) + rescue GRPC::Unknown => e + rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( + e.to_status) + end + expect(rpc_status).to be(nil) + end + + it 'should receive nil when we extract try to extract a google '\ + 'rpc status from an op views status object that didnt have it' do + stub = NoStatusDetailsBinTestServiceStub.new("localhost:#{@server_port}", + :this_channel_is_insecure) + op = stub.an_rpc(EchoMsg.new, return_op: true) + begin + op.execute + rescue GRPC::Unknown => e + status_from_exception = e.to_status + end + expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( + status_from_exception)).to be(nil) + expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status( + op.status)).to be nil + end +end diff --git a/src/ruby/spec/pb/health/checker_spec.rb b/src/ruby/spec/pb/health/checker_spec.rb index 6c9e206c3f..58a602327c 100644 --- a/src/ruby/spec/pb/health/checker_spec.rb +++ b/src/ruby/spec/pb/health/checker_spec.rb @@ -99,6 +99,35 @@ describe Grpc::Health::Checker do end end + context 'method `add_statuses`' do + it 'should add status to each service' do + checker = Grpc::Health::Checker.new + checker.add_statuses( + 'service1' => ServingStatus::SERVING, + 'service2' => ServingStatus::NOT_SERVING + ) + service1_health = checker.check(HCReq.new(service: 'service1'), nil) + service2_health = checker.check(HCReq.new(service: 'service2'), nil) + expect(service1_health).to eq(HCResp.new(status: ServingStatus::SERVING)) + expect(service2_health).to eq(HCResp.new(status: ServingStatus::NOT_SERVING)) + end + end + + context 'method `set_status_for_services`' do + it 'should add given status to all given services' do + checker = Grpc::Health::Checker.new + checker.set_status_for_services( + ServingStatus::SERVING, + 'service1', + 'service2' + ) + service1_health = checker.check(HCReq.new(service: 'service1'), nil) + service2_health = checker.check(HCReq.new(service: 'service2'), nil) + expect(service1_health).to eq(HCResp.new(status: ServingStatus::SERVING)) + expect(service2_health).to eq(HCResp.new(status: ServingStatus::SERVING)) + end + end + context 'method `check`' do success_tests.each do |t| it "should fail with NOT_FOUND when #{t[:desc]}" do @@ -163,7 +192,7 @@ describe Grpc::Health::Checker do server_opts = { poll_period: 1 } - @srv = RpcServer.new(**server_opts) + @srv = new_rpc_server_for_testing(**server_opts) server_port = @srv.add_http2_port(server_host, :this_port_is_insecure) @host = "localhost:#{server_port}" @ch = GRPC::Core::Channel.new(@host, nil, :this_channel_is_insecure) diff --git a/src/ruby/spec/server_spec.rb b/src/ruby/spec/server_spec.rb index c0a59572b1..a0d27b66f5 100644 --- a/src/ruby/spec/server_spec.rb +++ b/src/ruby/spec/server_spec.rb @@ -30,12 +30,12 @@ describe Server do describe '#start' do it 'runs without failing' do - blk = proc { Server.new(nil).start } + blk = proc { new_core_server_for_testing(nil).start } expect(&blk).to_not raise_error end it 'fails if the server is closed' do - s = Server.new(nil) + s = new_core_server_for_testing(nil) s.close expect { s.start }.to raise_error(RuntimeError) end @@ -85,7 +85,7 @@ describe Server do describe 'for insecure servers' do it 'runs without failing' do blk = proc do - s = Server.new(nil) + s = new_core_server_for_testing(nil) s.add_http2_port('localhost:0', :this_port_is_insecure) s.close end @@ -93,7 +93,7 @@ describe Server do end it 'fails if the server is closed' do - s = Server.new(nil) + s = new_core_server_for_testing(nil) s.close blk = proc do s.add_http2_port('localhost:0', :this_port_is_insecure) @@ -106,7 +106,7 @@ describe Server do let(:cert) { create_test_cert } it 'runs without failing' do blk = proc do - s = Server.new(nil) + s = new_core_server_for_testing(nil) s.add_http2_port('localhost:0', cert) s.close end @@ -114,7 +114,7 @@ describe Server do end it 'fails if the server is closed' do - s = Server.new(nil) + s = new_core_server_for_testing(nil) s.close blk = proc { s.add_http2_port('localhost:0', cert) } expect(&blk).to raise_error(RuntimeError) @@ -124,7 +124,7 @@ describe Server do shared_examples '#new' do it 'takes nil channel args' do - expect { Server.new(nil) }.to_not raise_error + expect { new_core_server_for_testing(nil) }.to_not raise_error end it 'does not take a hash with bad keys as channel args' do @@ -175,14 +175,14 @@ describe Server do describe '#new with an insecure channel' do def construct_with_args(a) - proc { Server.new(a) } + proc { new_core_server_for_testing(a) } end it_behaves_like '#new' end def start_a_server - s = Server.new(nil) + s = new_core_server_for_testing(nil) s.add_http2_port('0.0.0.0:0', :this_port_is_insecure) s.start s diff --git a/src/ruby/spec/spec_helper.rb b/src/ruby/spec/spec_helper.rb index 6e1eba1945..8fe9e6e808 100644 --- a/src/ruby/spec/spec_helper.rb +++ b/src/ruby/spec/spec_helper.rb @@ -32,6 +32,9 @@ require 'rspec' require 'logging' require 'rspec/logging_helper' +require_relative 'support/services' +require_relative 'support/helpers' + # GRPC is the general RPC module # # Configure its logging for fine-grained log control during test runs @@ -49,6 +52,7 @@ Logging.logger['GRPC::BidiCall'].level = :info RSpec.configure do |config| include RSpec::LoggingHelper config.capture_log_messages # comment this out to see logs during test runs + include GRPC::Spec::Helpers end RSpec::Expectations.configuration.warn_about_potential_false_positives = false diff --git a/src/ruby/spec/support/helpers.rb b/src/ruby/spec/support/helpers.rb new file mode 100644 index 0000000000..29028df8b7 --- /dev/null +++ b/src/ruby/spec/support/helpers.rb @@ -0,0 +1,107 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# GRPC contains the General RPC module. +module GRPC + ## + # GRPC RSpec base module + # + module Spec + ## + # A module that is used for providing generic helpers across the + # GRPC test suite + # + module Helpers + # Shortcut syntax for a GRPC RPC Server + RpcServer = GRPC::RpcServer + + ## + # Build an RPC server used for testing + # + def build_rpc_server(server_opts: {}, + client_opts: {}) + @server = new_rpc_server_for_testing({ poll_period: 1 }.merge(server_opts)) + @port = @server.add_http2_port('0.0.0.0:0', :this_port_is_insecure) + @host = "0.0.0.0:#{@port}" + @client_opts = client_opts + @server + end + + ## + # Run services on an RPC server, yielding to allow testing within + # + # @param [RpcServer] server + # @param [Array<Class>] services + # + def run_services_on_server(server, services: []) + services.each do |s| + server.handle(s) + end + t = Thread.new { server.run } + server.wait_till_running + + yield + + server.stop + t.join + end + + ## + # Build an insecure stub from a given stub class + # + # @param [Class] klass + # @param [String] host + # + def build_insecure_stub(klass, host: nil, opts: nil) + host ||= @host + opts ||= @client_opts + klass.new(host, :this_channel_is_insecure, **opts) + end + + ## + # Build an RPCServer for use in tests. Adds args + # that are useful for all tests. + # + # @param [Hash] server_opts + # + def new_rpc_server_for_testing(server_opts = {}) + server_opts[:server_args] ||= {} + update_server_args_hash(server_opts[:server_args]) + RpcServer.new(**server_opts) + end + + ## + # Build an GRPC::Core::Server for use in tests. Adds args + # that are useful for all tests. + # + # @param [Hash] server_args + # + def new_core_server_for_testing(server_args) + server_args.nil? && server_args = {} + update_server_args_hash(server_args) + GRPC::Core::Server.new(server_args) + end + + def update_server_args_hash(server_args) + so_reuseport_arg = 'grpc.so_reuseport' + unless server_args[so_reuseport_arg].nil? + fail 'Unexpected. grpc.so_reuseport already set.' + end + # Run tests without so_reuseport to eliminate the chance of + # cross-talk. + server_args[so_reuseport_arg] = 0 + end + end + end +end diff --git a/src/ruby/spec/support/services.rb b/src/ruby/spec/support/services.rb new file mode 100644 index 0000000000..27cc8e61ac --- /dev/null +++ b/src/ruby/spec/support/services.rb @@ -0,0 +1,147 @@ +# Copyright 2017 gRPC authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test stubs for various scenarios +require 'grpc' + +# A test message +class EchoMsg + def self.marshal(_o) + '' + end + + def self.unmarshal(_o) + EchoMsg.new + end +end + +# A test service with an echo implementation. +class EchoService + include GRPC::GenericService + rpc :an_rpc, EchoMsg, EchoMsg + rpc :a_client_streaming_rpc, stream(EchoMsg), EchoMsg + rpc :a_server_streaming_rpc, EchoMsg, stream(EchoMsg) + rpc :a_bidi_rpc, stream(EchoMsg), stream(EchoMsg) + attr_reader :received_md + + def initialize(**kw) + @trailing_metadata = kw + @received_md = [] + end + + def an_rpc(req, call) + GRPC.logger.info('echo service received a request') + call.output_metadata.update(@trailing_metadata) + @received_md << call.metadata unless call.metadata.nil? + req + end + + def a_client_streaming_rpc(call) + # iterate through requests so call can complete + call.output_metadata.update(@trailing_metadata) + call.each_remote_read.each { |r| p r } + EchoMsg.new + end + + def a_server_streaming_rpc(_req, call) + call.output_metadata.update(@trailing_metadata) + [EchoMsg.new, EchoMsg.new] + end + + def a_bidi_rpc(requests, call) + call.output_metadata.update(@trailing_metadata) + requests.each { |r| p r } + [EchoMsg.new, EchoMsg.new] + end +end + +EchoStub = EchoService.rpc_stub_class + +# For testing server interceptors +class TestServerInterceptor < GRPC::ServerInterceptor + def request_response(request:, call:, method:) + p "Received request/response call at method #{method}" \ + " with request #{request} for call #{call}" + call.output_metadata[:interc] = 'from_request_response' + p "[GRPC::Ok] (#{method.owner.name}.#{method.name})" + yield + end + + def client_streamer(call:, method:) + call.output_metadata[:interc] = 'from_client_streamer' + call.each_remote_read.each do |r| + p "In interceptor: #{r}" + end + p "Received client streamer call at method #{method} for call #{call}" + yield + end + + def server_streamer(request:, call:, method:) + p "Received server streamer call at method #{method} with request" \ + " #{request} for call #{call}" + call.output_metadata[:interc] = 'from_server_streamer' + yield + end + + def bidi_streamer(requests:, call:, method:) + requests.each do |r| + p "Bidi request: #{r}" + end + p "Received bidi streamer call at method #{method} with requests" \ + " #{requests} for call #{call}" + call.output_metadata[:interc] = 'from_bidi_streamer' + yield + end +end + +# For testing client interceptors +class TestClientInterceptor < GRPC::ClientInterceptor + def request_response(request:, call:, method:, metadata: {}) + p "Intercepted request/response call at method #{method}" \ + " with request #{request} for call #{call}" \ + " and metadata: #{metadata}" + metadata['foo'] = 'bar_from_request_response' + yield + end + + def client_streamer(requests:, call:, method:, metadata: {}) + p "Received client streamer call at method #{method}" \ + " with requests #{requests} for call #{call}" \ + " and metadata: #{metadata}" + requests.each do |r| + p "In client interceptor: #{r}" + end + metadata['foo'] = 'bar_from_client_streamer' + yield + end + + def server_streamer(request:, call:, method:, metadata: {}) + p "Received server streamer call at method #{method}" \ + " with request #{request} for call #{call}" \ + " and metadata: #{metadata}" + metadata['foo'] = 'bar_from_server_streamer' + yield + end + + def bidi_streamer(requests:, call:, method:, metadata: {}) + p "Received bidi streamer call at method #{method}" \ + "with requests #{requests} for call #{call}" \ + " and metadata: #{metadata}" + requests.each do |r| + p "In client interceptor: #{r}" + end + metadata['foo'] = 'bar_from_bidi_streamer' + yield + end +end |