Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Side by Side Diff: third_party/grpc/src/ruby/pb/test/client.rb

Issue 1932353002: Initial checkin of gRPC to third_party/ Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env ruby
2
3 # Copyright 2015-2016, Google Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
8 # met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following disclaimer
14 # in the documentation and/or other materials provided with the
15 # distribution.
16 # * Neither the name of Google Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived from
18 # this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 # client is a testing tool that accesses a gRPC interop testing server and runs
33 # a test on it.
34 #
35 # Helps validate interoperation b/w different gRPC implementations.
36 #
37 # Usage: $ path/to/client.rb --server_host=<hostname> \
38 # --server_port=<port> \
39 # --test_case=<testcase_name>
40
41 this_dir = File.expand_path(File.dirname(__FILE__))
42 lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib')
43 pb_dir = File.dirname(File.dirname(this_dir))
44 $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
45 $LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir)
46 $LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
47
48 require 'optparse'
49 require 'logger'
50
51 require 'grpc'
52 require 'googleauth'
53 require 'google/protobuf'
54
55 require 'test/proto/empty'
56 require 'test/proto/messages'
57 require 'test/proto/test_services'
58
59 AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR
60
61 # RubyLogger defines a logger for gRPC based on the standard ruby logger.
62 module RubyLogger
63 def logger
64 LOGGER
65 end
66
67 LOGGER = Logger.new(STDOUT)
68 LOGGER.level = Logger::INFO
69 end
70
71 # GRPC is the general RPC module
72 module GRPC
73 # Inject the noop #logger if no module-level logger method has been injected.
74 extend RubyLogger
75 end
76
77 # AssertionError is use to indicate interop test failures.
78 class AssertionError < RuntimeError; end
79
80 # Fails with AssertionError if the block does evaluate to true
81 def assert(msg = 'unknown cause')
82 fail 'No assertion block provided' unless block_given?
83 fail AssertionError, msg unless yield
84 end
85
86 # loads the certificates used to access the test server securely.
87 def load_test_certs
88 this_dir = File.expand_path(File.dirname(__FILE__))
89 data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata')
90 files = ['ca.pem', 'server1.key', 'server1.pem']
91 files.map { |f| File.open(File.join(data_dir, f)).read }
92 end
93
94 # creates SSL Credentials from the test certificates.
95 def test_creds
96 certs = load_test_certs
97 GRPC::Core::ChannelCredentials.new(certs[0])
98 end
99
100 # creates SSL Credentials from the production certificates.
101 def prod_creds
102 GRPC::Core::ChannelCredentials.new()
103 end
104
105 # creates the SSL Credentials.
106 def ssl_creds(use_test_ca)
107 return test_creds if use_test_ca
108 prod_creds
109 end
110
111 # creates a test stub that accesses host:port securely.
112 def create_stub(opts)
113 address = "#{opts.host}:#{opts.port}"
114 if opts.secure
115 creds = ssl_creds(opts.use_test_ca)
116 stub_opts = {
117 GRPC::Core::Channel::SSL_TARGET => opts.host_override
118 }
119
120 # Add service account creds if specified
121 wants_creds = %w(all compute_engine_creds service_account_creds)
122 if wants_creds.include?(opts.test_case)
123 unless opts.oauth_scope.nil?
124 auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
125 call_creds = GRPC::Core::CallCredentials.new(auth_creds.updater_proc)
126 creds = creds.compose call_creds
127 end
128 end
129
130 if opts.test_case == 'oauth2_auth_token'
131 auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
132 kw = auth_creds.updater_proc.call({}) # gives as an auth token
133
134 # use a metadata update proc that just adds the auth token.
135 call_creds = GRPC::Core::CallCredentials.new(proc { |md| md.merge(kw) })
136 creds = creds.compose call_creds
137 end
138
139 if opts.test_case == 'jwt_token_creds' # don't use a scope
140 auth_creds = Google::Auth.get_application_default
141 call_creds = GRPC::Core::CallCredentials.new(auth_creds.updater_proc)
142 creds = creds.compose call_creds
143 end
144
145 GRPC.logger.info("... connecting securely to #{address}")
146 Grpc::Testing::TestService::Stub.new(address, creds, **stub_opts)
147 else
148 GRPC.logger.info("... connecting insecurely to #{address}")
149 Grpc::Testing::TestService::Stub.new(address, :this_channel_is_insecure)
150 end
151 end
152
153 # produces a string of null chars (\0) of length l.
154 def nulls(l)
155 fail 'requires #{l} to be +ve' if l < 0
156 [].pack('x' * l).force_encoding('ascii-8bit')
157 end
158
159 # a PingPongPlayer implements the ping pong bidi test.
160 class PingPongPlayer
161 include Grpc::Testing
162 include Grpc::Testing::PayloadType
163 attr_accessor :queue
164 attr_accessor :canceller_op
165
166 # reqs is the enumerator over the requests
167 def initialize(msg_sizes)
168 @queue = Queue.new
169 @msg_sizes = msg_sizes
170 @canceller_op = nil # used to cancel after the first response
171 end
172
173 def each_item
174 return enum_for(:each_item) unless block_given?
175 req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters # short
176 count = 0
177 @msg_sizes.each do |m|
178 req_size, resp_size = m
179 req = req_cls.new(payload: Payload.new(body: nulls(req_size)),
180 response_type: :COMPRESSABLE,
181 response_parameters: [p_cls.new(size: resp_size)])
182 yield req
183 resp = @queue.pop
184 assert('payload type is wrong') { :COMPRESSABLE == resp.payload.type }
185 assert("payload body #{count} has the wrong length") do
186 resp_size == resp.payload.body.length
187 end
188 p "OK: ping_pong #{count}"
189 count += 1
190 unless @canceller_op.nil?
191 canceller_op.cancel
192 break
193 end
194 end
195 end
196 end
197
198 # defines methods corresponding to each interop test case.
199 class NamedTests
200 include Grpc::Testing
201 include Grpc::Testing::PayloadType
202
203 def initialize(stub, args)
204 @stub = stub
205 @args = args
206 end
207
208 def empty_unary
209 resp = @stub.empty_call(Empty.new)
210 assert('empty_unary: invalid response') { resp.is_a?(Empty) }
211 p 'OK: empty_unary'
212 end
213
214 def large_unary
215 perform_large_unary
216 p 'OK: large_unary'
217 end
218
219 def service_account_creds
220 # ignore this test if the oauth options are not set
221 if @args.oauth_scope.nil?
222 p 'NOT RUN: service_account_creds; no service_account settings'
223 return
224 end
225 json_key = File.read(ENV[AUTH_ENV])
226 wanted_email = MultiJson.load(json_key)['client_email']
227 resp = perform_large_unary(fill_username: true,
228 fill_oauth_scope: true)
229 assert("#{__callee__}: bad username") { wanted_email == resp.username }
230 assert("#{__callee__}: bad oauth scope") do
231 @args.oauth_scope.include?(resp.oauth_scope)
232 end
233 p "OK: #{__callee__}"
234 end
235
236 def jwt_token_creds
237 json_key = File.read(ENV[AUTH_ENV])
238 wanted_email = MultiJson.load(json_key)['client_email']
239 resp = perform_large_unary(fill_username: true)
240 assert("#{__callee__}: bad username") { wanted_email == resp.username }
241 p "OK: #{__callee__}"
242 end
243
244 def compute_engine_creds
245 resp = perform_large_unary(fill_username: true,
246 fill_oauth_scope: true)
247 assert("#{__callee__}: bad username") do
248 @args.default_service_account == resp.username
249 end
250 p "OK: #{__callee__}"
251 end
252
253 def oauth2_auth_token
254 resp = perform_large_unary(fill_username: true,
255 fill_oauth_scope: true)
256 json_key = File.read(ENV[AUTH_ENV])
257 wanted_email = MultiJson.load(json_key)['client_email']
258 assert("#{__callee__}: bad username") { wanted_email == resp.username }
259 assert("#{__callee__}: bad oauth scope") do
260 @args.oauth_scope.include?(resp.oauth_scope)
261 end
262 p "OK: #{__callee__}"
263 end
264
265 def per_rpc_creds
266 auth_creds = Google::Auth.get_application_default(@args.oauth_scope)
267 update_metadata = proc do |md|
268 kw = auth_creds.updater_proc.call({})
269 end
270
271 call_creds = GRPC::Core::CallCredentials.new(update_metadata)
272
273 resp = perform_large_unary(fill_username: true,
274 fill_oauth_scope: true,
275 credentials: call_creds)
276 json_key = File.read(ENV[AUTH_ENV])
277 wanted_email = MultiJson.load(json_key)['client_email']
278 assert("#{__callee__}: bad username") { wanted_email == resp.username }
279 assert("#{__callee__}: bad oauth scope") do
280 @args.oauth_scope.include?(resp.oauth_scope)
281 end
282 p "OK: #{__callee__}"
283 end
284
285 def client_streaming
286 msg_sizes = [27_182, 8, 1828, 45_904]
287 wanted_aggregate_size = 74_922
288 reqs = msg_sizes.map do |x|
289 req = Payload.new(body: nulls(x))
290 StreamingInputCallRequest.new(payload: req)
291 end
292 resp = @stub.streaming_input_call(reqs)
293 assert("#{__callee__}: aggregate payload size is incorrect") do
294 wanted_aggregate_size == resp.aggregated_payload_size
295 end
296 p "OK: #{__callee__}"
297 end
298
299 def server_streaming
300 msg_sizes = [31_415, 9, 2653, 58_979]
301 response_spec = msg_sizes.map { |s| ResponseParameters.new(size: s) }
302 req = StreamingOutputCallRequest.new(response_type: :COMPRESSABLE,
303 response_parameters: response_spec)
304 resps = @stub.streaming_output_call(req)
305 resps.each_with_index do |r, i|
306 assert("#{__callee__}: too many responses") { i < msg_sizes.length }
307 assert("#{__callee__}: payload body #{i} has the wrong length") do
308 msg_sizes[i] == r.payload.body.length
309 end
310 assert("#{__callee__}: payload type is wrong") do
311 :COMPRESSABLE == r.payload.type
312 end
313 end
314 p "OK: #{__callee__}"
315 end
316
317 def ping_pong
318 msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]]
319 ppp = PingPongPlayer.new(msg_sizes)
320 resps = @stub.full_duplex_call(ppp.each_item)
321 resps.each { |r| ppp.queue.push(r) }
322 p "OK: #{__callee__}"
323 end
324
325 def timeout_on_sleeping_server
326 msg_sizes = [[27_182, 31_415]]
327 ppp = PingPongPlayer.new(msg_sizes)
328 resps = @stub.full_duplex_call(ppp.each_item, timeout: 0.001)
329 resps.each { |r| ppp.queue.push(r) }
330 fail 'Should have raised GRPC::BadStatus(DEADLINE_EXCEEDED)'
331 rescue GRPC::BadStatus => e
332 assert("#{__callee__}: status was wrong") do
333 e.code == GRPC::Core::StatusCodes::DEADLINE_EXCEEDED
334 end
335 p "OK: #{__callee__}"
336 end
337
338 def empty_stream
339 ppp = PingPongPlayer.new([])
340 resps = @stub.full_duplex_call(ppp.each_item)
341 count = 0
342 resps.each do |r|
343 ppp.queue.push(r)
344 count += 1
345 end
346 assert("#{__callee__}: too many responses expected 0") do
347 count == 0
348 end
349 p "OK: #{__callee__}"
350 end
351
352 def cancel_after_begin
353 msg_sizes = [27_182, 8, 1828, 45_904]
354 reqs = msg_sizes.map do |x|
355 req = Payload.new(body: nulls(x))
356 StreamingInputCallRequest.new(payload: req)
357 end
358 op = @stub.streaming_input_call(reqs, return_op: true)
359 op.cancel
360 op.execute
361 fail 'Should have raised GRPC:Cancelled'
362 rescue GRPC::Cancelled
363 assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled }
364 p "OK: #{__callee__}"
365 end
366
367 def cancel_after_first_response
368 msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]]
369 ppp = PingPongPlayer.new(msg_sizes)
370 op = @stub.full_duplex_call(ppp.each_item, return_op: true)
371 ppp.canceller_op = op # causes ppp to cancel after the 1st message
372 op.execute.each { |r| ppp.queue.push(r) }
373 fail 'Should have raised GRPC:Cancelled'
374 rescue GRPC::Cancelled
375 assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled }
376 op.wait
377 p "OK: #{__callee__}"
378 end
379
380 def all
381 all_methods = NamedTests.instance_methods(false).map(&:to_s)
382 all_methods.each do |m|
383 next if m == 'all' || m.start_with?('assert')
384 p "TESTCASE: #{m}"
385 method(m).call
386 end
387 end
388
389 private
390
391 def perform_large_unary(fill_username: false, fill_oauth_scope: false, **kw)
392 req_size, wanted_response_size = 271_828, 314_159
393 payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
394 req = SimpleRequest.new(response_type: :COMPRESSABLE,
395 response_size: wanted_response_size,
396 payload: payload)
397 req.fill_username = fill_username
398 req.fill_oauth_scope = fill_oauth_scope
399 resp = @stub.unary_call(req, **kw)
400 assert('payload type is wrong') do
401 :COMPRESSABLE == resp.payload.type
402 end
403 assert('payload body has the wrong length') do
404 wanted_response_size == resp.payload.body.length
405 end
406 assert('payload body is invalid') do
407 nulls(wanted_response_size) == resp.payload.body
408 end
409 resp
410 end
411 end
412
413 # Args is used to hold the command line info.
414 Args = Struct.new(:default_service_account, :host, :host_override,
415 :oauth_scope, :port, :secure, :test_case,
416 :use_test_ca)
417
418 # validates the the command line options, returning them as a Hash.
419 def parse_args
420 args = Args.new
421 args.host_override = 'foo.test.google.fr'
422 OptionParser.new do |opts|
423 opts.on('--oauth_scope scope',
424 'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
425 opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
426 args['host'] = v
427 end
428 opts.on('--default_service_account email_address',
429 'email address of the default service account') do |v|
430 args['default_service_account'] = v
431 end
432 opts.on('--server_host_override HOST_OVERRIDE',
433 'override host via a HTTP header') do |v|
434 args['host_override'] = v
435 end
436 opts.on('--server_port SERVER_PORT', 'server port') { |v| args['port'] = v }
437 # instance_methods(false) gives only the methods defined in that class
438 test_cases = NamedTests.instance_methods(false).map(&:to_s)
439 test_case_list = test_cases.join(',')
440 opts.on('--test_case CODE', test_cases, {}, 'select a test_case',
441 " (#{test_case_list})") { |v| args['test_case'] = v }
442 opts.on('--use_tls USE_TLS', ['false', 'true'],
443 'require a secure connection?') do |v|
444 args['secure'] = v == 'true'
445 end
446 opts.on('--use_test_ca USE_TEST_CA', ['false', 'true'],
447 'if secure, use the test certificate?') do |v|
448 args['use_test_ca'] = v == 'true'
449 end
450 end.parse!
451 _check_args(args)
452 end
453
454 def _check_args(args)
455 %w(host port test_case).each do |a|
456 if args[a].nil?
457 fail(OptionParser::MissingArgument, "please specify --#{a}")
458 end
459 end
460 args
461 end
462
463 def main
464 opts = parse_args
465 stub = create_stub(opts)
466 NamedTests.new(stub, opts).method(opts['test_case']).call
467 end
468
469 main
OLDNEW
« no previous file with comments | « third_party/grpc/src/ruby/pb/grpc/health/v1/health_services.rb ('k') | third_party/grpc/src/ruby/pb/test/proto/empty.rb » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698