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