grpc 1.0.1-universal-darwin → 1.1.2-universal-darwin

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.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/etc/roots.pem +39 -111
  3. data/src/ruby/ext/grpc/extconf.rb +0 -1
  4. data/src/ruby/ext/grpc/rb_byte_buffer.c +8 -7
  5. data/src/ruby/ext/grpc/rb_call.c +15 -5
  6. data/src/ruby/ext/grpc/rb_channel.c +1 -1
  7. data/src/ruby/ext/grpc/rb_compression_options.c +466 -0
  8. data/src/ruby/ext/grpc/rb_compression_options.h +44 -0
  9. data/src/ruby/ext/grpc/rb_grpc.c +3 -1
  10. data/src/ruby/ext/grpc/rb_grpc_imports.generated.c +198 -190
  11. data/src/ruby/ext/grpc/rb_grpc_imports.generated.h +306 -294
  12. data/src/ruby/ext/grpc/rb_server.c +18 -12
  13. data/src/ruby/lib/grpc/2.0/grpc_c.bundle +0 -0
  14. data/src/ruby/lib/grpc/2.1/grpc_c.bundle +0 -0
  15. data/src/ruby/lib/grpc/2.2/grpc_c.bundle +0 -0
  16. data/src/ruby/lib/grpc/2.3/grpc_c.bundle +0 -0
  17. data/src/ruby/lib/grpc/2.4/grpc_c.bundle +0 -0
  18. data/src/ruby/lib/grpc/errors.rb +154 -2
  19. data/src/ruby/lib/grpc/generic/active_call.rb +144 -63
  20. data/src/ruby/lib/grpc/generic/bidi_call.rb +18 -2
  21. data/src/ruby/lib/grpc/generic/client_stub.rb +7 -5
  22. data/src/ruby/lib/grpc/generic/rpc_desc.rb +39 -13
  23. data/src/ruby/lib/grpc/generic/rpc_server.rb +51 -24
  24. data/src/ruby/lib/grpc/generic/service.rb +3 -2
  25. data/src/ruby/lib/grpc/version.rb +1 -1
  26. data/src/ruby/pb/grpc/health/checker.rb +3 -1
  27. data/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb +7 -0
  28. data/src/ruby/pb/test/client.rb +307 -7
  29. data/src/ruby/pb/test/server.rb +26 -1
  30. data/src/ruby/spec/compression_options_spec.rb +164 -0
  31. data/src/ruby/spec/error_sanity_spec.rb +64 -0
  32. data/src/ruby/spec/generic/active_call_spec.rb +290 -12
  33. data/src/ruby/spec/generic/client_stub_spec.rb +91 -41
  34. data/src/ruby/spec/generic/rpc_desc_spec.rb +36 -16
  35. data/src/ruby/spec/generic/rpc_server_pool_spec.rb +22 -28
  36. data/src/ruby/spec/generic/rpc_server_spec.rb +6 -6
  37. data/src/ruby/spec/pb/health/checker_spec.rb +27 -19
  38. data/src/ruby/spec/spec_helper.rb +2 -0
  39. metadata +18 -8
@@ -129,6 +129,27 @@ def nulls(l)
129
129
  [].pack('x' * l).force_encoding('ascii-8bit')
130
130
  end
131
131
 
132
+ def maybe_echo_metadata(_call)
133
+
134
+ # these are consistent for all interop tests
135
+ initial_metadata_key = "x-grpc-test-echo-initial"
136
+ trailing_metadata_key = "x-grpc-test-echo-trailing-bin"
137
+
138
+ if _call.metadata.has_key?(initial_metadata_key)
139
+ _call.metadata_to_send[initial_metadata_key] = _call.metadata[initial_metadata_key]
140
+ end
141
+ if _call.metadata.has_key?(trailing_metadata_key)
142
+ _call.output_metadata[trailing_metadata_key] = _call.metadata[trailing_metadata_key]
143
+ end
144
+ end
145
+
146
+ def maybe_echo_status_and_message(req)
147
+ unless req.response_status.nil?
148
+ fail GRPC::BadStatus.new_status_exception(
149
+ req.response_status.code, req.response_status.message)
150
+ end
151
+ end
152
+
132
153
  # A FullDuplexEnumerator passes requests to a block and yields generated responses
