1#!/usr/bin/env ruby 2 3# Copyright 2015 gRPC authors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17# client is a testing tool that accesses a gRPC interop testing server and runs 18# a test on it. 19# 20# Helps validate interoperation b/w different gRPC implementations. 21# 22# Usage: $ path/to/client.rb --server_host=<hostname> \ 23# --server_port=<port> \ 24# --test_case=<testcase_name> 25 26# These lines are required for the generated files to load grpc 27this_dir = File.expand_path(File.dirname(__FILE__)) 28lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib') 29pb_dir = File.dirname(this_dir) 30$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) 31$LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir) 32 33require 'optparse' 34require 'logger' 35 36require_relative '../../lib/grpc' 37require 'googleauth' 38require 'google/protobuf' 39 40require_relative '../src/proto/grpc/testing/empty_pb' 41require_relative '../src/proto/grpc/testing/messages_pb' 42require_relative '../src/proto/grpc/testing/test_services_pb' 43 44AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR 45 46# RubyLogger defines a logger for gRPC based on the standard ruby logger. 47module RubyLogger 48 def logger 49 LOGGER 50 end 51 52 LOGGER = Logger.new(STDOUT) 53 LOGGER.level = Logger::INFO 54end 55 56# GRPC is the general RPC module 57module GRPC 58 # Inject the noop #logger if no module-level logger method has been injected. 59 extend RubyLogger 60end 61 62# AssertionError is use to indicate interop test failures. 63class AssertionError < RuntimeError; end 64 65# Fails with AssertionError if the block does evaluate to true 66def assert(msg = 'unknown cause') 67 fail 'No assertion block provided' unless block_given? 68 fail AssertionError, msg unless yield 69end 70 71# loads the certificates used to access the test server securely. 72def load_test_certs 73 this_dir = File.expand_path(File.dirname(__FILE__)) 74 data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata') 75 files = ['ca.pem', 'server1.key', 'server1.pem'] 76 files.map { |f| File.open(File.join(data_dir, f)).read } 77end 78 79# creates SSL Credentials from the test certificates. 80def test_creds 81 certs = load_test_certs 82 GRPC::Core::ChannelCredentials.new(certs[0]) 83end 84 85# creates SSL Credentials from the production certificates. 86def prod_creds 87 GRPC::Core::ChannelCredentials.new() 88end 89 90# creates the SSL Credentials. 91def ssl_creds(use_test_ca) 92 return test_creds if use_test_ca 93 prod_creds 94end 95 96# creates a test stub that accesses host:port securely. 97def create_stub(opts) 98 address = "#{opts.host}:#{opts.port}" 99 100 # Provide channel args that request compression by default 101 # for compression interop tests 102 if ['client_compressed_unary', 103 'client_compressed_streaming'].include?(opts.test_case) 104 compression_options = 105 GRPC::Core::CompressionOptions.new(default_algorithm: :gzip) 106 compression_channel_args = compression_options.to_channel_arg_hash 107 else 108 compression_channel_args = {} 109 end 110 111 if opts.secure 112 creds = ssl_creds(opts.use_test_ca) 113 stub_opts = { 114 channel_args: { 115 GRPC::Core::Channel::SSL_TARGET => opts.host_override 116 } 117 } 118 119 # Add service account creds if specified 120 wants_creds = %w(all compute_engine_creds service_account_creds) 121 if wants_creds.include?(opts.test_case) 122 unless opts.oauth_scope.nil? 123 auth_creds = Google::Auth.get_application_default(opts.oauth_scope) 124 call_creds = GRPC::Core::CallCredentials.new(auth_creds.updater_proc) 125 creds = creds.compose call_creds 126 end 127 end 128 129 if opts.test_case == 'oauth2_auth_token' 130 auth_creds = Google::Auth.get_application_default(opts.oauth_scope) 131 kw = auth_creds.updater_proc.call({}) # gives as an auth token 132 133 # use a metadata update proc that just adds the auth token. 134 call_creds = GRPC::Core::CallCredentials.new(proc { |md| md.merge(kw) }) 135 creds = creds.compose call_creds 136 end 137 138 if opts.test_case == 'jwt_token_creds' # don't use a scope 139 auth_creds = Google::Auth.get_application_default 140 call_creds = GRPC::Core::CallCredentials.new(auth_creds.updater_proc) 141 creds = creds.compose call_creds 142 end 143 144 GRPC.logger.info("... connecting securely to #{address}") 145 stub_opts[:channel_args].merge!(compression_channel_args) 146 if opts.test_case == "unimplemented_service" 147 Grpc::Testing::UnimplementedService::Stub.new(address, creds, **stub_opts) 148 else 149 Grpc::Testing::TestService::Stub.new(address, creds, **stub_opts) 150 end 151 else 152 GRPC.logger.info("... connecting insecurely to #{address}") 153 if opts.test_case == "unimplemented_service" 154 Grpc::Testing::UnimplementedService::Stub.new( 155 address, 156 :this_channel_is_insecure, 157 channel_args: compression_channel_args 158 ) 159 else 160 Grpc::Testing::TestService::Stub.new( 161 address, 162 :this_channel_is_insecure, 163 channel_args: compression_channel_args 164 ) 165 end 166 end 167end 168 169# produces a string of null chars (\0) of length l. 170def nulls(l) 171 fail 'requires #{l} to be +ve' if l < 0 172 [].pack('x' * l).force_encoding('ascii-8bit') 173end 174 175# a PingPongPlayer implements the ping pong bidi test. 176class PingPongPlayer 177 include Grpc::Testing 178 include Grpc::Testing::PayloadType 179 attr_accessor :queue 180 attr_accessor :canceller_op 181 182 # reqs is the enumerator over the requests 183 def initialize(msg_sizes) 184 @queue = Queue.new 185 @msg_sizes = msg_sizes 186 @canceller_op = nil # used to cancel after the first response 187 end 188 189 def each_item 190 return enum_for(:each_item) unless block_given? 191 req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters # short 192 count = 0 193 @msg_sizes.each do |m| 194 req_size, resp_size = m 195 req = req_cls.new(payload: Payload.new(body: nulls(req_size)), 196 response_type: :COMPRESSABLE, 197 response_parameters: [p_cls.new(size: resp_size)]) 198 yield req 199 resp = @queue.pop 200 assert('payload type is wrong') { :COMPRESSABLE == resp.payload.type } 201 assert("payload body #{count} has the wrong length") do 202 resp_size == resp.payload.body.length 203 end 204 p "OK: ping_pong #{count}" 205 count += 1 206 unless @canceller_op.nil? 207 canceller_op.cancel 208 break 209 end 210 end 211 end 212end 213 214class BlockingEnumerator 215 include Grpc::Testing 216 include Grpc::Testing::PayloadType 217 218 def initialize(req_size, sleep_time) 219 @req_size = req_size 220 @sleep_time = sleep_time 221 end 222 223 def each_item 224 return enum_for(:each_item) unless block_given? 225 req_cls = StreamingOutputCallRequest 226 req = req_cls.new(payload: Payload.new(body: nulls(@req_size))) 227 yield req 228 # Sleep until after the deadline should have passed 229 sleep(@sleep_time) 230 end 231end 232 233# Intended to be used to wrap a call_op, and to adjust 234# the write flag of the call_op in between messages yielded to it. 235class WriteFlagSettingStreamingInputEnumerable 236 attr_accessor :call_op 237 238 def initialize(requests_and_write_flags) 239 @requests_and_write_flags = requests_and_write_flags 240 end 241 242 def each 243 @requests_and_write_flags.each do |request_and_flag| 244 @call_op.write_flag = request_and_flag[:write_flag] 245 yield request_and_flag[:request] 246 end 247 end 248end 249 250# defines methods corresponding to each interop test case. 251class NamedTests 252 include Grpc::Testing 253 include Grpc::Testing::PayloadType 254 include GRPC::Core::MetadataKeys 255 256 def initialize(stub, args) 257 @stub = stub 258 @args = args 259 end 260 261 def empty_unary 262 resp = @stub.empty_call(Empty.new) 263 assert('empty_unary: invalid response') { resp.is_a?(Empty) } 264 end 265 266 def large_unary 267 perform_large_unary 268 end 269 270 def client_compressed_unary 271 # first request used also for the probe 272 req_size, wanted_response_size = 271_828, 314_159 273 expect_compressed = BoolValue.new(value: true) 274 payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size)) 275 req = SimpleRequest.new(response_type: :COMPRESSABLE, 276 response_size: wanted_response_size, 277 payload: payload, 278 expect_compressed: expect_compressed) 279 280 # send a probe to see if CompressedResponse is supported on the server 281 send_probe_for_compressed_request_support do 282 request_uncompressed_args = { 283 COMPRESSION_REQUEST_ALGORITHM => 'identity' 284 } 285 @stub.unary_call(req, metadata: request_uncompressed_args) 286 end 287 288 # make a call with a compressed message 289 resp = @stub.unary_call(req) 290 assert('Expected second unary call with compression to work') do 291 resp.payload.body.length == wanted_response_size 292 end 293 294 # make a call with an uncompressed message 295 stub_options = { 296 COMPRESSION_REQUEST_ALGORITHM => 'identity' 297 } 298 299 req = SimpleRequest.new( 300 response_type: :COMPRESSABLE, 301 response_size: wanted_response_size, 302 payload: payload, 303 expect_compressed: BoolValue.new(value: false) 304 ) 305 306 resp = @stub.unary_call(req, metadata: stub_options) 307 assert('Expected second unary call with compression to work') do 308 resp.payload.body.length == wanted_response_size 309 end 310 end 311 312 def service_account_creds 313 # ignore this test if the oauth options are not set 314 if @args.oauth_scope.nil? 315 p 'NOT RUN: service_account_creds; no service_account settings' 316 return 317 end 318 json_key = File.read(ENV[AUTH_ENV]) 319 wanted_email = MultiJson.load(json_key)['client_email'] 320 resp = perform_large_unary(fill_username: true, 321 fill_oauth_scope: true) 322 assert("#{__callee__}: bad username") { wanted_email == resp.username } 323 assert("#{__callee__}: bad oauth scope") do 324 @args.oauth_scope.include?(resp.oauth_scope) 325 end 326 end 327 328 def jwt_token_creds 329 json_key = File.read(ENV[AUTH_ENV]) 330 wanted_email = MultiJson.load(json_key)['client_email'] 331 resp = perform_large_unary(fill_username: true) 332 assert("#{__callee__}: bad username") { wanted_email == resp.username } 333 end 334 335 def compute_engine_creds 336 resp = perform_large_unary(fill_username: true, 337 fill_oauth_scope: true) 338 assert("#{__callee__}: bad username") do 339 @args.default_service_account == resp.username 340 end 341 end 342 343 def oauth2_auth_token 344 resp = perform_large_unary(fill_username: true, 345 fill_oauth_scope: true) 346 json_key = File.read(ENV[AUTH_ENV]) 347 wanted_email = MultiJson.load(json_key)['client_email'] 348 assert("#{__callee__}: bad username") { wanted_email == resp.username } 349 assert("#{__callee__}: bad oauth scope") do 350 @args.oauth_scope.include?(resp.oauth_scope) 351 end 352 end 353 354 def per_rpc_creds 355 auth_creds = Google::Auth.get_application_default(@args.oauth_scope) 356 update_metadata = proc do |md| 357 kw = auth_creds.updater_proc.call({}) 358 end 359 360 call_creds = GRPC::Core::CallCredentials.new(update_metadata) 361 362 resp = perform_large_unary(fill_username: true, 363 fill_oauth_scope: true, 364 credentials: call_creds) 365 json_key = File.read(ENV[AUTH_ENV]) 366 wanted_email = MultiJson.load(json_key)['client_email'] 367 assert("#{__callee__}: bad username") { wanted_email == resp.username } 368 assert("#{__callee__}: bad oauth scope") do 369 @args.oauth_scope.include?(resp.oauth_scope) 370 end 371 end 372 373 def client_streaming 374 msg_sizes = [27_182, 8, 1828, 45_904] 375 wanted_aggregate_size = 74_922 376 reqs = msg_sizes.map do |x| 377 req = Payload.new(body: nulls(x)) 378 StreamingInputCallRequest.new(payload: req) 379 end 380 resp = @stub.streaming_input_call(reqs) 381 assert("#{__callee__}: aggregate payload size is incorrect") do 382 wanted_aggregate_size == resp.aggregated_payload_size 383 end 384 end 385 386 def client_compressed_streaming 387 # first request used also by the probe 388 first_request = StreamingInputCallRequest.new( 389 payload: Payload.new(type: :COMPRESSABLE, body: nulls(27_182)), 390 expect_compressed: BoolValue.new(value: true) 391 ) 392 393 # send a probe to see if CompressedResponse is supported on the server 394 send_probe_for_compressed_request_support do 395 request_uncompressed_args = { 396 COMPRESSION_REQUEST_ALGORITHM => 'identity' 397 } 398 @stub.streaming_input_call([first_request], 399 metadata: request_uncompressed_args) 400 end 401 402 second_request = StreamingInputCallRequest.new( 403 payload: Payload.new(type: :COMPRESSABLE, body: nulls(45_904)), 404 expect_compressed: BoolValue.new(value: false) 405 ) 406 407 # Create the requests messages and the corresponding write flags 408 # for each message 409 requests = WriteFlagSettingStreamingInputEnumerable.new([ 410 { request: first_request, 411 write_flag: 0 }, 412 { request: second_request, 413 write_flag: GRPC::Core::WriteFlags::NO_COMPRESS } 414 ]) 415 416 # Create the call_op, pass it to the requests enumerable, and 417 # run the call 418 call_op = @stub.streaming_input_call(requests, 419 return_op: true) 420 requests.call_op = call_op 421 resp = call_op.execute 422 423 wanted_aggregate_size = 73_086 424 425 assert("#{__callee__}: aggregate payload size is incorrect") do 426 wanted_aggregate_size == resp.aggregated_payload_size 427 end 428 end 429 430 def server_streaming 431 msg_sizes = [31_415, 9, 2653, 58_979] 432 response_spec = msg_sizes.map { |s| ResponseParameters.new(size: s) } 433 req = StreamingOutputCallRequest.new(response_type: :COMPRESSABLE, 434 response_parameters: response_spec) 435 resps = @stub.streaming_output_call(req) 436 resps.each_with_index do |r, i| 437 assert("#{__callee__}: too many responses") { i < msg_sizes.length } 438 assert("#{__callee__}: payload body #{i} has the wrong length") do 439 msg_sizes[i] == r.payload.body.length 440 end 441 assert("#{__callee__}: payload type is wrong") do 442 :COMPRESSABLE == r.payload.type 443 end 444 end 445 end 446 447 def ping_pong 448 msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]] 449 ppp = PingPongPlayer.new(msg_sizes) 450 resps = @stub.full_duplex_call(ppp.each_item) 451 resps.each { |r| ppp.queue.push(r) } 452 end 453 454 def timeout_on_sleeping_server 455 enum = BlockingEnumerator.new(27_182, 2) 456 deadline = GRPC::Core::TimeConsts::from_relative_time(1) 457 resps = @stub.full_duplex_call(enum.each_item, deadline: deadline) 458 resps.each { } # wait to receive each request (or timeout) 459 fail 'Should have raised GRPC::DeadlineExceeded' 460 rescue GRPC::DeadlineExceeded 461 end 462 463 def empty_stream 464 ppp = PingPongPlayer.new([]) 465 resps = @stub.full_duplex_call(ppp.each_item) 466 count = 0 467 resps.each do |r| 468 ppp.queue.push(r) 469 count += 1 470 end 471 assert("#{__callee__}: too many responses expected 0") do 472 count == 0 473 end 474 end 475 476 def cancel_after_begin 477 msg_sizes = [27_182, 8, 1828, 45_904] 478 reqs = msg_sizes.map do |x| 479 req = Payload.new(body: nulls(x)) 480 StreamingInputCallRequest.new(payload: req) 481 end 482 op = @stub.streaming_input_call(reqs, return_op: true) 483 op.cancel 484 op.execute 485 fail 'Should have raised GRPC:Cancelled' 486 rescue GRPC::Cancelled 487 assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled? } 488 end 489 490 def cancel_after_first_response 491 msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]] 492 ppp = PingPongPlayer.new(msg_sizes) 493 op = @stub.full_duplex_call(ppp.each_item, return_op: true) 494 ppp.canceller_op = op # causes ppp to cancel after the 1st message 495 op.execute.each { |r| ppp.queue.push(r) } 496 fail 'Should have raised GRPC:Cancelled' 497 rescue GRPC::Cancelled 498 assert("#{__callee__}: call operation should be CANCELLED") { op.cancelled? } 499 op.wait 500 end 501 502 def unimplemented_method 503 begin 504 resp = @stub.unimplemented_call(Empty.new) 505 rescue GRPC::Unimplemented => e 506 return 507 rescue Exception => e 508 fail AssertionError, "Expected BadStatus. Received: #{e.inspect}" 509 end 510 fail AssertionError, "GRPC::Unimplemented should have been raised. Was not." 511 end 512 513 def unimplemented_service 514 begin 515 resp = @stub.unimplemented_call(Empty.new) 516 rescue GRPC::Unimplemented => e 517 return 518 rescue Exception => e 519 fail AssertionError, "Expected BadStatus. Received: #{e.inspect}" 520 end 521 fail AssertionError, "GRPC::Unimplemented should have been raised. Was not." 522 end 523 524 def status_code_and_message 525 526 # Function wide constants. 527 message = "test status method" 528 code = GRPC::Core::StatusCodes::UNKNOWN 529 530 # Testing with UnaryCall. 531 payload = Payload.new(type: :COMPRESSABLE, body: nulls(1)) 532 echo_status = EchoStatus.new(code: code, message: message) 533 req = SimpleRequest.new(response_type: :COMPRESSABLE, 534 response_size: 1, 535 payload: payload, 536 response_status: echo_status) 537 seen_correct_exception = false 538 begin 539 resp = @stub.unary_call(req) 540 rescue GRPC::Unknown => e 541 if e.details != message 542 fail AssertionError, 543 "Expected message #{message}. Received: #{e.details}" 544 end 545 seen_correct_exception = true 546 rescue Exception => e 547 fail AssertionError, "Expected BadStatus. Received: #{e.inspect}" 548 end 549 550 if not seen_correct_exception 551 fail AssertionError, "Did not see expected status from UnaryCall" 552 end 553 554 # testing with FullDuplex 555 req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters 556 duplex_req = req_cls.new(payload: Payload.new(body: nulls(1)), 557 response_type: :COMPRESSABLE, 558 response_parameters: [p_cls.new(size: 1)], 559 response_status: echo_status) 560 seen_correct_exception = false 561 begin 562 resp = @stub.full_duplex_call([duplex_req]) 563 resp.each { |r| } 564 rescue GRPC::Unknown => e 565 if e.details != message 566 fail AssertionError, 567 "Expected message #{message}. Received: #{e.details}" 568 end 569 seen_correct_exception = true 570 rescue Exception => e 571 fail AssertionError, "Expected BadStatus. Received: #{e.inspect}" 572 end 573 574 if not seen_correct_exception 575 fail AssertionError, "Did not see expected status from FullDuplexCall" 576 end 577 578 end 579 580 581 def custom_metadata 582 583 # Function wide constants 584 req_size, wanted_response_size = 271_828, 314_159 585 initial_metadata_key = "x-grpc-test-echo-initial" 586 initial_metadata_value = "test_initial_metadata_value" 587 trailing_metadata_key = "x-grpc-test-echo-trailing-bin" 588 trailing_metadata_value = "\x0a\x0b\x0a\x0b\x0a\x0b" 589 590 metadata = { 591 initial_metadata_key => initial_metadata_value, 592 trailing_metadata_key => trailing_metadata_value 593 } 594 595 # Testing with UnaryCall 596 payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size)) 597 req = SimpleRequest.new(response_type: :COMPRESSABLE, 598 response_size: wanted_response_size, 599 payload: payload) 600 601 op = @stub.unary_call(req, metadata: metadata, return_op: true) 602 op.execute 603 if not op.metadata.has_key?(initial_metadata_key) 604 fail AssertionError, "Expected initial metadata. None received" 605 elsif op.metadata[initial_metadata_key] != metadata[initial_metadata_key] 606 fail AssertionError, 607 "Expected initial metadata: #{metadata[initial_metadata_key]}. "\ 608 "Received: #{op.metadata[initial_metadata_key]}" 609 end 610 if not op.trailing_metadata.has_key?(trailing_metadata_key) 611 fail AssertionError, "Expected trailing metadata. None received" 612 elsif op.trailing_metadata[trailing_metadata_key] != 613 metadata[trailing_metadata_key] 614 fail AssertionError, 615 "Expected trailing metadata: #{metadata[trailing_metadata_key]}. "\ 616 "Received: #{op.trailing_metadata[trailing_metadata_key]}" 617 end 618 619 # Testing with FullDuplex 620 req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters 621 duplex_req = req_cls.new(payload: Payload.new(body: nulls(req_size)), 622 response_type: :COMPRESSABLE, 623 response_parameters: [p_cls.new(size: wanted_response_size)]) 624 625 duplex_op = @stub.full_duplex_call([duplex_req], metadata: metadata, 626 return_op: true) 627 resp = duplex_op.execute 628 resp.each { |r| } # ensures that the server sends trailing data 629 duplex_op.wait 630 if not duplex_op.metadata.has_key?(initial_metadata_key) 631 fail AssertionError, "Expected initial metadata. None received" 632 elsif duplex_op.metadata[initial_metadata_key] != 633 metadata[initial_metadata_key] 634 fail AssertionError, 635 "Expected initial metadata: #{metadata[initial_metadata_key]}. "\ 636 "Received: #{duplex_op.metadata[initial_metadata_key]}" 637 end 638 if not duplex_op.trailing_metadata[trailing_metadata_key] 639 fail AssertionError, "Expected trailing metadata. None received" 640 elsif duplex_op.trailing_metadata[trailing_metadata_key] != 641 metadata[trailing_metadata_key] 642 fail AssertionError, 643 "Expected trailing metadata: #{metadata[trailing_metadata_key]}. "\ 644 "Received: #{duplex_op.trailing_metadata[trailing_metadata_key]}" 645 end 646 647 end 648 649 def all 650 all_methods = NamedTests.instance_methods(false).map(&:to_s) 651 all_methods.each do |m| 652 next if m == 'all' || m.start_with?('assert') 653 p "TESTCASE: #{m}" 654 method(m).call 655 end 656 end 657 658 private 659 660 def perform_large_unary(fill_username: false, fill_oauth_scope: false, **kw) 661 req_size, wanted_response_size = 271_828, 314_159 662 payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size)) 663 req = SimpleRequest.new(response_type: :COMPRESSABLE, 664 response_size: wanted_response_size, 665 payload: payload) 666 req.fill_username = fill_username 667 req.fill_oauth_scope = fill_oauth_scope 668 resp = @stub.unary_call(req, **kw) 669 assert('payload type is wrong') do 670 :COMPRESSABLE == resp.payload.type 671 end 672 assert('payload body has the wrong length') do 673 wanted_response_size == resp.payload.body.length 674 end 675 assert('payload body is invalid') do 676 nulls(wanted_response_size) == resp.payload.body 677 end 678 resp 679 end 680 681 # Send probing message for compressed request on the server, to see 682 # if it's implemented. 683 def send_probe_for_compressed_request_support(&send_probe) 684 bad_status_occurred = false 685 686 begin 687 send_probe.call 688 rescue GRPC::BadStatus => e 689 if e.code == GRPC::Core::StatusCodes::INVALID_ARGUMENT 690 bad_status_occurred = true 691 else 692 fail AssertionError, "Bad status received but code is #{e.code}" 693 end 694 rescue Exception => e 695 fail AssertionError, "Expected BadStatus. Received: #{e.inspect}" 696 end 697 698 assert('CompressedRequest probe failed') do 699 bad_status_occurred 700 end 701 end 702 703end 704 705# Args is used to hold the command line info. 706Args = Struct.new(:default_service_account, :host, :host_override, 707 :oauth_scope, :port, :secure, :test_case, 708 :use_test_ca) 709 710# validates the command line options, returning them as a Hash. 711def parse_args 712 args = Args.new 713 args.host_override = 'foo.test.google.fr' 714 OptionParser.new do |opts| 715 opts.on('--oauth_scope scope', 716 'Scope for OAuth tokens') { |v| args['oauth_scope'] = v } 717 opts.on('--server_host SERVER_HOST', 'server hostname') do |v| 718 args['host'] = v 719 end 720 opts.on('--default_service_account email_address', 721 'email address of the default service account') do |v| 722 args['default_service_account'] = v 723 end 724 opts.on('--server_host_override HOST_OVERRIDE', 725 'override host via a HTTP header') do |v| 726 args['host_override'] = v 727 end 728 opts.on('--server_port SERVER_PORT', 'server port') { |v| args['port'] = v } 729 # instance_methods(false) gives only the methods defined in that class 730 test_cases = NamedTests.instance_methods(false).map(&:to_s) 731 test_case_list = test_cases.join(',') 732 opts.on('--test_case CODE', test_cases, {}, 'select a test_case', 733 " (#{test_case_list})") { |v| args['test_case'] = v } 734 opts.on('--use_tls USE_TLS', ['false', 'true'], 735 'require a secure connection?') do |v| 736 args['secure'] = v == 'true' 737 end 738 opts.on('--use_test_ca USE_TEST_CA', ['false', 'true'], 739 'if secure, use the test certificate?') do |v| 740 args['use_test_ca'] = v == 'true' 741 end 742 end.parse! 743 _check_args(args) 744end 745 746def _check_args(args) 747 %w(host port test_case).each do |a| 748 if args[a].nil? 749 fail(OptionParser::MissingArgument, "please specify --#{a}") 750 end 751 end 752 args 753end 754 755def main 756 opts = parse_args 757 stub = create_stub(opts) 758 NamedTests.new(stub, opts).method(opts['test_case']).call 759 p "OK: #{opts['test_case']}" 760end 761 762if __FILE__ == $0 763 main 764end 765