# 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. require 'grpc' require 'grpc/generic/rpc_desc' describe GRPC::RpcDesc do RpcDesc = GRPC::RpcDesc Stream = RpcDesc::Stream OK = GRPC::Core::StatusCodes::OK INTERNAL = GRPC::Core::StatusCodes::INTERNAL UNKNOWN = GRPC::Core::StatusCodes::UNKNOWN CallError = GRPC::Core::CallError before(:each) do @request_response = RpcDesc.new('rr', Object.new, Object.new, 'encode', 'decode') @client_streamer = RpcDesc.new('cs', Stream.new(Object.new), Object.new, 'encode', 'decode') @server_streamer = RpcDesc.new('ss', Object.new, Stream.new(Object.new), 'encode', 'decode') @bidi_streamer = RpcDesc.new('ss', Stream.new(Object.new), Stream.new(Object.new), 'encode', 'decode') @bs_code = INTERNAL @ok_response = Object.new end shared_examples 'it handles errors' do it 'sends the specified status if BadStatus is raised' do expect(@call).to receive(:remote_read).once.and_return(Object.new) expect(@call).to receive(:send_status).once.with(@bs_code, 'NOK', false, metadata: {}) this_desc.run_server_method(@call, method(:bad_status)) end it 'sends status UNKNOWN if other StandardErrors are raised' do expect(@call).to receive(:remote_read).once.and_return(Object.new) expect(@call).to receive(:send_status).once.with(UNKNOWN, arg_error_msg, false, metadata: {}) this_desc.run_server_method(@call, method(:other_error)) end it 'absorbs CallError with no further action' do expect(@call).to receive(:remote_read).once.and_raise(CallError) blk = proc do this_desc.run_server_method(@call, method(:fake_reqresp)) end expect(&blk).to_not raise_error end end describe '#run_server_method' do let(:fake_md) { { k1: 'v1', k2: 'v2' } } describe 'for request responses' do let(:this_desc) { @request_response } before(:each) do @call = double('active_call') allow(@call).to receive(:single_req_view).and_return(@call) allow(@call).to receive(:output_metadata).and_return(@call) end it_behaves_like 'it handles errors' it 'sends a response and closes the stream if there no errors' do req = Object.new expect(@call).to receive(:remote_read).once.and_return(req) expect(@call).to receive(:output_metadata).once.and_return(fake_md) expect(@call).to receive(:server_unary_response).once .with(@ok_response, trailing_metadata: fake_md) this_desc.run_server_method(@call, method(:fake_reqresp)) end end describe 'for client streamers' do before(:each) do @call = double('active_call') allow(@call).to receive(:multi_req_view).and_return(@call) end it 'sends the specified status if BadStatus is raised' do expect(@call).to receive(:send_status).once.with(@bs_code, 'NOK', false, metadata: {}) @client_streamer.run_server_method(@call, method(:bad_status_alt)) end it 'sends status UNKNOWN if other StandardErrors are raised' do expect(@call).to receive(:send_status).once.with(UNKNOWN, arg_error_msg, false, metadata: {}) @client_streamer.run_server_method(@call, method(:other_error_alt)) end it 'absorbs CallError with no further action' do expect(@call).to receive(:server_unary_response).once.and_raise( CallError) allow(@call).to receive(:output_metadata).and_return({}) blk = proc do @client_streamer.run_server_method(@call, method(:fake_clstream)) end expect(&blk).to_not raise_error end it 'sends a response and closes the stream if there no errors' do expect(@call).to receive(:output_metadata).and_return( fake_md) expect(@call).to receive(:server_unary_response).once .with(@ok_response, trailing_metadata: fake_md) @client_streamer.run_server_method(@call, method(:fake_clstream)) end end describe 'for server streaming' do let(:this_desc) { @request_response } before(:each) do @call = double('active_call') allow(@call).to receive(:single_req_view).and_return(@call) end it_behaves_like 'it handles errors' it 'sends a response and closes the stream if there no errors' do req = Object.new expect(@call).to receive(:remote_read).once.and_return(req) expect(@call).to receive(:remote_send).twice.with(@ok_response) expect(@call).to receive(:output_metadata).and_return(fake_md) expect(@call).to receive(:send_status).once.with(OK, 'OK', true, metadata: fake_md) @server_streamer.run_server_method(@call, method(:fake_svstream)) end end describe 'for bidi streamers' do before(:each) do @call = double('active_call') enq_th, rwl_th = double('enqueue_th'), ('read_write_loop_th') allow(enq_th).to receive(:join) allow(rwl_th).to receive(:join) end it 'sends the specified status if BadStatus is raised' do e = GRPC::BadStatus.new(@bs_code, 'NOK') expect(@call).to receive(:run_server_bidi).and_raise(e) expect(@call).to receive(:send_status).once.with(@bs_code, 'NOK', false, metadata: {}) @bidi_streamer.run_server_method(@call, method(:bad_status_alt)) end it 'sends status UNKNOWN if other StandardErrors are raised' do error_msg = arg_error_msg(StandardError.new) expect(@call).to receive(:run_server_bidi).and_raise(StandardError) expect(@call).to receive(:send_status).once.with(UNKNOWN, error_msg, false, metadata: {}) @bidi_streamer.run_server_method(@call, method(:other_error_alt)) end it 'closes the stream if there no errors' do expect(@call).to receive(:run_server_bidi) expect(@call).to receive(:output_metadata).and_return(fake_md) expect(@call).to receive(:send_status).once.with(OK, 'OK', true, metadata: fake_md) @bidi_streamer.run_server_method(@call, method(:fake_bidistream)) end end end describe '#assert_arity_matches' do def no_arg end def fake_clstream(_arg) end def fake_svstream(_arg1, _arg2) end def fake_three_args(_arg1, _arg2, _arg3) end it 'raises when a request_response does not have 2 args' do [:fake_clstream, :no_arg].each do |mth| blk = proc do @request_response.assert_arity_matches(method(mth)) end expect(&blk).to raise_error end end it 'passes when a request_response has 2 args' do blk = proc do @request_response.assert_arity_matches(method(:fake_svstream)) end expect(&blk).to_not raise_error end it 'raises when a server_streamer does not have 2 args' do [:fake_clstream, :no_arg].each do |mth| blk = proc do @server_streamer.assert_arity_matches(method(mth)) end expect(&blk).to raise_error end end it 'passes when a server_streamer has 2 args' do blk = proc do @server_streamer.assert_arity_matches(method(:fake_svstream)) end expect(&blk).to_not raise_error end it 'raises when a client streamer does not have 1 arg' do [:fake_svstream, :no_arg].each do |mth| blk = proc do @client_streamer.assert_arity_matches(method(mth)) end expect(&blk).to raise_error end end it 'passes when a client_streamer has 1 arg' do blk = proc do @client_streamer.assert_arity_matches(method(:fake_clstream)) end expect(&blk).to_not raise_error end it 'raises when a bidi streamer does not have 1 or 2 args' do [:fake_three_args, :no_arg].each do |mth| blk = proc do @bidi_streamer.assert_arity_matches(method(mth)) end expect(&blk).to raise_error end end it 'passes when a bidi streamer has 1 arg' do blk = proc do @bidi_streamer.assert_arity_matches(method(:fake_clstream)) end expect(&blk).to_not raise_error end it 'passes when a bidi streamer has 2 args' do blk = proc do @bidi_streamer.assert_arity_matches(method(:fake_svstream)) end expect(&blk).to_not raise_error end end describe '#request_response?' do it 'is true only input and output are both not Streams' do expect(@request_response.request_response?).to be(true) expect(@client_streamer.request_response?).to be(false) expect(@bidi_streamer.request_response?).to be(false) expect(@server_streamer.request_response?).to be(false) end end describe '#client_streamer?' do it 'is true only when input is a Stream and output is not a Stream' do expect(@client_streamer.client_streamer?).to be(true) expect(@request_response.client_streamer?).to be(false) expect(@server_streamer.client_streamer?).to be(false) expect(@bidi_streamer.client_streamer?).to be(false) end end describe '#server_streamer?' do it 'is true only when output is a Stream and input is not a Stream' do expect(@server_streamer.server_streamer?).to be(true) expect(@client_streamer.server_streamer?).to be(false) expect(@request_response.server_streamer?).to be(false) expect(@bidi_streamer.server_streamer?).to be(false) end end describe '#bidi_streamer?' do it 'is true only when output is a Stream and input is a Stream' do expect(@bidi_streamer.bidi_streamer?).to be(true) expect(@server_streamer.bidi_streamer?).to be(false) expect(@client_streamer.bidi_streamer?).to be(false) expect(@request_response.bidi_streamer?).to be(false) end end def fake_reqresp(_req, _call) @ok_response end def fake_clstream(_call) @ok_response end def fake_svstream(_req, _call) [@ok_response, @ok_response] end def fake_bidistream(an_array) an_array end def bad_status(_req, _call) fail GRPC::BadStatus.new(@bs_code, 'NOK') end def other_error(_req, _call) fail(ArgumentError, 'other error') end def bad_status_alt(_call) fail GRPC::BadStatus.new(@bs_code, 'NOK') end def other_error_alt(_call) fail(ArgumentError, 'other error') end def arg_error_msg(error = nil) error ||= ArgumentError.new('other error') "#{error.class}: #{error.message}" end end