133
154
  class FullDuplexEnumerator
134
155
  include Grpc::Testing
@@ -143,6 +164,7 @@ class FullDuplexEnumerator
143
164
  begin
144
165
  cls = StreamingOutputCallResponse
145
166
  @requests.each do |req|
167
+ maybe_echo_status_and_message(req)
146
168
  req.response_parameters.each do |params|
147
169
  resp_size = params.size
148
170
  GRPC.logger.info("read a req, response size is #{resp_size}")
@@ -170,6 +192,8 @@ class TestTarget < Grpc::Testing::TestService::Service
170
192
  end
171
193
 
172
194
  def unary_call(simple_req, _call)
195
+ maybe_echo_metadata(_call)
196
+ maybe_echo_status_and_message(simple_req)
173
197
  req_size = simple_req.response_size
174
198
  SimpleResponse.new(payload: Payload.new(type: :COMPRESSABLE,
175
199
  body: nulls(req_size)))
@@ -189,7 +213,8 @@ class TestTarget < Grpc::Testing::TestService::Service
189
213
  end
190
214
  end
191
215
 
192
- def full_duplex_call(reqs)
216
+ def full_duplex_call(reqs, _call)
217
+ maybe_echo_metadata(_call)
193
218
  # reqs is a lazy Enumerator of the requests sent by the client.
194
219
  FullDuplexEnumerator.new(reqs).each_item
195
220
  end
