grpc 1.4.1 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of grpc might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Makefile +2 -2
- data/src/ruby/ext/grpc/rb_call.c +23 -10
- data/src/ruby/ext/grpc/rb_call.h +3 -0
- data/src/ruby/ext/grpc/rb_call_credentials.c +6 -1
- data/src/ruby/ext/grpc/rb_channel_credentials.c +6 -0
- data/src/ruby/lib/grpc/generic/active_call.rb +137 -33
- data/src/ruby/lib/grpc/generic/bidi_call.rb +28 -34
- data/src/ruby/lib/grpc/generic/rpc_desc.rb +2 -2
- data/src/ruby/lib/grpc/generic/rpc_server.rb +1 -0
- data/src/ruby/lib/grpc/version.rb +1 -1
- data/src/ruby/spec/client_auth_spec.rb +152 -0
- data/src/ruby/spec/client_server_spec.rb +29 -5
- data/src/ruby/spec/generic/active_call_spec.rb +2 -2
- data/src/ruby/spec/generic/client_stub_spec.rb +313 -70
- data/src/ruby/spec/generic/rpc_desc_spec.rb +5 -5
- data/src/ruby/spec/generic/rpc_server_spec.rb +145 -0
- data/src/ruby/spec/testdata/client.key +16 -0
- data/src/ruby/spec/testdata/client.pem +14 -0
- metadata +8 -2
| @@ -77,12 +77,19 @@ module GRPC | |
| 77 77 | 
             
                # block that can be invoked with each response.
         | 
| 78 78 | 
             
                #
         | 
| 79 79 | 
             
                # @param requests the Enumerable of requests to send
         | 
| 80 | 
            -
                # @param  | 
| 80 | 
            +
                # @param set_input_stream_done [Proc] called back when we're done
         | 
| 81 | 
            +
                #   reading the input stream
         | 
| 82 | 
            +
                # @param set_input_stream_done [Proc] called back when we're done
         | 
| 83 | 
            +
                #   sending data on the output stream
         | 
| 81 84 | 
             
                # @return an Enumerator of requests to yield
         | 
