Home | History | Annotate | Download | only in tests
      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