@@ -0,0 +1,164 @@
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
+ describe GRPC::Core::CompressionOptions do
33
+ # Note these constants should be updated
34
+ # according to what the core lib provides.
35
+
36
+ # Names of supported compression algorithms
37
+ ALGORITHMS = [:identity, :deflate, :gzip]
38
+
39
+ # Names of valid supported compression levels
40
+ COMPRESS_LEVELS = [:none, :low, :medium, :high]
41
+
42
+ it 'implements to_s' do
43
+ expect { GRPC::Core::CompressionOptions.new.to_s }.to_not raise_error
44
+ end
45
+
46
+ it '#to_channel_arg_hash gives the same result as #to_hash' do
47
+ options = GRPC::Core::CompressionOptions.new
48
+ expect(options.to_channel_arg_hash).to eq(options.to_hash)
49
+ end
50
+
51
+ # Test the normal call sequence of creating an instance
52
+ # and then obtaining the resulting channel-arg hash that
53
+ # corresponds to the compression settings of the instance
54
+ describe 'creating, reading, and converting to channel args hash' do
55
+ it 'works when no optional args were provided' do
56
+ options = GRPC::Core::CompressionOptions.new
57
+
58
+ ALGORITHMS.each do |algorithm|
59
+ expect(options.algorithm_enabled?(algorithm)).to be true
60
+ end
61
+
62
+ expect(options.disabled_algorithms).to be_empty
63
+ expect(options.default_algorithm).to be nil
64
+ expect(options.default_level).to be nil
65
+ expect(options.to_hash).to be_instance_of(Hash)
66
+ end
67
+
68
+ it 'works when disabling multiple algorithms' do
69
+ options = GRPC::Core::CompressionOptions.new(
70
+ default_algorithm: :identity,
71
+ default_level: :none,
72
+ disabled_algorithms: [:gzip, :deflate]
73
+ )
74
+
75
+ [:gzip, :deflate].each do |algorithm|
76
+ expect(options.algorithm_enabled?(algorithm)).to be false
77
+ expect(options.disabled_algorithms.include?(algorithm)).to be true
78
+ end
79
+
80
+ expect(options.default_algorithm).to be(:identity)
81
+ expect(options.default_level).to be(:none)
82
+ expect(options.to_hash).to be_instance_of(Hash)
83
+ end
84
+
85
+ it 'works when all optional args have been set' do
86
+ options = GRPC::Core::CompressionOptions.new(
87
+ default_algorithm: :gzip,
88
+ default_level: :low,
89
+ disabled_algorithms: [:deflate]
90
+ )
91
+
92
+ expect(options.algorithm_enabled?(:deflate)).to be false
93
+ expect(options.algorithm_enabled?(:gzip)).to be true
94
+ expect(options.disabled_algorithms).to eq([:deflate])
95
+
96
+ expect(options.default_algorithm).to be(:gzip)
97
+ expect(options.default_level).to be(:low)
98
+ expect(options.to_hash).to be_instance_of(Hash)
99
+ end
100
+
101
+ it 'doesnt fail when no algorithms are disabled' do
102
+ options = GRPC::Core::CompressionOptions.new(
103
+ default_algorithm: :identity,
104
+ default_level: :high
105
+ )
106
+
107
+ ALGORITHMS.each do |algorithm|
108
+ expect(options.algorithm_enabled?(algorithm)).to be(true)
109
+ end
110
+
111
+ expect(options.disabled_algorithms).to be_empty
112
+ expect(options.default_algorithm).to be(:identity)
113
+ expect(options.default_level).to be(:high)
114
+ expect(options.to_hash).to be_instance_of(Hash)
115
+ end
116
+ end
117
+
118
+ describe '#new with bad parameters' do
119
+ it 'should fail with more than one parameter' do
120
+ blk = proc { GRPC::Core::CompressionOptions.new(:gzip, :none) }
121
+ expect { blk.call }.to raise_error
122
+ end
123
+
124
+ it 'should fail with a non-hash parameter' do
125
+ blk = proc { GRPC::Core::CompressionOptions.new(:gzip) }
126
+ expect { blk.call }.to raise_error
127
+ end
128
+ end
129
+
130
+ describe '#default_algorithm' do
131
+ it 'returns nil if unset' do
132
+ options = GRPC::Core::CompressionOptions.new
133
+ expect(options.default_algorithm).to be(nil)
134
+ end
135
+ end
136
+
137
+ describe '#default_level' do
138
+ it 'returns nil if unset' do
139
+ options = GRPC::Core::CompressionOptions.new
140
+ expect(options.default_level).to be(nil)
141
+ end
142
+ end
143
+
144
+ describe '#disabled_algorithms' do
145
+ it 'returns an empty list if no algorithms were disabled' do
146
+ options = GRPC::Core::CompressionOptions.new
147
+ expect(options.disabled_algorithms).to be_empty
148
+ end
149
+ end
150
+
151
+ describe '#algorithm_enabled?' do
152
+ [:none, :any, 'gzip', Object.new, 1].each do |name|
153
+ it "should fail for parameter ${name} of class #{name.class}" do
154
+ options = GRPC::Core::CompressionOptions.new(
155
+ disabled_algorithms: [:gzip])
156
+
157
+ blk = proc do
158
+ options.algorithm_enabled?(name)
159
+ end
160
+ expect { blk.call }.to raise_error
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,64 @@
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
+ StatusCodes = GRPC::Core::StatusCodes
33
+
34
+ describe StatusCodes do
35
+ # convert upper snake-case to camel case.
36
+ # e.g., DEADLINE_EXCEEDED -> DeadlineExceeded
37
+ def upper_snake_to_camel(name)
38
+ name.to_s.split('_').map(&:downcase).map(&:capitalize).join('')
39
+ end
40
+
41
+ StatusCodes.constants.each do |status_name|
42
+ it 'there is a subclass of BadStatus corresponding to StatusCode: ' \
43
+ "#{status_name} that has code: #{StatusCodes.const_get(status_name)}" do
44
+ camel_case = upper_snake_to_camel(status_name)
45
+ error_class = GRPC.const_get(camel_case)
46
+ # expect the error class to be a subclass of BadStatus
47
+ expect(error_class < GRPC::BadStatus)
48
+
49
+ error_object = error_class.new
50
+ # check that the code matches the int value of the error's constant
51
+ status_code = StatusCodes.const_get(status_name)
52
+ expect(error_object.code).to eq(status_code)
53
+
54
+ # check default parameters
55
+ expect(error_object.details).to eq('unknown cause')
56
+ expect(error_object.metadata).to eq({})
57
+
58
+ # check that the BadStatus factory for creates the correct
59
+ # exception too
60
+ from_factory = GRPC::BadStatus.new_status_exception(status_code)
61
+ expect(from_factory.is_a?(error_class)).to be(true)
62
+ end
63
+ end
64
+ end
@@ -60,8 +60,10 @@ describe GRPC::ActiveCall do
60
60
  end
