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