| /* |
| * |
| * Copyright 2015 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #import <UIKit/UIKit.h> |
| #import <XCTest/XCTest.h> |
| #import <grpc/grpc.h> |
| |
| #import <GRPCClient/GRPCCall+ChannelArg.h> |
| #import <GRPCClient/GRPCCall+OAuth2.h> |
| #import <GRPCClient/GRPCCall+Tests.h> |
| #import <GRPCClient/GRPCCall.h> |
| #import <GRPCClient/internal_testing/GRPCCall+InternalTests.h> |
| #import <ProtoRPC/ProtoMethod.h> |
| #import <RemoteTest/Messages.pbobjc.h> |
| #import <RxLibrary/GRXBufferedPipe.h> |
| #import <RxLibrary/GRXWriteable.h> |
| #import <RxLibrary/GRXWriter+Immediate.h> |
| |
| #include <netinet/in.h> |
| |
| #import "version.h" |
| |
| #define TEST_TIMEOUT 16 |
| |
| static NSString *const kHostAddress = @"localhost:5050"; |
| static NSString *const kPackage = @"grpc.testing"; |
| static NSString *const kService = @"TestService"; |
| static NSString *const kRemoteSSLHost = @"grpc-test.sandbox.googleapis.com"; |
| |
| static GRPCProtoMethod *kInexistentMethod; |
| static GRPCProtoMethod *kEmptyCallMethod; |
| static GRPCProtoMethod *kUnaryCallMethod; |
| static GRPCProtoMethod *kFullDuplexCallMethod; |
| |
| /** Observer class for testing that responseMetadata is KVO-compliant */ |
| @interface PassthroughObserver : NSObject |
| - (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback |
| NS_DESIGNATED_INITIALIZER; |
| |
| - (void)observeValueForKeyPath:(NSString *)keyPath |
| ofObject:(id)object |
| change:(NSDictionary *)change |
| context:(void *)context; |
| @end |
| |
| @implementation PassthroughObserver { |
| void (^_callback)(NSString *, id, NSDictionary *); |
| } |
| |
| - (instancetype)init { |
| return [self initWithCallback:nil]; |
| } |
| |
| - (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback { |
| if (!callback) { |
| return nil; |
| } |
| if ((self = [super init])) { |
| _callback = callback; |
| } |
| return self; |
| } |
| |
| - (void)observeValueForKeyPath:(NSString *)keyPath |
| ofObject:(id)object |
| change:(NSDictionary *)change |
| context:(void *)context { |
| _callback(keyPath, object, change); |
| [object removeObserver:self forKeyPath:keyPath]; |
| } |
| |
| @end |
| |
| #pragma mark Tests |
| |
| /** |
| * A few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) rather than |
| * a generated proto library on top of it. Its RPCs are sent to a local cleartext server. |
| * |
| * TODO(jcanizales): Run them also against a local SSL server and against a remote server. |
| */ |
| @interface GRPCClientTests : XCTestCase |
| @end |
| |
| @implementation GRPCClientTests |
| |
| + (void)setUp { |
| NSLog(@"GRPCClientTests Started"); |
| } |
| |
| - (void)setUp { |
| // Add a custom user agent prefix that will be used in test |
| [GRPCCall setUserAgentPrefix:@"Foo" forHost:kHostAddress]; |
| // Register test server as non-SSL. |
| [GRPCCall useInsecureConnectionsForHost:kHostAddress]; |
| |
| // This method isn't implemented by the remote server. |
| kInexistentMethod = |
| [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"]; |
| kEmptyCallMethod = |
| [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"]; |
| kUnaryCallMethod = |
| [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"]; |
| kFullDuplexCallMethod = |
| [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"]; |
| } |
| |
| - (void)testConnectionToRemoteServer { |
| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Server reachable."]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kInexistentMethod.HTTPPath |
| requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTFail(@"Received unexpected response: %@", value); |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNotNil(errorOrNil, @"Finished without error!"); |
| XCTAssertEqual(errorOrNil.code, 12, @"Finished with unexpected error: %@", errorOrNil); |
| [expectation fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testEmptyRPC { |
| __weak XCTestExpectation *response = |
| [self expectationWithDescription:@"Empty response received."]; |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kEmptyCallMethod.HTTPPath |
| requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTAssertNotNil(value, @"nil value received as response."); |
| XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); |
| [response fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testSimpleProtoRPC { |
| __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
| |
| RMTSimpleRequest *request = [RMTSimpleRequest message]; |
| request.responseSize = 100; |
| request.fillUsername = YES; |
| request.fillOauthScope = YES; |
| GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kUnaryCallMethod.HTTPPath |
| requestsWriter:requestsWriter]; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTAssertNotNil(value, @"nil value received as response."); |
| XCTAssertGreaterThan(value.length, 0, @"Empty response received."); |
| RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL]; |
| // We expect empty strings, not nil: |
| XCTAssertNotNil(responseProto.username, @"Response's username is nil."); |
| XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil."); |
| [response fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testMetadata { |
| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."]; |
| |
| RMTSimpleRequest *request = [RMTSimpleRequest message]; |
| request.fillUsername = YES; |
| request.fillOauthScope = YES; |
| GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost |
| path:kUnaryCallMethod.HTTPPath |
| requestsWriter:requestsWriter]; |
| |
| call.oauth2AccessToken = @"bogusToken"; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTFail(@"Received unexpected response: %@", value); |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNotNil(errorOrNil, @"Finished without error!"); |
| XCTAssertEqual(errorOrNil.code, 16, @"Finished with unexpected error: %@", errorOrNil); |
| XCTAssertEqualObjects(call.responseHeaders, errorOrNil.userInfo[kGRPCHeadersKey], |
| @"Headers in the NSError object and call object differ."); |
| XCTAssertEqualObjects(call.responseTrailers, errorOrNil.userInfo[kGRPCTrailersKey], |
| @"Trailers in the NSError object and call object differ."); |
| NSString *challengeHeader = call.oauth2ChallengeHeader; |
| XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@", |
| call.responseHeaders); |
| [expectation fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testResponseMetadataKVO { |
| __weak XCTestExpectation *response = |
| [self expectationWithDescription:@"Empty response received."]; |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
| __weak XCTestExpectation *metadata = [self expectationWithDescription:@"Metadata changed."]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kEmptyCallMethod.HTTPPath |
| requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
| |
| PassthroughObserver *observer = [[PassthroughObserver alloc] |
| initWithCallback:^(NSString *keypath, id object, NSDictionary *change) { |
| if ([keypath isEqual:@"responseHeaders"]) { |
| [metadata fulfill]; |
| } |
| }]; |
| |
| [call addObserver:observer forKeyPath:@"responseHeaders" options:0 context:NULL]; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTAssertNotNil(value, @"nil value received as response."); |
| XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); |
| [response fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testUserAgentPrefix { |
| __weak XCTestExpectation *response = |
| [self expectationWithDescription:@"Empty response received."]; |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kEmptyCallMethod.HTTPPath |
| requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
| // Setting this special key in the header will cause the interop server to echo back the |
| // user-agent value, which we confirm. |
| call.requestHeaders[@"x-grpc-test-echo-useragent"] = @""; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTAssertNotNil(value, @"nil value received as response."); |
| XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); |
| |
| NSString *userAgent = call.responseHeaders[@"x-grpc-test-echo-useragent"]; |
| NSError *error = nil; |
| |
| // Test the regex is correct |
| NSString *expectedUserAgent = @"Foo grpc-objc/"; |
| expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING]; |
| expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"]; |
| expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING]; |
| expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "]; |
| expectedUserAgent = [expectedUserAgent |
| stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]]; |
| expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"]; |
| XCTAssertEqualObjects(userAgent, expectedUserAgent); |
| |
| // Change in format of user-agent field in a direction that does not match the regex will |
| // likely cause problem for certain gRPC users. For details, refer to internal doc |
| // https://goo.gl/c2diBc |
| NSRegularExpression *regex = [NSRegularExpression |
| regularExpressionWithPattern:@" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?" |
| options:0 |
| error:&error]; |
| NSString *customUserAgent = |
| [regex stringByReplacingMatchesInString:userAgent |
| options:0 |
| range:NSMakeRange(0, [userAgent length]) |
| withTemplate:@""]; |
| XCTAssertEqualObjects(customUserAgent, @"Foo"); |
| |
| [response fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testTrailers { |
| __weak XCTestExpectation *response = |
| [self expectationWithDescription:@"Empty response received."]; |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kEmptyCallMethod.HTTPPath |
| requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
| // Setting this special key in the header will cause the interop server to echo back the |
| // trailer data. |
| const unsigned char raw_bytes[] = {1, 2, 3, 4}; |
| NSData *trailer_data = [NSData dataWithBytes:raw_bytes length:sizeof(raw_bytes)]; |
| call.requestHeaders[@"x-grpc-test-echo-trailing-bin"] = trailer_data; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTAssertNotNil(value, @"nil value received as response."); |
| XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); |
| [response fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
| XCTAssertEqualObjects((NSData *)call.responseTrailers[@"x-grpc-test-echo-trailing-bin"], |
| trailer_data, @"Did not receive expected trailer"); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| // TODO(makarandd): Move to a different file that contains only unit tests |
| - (void)testExceptions { |
| // Try to set parameters to nil for GRPCCall. This should cause an exception |
| @try { |
| (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:nil]; |
| XCTFail(@"Did not receive an exception when parameters are nil"); |
| } @catch (NSException *theException) { |
| NSLog(@"Received exception as expected: %@", theException.name); |
| } |
| |
| // Set state to Finished by force |
| GRXWriter *requestsWriter = [GRXWriter emptyWriter]; |
| [requestsWriter finishWithError:nil]; |
| @try { |
| (void)[[GRPCCall alloc] initWithHost:kHostAddress |
| path:kUnaryCallMethod.HTTPPath |
| requestsWriter:requestsWriter]; |
| XCTFail(@"Did not receive an exception when GRXWriter has incorrect state."); |
| } @catch (NSException *theException) { |
| NSLog(@"Received exception as expected: %@", theException.name); |
| } |
| } |
| |
| - (void)testIdempotentProtoRPC { |
| __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
| |
| RMTSimpleRequest *request = [RMTSimpleRequest message]; |
| request.responseSize = 100; |
| request.fillUsername = YES; |
| request.fillOauthScope = YES; |
| GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kUnaryCallMethod.HTTPPath |
| requestsWriter:requestsWriter]; |
| [GRPCCall setCallSafety:GRPCCallSafetyIdempotentRequest |
| host:kHostAddress |
| path:kUnaryCallMethod.HTTPPath]; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTAssertNotNil(value, @"nil value received as response."); |
| XCTAssertGreaterThan(value.length, 0, @"Empty response received."); |
| RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL]; |
| // We expect empty strings, not nil: |
| XCTAssertNotNil(responseProto.username, @"Response's username is nil."); |
| XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil."); |
| [response fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testAlternateDispatchQueue { |
| const int32_t kPayloadSize = 100; |
| RMTSimpleRequest *request = [RMTSimpleRequest message]; |
| request.responseSize = kPayloadSize; |
| |
| __weak XCTestExpectation *expectation1 = |
| [self expectationWithDescription:@"AlternateDispatchQueue1"]; |
| |
| // Use default (main) dispatch queue |
| NSString *main_queue_label = |
| [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())]; |
| |
| GRXWriter *requestsWriter1 = [GRXWriter writerWithValue:[request data]]; |
| |
| GRPCCall *call1 = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kUnaryCallMethod.HTTPPath |
| requestsWriter:requestsWriter1]; |
| |
| id<GRXWriteable> responsesWriteable1 = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| NSString *label = |
| [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)]; |
| XCTAssert([label isEqualToString:main_queue_label]); |
| |
| [expectation1 fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil){ |
| }]; |
| |
| [call1 startWithWriteable:responsesWriteable1]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| |
| // Use a custom queue |
| __weak XCTestExpectation *expectation2 = |
| [self expectationWithDescription:@"AlternateDispatchQueue2"]; |
| |
| NSString *queue_label = @"test.queue1"; |
| dispatch_queue_t queue = dispatch_queue_create([queue_label UTF8String], DISPATCH_QUEUE_SERIAL); |
| |
| GRXWriter *requestsWriter2 = [GRXWriter writerWithValue:[request data]]; |
| |
| GRPCCall *call2 = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kUnaryCallMethod.HTTPPath |
| requestsWriter:requestsWriter2]; |
| |
| [call2 setResponseDispatchQueue:queue]; |
| |
| id<GRXWriteable> responsesWriteable2 = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| NSString *label = |
| [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)]; |
| XCTAssert([label isEqualToString:queue_label]); |
| |
| [expectation2 fulfill]; |
| } |
| completionHandler:^(NSError *errorOrNil){ |
| }]; |
| |
| [call2 startWithWriteable:responsesWriteable2]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testTimeout { |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
| |
| GRXBufferedPipe *pipe = [GRXBufferedPipe pipe]; |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
| path:kFullDuplexCallMethod.HTTPPath |
| requestsWriter:pipe]; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| XCTAssert(0, @"Failure: response received; Expect: no response received."); |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNotNil(errorOrNil, |
| @"Failure: no error received; Expect: receive deadline exceeded."); |
| XCTAssertEqual(errorOrNil.code, GRPCErrorCodeDeadlineExceeded); |
| [completion fulfill]; |
| }]; |
| |
| call.timeout = 0.001; |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (int)findFreePort { |
| struct sockaddr_in addr; |
| unsigned int addr_len = sizeof(addr); |
| memset(&addr, 0, sizeof(addr)); |
| addr.sin_family = AF_INET; |
| int fd = socket(AF_INET, SOCK_STREAM, 0); |
| XCTAssertEqual(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), 0); |
| XCTAssertEqual(getsockname(fd, (struct sockaddr *)&addr, &addr_len), 0); |
| XCTAssertEqual(addr_len, sizeof(addr)); |
| close(fd); |
| return addr.sin_port; |
| } |
| |
| - (void)testErrorCode { |
| int port = [self findFreePort]; |
| NSString *const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port]; |
| __weak XCTestExpectation *completion = |
| [self expectationWithDescription:@"Received correct error code."]; |
| |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress |
| path:kEmptyCallMethod.HTTPPath |
| requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
| |
| id<GRXWriteable> responsesWriteable = |
| [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
| // Should not reach here |
| XCTAssert(NO); |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNotNil(errorOrNil, @"Finished with no error"); |
| XCTAssertEqual(errorOrNil.code, GRPC_STATUS_UNAVAILABLE); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| - (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff { |
| const double maxConnectTime = timeout > backoff ? timeout : backoff; |
| const double kMargin = 0.1; |
| |
| __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."]; |
| NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"]; |
| GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress |
| path:@"" |
| requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
| [GRPCCall setMinConnectTimeout:timeout * 1000 |
| initialBackoff:backoff * 1000 |
| maxBackoff:0 |
| forHost:kDummyAddress]; |
| NSDate *startTime = [NSDate date]; |
| id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(id value) { |
| XCTAssert(NO, @"Received message. Should not reach here"); |
| } |
| completionHandler:^(NSError *errorOrNil) { |
| XCTAssertNotNil(errorOrNil, @"Finished with no error"); |
| // The call must fail before maxConnectTime. However there is no lower bound on the time |
| // taken for connection. A shorter time happens when connection is actively refused |
| // by 8.8.8.8:1 before maxConnectTime elapsed. |
| XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:startTime], |
| maxConnectTime + kMargin); |
| [completion fulfill]; |
| }]; |
| |
| [call startWithWriteable:responsesWriteable]; |
| |
| [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
| } |
| |
| // The numbers of the following three tests are selected to be smaller than the default values of |
| // initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default |
| // values fail to be overridden by the channel args. |
| - (void)testTimeoutBackoff2 { |
| [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3]; |
| } |
| |
| - (void)testTimeoutBackoff3 { |
| [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7]; |
| } |
| |
| @end |