61
61
 
62
62
  describe '#multi_req_view' do
63
- it 'exposes a fixed subset of the ActiveCall methods' do
64
- want = %w(cancelled?, deadline, each_remote_read, metadata, shutdown)
63
+ it 'exposes a fixed subset of the ActiveCall.methods' do
64
+ want = %w(cancelled?, deadline, each_remote_read, metadata, \
65
+ shutdown, peer, peer_cert, send_initial_metadata, \
66
+ initial_metadata_sent)
65
67
  v = @client_call.multi_req_view
66
68
  want.each do |w|
67
69
  expect(v.methods.include?(w))
@@ -70,8 +72,10 @@ describe GRPC::ActiveCall do
70
72
  end
71
73
 
72
74
  describe '#single_req_view' do
73
- it 'exposes a fixed subset of the ActiveCall methods' do
74
- want = %w(cancelled?, deadline, metadata, shutdown)
75
+ it 'exposes a fixed subset of the ActiveCall.methods' do
76
+ want = %w(cancelled?, deadline, metadata, shutdown, \
77
+ send_initial_metadata, metadata_to_send, \
78
+ merge_metadata_to_send, initial_metadata_sent)
75
79
  v = @client_call.single_req_view
76
80
  want.each do |w|
77
81
  expect(v.methods.include?(w))
@@ -133,6 +137,8 @@ describe GRPC::ActiveCall do
133
137
  msg = 'message is a string'
134
138
  client_call.write_flag = f
135
139
  client_call.remote_send(msg)
140
+ # flush the message in case writes are set to buffered
141
+ call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil) if f == 1
136
142
 
137
143
  # confirm that the message was marshalled
138
144
  recvd_rpc = @server.request_call
@@ -149,6 +155,146 @@ describe GRPC::ActiveCall do
149
155
  end
150
156
  end
151
157
 
