diff options
Diffstat (limited to 'src/ruby/spec/generic')
-rw-r--r-- | src/ruby/spec/generic/client_stub_spec.rb | 101 | ||||
-rw-r--r-- | src/ruby/spec/generic/rpc_server_spec.rb | 35 |
2 files changed, 131 insertions, 5 deletions
diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb index e1e7a535fb..9539e56c0f 100644 --- a/src/ruby/spec/generic/client_stub_spec.rb +++ b/src/ruby/spec/generic/client_stub_spec.rb @@ -472,7 +472,7 @@ describe 'ClientStub' do host = "localhost:#{server_port}" stub = GRPC::ClientStub.new(host, :this_channel_is_insecure) expect do - get_responses(stub) + get_responses(stub).collect { |r| r } end.to raise_error(ArgumentError, /Header values must be of type string or array/) end @@ -641,11 +641,101 @@ describe 'ClientStub' do expect(e.collect { |r| r }).to eq(@sent_msgs) th.join end + + # Prompted by grpc/github #10526 + describe 'surfacing of errors when sending requests' do + def run_server_bidi_send_one_then_read_indefinitely + @server.start + recvd_rpc = @server.request_call + recvd_call = recvd_rpc.call + server_call = GRPC::ActiveCall.new( + recvd_call, noop, noop, INFINITE_FUTURE, + metadata_received: true, started: false) + server_call.send_initial_metadata + server_call.remote_send('server response') + loop do + m = server_call.remote_read + break if m.nil? + end + # can't fail since initial metadata already sent + server_call.send_status(@pass, 'OK', true) + end + + def verify_error_from_write_thread(stub, requests_to_push, + request_queue, expected_description) + # TODO: an improvement might be to raise the original exception from + # bidi call write loops instead of only cancelling the call + failing_marshal_proc = proc do |req| + fail req if req.is_a?(StandardError) + req + end + begin + e = get_responses(stub, marshal_proc: failing_marshal_proc) + first_response = e.next + expect(first_response).to eq('server response') + requests_to_push.each { |req| request_queue.push(req) } + e.collect { |r| r } + rescue GRPC::Unknown => e + exception = e + end + expect(exception.message.include?(expected_description)).to be(true) + end + + # Provides an Enumerable view of a Queue + class BidiErrorTestingEnumerateForeverQueue + def initialize(queue) + @queue = queue + end + + def each + loop do + msg = @queue.pop + yield msg + end + end + end + + def run_error_in_client_request_stream_test(requests_to_push, + expected_error_message) + # start a server that waits on a read indefinitely - it should + # see a cancellation and be able to break out + th = Thread.new { run_server_bidi_send_one_then_read_indefinitely } + stub = GRPC::ClientStub.new(@host, :this_channel_is_insecure) + + request_queue = Queue.new + @sent_msgs = BidiErrorTestingEnumerateForeverQueue.new(request_queue) + + verify_error_from_write_thread(stub, + requests_to_push, + request_queue, + expected_error_message) + # the write loop errror should cancel the call and end the + # server's request stream + th.join + end + + it 'non-GRPC errors from the write loop surface when raised ' \ + 'at the start of a request stream' do + expected_error_message = 'expect error on first request' + requests_to_push = [StandardError.new(expected_error_message)] + run_error_in_client_request_stream_test(requests_to_push, + expected_error_message) + end + + it 'non-GRPC errors from the write loop surface when raised ' \ + 'during the middle of a request stream' do + expected_error_message = 'expect error on last request' + requests_to_push = %w( one two ) + requests_to_push << StandardError.new(expected_error_message) + run_error_in_client_request_stream_test(requests_to_push, + expected_error_message) + end + end end describe 'without a call operation' do - def get_responses(stub, deadline: nil) - e = stub.bidi_streamer(@method, @sent_msgs, noop, noop, + def get_responses(stub, deadline: nil, marshal_proc: noop) + e = stub.bidi_streamer(@method, @sent_msgs, marshal_proc, noop, metadata: @metadata, deadline: deadline) expect(e).to be_a(Enumerator) e @@ -658,8 +748,9 @@ describe 'ClientStub' do after(:each) do @op.wait # make sure wait doesn't hang end - def get_responses(stub, run_start_call_first: false, deadline: nil) - @op = stub.bidi_streamer(@method, @sent_msgs, noop, noop, + def get_responses(stub, run_start_call_first: false, deadline: nil, + marshal_proc: noop) + @op = stub.bidi_streamer(@method, @sent_msgs, marshal_proc, noop, return_op: true, metadata: @metadata, deadline: deadline) expect(@op).to be_a(GRPC::ActiveCall::Operation) diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb index e0646f4599..27642d6975 100644 --- a/src/ruby/spec/generic/rpc_server_spec.rb +++ b/src/ruby/spec/generic/rpc_server_spec.rb @@ -152,6 +152,18 @@ end CheckCallAfterFinishedServiceStub = CheckCallAfterFinishedService.rpc_stub_class +# A service with a bidi streaming method. +class BidiService + include GRPC::GenericService + rpc :server_sends_bad_input, stream(EchoMsg), stream(EchoMsg) + + def server_sends_bad_input(_, _) + 'bad response. (not an enumerable, client sees an error)' + end +end + +BidiStub = BidiService.rpc_stub_class + describe GRPC::RpcServer do RpcServer = GRPC::RpcServer StatusCodes = GRPC::Core::StatusCodes @@ -450,6 +462,29 @@ describe GRPC::RpcServer do t.join expect(one_failed_as_unavailable).to be(true) end + + it 'should send a status UNKNOWN with a relevant message when the' \ + 'servers response stream is not an enumerable' do + @srv.handle(BidiService) + t = Thread.new { @srv.run } + @srv.wait_till_running + stub = BidiStub.new(@host, :this_channel_is_insecure, **client_opts) + responses = stub.server_sends_bad_input([]) + exception = nil + begin + responses.each { |r| r } + rescue GRPC::Unknown => e + exception = e + end + # Erroneous responses sent from the server handler should cause an + # exception on the client with relevant info. + expected_details = 'NoMethodError: undefined method `each\' for '\ + '"bad response. (not an enumerable, client sees an error)"' + + expect(exception.inspect.include?(expected_details)).to be true + @srv.stop + t.join + end end context 'with connect metadata' do |