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 "InteropTests.h"
     20 
     21 #include <grpc/status.h>
     22 
     23 #import <Cronet/Cronet.h>
     24 #import <GRPCClient/GRPCCall+ChannelArg.h>
     25 #import <GRPCClient/GRPCCall+Cronet.h>
     26 #import <GRPCClient/GRPCCall+Tests.h>
     27 #import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
     28 #import <ProtoRPC/ProtoRPC.h>
     29 #import <RemoteTest/Messages.pbobjc.h>
     30 #import <RemoteTest/Test.pbobjc.h>
     31 #import <RemoteTest/Test.pbrpc.h>
     32 #import <RxLibrary/GRXBufferedPipe.h>
     33 #import <RxLibrary/GRXWriter+Immediate.h>
     34 #import <grpc/grpc.h>
     35 #import <grpc/support/log.h>
     36 
     37 #define TEST_TIMEOUT 32
     38 
     39 extern const char *kCFStreamVarName;
     40 
     41 // Convenience constructors for the generated proto messages:
     42 
     43 @interface RMTStreamingOutputCallRequest (Constructors)
     44 + (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize
     45                  requestedResponseSize:(NSNumber *)responseSize;
     46 @end
     47 
     48 @implementation RMTStreamingOutputCallRequest (Constructors)
     49 + (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize
     50                  requestedResponseSize:(NSNumber *)responseSize {
     51   RMTStreamingOutputCallRequest *request = [self message];
     52   RMTResponseParameters *parameters = [RMTResponseParameters message];
     53   parameters.size = responseSize.intValue;
     54   [request.responseParametersArray addObject:parameters];
     55   request.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue];
     56   return request;
     57 }
     58 @end
     59 
     60 @interface RMTStreamingOutputCallResponse (Constructors)
     61 + (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize;
     62 @end
     63 
     64 @implementation RMTStreamingOutputCallResponse (Constructors)
     65 + (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize {
     66   RMTStreamingOutputCallResponse *response = [self message];
     67   response.payload.type = RMTPayloadType_Compressable;
     68   response.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue];
     69   return response;
     70 }
     71 @end
     72 
     73 BOOL isRemoteInteropTest(NSString *host) {
     74   return [host isEqualToString:@"grpc-test.sandbox.googleapis.com"];
     75 }
     76 
     77 #pragma mark Tests
     78 
     79 @implementation InteropTests {
     80   RMTTestService *_service;
     81 }
     82 
     83 + (NSString *)host {
     84   return nil;
     85 }
     86 
     87 // This number indicates how many bytes of overhead does Protocol Buffers encoding add onto the
     88 // message. The number varies as different message.proto is used on different servers. The actual
     89 // number for each interop server is overridden in corresponding derived test classes.
     90 - (int32_t)encodingOverhead {
     91   return 0;
     92 }
     93 
     94 + (void)setUp {
     95   NSLog(@"InteropTest Started, class: %@", [[self class] description]);
     96 #ifdef GRPC_COMPILE_WITH_CRONET
     97   // Cronet setup
     98   [Cronet setHttp2Enabled:YES];
     99   [Cronet start];
    100   [GRPCCall useCronetWithEngine:[Cronet getGlobalEngine]];
    101 #endif
    102 #ifdef GRPC_CFSTREAM
    103   setenv(kCFStreamVarName, "1", 1);
    104 #endif
    105 }
    106 
    107 - (void)setUp {
    108   self.continueAfterFailure = NO;
    109 
    110   [GRPCCall resetHostSettings];
    111 
    112   _service = self.class.host ? [RMTTestService serviceWithHost:self.class.host] : nil;
    113 }
    114 
    115 - (void)testEmptyUnaryRPC {
    116   XCTAssertNotNil(self.class.host);
    117   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyUnary"];
    118 
    119   GPBEmpty *request = [GPBEmpty message];
    120 
    121   [_service emptyCallWithRequest:request
    122                          handler:^(GPBEmpty *response, NSError *error) {
    123                            XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    124 
    125                            id expectedResponse = [GPBEmpty message];
    126                            XCTAssertEqualObjects(response, expectedResponse);
    127 
    128                            [expectation fulfill];
    129                          }];
    130 
    131   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    132 }
    133 
    134 - (void)testLargeUnaryRPC {
    135   XCTAssertNotNil(self.class.host);
    136   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
    137 
    138   RMTSimpleRequest *request = [RMTSimpleRequest message];
    139   request.responseType = RMTPayloadType_Compressable;
    140   request.responseSize = 314159;
    141   request.payload.body = [NSMutableData dataWithLength:271828];
    142 
    143   [_service unaryCallWithRequest:request
    144                          handler:^(RMTSimpleResponse *response, NSError *error) {
    145                            XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    146 
    147                            RMTSimpleResponse *expectedResponse = [RMTSimpleResponse message];
    148                            expectedResponse.payload.type = RMTPayloadType_Compressable;
    149                            expectedResponse.payload.body = [NSMutableData dataWithLength:314159];
    150                            XCTAssertEqualObjects(response, expectedResponse);
    151 
    152                            [expectation fulfill];
    153                          }];
    154 
    155   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    156 }
    157 
    158 - (void)testPacketCoalescing {
    159   XCTAssertNotNil(self.class.host);
    160   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
    161 
    162   RMTSimpleRequest *request = [RMTSimpleRequest message];
    163   request.responseType = RMTPayloadType_Compressable;
    164   request.responseSize = 10;
    165   request.payload.body = [NSMutableData dataWithLength:10];
    166 
    167   [GRPCCall enableOpBatchLog:YES];
    168   [_service unaryCallWithRequest:request
    169                          handler:^(RMTSimpleResponse *response, NSError *error) {
    170                            XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    171 
    172                            RMTSimpleResponse *expectedResponse = [RMTSimpleResponse message];
    173                            expectedResponse.payload.type = RMTPayloadType_Compressable;
    174                            expectedResponse.payload.body = [NSMutableData dataWithLength:10];
    175                            XCTAssertEqualObjects(response, expectedResponse);
    176 
    177                            // The test is a success if there is a batch of exactly 3 ops
    178                            // (SEND_INITIAL_METADATA, SEND_MESSAGE, SEND_CLOSE_FROM_CLIENT). Without
    179                            // packet coalescing each batch of ops contains only one op.
    180                            NSArray *opBatches = [GRPCCall obtainAndCleanOpBatchLog];
    181                            const NSInteger kExpectedOpBatchSize = 3;
    182                            for (NSObject *o in opBatches) {
    183                              if ([o isKindOfClass:[NSArray class]]) {
    184                                NSArray *batch = (NSArray *)o;
    185                                if ([batch count] == kExpectedOpBatchSize) {
    186                                  [expectation fulfill];
    187                                  break;
    188                                }
    189                              }
    190                            }
    191                          }];
    192 
    193   [self waitForExpectationsWithTimeout:16 handler:nil];
    194   [GRPCCall enableOpBatchLog:NO];
    195 }
    196 
    197 - (void)test4MBResponsesAreAccepted {
    198   XCTAssertNotNil(self.class.host);
    199   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"MaxResponseSize"];
    200 
    201   RMTSimpleRequest *request = [RMTSimpleRequest message];
    202   const int32_t kPayloadSize = 4 * 1024 * 1024 - self.encodingOverhead;  // 4MB - encoding overhead
    203   request.responseSize = kPayloadSize;
    204 
    205   [_service unaryCallWithRequest:request
    206                          handler:^(RMTSimpleResponse *response, NSError *error) {
    207                            XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    208                            XCTAssertEqual(response.payload.body.length, kPayloadSize);
    209                            [expectation fulfill];
    210                          }];
    211 
    212   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    213 }
    214 
    215 - (void)testResponsesOverMaxSizeFailWithActionableMessage {
    216   XCTAssertNotNil(self.class.host);
    217   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"ResponseOverMaxSize"];
    218 
    219   RMTSimpleRequest *request = [RMTSimpleRequest message];
    220   const int32_t kPayloadSize = 4 * 1024 * 1024 - self.encodingOverhead + 1;  // 1B over max size
    221   request.responseSize = kPayloadSize;
    222 
    223   [_service unaryCallWithRequest:request
    224                          handler:^(RMTSimpleResponse *response, NSError *error) {
    225                            // TODO(jcanizales): Catch the error and rethrow it with an actionable
    226                            // message:
    227                            // - Use +[GRPCCall setResponseSizeLimit:forHost:] to set a higher limit.
    228                            // - If you're developing the server, consider using response streaming,
    229                            // or let clients filter
    230                            //   responses by setting a google.protobuf.FieldMask in the request:
    231                            //   https://github.com/google/protobuf/blob/master/src/google/protobuf/field_mask.proto
    232                            XCTAssertEqualObjects(
    233                                error.localizedDescription,
    234                                @"Received message larger than max (4194305 vs. 4194304)");
    235                            [expectation fulfill];
    236                          }];
    237 
    238   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    239 }
    240 
    241 - (void)testResponsesOver4MBAreAcceptedIfOptedIn {
    242   XCTAssertNotNil(self.class.host);
    243   __weak XCTestExpectation *expectation =
    244       [self expectationWithDescription:@"HigherResponseSizeLimit"];
    245 
    246   RMTSimpleRequest *request = [RMTSimpleRequest message];
    247   const size_t kPayloadSize = 5 * 1024 * 1024;  // 5MB
    248   request.responseSize = kPayloadSize;
    249 
    250   [GRPCCall setResponseSizeLimit:6 * 1024 * 1024 forHost:self.class.host];
    251 
    252   [_service unaryCallWithRequest:request
    253                          handler:^(RMTSimpleResponse *response, NSError *error) {
    254                            XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    255                            XCTAssertEqual(response.payload.body.length, kPayloadSize);
    256                            [expectation fulfill];
    257                          }];
    258 
    259   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    260 }
    261 
    262 - (void)testClientStreamingRPC {
    263   XCTAssertNotNil(self.class.host);
    264   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"ClientStreaming"];
    265 
    266   RMTStreamingInputCallRequest *request1 = [RMTStreamingInputCallRequest message];
    267   request1.payload.body = [NSMutableData dataWithLength:27182];
    268 
    269   RMTStreamingInputCallRequest *request2 = [RMTStreamingInputCallRequest message];
    270   request2.payload.body = [NSMutableData dataWithLength:8];
    271 
    272   RMTStreamingInputCallRequest *request3 = [RMTStreamingInputCallRequest message];
    273   request3.payload.body = [NSMutableData dataWithLength:1828];
    274 
    275   RMTStreamingInputCallRequest *request4 = [RMTStreamingInputCallRequest message];
    276   request4.payload.body = [NSMutableData dataWithLength:45904];
    277 
    278   GRXWriter *writer = [GRXWriter writerWithContainer:@[ request1, request2, request3, request4 ]];
    279 
    280   [_service streamingInputCallWithRequestsWriter:writer
    281                                          handler:^(RMTStreamingInputCallResponse *response,
    282                                                    NSError *error) {
    283                                            XCTAssertNil(
    284                                                error, @"Finished with unexpected error: %@", error);
    285 
    286                                            RMTStreamingInputCallResponse *expectedResponse =
    287                                                [RMTStreamingInputCallResponse message];
    288                                            expectedResponse.aggregatedPayloadSize = 74922;
    289                                            XCTAssertEqualObjects(response, expectedResponse);
    290 
    291                                            [expectation fulfill];
    292                                          }];
    293 
    294   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    295 }
    296 
    297 - (void)testServerStreamingRPC {
    298   XCTAssertNotNil(self.class.host);
    299   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"ServerStreaming"];
    300 
    301   NSArray *expectedSizes = @[ @31415, @9, @2653, @58979 ];
    302 
    303   RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message];
    304   for (NSNumber *size in expectedSizes) {
    305     RMTResponseParameters *parameters = [RMTResponseParameters message];
    306     parameters.size = [size intValue];
    307     [request.responseParametersArray addObject:parameters];
    308   }
    309 
    310   __block int index = 0;
    311   [_service
    312       streamingOutputCallWithRequest:request
    313                         eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response,
    314                                        NSError *error) {
    315                           XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    316                           XCTAssertTrue(done || response,
    317                                         @"Event handler called without an event.");
    318 
    319                           if (response) {
    320                             XCTAssertLessThan(index, 4, @"More than 4 responses received.");
    321                             id expected = [RMTStreamingOutputCallResponse
    322                                 messageWithPayloadSize:expectedSizes[index]];
    323                             XCTAssertEqualObjects(response, expected);
    324                             index += 1;
    325                           }
    326 
    327                           if (done) {
    328                             XCTAssertEqual(index, 4, @"Received %i responses instead of 4.", index);
    329                             [expectation fulfill];
    330                           }
    331                         }];
    332 
    333   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    334 }
    335 
    336 - (void)testPingPongRPC {
    337   XCTAssertNotNil(self.class.host);
    338   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPong"];
    339 
    340   NSArray *requests = @[ @27182, @8, @1828, @45904 ];
    341   NSArray *responses = @[ @31415, @9, @2653, @58979 ];
    342 
    343   GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init];
    344 
    345   __block int index = 0;
    346 
    347   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
    348                                                requestedResponseSize:responses[index]];
    349   [requestsBuffer writeValue:request];
    350 
    351   [_service fullDuplexCallWithRequestsWriter:requestsBuffer
    352                                 eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response,
    353                                                NSError *error) {
    354                                   XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    355                                   XCTAssertTrue(done || response,
    356                                                 @"Event handler called without an event.");
    357 
    358                                   if (response) {
    359                                     XCTAssertLessThan(index, 4, @"More than 4 responses received.");
    360                                     id expected = [RMTStreamingOutputCallResponse
    361                                         messageWithPayloadSize:responses[index]];
    362                                     XCTAssertEqualObjects(response, expected);
    363                                     index += 1;
    364                                     if (index < 4) {
    365                                       id request = [RMTStreamingOutputCallRequest
    366                                           messageWithPayloadSize:requests[index]
    367                                            requestedResponseSize:responses[index]];
    368                                       [requestsBuffer writeValue:request];
    369                                     } else {
    370                                       [requestsBuffer writesFinishedWithError:nil];
    371                                     }
    372                                   }
    373 
    374                                   if (done) {
    375                                     XCTAssertEqual(index, 4, @"Received %i responses instead of 4.",
    376                                                    index);
    377                                     [expectation fulfill];
    378                                   }
    379                                 }];
    380   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    381 }
    382 
    383 - (void)testEmptyStreamRPC {
    384   XCTAssertNotNil(self.class.host);
    385   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyStream"];
    386   [_service fullDuplexCallWithRequestsWriter:[GRXWriter emptyWriter]
    387                                 eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response,
    388                                                NSError *error) {
    389                                   XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    390                                   XCTAssert(done, @"Unexpected response: %@", response);
    391                                   [expectation fulfill];
    392                                 }];
    393   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    394 }
    395 
    396 - (void)testCancelAfterBeginRPC {
    397   XCTAssertNotNil(self.class.host);
    398   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"CancelAfterBegin"];
    399 
    400   // A buffered pipe to which we never write any value acts as a writer that just hangs.
    401   GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init];
    402 
    403   GRPCProtoCall *call = [_service
    404       RPCToStreamingInputCallWithRequestsWriter:requestsBuffer
    405                                         handler:^(RMTStreamingInputCallResponse *response,
    406                                                   NSError *error) {
    407                                           XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED);
    408                                           [expectation fulfill];
    409                                         }];
    410   XCTAssertEqual(call.state, GRXWriterStateNotStarted);
    411 
    412   [call start];
    413   XCTAssertEqual(call.state, GRXWriterStateStarted);
    414 
    415   [call cancel];
    416   XCTAssertEqual(call.state, GRXWriterStateFinished);
    417 
    418   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    419 }
    420 
    421 - (void)testCancelAfterFirstResponseRPC {
    422   XCTAssertNotNil(self.class.host);
    423   __weak XCTestExpectation *expectation =
    424       [self expectationWithDescription:@"CancelAfterFirstResponse"];
    425 
    426   // A buffered pipe to which we write a single value but never close
    427   GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init];
    428 
    429   __block BOOL receivedResponse = NO;
    430 
    431   id request =
    432       [RMTStreamingOutputCallRequest messageWithPayloadSize:@21782 requestedResponseSize:@31415];
    433 
    434   [requestsBuffer writeValue:request];
    435 
    436   __block GRPCProtoCall *call = [_service
    437       RPCToFullDuplexCallWithRequestsWriter:requestsBuffer
    438                                eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response,
    439                                               NSError *error) {
    440                                  if (receivedResponse) {
    441                                    XCTAssert(done, @"Unexpected extra response %@", response);
    442                                    XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED);
    443                                    [expectation fulfill];
    444                                  } else {
    445                                    XCTAssertNil(error, @"Finished with unexpected error: %@",
    446                                                 error);
    447                                    XCTAssertFalse(done, @"Finished without response");
    448                                    XCTAssertNotNil(response);
    449                                    receivedResponse = YES;
    450                                    [call cancel];
    451                                  }
    452                                }];
    453   [call start];
    454   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    455 }
    456 
    457 - (void)testRPCAfterClosingOpenConnections {
    458   XCTAssertNotNil(self.class.host);
    459   __weak XCTestExpectation *expectation =
    460       [self expectationWithDescription:@"RPC after closing connection"];
    461 
    462   GPBEmpty *request = [GPBEmpty message];
    463 
    464   [_service
    465       emptyCallWithRequest:request
    466                    handler:^(GPBEmpty *response, NSError *error) {
    467                      XCTAssertNil(error, @"First RPC finished with unexpected error: %@", error);
    468 
    469 #pragma clang diagnostic push
    470 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
    471                      [GRPCCall closeOpenConnections];
    472 #pragma clang diagnostic pop
    473 
    474                      [_service
    475                          emptyCallWithRequest:request
    476                                       handler:^(GPBEmpty *response, NSError *error) {
    477                                         XCTAssertNil(
    478                                             error, @"Second RPC finished with unexpected error: %@",
    479                                             error);
    480                                         [expectation fulfill];
    481                                       }];
    482                    }];
    483 
    484   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    485 }
    486 
    487 - (void)testCompressedUnaryRPC {
    488   // This test needs to be disabled for remote test because interop server grpc-test
    489   // does not support compression.
    490   if (isRemoteInteropTest(self.class.host)) {
    491     return;
    492   }
    493   XCTAssertNotNil(self.class.host);
    494   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
    495 
    496   RMTSimpleRequest *request = [RMTSimpleRequest message];
    497   request.responseType = RMTPayloadType_Compressable;
    498   request.responseSize = 314159;
    499   request.payload.body = [NSMutableData dataWithLength:271828];
    500   request.expectCompressed.value = YES;
    501   [GRPCCall setDefaultCompressMethod:GRPCCompressGzip forhost:self.class.host];
    502 
    503   [_service unaryCallWithRequest:request
    504                          handler:^(RMTSimpleResponse *response, NSError *error) {
    505                            XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    506 
    507                            RMTSimpleResponse *expectedResponse = [RMTSimpleResponse message];
    508                            expectedResponse.payload.type = RMTPayloadType_Compressable;
    509                            expectedResponse.payload.body = [NSMutableData dataWithLength:314159];
    510                            XCTAssertEqualObjects(response, expectedResponse);
    511 
    512                            [expectation fulfill];
    513                          }];
    514 
    515   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    516 }
    517 
    518 #ifndef GRPC_COMPILE_WITH_CRONET
    519 - (void)testKeepalive {
    520   XCTAssertNotNil(self.class.host);
    521   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Keepalive"];
    522 
    523   [GRPCCall setKeepaliveWithInterval:1500 timeout:0 forHost:self.class.host];
    524 
    525   NSArray *requests = @[ @27182, @8 ];
    526   NSArray *responses = @[ @31415, @9 ];
    527 
    528   GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init];
    529 
    530   __block int index = 0;
    531 
    532   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
    533                                                requestedResponseSize:responses[index]];
    534   [requestsBuffer writeValue:request];
    535 
    536   [_service
    537       fullDuplexCallWithRequestsWriter:requestsBuffer
    538                           eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response,
    539                                          NSError *error) {
    540                             if (index == 0) {
    541                               XCTAssertNil(error, @"Finished with unexpected error: %@", error);
    542                               XCTAssertTrue(response, @"Event handler called without an event.");
    543                               XCTAssertFalse(done);
    544                               index++;
    545                             } else {
    546                               // Keepalive should kick after 1s elapsed and fails the call.
    547                               XCTAssertNotNil(error);
    548                               XCTAssertEqual(error.code, GRPC_STATUS_INTERNAL);
    549                               XCTAssertEqualObjects(
    550                                   error.localizedDescription, @"keepalive watchdog timeout",
    551                                   @"Unexpected failure that is not keepalive watchdog timeout.");
    552                               XCTAssertTrue(done);
    553                               [expectation fulfill];
    554                             }
    555                           }];
    556 
    557   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
    558 }
    559 #endif
    560 
    561 @end
    562