158
+ describe 'sending initial metadata', send_initial_metadata: true do
159
+ it 'sends metadata before sending a message if it hasnt been sent yet' do
160
+ call = make_test_call
161
+ @client_call = ActiveCall.new(
162
+ call,
163
+ @pass_through,
164
+ @pass_through,
165
+ deadline,
166
+ started: false)
167
+
168
+ metadata = { key: 'dummy_val', other: 'other_val' }
169
+ expect(@client_call.metadata_sent).to eq(false)
170
+ @client_call.merge_metadata_to_send(metadata)
171
+
172
+ message = 'dummy message'
173
+
174
+ expect(call).to(
175
+ receive(:run_batch)
176
+ .with(
177
+ hash_including(
178
+ CallOps::SEND_INITIAL_METADATA => metadata)).once)
179
+
180
+ expect(call).to(
181
+ receive(:run_batch).with(hash_including(
182
+ CallOps::SEND_MESSAGE => message)).once)
183
+ @client_call.remote_send(message)
184
+
185
+ expect(@client_call.metadata_sent).to eq(true)
186
+ end
187
+
188
+ it 'doesnt send metadata if it thinks its already been sent' do
189
+ call = make_test_call
190
+
191
+ @client_call = ActiveCall.new(call,
192
+ @pass_through,
193
+ @pass_through,
194
+ deadline)
195
+
196
+ expect(@client_call.metadata_sent).to eql(true)
197
+ expect(call).to(
198
+ receive(:run_batch).with(hash_including(
199
+ CallOps::SEND_INITIAL_METADATA)).never)
200
+
201
+ @client_call.remote_send('test message')
202
+ end
203
+
204
+ it 'sends metadata if it is explicitly sent and ok to do so' do
205
+ call = make_test_call
206
+
207
+ @client_call = ActiveCall.new(call,
208
+ @pass_through,
209
+ @pass_through,
210
+ deadline,
211
+ started: false)
212
+
213
+ expect(@client_call.metadata_sent).to eql(false)
214
+
215
+ metadata = { test_key: 'val' }
216
+ @client_call.merge_metadata_to_send(metadata)
217
+ expect(@client_call.metadata_to_send).to eq(metadata)
218
+
219
+ expect(call).to(
220
+ receive(:run_batch).with(hash_including(
221
+ CallOps::SEND_INITIAL_METADATA =>
222
+ metadata)).once)
223
+ @client_call.send_initial_metadata
224
+ end
225
+
226
+ it 'explicit sending does nothing if metadata has already been sent' do
227
+ call = make_test_call
228
+
229
+ @client_call = ActiveCall.new(call,
230
+ @pass_through,
231
+ @pass_through,
232
+ deadline)
233
+
234
+ expect(@client_call.metadata_sent).to eql(true)
235
+
236
+ blk = proc do
237
+ @client_call.send_initial_metadata
238
+ end
239
+
240
+ expect { blk.call }.to_not raise_error
241
+ end
242
+ end
243
+
244
+ describe '#merge_metadata_to_send', merge_metadata_to_send: true do
245
+ it 'adds to existing metadata when there is existing metadata to send' do
246
+ call = make_test_call
247
+ starting_metadata = {
248
+ k1: 'key1_val',
249
+ k2: 'key2_val',
250
+ k3: 'key3_val'
251
+ }
252
+
253
+ @client_call = ActiveCall.new(
254
+ call,
255
+ @pass_through, @pass_through,
256
+ deadline,
257
+ started: false,
258
+ metadata_to_send: starting_metadata)
259
+
260
+ expect(@client_call.metadata_to_send).to eq(starting_metadata)
261
+
262
+ @client_call.merge_metadata_to_send(
263
+ k3: 'key3_new_val',
264
+ k4: 'key4_val')
265
+
266
+ expected_md_to_send = {
267
+ k1: 'key1_val',
268
+ k2: 'key2_val',
269
+ k3: 'key3_new_val',
270
+ k4: 'key4_val' }
271
+
272
+ expect(@client_call.metadata_to_send).to eq(expected_md_to_send)
273
+
274
+ @client_call.merge_metadata_to_send(k5: 'key5_val')
275
+ expected_md_to_send.merge!(k5: 'key5_val')
276
+ expect(@client_call.metadata_to_send).to eq(expected_md_to_send)
277
+ end
278
+
279
+ it 'fails when initial metadata has already been sent' do
280
+ call = make_test_call
281
+ @client_call = ActiveCall.new(
282
+ call,
283
+ @pass_through,
284
+ @pass_through,
285
+ deadline,
286
+ started: true)
287
+
288
+ expect(@client_call.metadata_sent).to eq(true)
289
+
290
+ blk = proc do
291
+ @client_call.merge_metadata_to_send(k1: 'key1_val')
292
+ end
293
+
294
+ expect { blk.call }.to raise_error
295
+ end
296
+ end
297
+
152
298
  describe '#client_invoke' do
153
299
  it 'sends metadata to the server when present' do
154
300
  call = make_test_call
@@ -163,7 +309,26 @@ describe GRPC::ActiveCall do
163
309
  end
164
310
  end
165
311
 
