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
39extern 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
73BOOL 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