| 82 | 
            -
                def run_on_client(requests, | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 85 | 
            +
                def run_on_client(requests,
         | 
| 86 | 
            +
                                  set_input_stream_done,
         | 
| 87 | 
            +
                                  set_output_stream_done,
         | 
| 88 | 
            +
                                  &blk)
         | 
| 89 | 
            +
                  @enq_th = Thread.new do
         | 
| 90 | 
            +
                    write_loop(requests, set_output_stream_done: set_output_stream_done)
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                  read_loop(set_input_stream_done, &blk)
         | 
| 86 93 | 
             
                end
         | 
| 87 94 |  | 
| 88 95 | 
             
                # Begins orchestration of the Bidi stream for a server generating replies.
         | 
| @@ -96,12 +103,17 @@ module GRPC | |
| 96 103 | 
             
                # produced by gen_each_reply could ignore the received_msgs
         | 
| 97 104 | 
             
                #
         | 
| 98 105 | 
             
                # @param gen_each_reply [Proc] generates the BiDi stream replies.
         | 
| 99 | 
            -
                 | 
| 106 | 
            +
                # @param set_input_steam_done [Proc] call back to call when
         | 
| 107 | 
            +
                #   the reads have been completely read through.
         | 
| 108 | 
            +
                def run_on_server(gen_each_reply, set_input_stream_done)
         | 
| 100 109 | 
             
                  # Pass in the optional call object parameter if possible
         | 
| 101 110 | 
             
                  if gen_each_reply.arity == 1
         | 
| 102 | 
            -
                    replys = gen_each_reply.call( | 
| 111 | 
            +
                    replys = gen_each_reply.call(
         | 
| 112 | 
            +
                      read_loop(set_input_stream_done, is_client: false))
         | 
| 103 113 | 
             
                  elsif gen_each_reply.arity == 2
         | 
| 104 | 
            -
                    replys = gen_each_reply.call( | 
| 114 | 
            +
                    replys = gen_each_reply.call(
         | 
| 115 | 
            +
                      read_loop(set_input_stream_done, is_client: false),
         | 
| 116 | 
            +
                      @req_view)
         | 
| 105 117 | 
             
                  else
         | 
| 106 118 | 
             
                    fail 'Illegal arity of reply generator'
         | 
| 107 119 | 
             
                  end
         | 
| @@ -114,22 +126,6 @@ module GRPC | |
| 114 126 | 
             
                END_OF_READS = :end_of_reads
         | 
| 115 127 | 
             
                END_OF_WRITES = :end_of_writes
         | 
| 116 128 |  | 
| 117 | 
            -
                # signals that bidi operation is complete
         | 
| 118 | 
            -
                def notify_done
         | 
| 119 | 
            -
                  return unless @op_notifier
         | 
| 120 | 
            -
                  GRPC.logger.debug("bidi-notify-done: notifying  #{@op_notifier}")
         | 
| 121 | 
            -
                  @op_notifier.notify(self)
         | 
| 122 | 
            -
                end
         | 
| 123 | 
            -
             | 
| 124 | 
            -
                # signals that a bidi operation is complete (read + write)
         | 
| 125 | 
            -
                def finished
         | 
| 126 | 
            -
                  @done_mutex.synchronize do
         | 
| 127 | 
            -
                    return unless @reads_complete && @writes_complete && !@complete
         | 
| 128 | 
            -
                    @call.close
         | 
| 129 | 
            -
                    @complete = true
         | 
| 130 | 
            -
                  end
         | 
| 131 | 
            -
                end
         | 
| 132 | 
            -
             | 
| 133 129 | 
             
                # performs a read using @call.run_batch, ensures metadata is set up
         | 
| 134 130 | 
             
                def read_using_run_batch
         | 
| 135 131 | 
             
                  ops = { RECV_MESSAGE => nil }
         | 
| @@ -142,7 +138,8 @@ module GRPC | |
| 142 138 | 
             
                  batch_result
         | 
| 143 139 | 
             
                end
         | 
| 144 140 |  | 
| 145 | 
            -
                 | 
| 141 | 
            +
                # set_output_stream_done is relevant on client-side
         | 
| 142 | 
            +
                def write_loop(requests, is_client: true, set_output_stream_done: nil)
         | 
| 146 143 | 
             
                  GRPC.logger.debug('bidi-write-loop: starting')
         | 
| 147 144 | 
             
                  count = 0
         | 
| 148 145 | 
             
                  requests.each do |req|
         | 
| @@ -166,23 +163,20 @@ module GRPC | |
| 166 163 | 
             
                    GRPC.logger.debug("bidi-write-loop: client sent #{count}, waiting")
         | 
| 167 164 | 
             
                    @call.run_batch(SEND_CLOSE_FROM_CLIENT => nil)
         | 
| 168 165 | 
             
                    GRPC.logger.debug('bidi-write-loop: done')
         | 
| 169 | 
            -
                    notify_done
         | 
| 170 | 
            -
                    @writes_complete = true
         | 
| 171 | 
            -
                    finished
         | 
| 172 166 | 
             
                  end
         | 
| 173 167 | 
             
                  GRPC.logger.debug('bidi-write-loop: finished')
         | 
| 174 168 | 
             
                rescue StandardError => e
         | 
| 175 169 | 
             
                  GRPC.logger.warn('bidi-write-loop: failed')
         | 
| 176 170 | 
             
                  GRPC.logger.warn(e)
         | 
| 177 | 
            -
                  notify_done
         | 
| 178 | 
            -
                  @writes_complete = true
         | 
| 179 | 
            -
                  finished
         | 
| 180 171 | 
             
                  raise e
         | 
| 172 | 
            +
                ensure
         | 
| 173 | 
            +
                  set_output_stream_done.call if is_client
         | 
| 181 174 | 
             
                end
         | 
| 182 175 |  | 
| 183 176 | 
             
                # Provides an enumerator that yields results of remote reads
         | 
| 184 | 
            -
                def read_loop(is_client: true)
         | 
| 177 | 
            +
                def read_loop(set_input_stream_done, is_client: true)
         | 
| 185 178 | 
             
                  return enum_for(:read_loop,
         | 
| 179 | 
            +
                                  set_input_stream_done,
         | 
| 186 180 | 
             
                                  is_client: is_client) unless block_given?
         | 
| 187 181 | 
             
                  GRPC.logger.debug('bidi-read-loop: starting')
         | 
| 188 182 | 
             
                  begin
         | 
| @@ -216,10 +210,10 @@ module GRPC | |
| 216 210 | 
             
                    GRPC.logger.warn('bidi: read-loop failed')
         | 
| 217 211 | 
             
                    GRPC.logger.warn(e)
         | 
| 218 212 | 
             
                    raise e
         | 
| 213 | 
            +
                  ensure
         | 
| 214 | 
            +
                    set_input_stream_done.call
         | 
| 219 215 | 
             
                  end
         | 
| 220 216 | 
             
                  GRPC.logger.debug('bidi-read-loop: finished')
         | 
| 221 | 
            -
                  @reads_complete = true
         | 
| 222 | 
            -
                  finished
         | 
| 223 217 | 
             
                  # Make sure that the write loop is done done before finishing the call.
         | 
| 224 218 | 
             
                  # Note that blocking is ok at this point because we've already received
         | 
| 225 219 | 
             
                  # a status
         | 
| @@ -63,7 +63,7 @@ module GRPC | |
| 63 63 | 
             
                end
         | 
| 64 64 |  | 
| 65 65 | 
             
                def handle_request_response(active_call, mth)
         | 
| 66 | 
            -
                  req = active_call. | 
| 66 | 
            +
                  req = active_call.read_unary_request
         | 
| 67 67 | 
             
                  resp = mth.call(req, active_call.single_req_view)
         | 
| 68 68 | 
             
                  active_call.server_unary_response(
         | 
| 69 69 | 
             
                    resp, trailing_metadata: active_call.output_metadata)
         | 
| @@ -76,7 +76,7 @@ module GRPC | |
| 76 76 | 
             
                end
         | 
| 77 77 |  | 
| 78 78 | 
             
                def handle_server_streamer(active_call, mth)
         | 
| 79 | 
            -
                  req = active_call. | 
| 79 | 
            +
                  req = active_call.read_unary_request
         | 
| 80 80 | 
             
                  replys = mth.call(req, active_call.single_req_view)
         | 
| 81 81 | 
             
                  replys.each { |r| active_call.remote_send(r) }
         | 
| 82 82 | 
             
                  send_status(active_call, OK, 'OK', active_call.output_metadata)
         | 
| @@ -0,0 +1,152 @@ | |
| 1 | 
            +
            # Copyright 2015, Google Inc.
         | 
| 2 | 
            +
            # All rights reserved.
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # Redistribution and use in source and binary forms, with or without
         | 
| 5 | 
            +
            # modification, are permitted provided that the following conditions are
         | 
| 6 | 
            +
            # met:
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            #     * Redistributions of source code must retain the above copyright
         | 
| 9 | 
            +
            # notice, this list of conditions and the following disclaimer.
         | 
| 10 | 
            +
            #     * Redistributions in binary form must reproduce the above
         | 
| 11 | 
            +
            # copyright notice, this list of conditions and the following disclaimer
         | 
| 12 | 
            +
            # in the documentation and/or other materials provided with the
         | 
| 13 | 
            +
            # distribution.
         | 
| 14 | 
            +
            #     * Neither the name of Google Inc. nor the names of its
         | 
| 15 | 
            +
            # contributors may be used to endorse or promote products derived from
         | 
| 16 | 
            +
            # this software without specific prior written permission.
         | 
| 17 | 
            +
            #
         | 
| 18 | 
            +
            # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
         | 
| 19 | 
            +
            # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
         | 
| 20 | 
            +
            # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
         | 
| 21 | 
            +
            # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
         | 
| 22 | 
            +
            # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
         | 
| 23 | 
            +
            # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
         | 
| 24 | 
            +
            # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
         | 
| 25 | 
            +
            # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
         | 
| 26 | 
            +
            # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
         | 
| 27 | 
            +
            # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
         | 
| 28 | 
            +
            # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            require 'grpc'
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            def create_channel_creds
         | 
| 33 | 
            +
              test_root = File.join(File.dirname(__FILE__), 'testdata')
         | 
| 34 | 
            +
              files = ['ca.pem', 'client.key', 'client.pem']
         | 
| 35 | 
            +
              creds = files.map { |f| File.open(File.join(test_root, f)).read }
         | 
| 36 | 
            +
              GRPC::Core::ChannelCredentials.new(creds[0], creds[1], creds[2])
         | 
| 37 | 
            +
            end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            def client_cert
         | 
| 40 | 
            +
              test_root = File.join(File.dirname(__FILE__), 'testdata')
         | 
| 41 | 
            +
              cert = File.open(File.join(test_root, 'client.pem')).read
         | 
| 42 | 
            +
              fail unless cert.is_a?(String)
         | 
| 43 | 
            +
              cert
         | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            def create_server_creds
         | 
| 47 | 
            +
              test_root = File.join(File.dirname(__FILE__), 'testdata')
         | 
| 48 | 
            +
              p "test root: #{test_root}"
         | 
| 49 | 
            +
              files = ['ca.pem', 'server1.key', 'server1.pem']
         | 
| 50 | 
            +
              creds = files.map { |f| File.open(File.join(test_root, f)).read }
         | 
| 51 | 
            +
              GRPC::Core::ServerCredentials.new(
         | 
| 52 | 
            +
                creds[0],
         | 
| 53 | 
            +
                [{ private_key: creds[1], cert_chain: creds[2] }],
         | 
| 54 | 
            +
                true) # force client auth
         | 
| 55 | 
            +
            end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            # A test message
         | 
| 58 | 
            +
            class EchoMsg
         | 
| 59 | 
            +
              def self.marshal(_o)
         | 
| 60 | 
            +
                ''
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def self.unmarshal(_o)
         | 
| 64 | 
            +
                EchoMsg.new
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            # a test service that checks the cert of its peer
         | 
| 69 | 
            +
            class SslTestService
         | 
| 70 | 
            +
              include GRPC::GenericService
         | 
| 71 | 
            +
              rpc :an_rpc, EchoMsg, EchoMsg
         | 
| 72 | 
            +
              rpc :a_client_streaming_rpc, stream(EchoMsg), EchoMsg
         | 
| 73 | 
            +
              rpc :a_server_streaming_rpc, EchoMsg, stream(EchoMsg)
         | 
| 74 | 
            +
              rpc :a_bidi_rpc, stream(EchoMsg), stream(EchoMsg)
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              def check_peer_cert(call)
         | 
| 77 | 
            +
                error_msg = "want:\n#{client_cert}\n\ngot:\n#{call.peer_cert}"
         | 
| 78 | 
            +
                fail(error_msg) unless call.peer_cert == client_cert
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              def an_rpc(req, call)
         | 
| 82 | 
            +
                check_peer_cert(call)
         | 
| 83 | 
            +
                req
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              def a_client_streaming_rpc(call)
         | 
| 87 | 
            +
                check_peer_cert(call)
         | 
| 88 | 
            +
                call.each_remote_read.each { |r| p r }
         | 
| 89 | 
            +
                EchoMsg.new
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              def a_server_streaming_rpc(_, call)
         | 
| 93 | 
            +
                check_peer_cert(call)
         | 
| 94 | 
            +
                [EchoMsg.new, EchoMsg.new]
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              def a_bidi_rpc(requests, call)
         | 
| 98 | 
            +
                check_peer_cert(call)
         | 
| 99 | 
            +
                requests.each { |r| p r }
         | 
| 100 | 
            +
                [EchoMsg.new, EchoMsg.new]
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
            end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            SslTestServiceStub = SslTestService.rpc_stub_class
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            describe 'client-server auth' do
         | 
| 107 | 
            +
              RpcServer = GRPC::RpcServer
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              before(:all) do
         | 
| 110 | 
            +
                server_opts = {
         | 
| 111 | 
            +
                  poll_period: 1
         | 
| 112 | 
            +
                }
         | 
| 113 | 
            +
                @srv = RpcServer.new(**server_opts)
         | 
| 114 | 
            +
                port = @srv.add_http2_port('0.0.0.0:0', create_server_creds)
         | 
| 115 | 
            +
                @srv.handle(SslTestService)
         | 
| 116 | 
            +
                @srv_thd = Thread.new { @srv.run }
         | 
| 117 | 
            +
                @srv.wait_till_running
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                client_opts = {
         | 
| 120 | 
            +
                  channel_args: {
         | 
| 121 | 
            +
                    GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.fr'
         | 
| 122 | 
            +
                  }
         | 
| 123 | 
            +
                }
         | 
| 124 | 
            +
                @stub = SslTestServiceStub.new("localhost:#{port}",
         | 
| 125 | 
            +
                                               create_channel_creds,
         | 
| 126 | 
            +
                                               **client_opts)
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
              after(:all) do
         | 
| 130 | 
            +
                expect(@srv.stopped?).to be(false)
         | 
| 131 | 
            +
                @srv.stop
         | 
| 132 | 
            +
                @srv_thd.join
         | 
| 133 | 
            +
              end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
              it 'client-server auth with unary RPCs' do
         | 
| 136 | 
            +
                @stub.an_rpc(EchoMsg.new)
         | 
| 137 | 
            +
              end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
              it 'client-server auth with client streaming RPCs' do
         | 
| 140 | 
            +
                @stub.a_client_streaming_rpc([EchoMsg.new, EchoMsg.new])
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
              it 'client-server auth with server streaming RPCs' do
         | 
| 144 | 
            +
                responses = @stub.a_server_streaming_rpc(EchoMsg.new)
         | 
| 145 | 
            +
                responses.each { |r| p r }
         | 
| 146 | 
            +
              end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
              it 'client-server auth with bidi RPCs' do
         | 
| 149 | 
            +
                responses = @stub.a_bidi_rpc([EchoMsg.new, EchoMsg.new])
         | 
| 150 | 
            +
                responses.each { |r| p r }
         | 
| 151 | 
            +
              end
         | 
| 152 | 
            +
            end
         | 
| @@ -463,11 +463,18 @@ describe 'the secure http client/server' do | |
| 463 463 | 
             
              it_behaves_like 'GRPC metadata delivery works OK' do
         | 
| 464 464 | 
             
              end
         | 
| 465 465 |  | 
| 466 | 
            -
               | 
| 467 | 
            -
                auth_proc = proc {  | 
| 466 | 
            +
              def credentials_update_test(creds_update_md)
         | 
| 467 | 
            +
                auth_proc = proc { creds_update_md }
         | 
| 468 468 | 
             
                call_creds = GRPC::Core::CallCredentials.new(auth_proc)
         | 
| 469 | 
            -
             | 
| 470 | 
            -
                 | 
| 469 | 
            +
             | 
| 470 | 
            +
                initial_md_key = 'k2'
         | 
| 471 | 
            +
                initial_md_val = 'v2'
         | 
| 472 | 
            +
                initial_md = {}
         | 
| 473 | 
            +
                initial_md[initial_md_key] = initial_md_val
         | 
| 474 | 
            +
                expected_md = creds_update_md.clone
         | 
| 475 | 
            +
                fail 'bad test param' unless expected_md[initial_md_key].nil?
         | 
| 476 | 
            +
                expected_md[initial_md_key] = initial_md_val
         | 
| 477 | 
            +
             | 
| 471 478 | 
             
                recvd_rpc = nil
         | 
| 472 479 | 
             
                rcv_thread = Thread.new do
         | 
| 473 480 | 
             
                  recvd_rpc = @server.request_call
         | 
| @@ -476,7 +483,7 @@ describe 'the secure http client/server' do | |
| 476 483 | 
             
                call = new_client_call
         | 
| 477 484 | 
             
                call.set_credentials! call_creds
         | 
| 478 485 | 
             
                client_ops = {
         | 
| 479 | 
            -
                  CallOps::SEND_INITIAL_METADATA =>  | 
| 486 | 
            +
                  CallOps::SEND_INITIAL_METADATA => initial_md
         | 
| 480 487 | 
             
                }
         | 
| 481 488 | 
             
                batch_result = call.run_batch(client_ops)
         | 
| 482 489 | 
             
                expect(batch_result.send_metadata).to be true
         | 
| @@ -488,4 +495,21 @@ describe 'the secure http client/server' do | |
| 488 495 | 
             
                replace_symbols = Hash[expected_md.each_pair.collect { |x, y| [x.to_s, y] }]
         | 
| 489 496 | 
             
                expect(recvd_md).to eq(recvd_md.merge(replace_symbols))
         | 
| 490 497 | 
             
              end
         | 
| 498 | 
            +
             | 
| 499 | 
            +
              it 'modifies metadata with CallCredentials' do
         | 
| 500 | 
            +
                credentials_update_test('k1' => 'updated-v1')
         | 
| 501 | 
            +
              end
         | 
| 502 | 
            +
             | 
| 503 | 
            +
              it 'modifies large metadata with CallCredentials' do
         | 
| 504 | 
            +
                val_array = %w(
         | 
| 505 | 
            +
                  '00000000000000000000000000000000000000000000000000000000000000',
         | 
| 506 | 
            +
                  '11111111111111111111111111111111111111111111111111111111111111',
         | 
| 507 | 
            +
                )
         | 
| 508 | 
            +
                md = {
         | 
| 509 | 
            +
                  k3: val_array,
         | 
| 510 | 
            +
                  k4: '0000000000000000000000000000000000000000000000000000000000',
         | 
| 511 | 
            +
                  keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey5: 'v1'
         | 
| 512 | 
            +
                }
         | 
| 513 | 
            +
                credentials_update_test(md)
         | 
| 514 | 
            +
              end
         | 
| 491 515 | 
             
            end
         | 
| @@ -488,7 +488,7 @@ describe GRPC::ActiveCall do | |
| 488 488 | 
             
                  server_call.remote_send('server_response')
         | 
| 489 489 | 
             
                  expect(client_call.remote_read).to eq('server_response')
         | 
| 490 490 | 
             
                  server_call.send_status(OK, 'status code is OK')
         | 
| 491 | 
            -
                  expect { client_call. | 
| 491 | 
            +
                  expect { client_call.receive_and_check_status }.to_not raise_error
         | 
| 492 492 | 
             
                end
         | 
| 493 493 |  | 
| 494 494 | 
             
                it 'finishes ok if the server sends an early status response' do
         | 
| @@ -505,7 +505,7 @@ describe GRPC::ActiveCall do | |
| 505 505 | 
             
                  expect do
         | 
| 506 506 | 
             
                    call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil)
         | 
| 507 507 | 
             
                  end.to_not raise_error
         | 
| 508 | 
            -
                  expect { client_call. | 
| 508 | 
            +
                  expect { client_call.receive_and_check_status }.to_not raise_error
         | 
| 509 509 | 
             
                end
         | 
| 510 510 |  | 
| 511 511 | 
             
                it 'finishes ok if SEND_CLOSE and RECV_STATUS has been sent' do
         | 
| @@ -51,6 +51,53 @@ include GRPC::Core::StatusCodes | |
| 51 51 | 
             
            include GRPC::Core::TimeConsts
         | 
| 52 52 | 
             
            include GRPC::Core::CallOps
         | 
| 53 53 |  | 
| 54 | 
            +
            # check that methods on a finished/closed call t crash
         | 
| 55 | 
            +
            def check_op_view_of_finished_client_call(op_view,
         | 
| 56 | 
            +
                                                      expected_metadata,
         | 
| 57 | 
            +
                                                      expected_trailing_metadata)
         | 
| 58 | 
            +
              # use read_response_stream to try to iterate through
         | 
| 59 | 
            +
              # possible response stream
         | 
| 60 | 
            +
              fail('need something to attempt reads') unless block_given?
         | 
| 61 | 
            +
              expect do
         | 
| 62 | 
            +
                resp = op_view.execute
         | 
| 63 | 
            +
                yield resp
         | 
| 64 | 
            +
              end.to raise_error(GRPC::Core::CallError)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              expect { op_view.start_call }.to raise_error(RuntimeError)
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              sanity_check_values_of_accessors(op_view,
         | 
| 69 | 
            +
                                               expected_metadata,
         | 
| 70 | 
            +
                                               expected_trailing_metadata)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              expect do
         | 
| 73 | 
            +
                op_view.wait
         | 
| 74 | 
            +
                op_view.cancel
         | 
| 75 | 
            +
                op_view.write_flag = 1
         | 
| 76 | 
            +
              end.to_not raise_error
         | 
| 77 | 
            +
            end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            def sanity_check_values_of_accessors(op_view,
         | 
| 80 | 
            +
                                                 expected_metadata,
         | 
| 81 | 
            +
                                                 expected_trailing_metadata)
         | 
| 82 | 
            +
              expected_status = Struct::Status.new
         | 
| 83 | 
            +
              expected_status.code = 0
         | 
| 84 | 
            +
              expected_status.details = 'OK'
         | 
| 85 | 
            +
              expected_status.metadata = expected_trailing_metadata
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              expect(op_view.status).to eq(expected_status)
         | 
| 88 | 
            +
              expect(op_view.metadata).to eq(expected_metadata)
         | 
| 89 | 
            +
              expect(op_view.trailing_metadata).to eq(expected_trailing_metadata)
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              expect(op_view.cancelled?).to be(false)
         | 
| 92 | 
            +
              expect(op_view.write_flag).to be(nil)
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              # The deadline attribute of a call can be either
         | 
| 95 | 
            +
              # a GRPC::Core::TimeSpec or a Time, which are mutually exclusive.
         | 
| 96 | 
            +
              # TODO: fix so that the accessor always returns the same type.
         | 
| 97 | 
            +
              expect(op_view.deadline.is_a?(GRPC::Core::TimeSpec) ||
         | 
| 98 | 
            +
                     op_view.deadline.is_a?(Time)).to be(true)
         | 
| 99 | 
            +
            end
         | 
| 100 | 
            +
             | 
| 54 101 | 
             
            describe 'ClientStub' do
         | 
| 55 102 | 
             
              let(:noop) { proc { |x| x } }
         | 
| 56 103 |  | 
| @@ -60,6 +107,7 @@ describe 'ClientStub' do | |
| 60 107 | 
             
                @method = 'an_rpc_method'
         | 
| 61 108 | 
             
                @pass = OK
         | 
| 62 109 | 
             
                @fail = INTERNAL
         | 
| 110 | 
            +
                @metadata = { k1: 'v1', k2: 'v2' }
         | 
| 63 111 | 
             
              end
         | 
| 64 112 |  | 
| 65 113 | 
             
              after(:each) do
         | 
| @@ -122,7 +170,7 @@ describe 'ClientStub' do | |
| 122 170 | 
             
                end
         | 
| 123 171 | 
             
              end
         | 
| 124 172 |  | 
| 125 | 
            -
              describe '#request_response' do
         | 
| 173 | 
            +
              describe '#request_response', request_response: true do
         | 
| 126 174 | 
             
                before(:each) do
         | 
| 127 175 | 
             
                  @sent_msg, @resp = 'a_msg', 'a_reply'
         | 
| 128 176 | 
             
                end
         | 
| @@ -137,16 +185,42 @@ describe 'ClientStub' do | |
| 137 185 | 
             
                    th.join
         | 
| 138 186 | 
             
                  end
         | 
| 139 187 |  | 
| 140 | 
            -
                   | 
| 188 | 
            +
                  def metadata_test(md)
         | 
| 141 189 | 
             
                    server_port = create_test_server
         | 
| 142 190 | 
             
                    host = "localhost:#{server_port}"
         | 
| 143 191 | 
             
                    th = run_request_response(@sent_msg, @resp, @pass,
         | 
| 144 | 
            -
                                               | 
| 192 | 
            +
                                              expected_metadata: md)
         | 
| 145 193 | 
             
                    stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 194 | 
            +
                    @metadata = md
         | 
| 146 195 | 
             
                    expect(get_response(stub)).to eq(@resp)
         | 
| 147 196 | 
             
                    th.join
         | 
| 148 197 | 
             
                  end
         | 
| 149 198 |  | 
| 199 | 
            +
                  it 'should send metadata to the server ok' do
         | 
| 200 | 
            +
                    metadata_test(k1: 'v1', k2: 'v2')
         | 
| 201 | 
            +
                  end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  # these tests mostly try to exercise when md might be allocated
         | 
| 204 | 
            +
                  # instead of inlined
         | 
| 205 | 
            +
                  it 'should send metadata with multiple large md to the server ok' do
         | 
| 206 | 
            +
                    val_array = %w(
         | 
| 207 | 
            +
                      '00000000000000000000000000000000000000000000000000000000000000',
         | 
| 208 | 
            +
                      '11111111111111111111111111111111111111111111111111111111111111',
         | 
| 209 | 
            +
                      '22222222222222222222222222222222222222222222222222222222222222',
         | 
| 210 | 
            +
                    )
         | 
| 211 | 
            +
                    md = {
         | 
| 212 | 
            +
                      k1: val_array,
         | 
| 213 | 
            +
                      k2: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
         | 
| 214 | 
            +
                      k3: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
         | 
| 215 | 
            +
                      k4: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
         | 
| 216 | 
            +
                      keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey5: 'v5',
         | 
| 217 | 
            +
                      'k66666666666666666666666666666666666666666666666666666' => 'v6',
         | 
| 218 | 
            +
                      'k77777777777777777777777777777777777777777777777777777' => 'v7',
         | 
| 219 | 
            +
                      'k88888888888888888888888888888888888888888888888888888' => 'v8'
         | 
| 220 | 
            +
                    }
         | 
| 221 | 
            +
                    metadata_test(md)
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
             | 
| 150 224 | 
             
                  it 'should send a request when configured using an override channel' do
         | 
| 151 225 | 
             
                    server_port = create_test_server
         | 
| 152 226 | 
             
                    alt_host = "localhost:#{server_port}"
         | 
| @@ -202,13 +276,24 @@ describe 'ClientStub' do | |
| 202 276 | 
             
                    # Kill the server thread so tests can complete
         | 
| 203 277 | 
             
                    th.kill
         | 
| 204 278 | 
             
                  end
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                  it 'should raise ArgumentError if metadata contains invalid values' do
         | 
| 281 | 
            +
                    @metadata.merge!(k3: 3)
         | 
| 282 | 
            +
                    server_port = create_test_server
         | 
| 283 | 
            +
                    host = "localhost:#{server_port}"
         | 
| 284 | 
            +
                    stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 285 | 
            +
                    expect do
         | 
| 286 | 
            +
                      get_response(stub)
         | 
| 287 | 
            +
                    end.to raise_error(ArgumentError,
         | 
| 288 | 
            +
                                       /Header values must be of type string or array/)
         | 
| 289 | 
            +
                  end
         | 
| 205 290 | 
             
                end
         | 
| 206 291 |  | 
| 207 292 | 
             
                describe 'without a call operation' do
         | 
| 208 293 | 
             
                  def get_response(stub, credentials: nil)
         | 
| 209 294 | 
             
                    puts credentials.inspect
         | 
| 210 295 | 
             
                    stub.request_response(@method, @sent_msg, noop, noop,
         | 
| 211 | 
            -
                                          metadata:  | 
| 296 | 
            +
                                          metadata: @metadata,
         | 
| 212 297 | 
             
                                          credentials: credentials)
         | 
| 213 298 | 
             
                  end
         | 
| 214 299 |  | 
| @@ -216,40 +301,62 @@ describe 'ClientStub' do | |
| 216 301 | 
             
                end
         | 
| 217 302 |  | 
| 218 303 | 
             
                describe 'via a call operation' do
         | 
| 304 | 
            +
                  after(:each) do
         | 
| 305 | 
            +
                    # make sure op.wait doesn't hang, even if there's a bad status
         | 
| 306 | 
            +
                    @op.wait
         | 
| 307 | 
            +
                  end
         | 
| 219 308 | 
             
                  def get_response(stub, run_start_call_first: false, credentials: nil)
         | 
| 220 | 
            -
                    op = stub.request_response(@method, @sent_msg, noop, noop,
         | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 225 | 
            -
                    expect(op).to be_a(GRPC::ActiveCall::Operation)
         | 
| 226 | 
            -
                    op.start_call if run_start_call_first
         | 
| 227 | 
            -
                    result = op.execute
         | 
| 228 | 
            -
                    op.wait # make sure wait doesn't hang
         | 
| 309 | 
            +
                    @op = stub.request_response(@method, @sent_msg, noop, noop,
         | 
| 310 | 
            +
                                                return_op: true,
         | 
| 311 | 
            +
                                                metadata: @metadata,
         | 
| 312 | 
            +
                                                deadline: from_relative_time(2),
         | 
| 313 | 
            +
                                                credentials: credentials)
         | 
| 314 | 
            +
                    expect(@op).to be_a(GRPC::ActiveCall::Operation)
         | 
| 315 | 
            +
                    @op.start_call if run_start_call_first
         | 
| 316 | 
            +
                    result = @op.execute
         | 
| 229 317 | 
             
                    result
         | 
| 230 318 | 
             
                  end
         | 
| 231 319 |  | 
| 232 320 | 
             
                  it_behaves_like 'request response'
         | 
| 233 321 |  | 
| 234 | 
            -
                   | 
| 322 | 
            +
                  def run_op_view_metadata_test(run_start_call_first)
         | 
| 235 323 | 
             
                    server_port = create_test_server
         | 
| 236 324 | 
             
                    host = "localhost:#{server_port}"
         | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 325 | 
            +
             | 
| 326 | 
            +
                    @server_initial_md = { 'sk1' => 'sv1', 'sk2' => 'sv2' }
         | 
| 327 | 
            +
                    @server_trailing_md = { 'tk1' => 'tv1', 'tk2' => 'tv2' }
         | 
| 328 | 
            +
                    th = run_request_response(
         | 
| 329 | 
            +
                      @sent_msg, @resp, @pass,
         | 
| 330 | 
            +
                      expected_metadata: @metadata,
         | 
| 331 | 
            +
                      server_initial_md: @server_initial_md,
         | 
| 332 | 
            +
                      server_trailing_md: @server_trailing_md)
         | 
| 239 333 | 
             
                    stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 240 | 
            -
                    expect( | 
| 334 | 
            +
                    expect(
         | 
| 335 | 
            +
                      get_response(stub,
         | 
| 336 | 
            +
                                   run_start_call_first: run_start_call_first)).to eq(@resp)
         | 
| 241 337 | 
             
                    th.join
         | 
| 242 338 | 
             
                  end
         | 
| 339 | 
            +
             | 
| 340 | 
            +
                  it 'sends metadata to the server ok when running start_call first' do
         | 
| 341 | 
            +
                    run_op_view_metadata_test(true)
         | 
| 342 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 343 | 
            +
                      @op, @server_initial_md, @server_trailing_md) { |r| p r }
         | 
| 344 | 
            +
                  end
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                  it 'does not crash when used after the call has been finished' do
         | 
| 347 | 
            +
                    run_op_view_metadata_test(false)
         | 
| 348 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 349 | 
            +
                      @op, @server_initial_md, @server_trailing_md) { |r| p r }
         | 
| 350 | 
            +
                  end
         | 
| 243 351 | 
             
                end
         | 
| 244 352 | 
             
              end
         | 
| 245 353 |  | 
| 246 | 
            -
              describe '#client_streamer' do
         | 
| 354 | 
            +
              describe '#client_streamer', client_streamer: true do
         | 
| 247 355 | 
             
                before(:each) do
         | 
| 248 356 | 
             
                  Thread.abort_on_exception = true
         | 
| 249 357 | 
             
                  server_port = create_test_server
         | 
| 250 358 | 
             
                  host = "localhost:#{server_port}"
         | 
| 251 359 | 
             
                  @stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 252 | 
            -
                  @metadata = { k1: 'v1', k2: 'v2' }
         | 
| 253 360 | 
             
                  @sent_msgs = Array.new(3) { |i| 'msg_' + (i + 1).to_s }
         | 
| 254 361 | 
             
                  @resp = 'a_reply'
         | 
| 255 362 | 
             
                end
         | 
| @@ -262,7 +369,8 @@ describe 'ClientStub' do | |
| 262 369 | 
             
                  end
         | 
| 263 370 |  | 
| 264 371 | 
             
                  it 'should send metadata to the server ok' do
         | 
| 265 | 
            -
                    th = run_client_streamer(@sent_msgs, @resp, @pass, | 
| 372 | 
            +
                    th = run_client_streamer(@sent_msgs, @resp, @pass,
         | 
| 373 | 
            +
                                             expected_metadata: @metadata)
         | 
| 266 374 | 
             
                    expect(get_response(@stub)).to eq(@resp)
         | 
| 267 375 | 
             
                    th.join
         | 
| 268 376 | 
             
                  end
         | 
| @@ -293,27 +401,50 @@ describe 'ClientStub' do | |
| 293 401 | 
             
                end
         | 
| 294 402 |  | 
| 295 403 | 
             
                describe 'via a call operation' do
         | 
| 404 | 
            +
                  after(:each) do
         | 
| 405 | 
            +
                    # make sure op.wait doesn't hang, even if there's a bad status
         | 
| 406 | 
            +
                    @op.wait
         | 
| 407 | 
            +
                  end
         | 
| 296 408 | 
             
                  def get_response(stub, run_start_call_first: false)
         | 
| 297 | 
            -
                    op = stub.client_streamer(@method, @sent_msgs, noop, noop,
         | 
| 298 | 
            -
             | 
| 299 | 
            -
                    expect(op).to be_a(GRPC::ActiveCall::Operation)
         | 
| 300 | 
            -
                    op.start_call if run_start_call_first
         | 
| 301 | 
            -
                    result = op.execute
         | 
| 302 | 
            -
                    op.wait # make sure wait doesn't hang
         | 
| 409 | 
            +
                    @op = stub.client_streamer(@method, @sent_msgs, noop, noop,
         | 
| 410 | 
            +
                                               return_op: true, metadata: @metadata)
         | 
| 411 | 
            +
                    expect(@op).to be_a(GRPC::ActiveCall::Operation)
         | 
| 412 | 
            +
                    @op.start_call if run_start_call_first
         | 
| 413 | 
            +
                    result = @op.execute
         | 
| 303 414 | 
             
                    result
         | 
| 304 415 | 
             
                  end
         | 
| 305 416 |  | 
| 306 417 | 
             
                  it_behaves_like 'client streaming'
         | 
| 307 418 |  | 
| 308 | 
            -
                   | 
| 309 | 
            -
                     | 
| 310 | 
            -
                     | 
| 419 | 
            +
                  def run_op_view_metadata_test(run_start_call_first)
         | 
| 420 | 
            +
                    @server_initial_md = { 'sk1' => 'sv1', 'sk2' => 'sv2' }
         | 
| 421 | 
            +
                    @server_trailing_md = { 'tk1' => 'tv1', 'tk2' => 'tv2' }
         | 
| 422 | 
            +
                    th = run_client_streamer(
         | 
| 423 | 
            +
                      @sent_msgs, @resp, @pass,
         | 
| 424 | 
            +
                      expected_metadata: @metadata,
         | 
| 425 | 
            +
                      server_initial_md: @server_initial_md,
         | 
| 426 | 
            +
                      server_trailing_md: @server_trailing_md)
         | 
| 427 | 
            +
                    expect(
         | 
| 428 | 
            +
                      get_response(@stub,
         | 
| 429 | 
            +
                                   run_start_call_first: run_start_call_first)).to eq(@resp)
         | 
| 311 430 | 
             
                    th.join
         | 
| 312 431 | 
             
                  end
         | 
| 432 | 
            +
             | 
| 433 | 
            +
                  it 'sends metadata to the server ok when running start_call first' do
         | 
| 434 | 
            +
                    run_op_view_metadata_test(true)
         | 
| 435 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 436 | 
            +
                      @op, @server_initial_md, @server_trailing_md) { |r| p r }
         | 
| 437 | 
            +
                  end
         | 
| 438 | 
            +
             | 
| 439 | 
            +
                  it 'does not crash when used after the call has been finished' do
         | 
| 440 | 
            +
                    run_op_view_metadata_test(false)
         | 
| 441 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 442 | 
            +
                      @op, @server_initial_md, @server_trailing_md) { |r| p r }
         | 
| 443 | 
            +
                  end
         | 
| 313 444 | 
             
                end
         | 
| 314 445 | 
             
              end
         | 
| 315 446 |  | 
| 316 | 
            -
              describe '#server_streamer' do
         | 
| 447 | 
            +
              describe '#server_streamer', server_streamer: true do
         | 
| 317 448 | 
             
                before(:each) do
         | 
| 318 449 | 
             
                  @sent_msg = 'a_msg'
         | 
| 319 450 | 
             
                  @replys = Array.new(3) { |i| 'reply_' + (i + 1).to_s }
         | 
| @@ -343,18 +474,42 @@ describe 'ClientStub' do | |
| 343 474 | 
             
                    server_port = create_test_server
         | 
| 344 475 | 
             
                    host = "localhost:#{server_port}"
         | 
| 345 476 | 
             
                    th = run_server_streamer(@sent_msg, @replys, @fail,
         | 
| 346 | 
            -
                                             k1: 'v1', k2: 'v2')
         | 
| 477 | 
            +
                                             expected_metadata: { k1: 'v1', k2: 'v2' })
         | 
| 347 478 | 
             
                    stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 348 479 | 
             
                    e = get_responses(stub)
         | 
| 349 480 | 
             
                    expect { e.collect { |r| r } }.to raise_error(GRPC::BadStatus)
         | 
| 350 481 | 
             
                    th.join
         | 
| 351 482 | 
             
                  end
         | 
| 483 | 
            +
             | 
| 484 | 
            +
                  it 'should raise ArgumentError if metadata contains invalid values' do
         | 
| 485 | 
            +
                    @metadata.merge!(k3: 3)
         | 
| 486 | 
            +
                    server_port = create_test_server
         | 
| 487 | 
            +
                    host = "localhost:#{server_port}"
         | 
| 488 | 
            +
                    stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 489 | 
            +
                    expect do
         | 
| 490 | 
            +
                      get_responses(stub)
         | 
| 491 | 
            +
                    end.to raise_error(ArgumentError,
         | 
| 492 | 
            +
                                       /Header values must be of type string or array/)
         | 
| 493 | 
            +
                  end
         | 
| 494 | 
            +
             | 
| 495 | 
            +
                  it 'the call terminates when there is an unmarshalling error' do
         | 
| 496 | 
            +
                    server_port = create_test_server
         | 
| 497 | 
            +
                    host = "localhost:#{server_port}"
         | 
| 498 | 
            +
                    th = run_server_streamer(@sent_msg, @replys, @pass)
         | 
| 499 | 
            +
                    stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 500 | 
            +
             | 
| 501 | 
            +
                    unmarshal = proc { fail(ArgumentError, 'test unmarshalling error') }
         | 
| 502 | 
            +
                    expect do
         | 
| 503 | 
            +
                      get_responses(stub, unmarshal: unmarshal).collect { |r| r }
         | 
| 504 | 
            +
                    end.to raise_error(ArgumentError, 'test unmarshalling error')
         | 
| 505 | 
            +
                    th.join
         | 
| 506 | 
            +
                  end
         | 
| 352 507 | 
             
                end
         | 
| 353 508 |  | 
| 354 509 | 
             
                describe 'without a call operation' do
         | 
| 355 | 
            -
                  def get_responses(stub)
         | 
| 356 | 
            -
                    e = stub.server_streamer(@method, @sent_msg, noop,  | 
| 357 | 
            -
                                             metadata:  | 
| 510 | 
            +
                  def get_responses(stub, unmarshal: noop)
         | 
| 511 | 
            +
                    e = stub.server_streamer(@method, @sent_msg, noop, unmarshal,
         | 
| 512 | 
            +
                                             metadata: @metadata)
         | 
| 358 513 | 
             
                    expect(e).to be_a(Enumerator)
         | 
| 359 514 | 
             
                    e
         | 
| 360 515 | 
             
                  end
         | 
| @@ -366,10 +521,10 @@ describe 'ClientStub' do | |
| 366 521 | 
             
                  after(:each) do
         | 
| 367 522 | 
             
                    @op.wait # make sure wait doesn't hang
         | 
| 368 523 | 
             
                  end
         | 
| 369 | 
            -
                  def get_responses(stub, run_start_call_first: false)
         | 
| 370 | 
            -
                    @op = stub.server_streamer(@method, @sent_msg, noop,  | 
| 524 | 
            +
                  def get_responses(stub, run_start_call_first: false, unmarshal: noop)
         | 
| 525 | 
            +
                    @op = stub.server_streamer(@method, @sent_msg, noop, unmarshal,
         | 
| 371 526 | 
             
                                               return_op: true,
         | 
| 372 | 
            -
                                               metadata:  | 
| 527 | 
            +
                                               metadata: @metadata)
         | 
| 373 528 | 
             
                    expect(@op).to be_a(GRPC::ActiveCall::Operation)
         | 
| 374 529 | 
             
                    @op.start_call if run_start_call_first
         | 
| 375 530 | 
             
                    e = @op.execute
         | 
| @@ -379,20 +534,41 @@ describe 'ClientStub' do | |
| 379 534 |  | 
| 380 535 | 
             
                  it_behaves_like 'server streaming'
         | 
| 381 536 |  | 
| 382 | 
            -
                   | 
| 537 | 
            +
                  def run_op_view_metadata_test(run_start_call_first)
         | 
| 383 538 | 
             
                    server_port = create_test_server
         | 
| 384 539 | 
             
                    host = "localhost:#{server_port}"
         | 
| 385 | 
            -
                     | 
| 386 | 
            -
             | 
| 540 | 
            +
                    @server_initial_md = { 'sk1' => 'sv1', 'sk2' => 'sv2' }
         | 
| 541 | 
            +
                    @server_trailing_md = { 'tk1' => 'tv1', 'tk2' => 'tv2' }
         | 
| 542 | 
            +
                    th = run_server_streamer(
         | 
| 543 | 
            +
                      @sent_msg, @replys, @pass,
         | 
| 544 | 
            +
                      expected_metadata: @metadata,
         | 
| 545 | 
            +
                      server_initial_md: @server_initial_md,
         | 
| 546 | 
            +
                      server_trailing_md: @server_trailing_md)
         | 
| 387 547 | 
             
                    stub = GRPC::ClientStub.new(host, :this_channel_is_insecure)
         | 
| 388 | 
            -
                    e = get_responses(stub, run_start_call_first:  | 
| 389 | 
            -
                    expect | 
| 548 | 
            +
                    e = get_responses(stub, run_start_call_first: run_start_call_first)
         | 
| 549 | 
            +
                    expect(e.collect { |r| r }).to eq(@replys)
         | 
| 390 550 | 
             
                    th.join
         | 
| 391 551 | 
             
                  end
         | 
| 552 | 
            +
             | 
| 553 | 
            +
                  it 'should send metadata to the server ok when start_call is run first' do
         | 
| 554 | 
            +
                    run_op_view_metadata_test(true)
         | 
| 555 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 556 | 
            +
                      @op, @server_initial_md, @server_trailing_md) do |responses|
         | 
| 557 | 
            +
                      responses.each { |r| p r }
         | 
| 558 | 
            +
                    end
         | 
| 559 | 
            +
                  end
         | 
| 560 | 
            +
             | 
| 561 | 
            +
                  it 'does not crash when used after the call has been finished' do
         | 
| 562 | 
            +
                    run_op_view_metadata_test(false)
         | 
| 563 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 564 | 
            +
                      @op, @server_initial_md, @server_trailing_md) do |responses|
         | 
| 565 | 
            +
                      responses.each { |r| p r }
         | 
| 566 | 
            +
                    end
         | 
| 567 | 
            +
                  end
         | 
| 392 568 | 
             
                end
         | 
| 393 569 | 
             
              end
         | 
| 394 570 |  | 
| 395 | 
            -
              describe '#bidi_streamer' do
         | 
| 571 | 
            +
              describe '#bidi_streamer', bidi: true do
         | 
| 396 572 | 
             
                before(:each) do
         | 
| 397 573 | 
             
                  @sent_msgs = Array.new(3) { |i| 'msg_' + (i + 1).to_s }
         | 
| 398 574 | 
             
                  @replys = Array.new(3) { |i| 'reply_' + (i + 1).to_s }
         | 
| @@ -401,7 +577,7 @@ describe 'ClientStub' do | |
| 401 577 | 
             
                end
         | 
| 402 578 |  | 
| 403 579 | 
             
                shared_examples 'bidi streaming' do
         | 
| 404 | 
            -
                  it 'supports sending all the requests first' | 
| 580 | 
            +
                  it 'supports sending all the requests first' do
         | 
| 405 581 | 
             
                    th = run_bidi_streamer_handle_inputs_first(@sent_msgs, @replys,
         | 
| 406 582 | 
             
                                                               @pass)
         | 
| 407 583 | 
             
                    stub = GRPC::ClientStub.new(@host, :this_channel_is_insecure)
         | 
| @@ -410,7 +586,7 @@ describe 'ClientStub' do | |
| 410 586 | 
             
                    th.join
         | 
| 411 587 | 
             
                  end
         | 
| 412 588 |  | 
| 413 | 
            -
                  it 'supports client-initiated ping pong' | 
| 589 | 
            +
                  it 'supports client-initiated ping pong' do
         | 
| 414 590 | 
             
                    th = run_bidi_streamer_echo_ping_pong(@sent_msgs, @pass, true)
         | 
| 415 591 | 
             
                    stub = GRPC::ClientStub.new(@host, :this_channel_is_insecure)
         | 
| 416 592 | 
             
                    e = get_responses(stub)
         | 
| @@ -418,18 +594,39 @@ describe 'ClientStub' do | |
| 418 594 | 
             
                    th.join
         | 
| 419 595 | 
             
                  end
         | 
| 420 596 |  | 
| 421 | 
            -
                  it 'supports a server-initiated ping pong' | 
| 597 | 
            +
                  it 'supports a server-initiated ping pong' do
         | 
| 422 598 | 
             
                    th = run_bidi_streamer_echo_ping_pong(@sent_msgs, @pass, false)
         | 
| 423 599 | 
             
                    stub = GRPC::ClientStub.new(@host, :this_channel_is_insecure)
         | 
| 424 600 | 
             
                    e = get_responses(stub)
         | 
| 425 601 | 
             
                    expect(e.collect { |r| r }).to eq(@sent_msgs)
         | 
| 426 602 | 
             
                    th.join
         | 
| 427 603 | 
             
                  end
         | 
| 604 | 
            +
             | 
| 605 | 
            +
                  it 'should raise an error if the status is not ok' do
         | 
| 606 | 
            +
                    th = run_bidi_streamer_echo_ping_pong(@sent_msgs, @fail, false)
         | 
| 607 | 
            +
                    stub = GRPC::ClientStub.new(@host, :this_channel_is_insecure)
         | 
| 608 | 
            +
                    e = get_responses(stub)
         | 
| 609 | 
            +
                    expect { e.collect { |r| r } }.to raise_error(GRPC::BadStatus)
         | 
| 610 | 
            +
                    th.join
         | 
| 611 | 
            +
                  end
         | 
| 612 | 
            +
             | 
| 613 | 
            +
                  # TODO: add test for metadata-related ArgumentError in a bidi call once
         | 
| 614 | 
            +
                  # issue mentioned in https://github.com/grpc/grpc/issues/10526 is fixed
         | 
| 615 | 
            +
             | 
| 616 | 
            +
                  it 'should send metadata to the server ok' do
         | 
| 617 | 
            +
                    th = run_bidi_streamer_echo_ping_pong(@sent_msgs, @pass, true,
         | 
| 618 | 
            +
                                                          expected_metadata: @metadata)
         | 
| 619 | 
            +
                    stub = GRPC::ClientStub.new(@host, :this_channel_is_insecure)
         | 
| 620 | 
            +
                    e = get_responses(stub)
         | 
| 621 | 
            +
                    expect(e.collect { |r| r }).to eq(@sent_msgs)
         | 
| 622 | 
            +
                    th.join
         | 
| 623 | 
            +
                  end
         | 
| 428 624 | 
             
                end
         | 
| 429 625 |  | 
| 430 626 | 
             
                describe 'without a call operation' do
         | 
| 431 627 | 
             
                  def get_responses(stub)
         | 
| 432 | 
            -
                    e = stub.bidi_streamer(@method, @sent_msgs, noop, noop | 
| 628 | 
            +
                    e = stub.bidi_streamer(@method, @sent_msgs, noop, noop,
         | 
| 629 | 
            +
                                           metadata: @metadata)
         | 
| 433 630 | 
             
                    expect(e).to be_a(Enumerator)
         | 
| 434 631 | 
             
                    e
         | 
| 435 632 | 
             
                  end
         | 
| @@ -443,7 +640,8 @@ describe 'ClientStub' do | |
| 443 640 | 
             
                  end
         | 
| 444 641 | 
             
                  def get_responses(stub, run_start_call_first: false)
         | 
| 445 642 | 
             
                    @op = stub.bidi_streamer(@method, @sent_msgs, noop, noop,
         | 
| 446 | 
            -
                                             return_op: true | 
| 643 | 
            +
                                             return_op: true,
         | 
| 644 | 
            +
                                             metadata: @metadata)
         | 
| 447 645 | 
             
                    expect(@op).to be_a(GRPC::ActiveCall::Operation)
         | 
| 448 646 | 
             
                    @op.start_call if run_start_call_first
         | 
| 449 647 | 
             
                    e = @op.execute
         | 
| @@ -453,27 +651,53 @@ describe 'ClientStub' do | |
| 453 651 |  | 
| 454 652 | 
             
                  it_behaves_like 'bidi streaming'
         | 
| 455 653 |  | 
| 456 | 
            -
                   | 
| 457 | 
            -
                     | 
| 458 | 
            -
             | 
| 654 | 
            +
                  def run_op_view_metadata_test(run_start_call_first)
         | 
| 655 | 
            +
                    @server_initial_md = { 'sk1' => 'sv1', 'sk2' => 'sv2' }
         | 
| 656 | 
            +
                    @server_trailing_md = { 'tk1' => 'tv1', 'tk2' => 'tv2' }
         | 
| 657 | 
            +
                    th = run_bidi_streamer_echo_ping_pong(
         | 
| 658 | 
            +
                      @sent_msgs, @pass, true,
         | 
| 659 | 
            +
                      expected_metadata: @metadata,
         | 
| 660 | 
            +
                      server_initial_md: @server_initial_md,
         | 
| 661 | 
            +
                      server_trailing_md: @server_trailing_md)
         | 
| 459 662 | 
             
                    stub = GRPC::ClientStub.new(@host, :this_channel_is_insecure)
         | 
| 460 | 
            -
                    e = get_responses(stub, run_start_call_first:  | 
| 461 | 
            -
                    expect(e.collect { |r| r }).to eq(@ | 
| 663 | 
            +
                    e = get_responses(stub, run_start_call_first: run_start_call_first)
         | 
| 664 | 
            +
                    expect(e.collect { |r| r }).to eq(@sent_msgs)
         | 
| 462 665 | 
             
                    th.join
         | 
| 463 666 | 
             
                  end
         | 
| 667 | 
            +
             | 
| 668 | 
            +
                  it 'can run start_call before executing the call' do
         | 
| 669 | 
            +
                    run_op_view_metadata_test(true)
         | 
| 670 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 671 | 
            +
                      @op, @server_initial_md, @server_trailing_md) do |responses|
         | 
| 672 | 
            +
                      responses.each { |r| p r }
         | 
| 673 | 
            +
                    end
         | 
| 674 | 
            +
                  end
         | 
| 675 | 
            +
             | 
| 676 | 
            +
                  it 'doesnt crash when op_view used after call has finished' do
         | 
| 677 | 
            +
                    run_op_view_metadata_test(false)
         | 
| 678 | 
            +
                    check_op_view_of_finished_client_call(
         | 
| 679 | 
            +
                      @op, @server_initial_md, @server_trailing_md) do |responses|
         | 
| 680 | 
            +
                      responses.each { |r| p r }
         | 
| 681 | 
            +
                    end
         | 
| 682 | 
            +
                  end
         | 
| 464 683 | 
             
                end
         | 
| 465 684 | 
             
              end
         | 
| 466 685 |  | 
| 467 | 
            -
              def run_server_streamer(expected_input, replys, status, | 
| 468 | 
            -
             | 
| 686 | 
            +
              def run_server_streamer(expected_input, replys, status,
         | 
| 687 | 
            +
                                      expected_metadata: {},
         | 
| 688 | 
            +
                                      server_initial_md: {},
         | 
| 689 | 
            +
                                      server_trailing_md: {})
         | 
| 690 | 
            +
                wanted_metadata = expected_metadata.clone
         | 
| 469 691 | 
             
                wakey_thread do |notifier|
         | 
| 470 | 
            -
                  c = expect_server_to_be_invoked( | 
| 692 | 
            +
                  c = expect_server_to_be_invoked(
         | 
| 693 | 
            +
                    notifier, metadata_to_send: server_initial_md)
         | 
| 471 694 | 
             
                  wanted_metadata.each do |k, v|
         | 
| 472 695 | 
             
                    expect(c.metadata[k.to_s]).to eq(v)
         | 
| 473 696 | 
             
                  end
         | 
| 474 697 | 
             
                  expect(c.remote_read).to eq(expected_input)
         | 
| 475 698 | 
             
                  replys.each { |r| c.remote_send(r) }
         | 
| 476 | 
            -
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true | 
| 699 | 
            +
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true,
         | 
| 700 | 
            +
                                metadata: server_trailing_md)
         | 
| 477 701 | 
             
                end
         | 
| 478 702 | 
             
              end
         | 
| 479 703 |  | 
| @@ -487,9 +711,17 @@ describe 'ClientStub' do | |
| 487 711 | 
             
                end
         | 
| 488 712 | 
             
              end
         | 
| 489 713 |  | 
| 490 | 
            -
              def run_bidi_streamer_echo_ping_pong(expected_inputs, status, client_starts | 
| 714 | 
            +
              def run_bidi_streamer_echo_ping_pong(expected_inputs, status, client_starts,
         | 
| 715 | 
            +
                                                   expected_metadata: {},
         | 
| 716 | 
            +
                                                   server_initial_md: {},
         | 
| 717 | 
            +
                                                   server_trailing_md: {})
         | 
| 718 | 
            +
                wanted_metadata = expected_metadata.clone
         | 
| 491 719 | 
             
                wakey_thread do |notifier|
         | 
| 492 | 
            -
                  c = expect_server_to_be_invoked( | 
| 720 | 
            +
                  c = expect_server_to_be_invoked(
         | 
| 721 | 
            +
                    notifier, metadata_to_send: server_initial_md)
         | 
| 722 | 
            +
                  wanted_metadata.each do |k, v|
         | 
| 723 | 
            +
                    expect(c.metadata[k.to_s]).to eq(v)
         | 
| 724 | 
            +
                  end
         | 
| 493 725 | 
             
                  expected_inputs.each do |i|
         | 
| 494 726 | 
             
                    if client_starts
         | 
| 495 727 | 
             
                      expect(c.remote_read).to eq(i)
         | 
| @@ -499,33 +731,44 @@ describe 'ClientStub' do | |
| 499 731 | 
             
                      expect(c.remote_read).to eq(i)
         | 
| 500 732 | 
             
                    end
         | 
| 501 733 | 
             
                  end
         | 
| 502 | 
            -
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true | 
| 734 | 
            +
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true,
         | 
| 735 | 
            +
                                metadata: server_trailing_md)
         | 
| 503 736 | 
             
                end
         | 
| 504 737 | 
             
              end
         | 
| 505 738 |  | 
| 506 | 
            -
              def run_client_streamer(expected_inputs, resp, status, | 
| 507 | 
            -
             | 
| 739 | 
            +
              def run_client_streamer(expected_inputs, resp, status,
         | 
| 740 | 
            +
                                      expected_metadata: {},
         | 
| 741 | 
            +
                                      server_initial_md: {},
         | 
| 742 | 
            +
                                      server_trailing_md: {})
         | 
| 743 | 
            +
                wanted_metadata = expected_metadata.clone
         | 
| 508 744 | 
             
                wakey_thread do |notifier|
         | 
| 509 | 
            -
                  c = expect_server_to_be_invoked( | 
| 745 | 
            +
                  c = expect_server_to_be_invoked(
         | 
| 746 | 
            +
                    notifier, metadata_to_send: server_initial_md)
         | 
| 510 747 | 
             
                  expected_inputs.each { |i| expect(c.remote_read).to eq(i) }
         | 
| 511 748 | 
             
                  wanted_metadata.each do |k, v|
         | 
| 512 749 | 
             
                    expect(c.metadata[k.to_s]).to eq(v)
         | 
| 513 750 | 
             
                  end
         | 
| 514 751 | 
             
                  c.remote_send(resp)
         | 
| 515 | 
            -
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true | 
| 752 | 
            +
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true,
         | 
| 753 | 
            +
                                metadata: server_trailing_md)
         | 
| 516 754 | 
             
                end
         | 
| 517 755 | 
             
              end
         | 
| 518 756 |  | 
| 519 | 
            -
              def run_request_response(expected_input, resp, status, | 
| 520 | 
            -
             | 
| 757 | 
            +
              def run_request_response(expected_input, resp, status,
         | 
| 758 | 
            +
                                       expected_metadata: {},
         | 
| 759 | 
            +
                                       server_initial_md: {},
         | 
| 760 | 
            +
                                       server_trailing_md: {})
         | 
| 761 | 
            +
                wanted_metadata = expected_metadata.clone
         | 
| 521 762 | 
             
                wakey_thread do |notifier|
         | 
| 522 | 
            -
                  c = expect_server_to_be_invoked( | 
| 763 | 
            +
                  c = expect_server_to_be_invoked(
         | 
| 764 | 
            +
                    notifier, metadata_to_send: server_initial_md)
         | 
| 523 765 | 
             
                  expect(c.remote_read).to eq(expected_input)
         | 
| 524 766 | 
             
                  wanted_metadata.each do |k, v|
         | 
| 525 767 | 
             
                    expect(c.metadata[k.to_s]).to eq(v)
         | 
| 526 768 | 
             
                  end
         | 
| 527 769 | 
             
                  c.remote_send(resp)
         | 
| 528 | 
            -
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true | 
| 770 | 
            +
                  c.send_status(status, status == @pass ? 'OK' : 'NOK', true,
         | 
| 771 | 
            +
                                metadata: server_trailing_md)
         | 
| 529 772 | 
             
                end
         | 
| 530 773 | 
             
              end
         | 
| 531 774 |  | 
| @@ -543,13 +786,13 @@ describe 'ClientStub' do | |
| 543 786 | 
             
                @server.add_http2_port('0.0.0.0:0', :this_port_is_insecure)
         | 
| 544 787 | 
             
              end
         | 
| 545 788 |  | 
| 546 | 
            -
              def expect_server_to_be_invoked(notifier)
         | 
| 789 | 
            +
              def expect_server_to_be_invoked(notifier, metadata_to_send: nil)
         | 
| 547 790 | 
             
                @server.start
         | 
| 548 791 | 
             
                notifier.notify(nil)
         | 
| 549 792 | 
             
                recvd_rpc = @server.request_call
         | 
| 550 793 | 
             
                recvd_call = recvd_rpc.call
         | 
| 551 794 | 
             
                recvd_call.metadata = recvd_rpc.metadata
         | 
| 552 | 
            -
                recvd_call.run_batch(SEND_INITIAL_METADATA =>  | 
| 795 | 
            +
                recvd_call.run_batch(SEND_INITIAL_METADATA => metadata_to_send)
         | 
| 553 796 | 
             
                GRPC::ActiveCall.new(recvd_call, noop, noop, INFINITE_FUTURE,
         | 
| 554 797 | 
             
                                     metadata_received: true)
         | 
| 555 798 | 
             
              end
         |