166
- describe '#remote_read' do
312
+ describe '#send_status', send_status: true do
313
+ it 'works when no metadata or messages have been sent yet' do
314
+ call = make_test_call
315
+ ActiveCall.client_invoke(call)
316
+
317
+ recvd_rpc = @server.request_call
318
+ server_call = ActiveCall.new(
319
+ recvd_rpc.call,
320
+ @pass_through,
321
+ @pass_through,
322
+ deadline,
323
+ started: false)
324
+
325
+ expect(server_call.metadata_sent).to eq(false)
326
+ blk = proc { server_call.send_status(OK) }
327
+ expect { blk.call }.to_not raise_error
328
+ end
329
+ end
330
+
331
+ describe '#remote_read', remote_read: true do
167
332
  it 'reads the response sent by a server' do
168
333
  call = make_test_call
169
334
  ActiveCall.client_invoke(call)
@@ -205,6 +370,31 @@ describe GRPC::ActiveCall do
205
370
  expect(client_call.metadata).to eq(expected)
206
371
  end
207
372
 
373
+ it 'get a status from server when nothing else sent from server' do
374
+ client_call = make_test_call
375
+ ActiveCall.client_invoke(client_call)
376
+
377
+ recvd_rpc = @server.request_call
378
+ recvd_call = recvd_rpc.call
379
+
380
+ server_call = ActiveCall.new(
381
+ recvd_call,
382
+ @pass_through,
383
+ @pass_through,
384
+ deadline,
385
+ started: false)
386
+
387
+ server_call.send_status(OK, 'OK')
388
+
389
+ # Check that we can receive initial metadata and a status
390
+ client_call.run_batch(
391
+ CallOps::RECV_INITIAL_METADATA => nil)
392
+ batch_result = client_call.run_batch(
393
+ CallOps::RECV_STATUS_ON_CLIENT => nil)
394
+
395
+ expect(batch_result.status.code).to eq(OK)
396
+ end
397
+
208
398
  it 'get a nil msg before a status when an OK status is sent' do
209
399
  call = make_test_call
210
400
  ActiveCall.client_invoke(call)
@@ -212,7 +402,7 @@ describe GRPC::ActiveCall do
212
402
  @pass_through, deadline)
213
403
  msg = 'message is a string'
214
404
  client_call.remote_send(msg)
215
- client_call.writes_done(false)
405
+ call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil)
216
406
  server_call = expect_server_to_receive(msg)
217
407
  server_call.remote_send('server_response')
218
408
  server_call.send_status(OK, 'OK')
@@ -270,7 +460,7 @@ describe GRPC::ActiveCall do
270
460
  msg = 'message is a string'
271
461
  reply = 'server_response'
272
462
  client_call.remote_send(msg)
273
- client_call.writes_done(false)
463
+ call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil)
274
464
  server_call = expect_server_to_receive(msg)
275
465
  e = client_call.each_remote_read
276
466
  n = 3 # arbitrary value > 1
@@ -283,7 +473,7 @@ describe GRPC::ActiveCall do
283
473
  end
284
474
  end
285
475
 
286
- describe '#writes_done' do
476
+ describe '#closing the call from the client' do
287
477
  it 'finishes ok if the server sends a status response' do
288
478
  call = make_test_call
289
479
  ActiveCall.client_invoke(call)
@@ -291,7 +481,9 @@ describe GRPC::ActiveCall do
291
481
  @pass_through, deadline)
292
482
  msg = 'message is a string'
293
483
  client_call.remote_send(msg)
294
- expect { client_call.writes_done(false) }.to_not raise_error
484
+ expect do
485
+ call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil)
486
+ end.to_not raise_error
295
487
  server_call = expect_server_to_receive(msg)
296
488
  server_call.remote_send('server_response')
297
489
  expect(client_call.remote_read).to eq('server_response')
@@ -310,11 +502,13 @@ describe GRPC::ActiveCall do
310
502
  server_call.remote_send('server_response')
311
503
  server_call.send_status(OK, 'status code is OK')
312
504
  expect(client_call.remote_read).to eq('server_response')
313
- expect { client_call.writes_done(false) }.to_not raise_error
505
+ expect do
506
+ call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil)
507
+ end.to_not raise_error
314
508
  expect { client_call.finished }.to_not raise_error
315
509
  end
316
510
 
