grpc 1.4.1 → 1.4.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grpc might be problematic. Click here for more details.

@@ -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 op_notifier a Notifier used to signal completion
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, op_notifier, &blk)
83
- @op_notifier = op_notifier
84
- @enq_th = Thread.new { write_loop(requests) }
85
- read_loop(&blk)
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
- def run_on_server(gen_each_reply)
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(read_loop(is_client: false))
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(read_loop(is_client: false), @req_view)
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
- def write_loop(requests, is_client: true)
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.remote_read
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.remote_read
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)
@@ -433,6 +433,7 @@ module GRPC
433
433
  metadata_received: true,
434
434
  started: false,
435
435
  metadata_to_send: connect_md)
436
+ c.attach_peer_cert(an_rpc.call.peer_cert)
436
437
  mth = an_rpc.method.to_sym
437
438
  [c, mth]
438
439
  end
@@ -29,5 +29,5 @@
29
29
 
30
30
  # GRPC contains the General RPC module.
31
31
  module GRPC
32
- VERSION = '1.4.1'
32
+ VERSION = '1.4.5'
33
33
  end
@@ -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
- it 'modifies metadata with CallCredentials' do
467
- auth_proc = proc { { 'k1' => 'updated-v1' } }
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
- md = { 'k2' => 'v2' }
470
- expected_md = { 'k1' => 'updated-v1', 'k2' => 'v2' }
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 => md
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.finished }.to_not raise_error
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.finished }.to_not raise_error
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
- it 'should send metadata to the server ok' do
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
- k1: 'v1', k2: 'v2')
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: { k1: 'v1', k2: 'v2' },
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
- return_op: true,
222
- metadata: { k1: 'v1', k2: 'v2' },
223
- deadline: from_relative_time(2),
224
- credentials: credentials)
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
- it 'sends metadata to the server ok when running start_call first' do
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
- th = run_request_response(@sent_msg, @resp, @pass,
238
- k1: 'v1', k2: 'v2')
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(get_response(stub)).to eq(@resp)
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, **@metadata)
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
- return_op: true, metadata: @metadata)
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
- it 'sends metadata to the server ok when running start_call first' do
309
- th = run_client_streamer(@sent_msgs, @resp, @pass, **@metadata)
310
- expect(get_response(@stub, run_start_call_first: true)).to eq(@resp)
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, noop,
357
- metadata: { k1: 'v1', k2: 'v2' })
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, 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: { k1: 'v1', k2: 'v2' })
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
- it 'should send metadata to the server ok when start_call is run first' do
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
- th = run_server_streamer(@sent_msg, @replys, @fail,
386
- k1: 'v1', k2: 'v2')
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: true)
389
- expect { e.collect { |r| r } }.to raise_error(GRPC::BadStatus)
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', bidi: true do
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', bidi: true do
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', bidi: true do
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
- it 'can run start_call before executing the call' do
457
- th = run_bidi_streamer_handle_inputs_first(@sent_msgs, @replys,
458
- @pass)
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: true)
461
- expect(e.collect { |r| r }).to eq(@replys)
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, **kw)
468
- wanted_metadata = kw.clone
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(notifier)
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(notifier)
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, **kw)
507
- wanted_metadata = kw.clone
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(notifier)
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, **kw)
520
- wanted_metadata = kw.clone
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(notifier)
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 => nil)
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