1/* 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 */ 18 19#import <XCTest/XCTest.h> 20#import <grpc/grpc.h> 21#import <grpc/support/port_platform.h> 22 23#import <GRPCClient/GRPCCall+ChannelArg.h> 24#import <GRPCClient/GRPCCall+OAuth2.h> 25#import <GRPCClient/GRPCCall+Tests.h> 26#import <GRPCClient/GRPCCall.h> 27#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h> 28#import <ProtoRPC/ProtoMethod.h> 29#import <RxLibrary/GRXBufferedPipe.h> 30#import <RxLibrary/GRXWriteable.h> 31#import <RxLibrary/GRXWriter+Immediate.h> 32#import "src/objective-c/tests/RemoteTestClient/Messages.pbobjc.h" 33 34#include <netinet/in.h> 35 36#import "../version.h" 37 38#define TEST_TIMEOUT 16 39 40// The server address is derived from preprocessor macro, which is 41// in turn derived from environment variable of the same name. 42#define NSStringize_helper(x) #x 43#define NSStringize(x) @NSStringize_helper(x) 44static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL); 45static NSString *const kPackage = @"grpc.testing"; 46static NSString *const kService = @"TestService"; 47static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE); 48 49static GRPCProtoMethod *kInexistentMethod; 50static GRPCProtoMethod *kEmptyCallMethod; 51static GRPCProtoMethod *kUnaryCallMethod; 52static GRPCProtoMethod *kFullDuplexCallMethod; 53 54/** Observer class for testing that responseMetadata is KVO-compliant */ 55@interface PassthroughObserver : NSObject 56- (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback 57 NS_DESIGNATED_INITIALIZER; 58 59- (void)observeValueForKeyPath:(NSString *)keyPath 60 ofObject:(id)object 61 change:(NSDictionary *)change 62 context:(void *)context; 63@end 64 65@implementation PassthroughObserver { 66 void (^_callback)(NSString *, id, NSDictionary *); 67} 68 69- (instancetype)init { 70 return [self initWithCallback:nil]; 71} 72 73- (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback { 74 if (!callback) { 75 return nil; 76 } 77 if ((self = [super init])) { 78 _callback = callback; 79 } 80 return self; 81} 82 83- (void)observeValueForKeyPath:(NSString *)keyPath 84 ofObject:(id)object 85 change:(NSDictionary *)change 86 context:(void *)context { 87 _callback(keyPath, object, change); 88 [object removeObserver:self forKeyPath:keyPath]; 89} 90 91@end 92 93#pragma mark Tests 94 95/** 96 * A few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) rather than 97 * a generated proto library on top of it. Its RPCs are sent to a local cleartext server. 98 * 99 * TODO(jcanizales): Run them also against a local SSL server and against a remote server. 100 */ 101@interface GRPCClientTests : XCTestCase 102@end 103 104@implementation GRPCClientTests 105 106+ (void)setUp { 107 NSLog(@"GRPCClientTests Started"); 108} 109 110- (void)setUp { 111 // Add a custom user agent prefix that will be used in test 112 [GRPCCall setUserAgentPrefix:@"Foo" forHost:kHostAddress]; 113 // Register test server as non-SSL. 114 [GRPCCall useInsecureConnectionsForHost:kHostAddress]; 115 116 // This method isn't implemented by the remote server. 117 kInexistentMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage 118 service:kService 119 method:@"Inexistent"]; 120 kEmptyCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage 121 service:kService 122 method:@"EmptyCall"]; 123 kUnaryCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage 124 service:kService 125 method:@"UnaryCall"]; 126 kFullDuplexCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage 127 service:kService 128 method:@"FullDuplexCall"]; 129} 130 131- (void)testConnectionToRemoteServer { 132 __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Server reachable."]; 133 134 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 135 path:kInexistentMethod.HTTPPath 136 requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; 137 138 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 139 initWithValueHandler:^(NSData *value) { 140 XCTFail(@"Received unexpected response: %@", value); 141 } 142 completionHandler:^(NSError *errorOrNil) { 143 XCTAssertNotNil(errorOrNil, @"Finished without error!"); 144 XCTAssertEqual(errorOrNil.code, 12, @"Finished with unexpected error: %@", errorOrNil); 145 [expectation fulfill]; 146 }]; 147 148 [call startWithWriteable:responsesWriteable]; 149 150 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 151} 152 153- (void)testEmptyRPC { 154 __weak XCTestExpectation *response = 155 [self expectationWithDescription:@"Empty response received."]; 156 __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; 157 158 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 159 path:kEmptyCallMethod.HTTPPath 160 requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; 161 162 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 163 initWithValueHandler:^(NSData *value) { 164 XCTAssertNotNil(value, @"nil value received as response."); 165 XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); 166 [response fulfill]; 167 } 168 completionHandler:^(NSError *errorOrNil) { 169 XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); 170 [completion fulfill]; 171 }]; 172 173 [call startWithWriteable:responsesWriteable]; 174 175 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 176} 177 178- (void)testSimpleProtoRPC { 179 __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; 180 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; 181 182 RMTSimpleRequest *request = [RMTSimpleRequest message]; 183 request.responseSize = 100; 184 request.fillUsername = YES; 185 request.fillOauthScope = YES; 186 GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; 187 188 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 189 path:kUnaryCallMethod.HTTPPath 190 requestsWriter:requestsWriter]; 191 192 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 193 initWithValueHandler:^(NSData *value) { 194 XCTAssertNotNil(value, @"nil value received as response."); 195 XCTAssertGreaterThan(value.length, 0, @"Empty response received."); 196 RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL]; 197 // We expect empty strings, not nil: 198 XCTAssertNotNil(responseProto.username, @"Response's username is nil."); 199 XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil."); 200 [response fulfill]; 201 } 202 completionHandler:^(NSError *errorOrNil) { 203 XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); 204 [completion fulfill]; 205 }]; 206 207 [call startWithWriteable:responsesWriteable]; 208 209 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 210} 211 212- (void)testMetadata { 213 __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."]; 214 215 RMTSimpleRequest *request = [RMTSimpleRequest message]; 216 request.fillUsername = YES; 217 request.fillOauthScope = YES; 218 GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; 219 220 GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost 221 path:kUnaryCallMethod.HTTPPath 222 requestsWriter:requestsWriter]; 223 224 call.oauth2AccessToken = @"bogusToken"; 225 226 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 227 initWithValueHandler:^(NSData *value) { 228 XCTFail(@"Received unexpected response: %@", value); 229 } 230 completionHandler:^(NSError *errorOrNil) { 231 XCTAssertNotNil(errorOrNil, @"Finished without error!"); 232 XCTAssertEqual(errorOrNil.code, 16, @"Finished with unexpected error: %@", errorOrNil); 233 XCTAssertEqualObjects(call.responseHeaders, errorOrNil.userInfo[kGRPCHeadersKey], 234 @"Headers in the NSError object and call object differ."); 235 XCTAssertEqualObjects(call.responseTrailers, errorOrNil.userInfo[kGRPCTrailersKey], 236 @"Trailers in the NSError object and call object differ."); 237 NSString *challengeHeader = call.oauth2ChallengeHeader; 238 XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@", 239 call.responseHeaders); 240 [expectation fulfill]; 241 }]; 242 243 [call startWithWriteable:responsesWriteable]; 244 245 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 246} 247 248- (void)testResponseMetadataKVO { 249 __weak XCTestExpectation *response = 250 [self expectationWithDescription:@"Empty response received."]; 251 __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; 252 __weak XCTestExpectation *metadata = [self expectationWithDescription:@"Metadata changed."]; 253 254 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 255 path:kEmptyCallMethod.HTTPPath 256 requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; 257 258 PassthroughObserver *observer = [[PassthroughObserver alloc] 259 initWithCallback:^(NSString *keypath, id object, NSDictionary *change) { 260 if ([keypath isEqual:@"responseHeaders"]) { 261 [metadata fulfill]; 262 } 263 }]; 264 265 [call addObserver:observer forKeyPath:@"responseHeaders" options:0 context:NULL]; 266 267 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 268 initWithValueHandler:^(NSData *value) { 269 XCTAssertNotNil(value, @"nil value received as response."); 270 XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); 271 [response fulfill]; 272 } 273 completionHandler:^(NSError *errorOrNil) { 274 XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); 275 [completion fulfill]; 276 }]; 277 278 [call startWithWriteable:responsesWriteable]; 279 280 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 281} 282 283- (void)testUserAgentPrefix { 284 __weak XCTestExpectation *response = 285 [self expectationWithDescription:@"Empty response received."]; 286 __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; 287 288 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 289 path:kEmptyCallMethod.HTTPPath 290 requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; 291 // Setting this special key in the header will cause the interop server to echo back the 292 // user-agent value, which we confirm. 293 call.requestHeaders[@"x-grpc-test-echo-useragent"] = @""; 294 295 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 296 initWithValueHandler:^(NSData *value) { 297 XCTAssertNotNil(value, @"nil value received as response."); 298 XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); 299 300 NSString *userAgent = call.responseHeaders[@"x-grpc-test-echo-useragent"]; 301 NSError *error = nil; 302 303 // Test the regex is correct 304 NSString *expectedUserAgent = @"Foo grpc-objc/"; 305 expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING]; 306 expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"]; 307 expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING]; 308 expectedUserAgent = [expectedUserAgent stringByAppendingString:@" ("]; 309 expectedUserAgent = [expectedUserAgent stringByAppendingString:@GPR_PLATFORM_STRING]; 310 expectedUserAgent = [expectedUserAgent stringByAppendingString:@"; chttp2)"]; 311 XCTAssertEqualObjects(userAgent, expectedUserAgent); 312 313 // Change in format of user-agent field in a direction that does not match the regex will 314 // likely cause problem for certain gRPC users. For details, refer to internal doc 315 // https://goo.gl/c2diBc 316 NSRegularExpression *regex = [NSRegularExpression 317 regularExpressionWithPattern:@" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?" 318 options:0 319 error:&error]; 320 NSString *customUserAgent = 321 [regex stringByReplacingMatchesInString:userAgent 322 options:0 323 range:NSMakeRange(0, [userAgent length]) 324 withTemplate:@""]; 325 XCTAssertEqualObjects(customUserAgent, @"Foo"); 326 327 [response fulfill]; 328 } 329 completionHandler:^(NSError *errorOrNil) { 330 XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); 331 [completion fulfill]; 332 }]; 333 334 [call startWithWriteable:responsesWriteable]; 335 336 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 337} 338 339- (void)testTrailers { 340 __weak XCTestExpectation *response = 341 [self expectationWithDescription:@"Empty response received."]; 342 __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; 343 344 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 345 path:kEmptyCallMethod.HTTPPath 346 requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; 347 // Setting this special key in the header will cause the interop server to echo back the 348 // trailer data. 349 const unsigned char raw_bytes[] = {1, 2, 3, 4}; 350 NSData *trailer_data = [NSData dataWithBytes:raw_bytes length:sizeof(raw_bytes)]; 351 call.requestHeaders[@"x-grpc-test-echo-trailing-bin"] = trailer_data; 352 353 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 354 initWithValueHandler:^(NSData *value) { 355 XCTAssertNotNil(value, @"nil value received as response."); 356 XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); 357 [response fulfill]; 358 } 359 completionHandler:^(NSError *errorOrNil) { 360 XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); 361 XCTAssertEqualObjects((NSData *)call.responseTrailers[@"x-grpc-test-echo-trailing-bin"], 362 trailer_data, @"Did not receive expected trailer"); 363 [completion fulfill]; 364 }]; 365 366 [call startWithWriteable:responsesWriteable]; 367 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 368} 369 370// TODO(makarandd): Move to a different file that contains only unit tests 371- (void)testExceptions { 372 GRXWriter *writer = [GRXWriter writerWithValue:[NSData data]]; 373 // Try to set parameters to nil for GRPCCall. This should cause an exception 374 @try { 375 (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:writer]; 376 XCTFail(@"Did not receive an exception when parameters are nil"); 377 } @catch (NSException *theException) { 378 NSLog(@"Received exception as expected: %@", theException.name); 379 } 380 381 // Set state to Finished by force 382 GRXWriter *requestsWriter = [GRXWriter emptyWriter]; 383 [requestsWriter finishWithError:nil]; 384 @try { 385 (void)[[GRPCCall alloc] initWithHost:kHostAddress 386 path:kUnaryCallMethod.HTTPPath 387 requestsWriter:requestsWriter]; 388 XCTFail(@"Did not receive an exception when GRXWriter has incorrect state."); 389 } @catch (NSException *theException) { 390 NSLog(@"Received exception as expected: %@", theException.name); 391 } 392} 393 394- (void)testIdempotentProtoRPC { 395 __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; 396 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; 397 398 RMTSimpleRequest *request = [RMTSimpleRequest message]; 399 request.responseSize = 100; 400 request.fillUsername = YES; 401 request.fillOauthScope = YES; 402 GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; 403 404 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 405 path:kUnaryCallMethod.HTTPPath 406 requestsWriter:requestsWriter]; 407 [GRPCCall setCallSafety:GRPCCallSafetyIdempotentRequest 408 host:kHostAddress 409 path:kUnaryCallMethod.HTTPPath]; 410 411 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 412 initWithValueHandler:^(NSData *value) { 413 XCTAssertNotNil(value, @"nil value received as response."); 414 XCTAssertGreaterThan(value.length, 0, @"Empty response received."); 415 RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL]; 416 // We expect empty strings, not nil: 417 XCTAssertNotNil(responseProto.username, @"Response's username is nil."); 418 XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil."); 419 [response fulfill]; 420 } 421 completionHandler:^(NSError *errorOrNil) { 422 XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); 423 [completion fulfill]; 424 }]; 425 426 [call startWithWriteable:responsesWriteable]; 427 428 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 429} 430 431- (void)testAlternateDispatchQueue { 432 const int32_t kPayloadSize = 100; 433 RMTSimpleRequest *request = [RMTSimpleRequest message]; 434 request.responseSize = kPayloadSize; 435 436 __weak XCTestExpectation *expectation1 = 437 [self expectationWithDescription:@"AlternateDispatchQueue1"]; 438 439 // Use default (main) dispatch queue 440 NSString *main_queue_label = 441 [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())]; 442 443 GRXWriter *requestsWriter1 = [GRXWriter writerWithValue:[request data]]; 444 445 GRPCCall *call1 = [[GRPCCall alloc] initWithHost:kHostAddress 446 path:kUnaryCallMethod.HTTPPath 447 requestsWriter:requestsWriter1]; 448 449 id<GRXWriteable> responsesWriteable1 = [[GRXWriteable alloc] 450 initWithValueHandler:^(NSData *value) { 451 NSString *label = 452 [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)]; 453 XCTAssert([label isEqualToString:main_queue_label]); 454 455 [expectation1 fulfill]; 456 } 457 completionHandler:^(NSError *errorOrNil){ 458 }]; 459 460 [call1 startWithWriteable:responsesWriteable1]; 461 462 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 463 464 // Use a custom queue 465 __weak XCTestExpectation *expectation2 = 466 [self expectationWithDescription:@"AlternateDispatchQueue2"]; 467 468 NSString *queue_label = @"test.queue1"; 469 dispatch_queue_t queue = dispatch_queue_create([queue_label UTF8String], DISPATCH_QUEUE_SERIAL); 470 471 GRXWriter *requestsWriter2 = [GRXWriter writerWithValue:[request data]]; 472 473 GRPCCall *call2 = [[GRPCCall alloc] initWithHost:kHostAddress 474 path:kUnaryCallMethod.HTTPPath 475 requestsWriter:requestsWriter2]; 476 477 [call2 setResponseDispatchQueue:queue]; 478 479 id<GRXWriteable> responsesWriteable2 = [[GRXWriteable alloc] 480 initWithValueHandler:^(NSData *value) { 481 NSString *label = 482 [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)]; 483 XCTAssert([label isEqualToString:queue_label]); 484 485 [expectation2 fulfill]; 486 } 487 completionHandler:^(NSError *errorOrNil){ 488 }]; 489 490 [call2 startWithWriteable:responsesWriteable2]; 491 492 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 493} 494 495- (void)testTimeout { 496 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; 497 498 GRXBufferedPipe *pipe = [GRXBufferedPipe pipe]; 499 GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress 500 path:kFullDuplexCallMethod.HTTPPath 501 requestsWriter:pipe]; 502 503 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 504 initWithValueHandler:^(NSData *value) { 505 XCTAssert(0, @"Failure: response received; Expect: no response received."); 506 } 507 completionHandler:^(NSError *errorOrNil) { 508 XCTAssertNotNil(errorOrNil, 509 @"Failure: no error received; Expect: receive deadline exceeded."); 510 XCTAssertEqual(errorOrNil.code, GRPCErrorCodeDeadlineExceeded); 511 [completion fulfill]; 512 }]; 513 514 call.timeout = 0.001; 515 [call startWithWriteable:responsesWriteable]; 516 517 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 518} 519 520- (int)findFreePort { 521 struct sockaddr_in addr; 522 unsigned int addr_len = sizeof(addr); 523 memset(&addr, 0, sizeof(addr)); 524 addr.sin_family = AF_INET; 525 int fd = socket(AF_INET, SOCK_STREAM, 0); 526 XCTAssertEqual(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), 0); 527 XCTAssertEqual(getsockname(fd, (struct sockaddr *)&addr, &addr_len), 0); 528 XCTAssertEqual(addr_len, sizeof(addr)); 529 close(fd); 530 return addr.sin_port; 531} 532 533- (void)testErrorCode { 534 int port = [self findFreePort]; 535 NSString *const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port]; 536 __weak XCTestExpectation *completion = 537 [self expectationWithDescription:@"Received correct error code."]; 538 539 GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress 540 path:kEmptyCallMethod.HTTPPath 541 requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; 542 543 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 544 initWithValueHandler:^(NSData *value) { 545 // Should not reach here 546 XCTAssert(NO); 547 } 548 completionHandler:^(NSError *errorOrNil) { 549 XCTAssertNotNil(errorOrNil, @"Finished with no error"); 550 XCTAssertEqual(errorOrNil.code, GRPC_STATUS_UNAVAILABLE); 551 [completion fulfill]; 552 }]; 553 554 [call startWithWriteable:responsesWriteable]; 555 556 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 557} 558 559- (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff { 560 const double maxConnectTime = timeout > backoff ? timeout : backoff; 561 const double kMargin = 0.1; 562 563 __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."]; 564 NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"]; 565 [GRPCCall useInsecureConnectionsForHost:kDummyAddress]; 566 [GRPCCall setMinConnectTimeout:timeout * 1000 567 initialBackoff:backoff * 1000 568 maxBackoff:0 569 forHost:kDummyAddress]; 570 GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress 571 path:@"/dummyPath" 572 requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; 573 NSDate *startTime = [NSDate date]; 574 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 575 initWithValueHandler:^(id value) { 576 XCTAssert(NO, @"Received message. Should not reach here"); 577 } 578 completionHandler:^(NSError *errorOrNil) { 579 XCTAssertNotNil(errorOrNil, @"Finished with no error"); 580 // The call must fail before maxConnectTime. However there is no lower bound on the time 581 // taken for connection. A shorter time happens when connection is actively refused 582 // by 8.8.8.8:1 before maxConnectTime elapsed. 583 XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:startTime], 584 maxConnectTime + kMargin); 585 [completion fulfill]; 586 }]; 587 588 [call startWithWriteable:responsesWriteable]; 589 590 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 591} 592 593// The numbers of the following three tests are selected to be smaller than the default values of 594// initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default 595// values fail to be overridden by the channel args. 596- (void)testTimeoutBackoff1 { 597 [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3]; 598} 599 600- (void)testTimeoutBackoff2 { 601 [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7]; 602} 603 604- (void)testErrorDebugInformation { 605 __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."]; 606 607 RMTSimpleRequest *request = [RMTSimpleRequest message]; 608 request.fillUsername = YES; 609 request.fillOauthScope = YES; 610 GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; 611 612 GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost 613 path:kUnaryCallMethod.HTTPPath 614 requestsWriter:requestsWriter]; 615 616 call.oauth2AccessToken = @"bogusToken"; 617 618 id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] 619 initWithValueHandler:^(NSData *value) { 620 XCTFail(@"Received unexpected response: %@", value); 621 } 622 completionHandler:^(NSError *errorOrNil) { 623 XCTAssertNotNil(errorOrNil, @"Finished without error!"); 624 NSDictionary *userInfo = errorOrNil.userInfo; 625 NSString *debugInformation = userInfo[NSDebugDescriptionErrorKey]; 626 XCTAssertNotNil(debugInformation); 627 XCTAssertNotEqual([debugInformation length], 0); 628 NSString *challengeHeader = call.oauth2ChallengeHeader; 629 XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@", 630 call.responseHeaders); 631 [expectation fulfill]; 632 }]; 633 634 [call startWithWriteable:responsesWriteable]; 635 636 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 637} 638 639@end 640