317
- it 'finishes ok if writes_done is true' do
511
+ it 'finishes ok if SEND_CLOSE and RECV_STATUS has been sent' do
318
512
  call = make_test_call
319
513
  ActiveCall.client_invoke(call)
320
514
  client_call = ActiveCall.new(call, @pass_through,
@@ -325,7 +519,91 @@ describe GRPC::ActiveCall do
325
519
  server_call.remote_send('server_response')
326
520
  server_call.send_status(OK, 'status code is OK')
327
521
  expect(client_call.remote_read).to eq('server_response')
328
- expect { client_call.writes_done(true) }.to_not raise_error
522
+ expect do
523
+ call.run_batch(
524
+ CallOps::SEND_CLOSE_FROM_CLIENT => nil,
525
+ CallOps::RECV_STATUS_ON_CLIENT => nil)
526
+ end.to_not raise_error
527
+ end
528
+ end
529
+
530
+ # Test sending of the initial metadata in #run_server_bidi
531
+ # from the server handler both implicitly and explicitly.
532
+ describe '#run_server_bidi metadata sending tests', run_server_bidi: true do
533
+ before(:each) do
534
+ @requests = ['first message', 'second message']
535
+ @server_to_client_metadata = { 'test_key' => 'test_val' }
536
+ @server_status = OK
537
+
538
+ @client_call = make_test_call
539
+ @client_call.run_batch(CallOps::SEND_INITIAL_METADATA => {})
540
+
541
+ recvd_rpc = @server.request_call
542
+ recvd_call = recvd_rpc.call
543
+ @server_call = ActiveCall.new(
544
+ recvd_call,
545
+ @pass_through,
546
+ @pass_through,
547
+ deadline,
548
+ metadata_received: true,
549
+ started: false,
550
+ metadata_to_send: @server_to_client_metadata)
551
+ end
552
+
553
+ after(:each) do
554
+ # Send the requests and send a close so the server can send a status
555
+ @requests.each do |message|
556
+ @client_call.run_batch(CallOps::SEND_MESSAGE => message)
557
+ end
558
+ @client_call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil)
559
+
560
+ @server_thread.join
561
+
562
+ # Expect that initial metadata was sent,
563
+ # the requests were echoed, and a status was sent
564
+ batch_result = @client_call.run_batch(
565
+ CallOps::RECV_INITIAL_METADATA => nil)
566
+ expect(batch_result.metadata).to eq(@server_to_client_metadata)
567
+
568
+ @requests.each do |message|
569
+ batch_result = @client_call.run_batch(
570
+ CallOps::RECV_MESSAGE => nil)
571
+ expect(batch_result.message).to eq(message)
572
+ end
573
+
574
+ batch_result = @client_call.run_batch(
575
+ CallOps::RECV_STATUS_ON_CLIENT => nil)
576
+ expect(batch_result.status.code).to eq(@server_status)
577
+ end
578
+
579
+ it 'sends the initial metadata implicitly if not already sent' do
580
+ # Server handler that doesn't have access to a "call"
581
+ # It echoes the requests
582
+ fake_gen_each_reply_with_no_call_param = proc do |msgs|
583
+ msgs
584
+ end
585
+
586
+ @server_thread = Thread.new do
587
+ @server_call.run_server_bidi(
588
+ fake_gen_each_reply_with_no_call_param)
589
+ @server_call.send_status(@server_status)
590
+ end
591
+ end
592
+
593
+ it 'sends the metadata when sent explicitly and not already sent' do
594
+ # Fake server handler that has access to a "call" object and
595
+ # uses it to explicitly update and send the initial metadata
596
+ fake_gen_each_reply_with_call_param = proc do |msgs, call_param|
597
+ call_param.merge_metadata_to_send(@server_to_client_metadata)
598
+ call_param.send_initial_metadata
599
+ msgs
600
+ end
601
+
602
+ @server_thread = Thread.new do
603
+ @server_call.run_server_bidi(
604
+ fake_gen_each_reply_with_call_param)
605
+ @server_call.send_status(@server_status)
606
+ end
329
607
  end
330
608
  end
331
609