From 2b6e7c44235522c125dbc14e6b82e18c8aab62cd Mon Sep 17 00:00:00 2001 From: Prashant Jaikumar Date: Wed, 8 May 2019 14:29:11 -0700 Subject: [PATCH] Added some Objective C tests and minor bug fixes. Objective-C tests: metadata, compression, keepalives, channel args. Stress tests: network flap while streaming call in progress. Bug fixes: Stream gzip handling in interop server. Keep alive, backoff time truncation bug in Obj-C layer. --- .../GRPCClient/GRPCCall+ChannelArg.m | 3 + src/objective-c/GRPCClient/GRPCCallOptions.h | 5 +- src/objective-c/GRPCClient/GRPCCallOptions.m | 10 +- .../GRPCClient/private/GRPCChannel.m | 2 + .../GRPCClient/private/GRPCChannelPool.m | 4 +- src/objective-c/GRPCClient/private/GRPCHost.m | 10 +- .../InteropTestsRemoteWithCronet.m | 4 + .../tests/InteropTests/InteropTests.h | 6 + .../tests/InteropTests/InteropTests.m | 190 ++++++++- .../tests/InteropTests/InteropTestsRemote.m | 4 + src/objective-c/tests/MacTests/StressTests.m | 383 +++++++++++++++++- src/objective-c/tests/UnitTests/APIv2Tests.m | 330 ++++++++++++++- test/cpp/interop/interop_server.cc | 3 +- 13 files changed, 921 insertions(+), 33 deletions(-) diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m index ae60d6208e1..78fe687b3d4 100644 --- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m +++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m @@ -51,6 +51,9 @@ case GRPCCompressGzip: hostConfig.compressAlgorithm = GRPC_COMPRESS_GZIP; break; + case GRPCStreamCompressGzip: + hostConfig.compressAlgorithm = GRPC_COMPRESS_STREAM_GZIP; + break; default: NSLog(@"Invalid compression algorithm"); abort(); diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.h b/src/objective-c/GRPCClient/GRPCCallOptions.h index 98511e3f5cb..b957e11fbea 100644 --- a/src/objective-c/GRPCClient/GRPCCallOptions.h +++ b/src/objective-c/GRPCClient/GRPCCallOptions.h @@ -156,7 +156,8 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) { // HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two // PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the // call should wait for PING ACK. If PING ACK is not received after this period, the call fails. -// Negative values are not allowed. +// Negative values are invalid; setting these parameters to negative value will reset the +// corresponding parameter to the internal default value. @property(readonly) NSTimeInterval keepaliveInterval; @property(readonly) NSTimeInterval keepaliveTimeout; @@ -320,7 +321,7 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) { // PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the // call should wait for PING ACK. If PING ACK is not received after this period, the call fails. // Negative values are invalid; setting these parameters to negative value will reset the -// corresponding parameter to 0. +// corresponding parameter to the internal default value. @property(readwrite) NSTimeInterval keepaliveInterval; @property(readwrite) NSTimeInterval keepaliveTimeout; diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.m b/src/objective-c/GRPCClient/GRPCCallOptions.m index 392e42a9d47..f02486a7c44 100644 --- a/src/objective-c/GRPCClient/GRPCCallOptions.m +++ b/src/objective-c/GRPCClient/GRPCCallOptions.m @@ -30,7 +30,7 @@ static const NSUInteger kDefaultResponseSizeLimit = 0; static const GRPCCompressionAlgorithm kDefaultCompressionAlgorithm = GRPCCompressNone; static const BOOL kDefaultRetryEnabled = YES; static const NSTimeInterval kDefaultKeepaliveInterval = 0; -static const NSTimeInterval kDefaultKeepaliveTimeout = 0; +static const NSTimeInterval kDefaultKeepaliveTimeout = -1; static const NSTimeInterval kDefaultConnectMinTimeout = 0; static const NSTimeInterval kDefaultConnectInitialBackoff = 0; static const NSTimeInterval kDefaultConnectMaxBackoff = 0; @@ -181,7 +181,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) { _compressionAlgorithm = compressionAlgorithm; _retryEnabled = retryEnabled; _keepaliveInterval = keepaliveInterval < 0 ? 0 : keepaliveInterval; - _keepaliveTimeout = keepaliveTimeout < 0 ? 0 : keepaliveTimeout; + _keepaliveTimeout = keepaliveTimeout; _connectMinTimeout = connectMinTimeout < 0 ? 0 : connectMinTimeout; _connectInitialBackoff = connectInitialBackoff < 0 ? 0 : connectInitialBackoff; _connectMaxBackoff = connectMaxBackoff < 0 ? 0 : connectMaxBackoff; @@ -486,11 +486,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) { } - (void)setKeepaliveTimeout:(NSTimeInterval)keepaliveTimeout { - if (keepaliveTimeout < 0) { - _keepaliveTimeout = 0; - } else { - _keepaliveTimeout = keepaliveTimeout; - } + _keepaliveTimeout = keepaliveTimeout; } - (void)setConnectMinTimeout:(NSTimeInterval)connectMinTimeout { diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m index 1a79fb04a0d..4a187ae9081 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCChannel.m @@ -109,6 +109,8 @@ if (_callOptions.keepaliveInterval != 0) { args[@GRPC_ARG_KEEPALIVE_TIME_MS] = [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveInterval * 1000)]; + } + if (_callOptions.keepaliveTimeout >= 0) { args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] = [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveTimeout * 1000)]; } diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.m b/src/objective-c/GRPCClient/private/GRPCChannelPool.m index 60a33eda824..24ed83d3341 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannelPool.m +++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.m @@ -118,9 +118,7 @@ static const NSTimeInterval kDefaultChannelDestroyDelay = 30; _lastTimedDestroy = nil; grpc_call *unmanagedCall = - [_wrappedChannel unmanagedCallWithPath:path - completionQueue:[GRPCCompletionQueue completionQueue] - callOptions:callOptions]; + [_wrappedChannel unmanagedCallWithPath:path completionQueue:queue callOptions:callOptions]; if (unmanagedCall == NULL) { NSAssert(unmanagedCall != NULL, @"Unable to create grpc_call object"); return nil; diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m index 24348c3aed7..c4287731efd 100644 --- a/src/objective-c/GRPCClient/private/GRPCHost.m +++ b/src/objective-c/GRPCClient/private/GRPCHost.m @@ -105,11 +105,11 @@ static NSMutableDictionary *gHostCache; options.responseSizeLimit = _responseSizeLimitOverride; options.compressionAlgorithm = (GRPCCompressionAlgorithm)_compressAlgorithm; options.retryEnabled = _retryEnabled; - options.keepaliveInterval = (NSTimeInterval)_keepaliveInterval / 1000; - options.keepaliveTimeout = (NSTimeInterval)_keepaliveTimeout / 1000; - options.connectMinTimeout = (NSTimeInterval)_minConnectTimeout / 1000; - options.connectInitialBackoff = (NSTimeInterval)_initialConnectBackoff / 1000; - options.connectMaxBackoff = (NSTimeInterval)_maxConnectBackoff / 1000; + options.keepaliveInterval = (NSTimeInterval)_keepaliveInterval / 1000.0; + options.keepaliveTimeout = (NSTimeInterval)_keepaliveTimeout / 1000.0; + options.connectMinTimeout = (NSTimeInterval)_minConnectTimeout / 1000.0; + options.connectInitialBackoff = (NSTimeInterval)_initialConnectBackoff / 1000.0; + options.connectMaxBackoff = (NSTimeInterval)_maxConnectBackoff / 1000.0; options.PEMRootCertificates = _PEMRootCertificates; options.PEMPrivateKey = _PEMPrivateKey; options.PEMCertificateChain = _PEMCertificateChain; diff --git a/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m b/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m index a2a79c46316..30741e9a0da 100644 --- a/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m +++ b/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m @@ -48,6 +48,10 @@ static int32_t kRemoteInteropServerOverhead = 12; return YES; } ++ (BOOL)canRunCompressionTest { + return NO; +} + - (int32_t)encodingOverhead { return kRemoteInteropServerOverhead; // bytes } diff --git a/src/objective-c/tests/InteropTests/InteropTests.h b/src/objective-c/tests/InteropTests/InteropTests.h index cffa90ac497..0c896ae18a8 100644 --- a/src/objective-c/tests/InteropTests/InteropTests.h +++ b/src/objective-c/tests/InteropTests/InteropTests.h @@ -64,4 +64,10 @@ */ + (BOOL)useCronet; +/** + * Whether we can run compression tests in the test suite. + */ + ++ (BOOL)canRunCompressionTest; + @end diff --git a/src/objective-c/tests/InteropTests/InteropTests.m b/src/objective-c/tests/InteropTests/InteropTests.m index 7d4aee0bc90..aa7a41163d4 100644 --- a/src/objective-c/tests/InteropTests/InteropTests.m +++ b/src/objective-c/tests/InteropTests/InteropTests.m @@ -347,6 +347,10 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager return NO; } ++ (BOOL)canRunCompressionTest { + return YES; +} + + (void)setUp { #ifdef GRPC_COMPILE_WITH_CRONET configureCronet(); @@ -430,10 +434,16 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager __block BOOL messageReceived = NO; __block BOOL done = NO; + __block BOOL initialMetadataReceived = YES; NSCondition *cond = [[NSCondition alloc] init]; GRPCUnaryProtoCall *call = [_service emptyCallWithMessage:request - responseHandler:[[InteropTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + responseHandler:[[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [cond lock]; + initialMetadataReceived = YES; + [cond unlock]; + } messageCallback:^(id message) { if (message) { id expectedResponse = [GPBEmpty message]; @@ -459,6 +469,7 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager while (!done && [deadline timeIntervalSinceNow] > 0) { [cond waitUntilDate:deadline]; } + XCTAssertTrue(initialMetadataReceived); XCTAssertTrue(messageReceived); XCTAssertTrue(done); [cond unlock]; @@ -1014,6 +1025,78 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testInitialMetadataWithV2API { + __weak XCTestExpectation *initialMetadataReceived = + [self expectationWithDescription:@"Received initial metadata."]; + __weak XCTestExpectation *closeReceived = [self expectationWithDescription:@"RPC completed."]; + + __block NSDictionary *init_md = + [NSDictionary dictionaryWithObjectsAndKeys:@"FOOBAR", @"x-grpc-test-echo-initial", nil]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.initialMetadata = init_md; + options.transportType = self.class.transportType; + options.PEMRootCertificates = self.class.PEMRootCertificates; + options.hostNameOverride = [[self class] hostNameOverride]; + RMTSimpleRequest *request = [RMTSimpleRequest message]; + __block bool init_md_received = NO; + GRPCUnaryProtoCall *call = [_service + unaryCallWithMessage:request + responseHandler:[[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + XCTAssertEqualObjects(initialMetadata[@"x-grpc-test-echo-initial"], + init_md[@"x-grpc-test-echo-initial"]); + init_md_received = YES; + [initialMetadataReceived fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + [closeReceived fulfill]; + }] + callOptions:options]; + + [call start]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testTrailingMetadataWithV2API { + // This test needs to be disabled for remote test because interop server grpc-test + // does not send trailing binary metadata. + if (isRemoteInteropTest([[self class] host])) { + return; + } + + __weak XCTestExpectation *expectation = + [self expectationWithDescription:@"Received trailing metadata."]; + const unsigned char raw_bytes[] = {0x1, 0x2, 0x3, 0x4}; + NSData *trailer_data = [NSData dataWithBytes:raw_bytes length:sizeof(raw_bytes)]; + __block NSDictionary *trailer = [NSDictionary + dictionaryWithObjectsAndKeys:trailer_data, @"x-grpc-test-echo-trailing-bin", nil]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.initialMetadata = trailer; + options.transportType = self.class.transportType; + options.PEMRootCertificates = self.class.PEMRootCertificates; + options.hostNameOverride = [[self class] hostNameOverride]; + RMTSimpleRequest *request = [RMTSimpleRequest message]; + GRPCUnaryProtoCall *call = [_service + unaryCallWithMessage:request + responseHandler: + [[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + XCTAssertEqualObjects( + trailingMetadata[@"x-grpc-test-echo-trailing-bin"], + trailer[@"x-grpc-test-echo-trailing-bin"]); + [expectation fulfill]; + }] + callOptions:options]; + [call start]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testCancelAfterFirstResponseRPC { XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectation = @@ -1148,13 +1231,7 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } -- (void)testCompressedUnaryRPC { - // This test needs to be disabled for remote test because interop server grpc-test - // does not support compression. - if (isRemoteInteropTest([[self class] host])) { - return; - } - XCTAssertNotNil([[self class] host]); +- (void)RPCWithCompressMethod:(GRPCCompressionAlgorithm)compressMethod { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"]; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -1162,7 +1239,7 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager request.responseSize = 314159; request.payload.body = [NSMutableData dataWithLength:271828]; request.expectCompressed.value = YES; - [GRPCCall setDefaultCompressMethod:GRPCCompressGzip forhost:[[self class] host]]; + [GRPCCall setDefaultCompressMethod:compressMethod forhost:[[self class] host]]; [_service unaryCallWithRequest:request handler:^(RMTSimpleResponse *response, NSError *error) { @@ -1179,6 +1256,67 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)RPCWithCompressMethodWithV2API:(GRPCCompressionAlgorithm)compressMethod { + __weak XCTestExpectation *expectMessage = + [self expectationWithDescription:@"Reived response from server."]; + __weak XCTestExpectation *expectComplete = [self expectationWithDescription:@"RPC completed."]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.responseType = RMTPayloadType_Compressable; + request.responseSize = 314159; + request.payload.body = [NSMutableData dataWithLength:271828]; + request.expectCompressed.value = YES; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = self.class.transportType; + options.PEMRootCertificates = self.class.PEMRootCertificates; + options.hostNameOverride = [[self class] hostNameOverride]; + options.compressionAlgorithm = compressMethod; + + GRPCUnaryProtoCall *call = [_service + unaryCallWithMessage:request + responseHandler:[[InteropTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + XCTAssertNotNil(message); + if (message) { + RMTSimpleResponse *expectedResponse = + [RMTSimpleResponse message]; + expectedResponse.payload.type = RMTPayloadType_Compressable; + expectedResponse.payload.body = + [NSMutableData dataWithLength:314159]; + XCTAssertEqualObjects(message, expectedResponse); + + [expectMessage fulfill]; + } + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + [expectComplete fulfill]; + }] + callOptions:options]; + [call start]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testCompressedUnaryRPC { + if ([[self class] canRunCompressionTest]) { + for (GRPCCompressionAlgorithm compress = GRPCCompressDeflate; + compress <= GRPCStreamCompressGzip; ++compress) { + [self RPCWithCompressMethod:compress]; + } + } +} + +- (void)testCompressedUnaryRPCWithV2API { + if ([[self class] canRunCompressionTest]) { + for (GRPCCompressionAlgorithm compress = GRPCCompressDeflate; + compress <= GRPCStreamCompressGzip; ++compress) { + [self RPCWithCompressMethodWithV2API:compress]; + } + } +} + #ifndef GRPC_COMPILE_WITH_CRONET - (void)testKeepalive { XCTAssertNotNil([[self class] host]); @@ -1220,6 +1358,40 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } + +- (void)testKeepaliveWithV2API { + XCTAssertNotNil([[self class] host]); + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Keepalive"]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = self.class.transportType; + options.PEMRootCertificates = self.class.PEMRootCertificates; + options.hostNameOverride = [[self class] hostNameOverride]; + options.keepaliveInterval = 1.5; + options.keepaliveTimeout = 0; + + id request = + [RMTStreamingOutputCallRequest messageWithPayloadSize:@21782 requestedResponseSize:@31415]; + + __block GRPCStreamingProtoCall *call = [_service + fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^( + NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual( + error.code, + GRPC_STATUS_UNAVAILABLE); + [expectation fulfill]; + }] + callOptions:options]; + [call start]; + [call writeMessage:request]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} #endif - (void)testDefaultInterceptor { diff --git a/src/objective-c/tests/InteropTests/InteropTestsRemote.m b/src/objective-c/tests/InteropTests/InteropTestsRemote.m index c1cd9b81efc..7bd5b28780c 100644 --- a/src/objective-c/tests/InteropTests/InteropTestsRemote.m +++ b/src/objective-c/tests/InteropTests/InteropTestsRemote.m @@ -49,6 +49,10 @@ static int32_t kRemoteInteropServerOverhead = 12; return nil; } ++ (BOOL)canRunCompressionTest { + return NO; +} + - (int32_t)encodingOverhead { return kRemoteInteropServerOverhead; // bytes } diff --git a/src/objective-c/tests/MacTests/StressTests.m b/src/objective-c/tests/MacTests/StressTests.m index 22174b58665..7475dc77fcc 100644 --- a/src/objective-c/tests/MacTests/StressTests.m +++ b/src/objective-c/tests/MacTests/StressTests.m @@ -29,7 +29,7 @@ #import #import -#define TEST_TIMEOUT 32 +#define TEST_TIMEOUT 64 extern const char *kCFStreamVarName; @@ -136,7 +136,11 @@ extern const char *kCFStreamVarName; return GRPCTransportTypeChttp2BoringSSL; } -- (void)testNetworkFlapWithV2API { +- (int)getRandomNumberBetween:(int)min max:(int)max { + return min + arc4random_uniform((max - min + 1)); +} + +- (void)testNetworkFlapOnUnaryCallWithV2API { NSMutableArray *completeExpectations = [NSMutableArray array]; NSMutableArray *calls = [NSMutableArray array]; int num_rpcs = 100; @@ -175,6 +179,7 @@ extern const char *kCFStreamVarName; UTF8String]); address_removed = YES; } else if (error != nil && !address_readded) { + XCTAssertTrue(address_removed); system([ [NSString stringWithFormat:@"sudo ifconfig lo0 alias %@", [[self class] hostAddress]] @@ -196,7 +201,241 @@ extern const char *kCFStreamVarName; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } -- (void)testNetworkFlapWithV1API { +- (void)testNetworkFlapOnClientStreamingCallWithV2API { + NSMutableArray *completeExpectations = [NSMutableArray array]; + NSMutableArray *calls = [NSMutableArray array]; + int num_rpcs = 100; + __block BOOL address_removed = FALSE; + __block BOOL address_readded = FALSE; + for (int i = 0; i < num_rpcs; ++i) { + [completeExpectations + addObject:[self expectationWithDescription: + [NSString stringWithFormat:@"Received trailer for RPC %d", i]]]; + + GRPCStreamingProtoCall *call = [_service + streamingInputCallWithResponseHandler: + [[MacTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + @synchronized(self) { + if (error == nil && !address_removed) { + system([[NSString + stringWithFormat:@"sudo ifconfig lo0 -alias %@", + [[self class] hostAddress]] + UTF8String]); + address_removed = YES; + } else if (error != nil && !address_readded) { + XCTAssertTrue(address_removed); + system([[NSString + stringWithFormat:@"sudo ifconfig lo0 alias %@", + [[self class] hostAddress]] + UTF8String]); + address_readded = YES; + } + } + [completeExpectations[i] fulfill]; + }] + callOptions:nil]; + [calls addObject:call]; + } + + for (int i = 0; i < num_rpcs; ++i) { + GRPCStreamingProtoCall *call = calls[i]; + [call start]; + RMTStreamingInputCallRequest *request1 = [RMTStreamingInputCallRequest message]; + request1.payload.body = [NSMutableData dataWithLength:27182]; + RMTStreamingInputCallRequest *request2 = [RMTStreamingInputCallRequest message]; + request2.payload.body = [NSMutableData dataWithLength:8]; + + [call writeMessage:request1]; + [NSThread sleepForTimeInterval:0.1f]; + [call writeMessage:request2]; + [call finish]; + } + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testNetworkFlapOnServerStreamingCallWithV2API { + NSMutableArray *completeExpectations = [NSMutableArray array]; + NSMutableArray *calls = [NSMutableArray array]; + int num_rpcs = 100; + __block BOOL address_removed = FALSE; + __block BOOL address_readded = FALSE; + for (int i = 0; i < num_rpcs; ++i) { + [completeExpectations + addObject:[self expectationWithDescription: + [NSString stringWithFormat:@"Received trailer for RPC %d", i]]]; + + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + for (int i = 0; i < 5; i++) { + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = 10000; + [request.responseParametersArray addObject:parameters]; + } + + request.payload.body = [NSMutableData dataWithLength:100]; + + GRPCUnaryProtoCall *call = [_service + streamingOutputCallWithMessage:request + responseHandler: + [[MacTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + @synchronized(self) { + if (error == nil && !address_removed) { + system([[NSString + stringWithFormat: + @"sudo ifconfig lo0 -alias %@", + [[self class] hostAddress]] + UTF8String]); + address_removed = YES; + } else if (error != nil && !address_readded) { + XCTAssertTrue(address_removed); + system([[NSString + stringWithFormat: + @"sudo ifconfig lo0 alias %@", + [[self class] hostAddress]] + UTF8String]); + address_readded = YES; + } + } + [completeExpectations[i] fulfill]; + }] + callOptions:nil]; + [calls addObject:call]; + } + + for (int i = 0; i < num_rpcs; ++i) { + GRPCStreamingProtoCall *call = calls[i]; + [call start]; + [NSThread sleepForTimeInterval:0.1f]; + } + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testNetworkFlapOnHalfDuplexCallWithV2API { + NSMutableArray *completeExpectations = [NSMutableArray array]; + NSMutableArray *calls = [NSMutableArray array]; + int num_rpcs = 100; + __block BOOL address_removed = FALSE; + __block BOOL address_readded = FALSE; + for (int i = 0; i < num_rpcs; ++i) { + [completeExpectations + addObject:[self expectationWithDescription: + [NSString stringWithFormat:@"Received trailer for RPC %d", i]]]; + + GRPCStreamingProtoCall *call = [_service + halfDuplexCallWithResponseHandler: + [[MacTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + @synchronized(self) { + if (error == nil && !address_removed) { + system([[NSString + stringWithFormat:@"sudo ifconfig lo0 -alias %@", + [[self class] hostAddress]] + UTF8String]); + address_removed = YES; + } else if (error != nil && !address_readded) { + XCTAssertTrue(address_removed); + system([[NSString + stringWithFormat:@"sudo ifconfig lo0 alias %@", + [[self class] hostAddress]] + UTF8String]); + address_readded = YES; + } + } + [completeExpectations[i] fulfill]; + }] + callOptions:nil]; + [calls addObject:call]; + } + + for (int i = 0; i < num_rpcs; ++i) { + GRPCStreamingProtoCall *call = calls[i]; + [call start]; + RMTStreamingOutputCallRequest *request1 = [RMTStreamingOutputCallRequest message]; + RMTStreamingOutputCallRequest *request2 = [RMTStreamingOutputCallRequest message]; + for (int i = 0; i < 5; i++) { + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = 1000; + [request1.responseParametersArray addObject:parameters]; + [request2.responseParametersArray addObject:parameters]; + } + + request1.payload.body = [NSMutableData dataWithLength:100]; + request2.payload.body = [NSMutableData dataWithLength:100]; + + [call writeMessage:request1]; + [NSThread sleepForTimeInterval:0.1f]; + [call writeMessage:request2]; + [call finish]; + } + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testNetworkFlapOnFullDuplexCallWithV2API { + NSMutableArray *completeExpectations = [NSMutableArray array]; + NSMutableArray *calls = [NSMutableArray array]; + int num_rpcs = 100; + __block BOOL address_removed = FALSE; + __block BOOL address_readded = FALSE; + for (int i = 0; i < num_rpcs; ++i) { + [completeExpectations + addObject:[self expectationWithDescription: + [NSString stringWithFormat:@"Received trailer for RPC %d", i]]]; + + GRPCStreamingProtoCall *call = [_service + fullDuplexCallWithResponseHandler: + [[MacTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + @synchronized(self) { + if (error == nil && !address_removed) { + system([[NSString + stringWithFormat:@"sudo ifconfig lo0 -alias %@", + [[self class] hostAddress]] + UTF8String]); + address_removed = YES; + } else if (error != nil && !address_readded) { + XCTAssertTrue(address_removed); + system([[NSString + stringWithFormat:@"sudo ifconfig lo0 alias %@", + [[self class] hostAddress]] + UTF8String]); + address_readded = YES; + } + } + [completeExpectations[i] fulfill]; + }] + callOptions:nil]; + [calls addObject:call]; + } + + for (int i = 0; i < num_rpcs; ++i) { + GRPCStreamingProtoCall *call = calls[i]; + [call start]; + + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = 1000; + for (int i = 0; i < 5; i++) { + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:100]; + [call writeMessage:request]; + } + [call finish]; + [NSThread sleepForTimeInterval:0.1f]; + } + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testNetworkFlapOnUnaryCallWithV1API { NSMutableArray *completeExpectations = [NSMutableArray array]; int num_rpcs = 100; __block BOOL address_removed = FALSE; @@ -220,6 +459,7 @@ extern const char *kCFStreamVarName; UTF8String]); address_removed = YES; } else if (error != nil && !address_readded) { + XCTAssertTrue(address_removed); system([[NSString stringWithFormat:@"sudo ifconfig lo0 alias %@", [[self class] hostAddress]] UTF8String]); @@ -234,4 +474,141 @@ extern const char *kCFStreamVarName; } } +- (void)testTimeoutOnFullDuplexCallWithV2API { + NSMutableArray *completeExpectations = [NSMutableArray array]; + NSMutableArray *calls = [NSMutableArray array]; + int num_rpcs = 100; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = [[self class] transportType]; + options.PEMRootCertificates = [[self class] PEMRootCertificates]; + options.hostNameOverride = [[self class] hostNameOverride]; + options.timeout = 0.3; + for (int i = 0; i < num_rpcs; ++i) { + [completeExpectations + addObject:[self expectationWithDescription: + [NSString stringWithFormat:@"Received trailer for RPC %d", i]]]; + + GRPCStreamingProtoCall *call = [_service + fullDuplexCallWithResponseHandler: + [[MacTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + if (error != nil) { + XCTAssertEqual(error.code, GRPC_STATUS_DEADLINE_EXCEEDED); + } + [completeExpectations[i] fulfill]; + }] + callOptions:options]; + [calls addObject:call]; + } + + for (int i = 0; i < num_rpcs; ++i) { + GRPCStreamingProtoCall *call = calls[i]; + [call start]; + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = 1000; + // delay response by 100-200 milliseconds + parameters.intervalUs = [self getRandomNumberBetween:100 * 1000 max:200 * 1000]; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:100]; + + [call writeMessage:request]; + [call writeMessage:request]; + [call finish]; + } + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testServerStreamingCallSlowClientWithV2API { + NSMutableArray *completeExpectations = [NSMutableArray array]; + NSMutableArray *calls = [NSMutableArray array]; + int num_rpcs = 100; + dispatch_queue_t q = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT); + for (int i = 0; i < num_rpcs; ++i) { + [completeExpectations + addObject:[self expectationWithDescription: + [NSString stringWithFormat:@"Received trailer for RPC %d", i]]]; + + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + for (int i = 0; i < 5; i++) { + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = 10000; + [request.responseParametersArray addObject:parameters]; + [request.responseParametersArray addObject:parameters]; + [request.responseParametersArray addObject:parameters]; + [request.responseParametersArray addObject:parameters]; + [request.responseParametersArray addObject:parameters]; + } + + request.payload.body = [NSMutableData dataWithLength:100]; + + GRPCUnaryProtoCall *call = [_service + streamingOutputCallWithMessage:request + responseHandler:[[MacTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + // inject a delay + [NSThread sleepForTimeInterval:0.5f]; + } + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + [completeExpectations[i] fulfill]; + }] + callOptions:nil]; + [calls addObject:call]; + } + + for (int i = 0; i < num_rpcs; ++i) { + dispatch_async(q, ^{ + GRPCStreamingProtoCall *call = calls[i]; + [call start]; + }); + } + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +- (void)testCancelOnFullDuplexCallWithV2API { + NSMutableArray *completeExpectations = [NSMutableArray array]; + NSMutableArray *calls = [NSMutableArray array]; + int num_rpcs = 100; + dispatch_queue_t q = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT); + for (int i = 0; i < num_rpcs; ++i) { + [completeExpectations + addObject:[self expectationWithDescription: + [NSString stringWithFormat:@"Received trailer for RPC %d", i]]]; + + GRPCStreamingProtoCall *call = [_service + fullDuplexCallWithResponseHandler:[[MacTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + messageCallback:nil + closeCallback:^( + NSDictionary *trailingMetadata, + NSError *error) { + [completeExpectations[i] fulfill]; + }] + callOptions:nil]; + [calls addObject:call]; + } + + for (int i = 0; i < num_rpcs; ++i) { + GRPCStreamingProtoCall *call = calls[i]; + [call start]; + dispatch_async(q, ^{ + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = 1000; + for (int i = 0; i < 100; i++) { + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + [request.responseParametersArray addObject:parameters]; + [call writeMessage:request]; + } + [NSThread sleepForTimeInterval:0.01f]; + [call cancel]; + }); + } + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + @end diff --git a/src/objective-c/tests/UnitTests/APIv2Tests.m b/src/objective-c/tests/UnitTests/APIv2Tests.m index db293750ca3..066800613f6 100644 --- a/src/objective-c/tests/UnitTests/APIv2Tests.m +++ b/src/objective-c/tests/UnitTests/APIv2Tests.m @@ -21,6 +21,11 @@ #import #import +#import "../../GRPCClient/private/GRPCCallInternal.h" +#import "../../GRPCClient/private/GRPCChannel.h" +#import "../../GRPCClient/private/GRPCChannelPool.h" +#import "../../GRPCClient/private/GRPCWrappedCall.h" + #include #include @@ -48,12 +53,41 @@ static const int kSimpleDataLength = 100; static const NSTimeInterval kTestTimeout = 8; static const NSTimeInterval kInvertedTimeout = 2; -// Reveal the _class ivar for testing access +// Reveal the _class ivars for testing access @interface GRPCCall2 () { + @public + id _firstInterceptor; +} +@end + +@interface GRPCCall2Internal () { @public GRPCCall *_call; } +@end + +@interface GRPCCall () { + @public + GRPCWrappedCall *_wrappedCall; +} +@end + +@interface GRPCWrappedCall () { + @public + GRPCPooledChannel *_pooledChannel; +} +@end + +@interface GRPCPooledChannel () { + @public + GRPCChannel *_wrappedChannel; +} +@end +@interface GRPCChannel () { + @public + grpc_channel *_unmanagedChannel; +} @end // Convenience class to use blocks as callbacks @@ -148,6 +182,7 @@ static const NSTimeInterval kInvertedTimeout = 2; kOutputStreamingCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"StreamingOutputCall"]; + kFullDuplexCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"]; } @@ -179,7 +214,7 @@ static const NSTimeInterval kInvertedTimeout = 2; closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { trailing_md = trailingMetadata; if (error) { - XCTAssertEqual(error.code, 16, + XCTAssertEqual(error.code, GRPCErrorCodeUnauthenticated, @"Finished with unexpected error: %@", error); XCTAssertEqualObjects(init_md, error.userInfo[kGRPCHeadersKey]); @@ -759,7 +794,7 @@ static const NSTimeInterval kInvertedTimeout = 2; } closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { XCTAssertNotNil(error, @"Expecting non-nil error"); - XCTAssertEqual(error.code, 2); + XCTAssertEqual(error.code, GRPCErrorCodeUnknown); [completion fulfill]; }] callOptions:options]; @@ -770,4 +805,293 @@ static const NSTimeInterval kInvertedTimeout = 2; [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; } +- (void)testAdditionalChannelArgs { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kUnaryCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + // set max message length = 1 byte. + options.additionalChannelArgs = + [NSDictionary dictionaryWithObjectsAndKeys:@1, @GRPC_ARG_MAX_SEND_MESSAGE_LENGTH, nil]; + options.transportType = GRPCTransportTypeInsecure; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit]; + + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(NSData *data) { + XCTFail(@"Received unexpected message"); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNotNil(error, @"Expecting non-nil error"); + NSLog(@"Got error: %@", error); + XCTAssertEqual(error.code, GRPCErrorCodeResourceExhausted, + @"Finished with unexpected error: %@", error); + + [completion fulfill]; + }] + callOptions:options]; + [call writeData:[request data]]; + [call start]; + [call finish]; + + [self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; +} + +- (void)testChannelReuseIdentical { + __weak XCTestExpectation *completion1 = [self expectationWithDescription:@"RPC1 completed."]; + __weak XCTestExpectation *completion2 = [self expectationWithDescription:@"RPC2 completed."]; + NSArray *rpcDone = [NSArray arrayWithObjects:completion1, completion2, nil]; + __weak XCTestExpectation *metadata1 = + [self expectationWithDescription:@"Received initial metadata for RPC1."]; + __weak XCTestExpectation *metadata2 = + [self expectationWithDescription:@"Received initial metadata for RPC2."]; + NSArray *initialMetadataDone = [NSArray arrayWithObjects:metadata1, metadata2, nil]; + + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = kSimpleDataLength; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength]; + + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *callOptions = [[GRPCMutableCallOptions alloc] init]; + callOptions.transportType = GRPCTransportTypeInsecure; + GRPCCall2 *call1 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata1 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion1 fulfill]; + }] + callOptions:callOptions]; + GRPCCall2 *call2 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata2 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion2 fulfill]; + }] + callOptions:callOptions]; + [call1 start]; + [call2 start]; + [call1 writeData:[request data]]; + [call2 writeData:[request data]]; + [self waitForExpectations:initialMetadataDone timeout:kTestTimeout]; + GRPCCall2Internal *internalCall1 = call1->_firstInterceptor; + GRPCCall2Internal *internalCall2 = call2->_firstInterceptor; + XCTAssertEqual( + internalCall1->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel, + internalCall2->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel); + [call1 finish]; + [call2 finish]; + [self waitForExpectations:rpcDone timeout:kTestTimeout]; +} + +- (void)testChannelReuseDifferentCallSafety { + __weak XCTestExpectation *completion1 = [self expectationWithDescription:@"RPC1 completed."]; + __weak XCTestExpectation *completion2 = [self expectationWithDescription:@"RPC2 completed."]; + NSArray *rpcDone = [NSArray arrayWithObjects:completion1, completion2, nil]; + __weak XCTestExpectation *metadata1 = + [self expectationWithDescription:@"Received initial metadata for RPC1."]; + __weak XCTestExpectation *metadata2 = + [self expectationWithDescription:@"Received initial metadata for RPC2."]; + NSArray *initialMetadataDone = [NSArray arrayWithObjects:metadata1, metadata2, nil]; + + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = kSimpleDataLength; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength]; + + GRPCRequestOptions *requestOptions1 = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + GRPCRequestOptions *requestOptions2 = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyIdempotentRequest]; + + GRPCMutableCallOptions *callOptions = [[GRPCMutableCallOptions alloc] init]; + callOptions.transportType = GRPCTransportTypeInsecure; + GRPCCall2 *call1 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions1 + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata1 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion1 fulfill]; + }] + callOptions:callOptions]; + GRPCCall2 *call2 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions2 + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata2 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion2 fulfill]; + }] + callOptions:callOptions]; + [call1 start]; + [call2 start]; + [call1 writeData:[request data]]; + [call2 writeData:[request data]]; + [self waitForExpectations:initialMetadataDone timeout:kTestTimeout]; + GRPCCall2Internal *internalCall1 = call1->_firstInterceptor; + GRPCCall2Internal *internalCall2 = call2->_firstInterceptor; + XCTAssertEqual( + internalCall1->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel, + internalCall2->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel); + [call1 finish]; + [call2 finish]; + [self waitForExpectations:rpcDone timeout:kTestTimeout]; +} + +- (void)testChannelReuseDifferentHost { + __weak XCTestExpectation *completion1 = [self expectationWithDescription:@"RPC1 completed."]; + __weak XCTestExpectation *completion2 = [self expectationWithDescription:@"RPC2 completed."]; + NSArray *rpcDone = [NSArray arrayWithObjects:completion1, completion2, nil]; + __weak XCTestExpectation *metadata1 = + [self expectationWithDescription:@"Received initial metadata for RPC1."]; + __weak XCTestExpectation *metadata2 = + [self expectationWithDescription:@"Received initial metadata for RPC2."]; + NSArray *initialMetadataDone = [NSArray arrayWithObjects:metadata1, metadata2, nil]; + + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = kSimpleDataLength; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength]; + + GRPCRequestOptions *requestOptions1 = + [[GRPCRequestOptions alloc] initWithHost:@"[::1]:5050" + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + GRPCRequestOptions *requestOptions2 = + [[GRPCRequestOptions alloc] initWithHost:@"127.0.0.1:5050" + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + + GRPCMutableCallOptions *callOptions = [[GRPCMutableCallOptions alloc] init]; + callOptions.transportType = GRPCTransportTypeInsecure; + + GRPCCall2 *call1 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions1 + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata1 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion1 fulfill]; + }] + callOptions:callOptions]; + GRPCCall2 *call2 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions2 + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata2 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion2 fulfill]; + }] + callOptions:callOptions]; + [call1 start]; + [call2 start]; + [call1 writeData:[request data]]; + [call2 writeData:[request data]]; + [self waitForExpectations:initialMetadataDone timeout:kTestTimeout]; + GRPCCall2Internal *internalCall1 = call1->_firstInterceptor; + GRPCCall2Internal *internalCall2 = call2->_firstInterceptor; + XCTAssertNotEqual( + internalCall1->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel, + internalCall2->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel); + [call1 finish]; + [call2 finish]; + [self waitForExpectations:rpcDone timeout:kTestTimeout]; +} + +- (void)testChannelReuseDifferentChannelArgs { + __weak XCTestExpectation *completion1 = [self expectationWithDescription:@"RPC1 completed."]; + __weak XCTestExpectation *completion2 = [self expectationWithDescription:@"RPC2 completed."]; + NSArray *rpcDone = [NSArray arrayWithObjects:completion1, completion2, nil]; + __weak XCTestExpectation *metadata1 = + [self expectationWithDescription:@"Received initial metadata for RPC1."]; + __weak XCTestExpectation *metadata2 = + [self expectationWithDescription:@"Received initial metadata for RPC2."]; + NSArray *initialMetadataDone = [NSArray arrayWithObjects:metadata1, metadata2, nil]; + + RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message]; + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = kSimpleDataLength; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength]; + + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *callOptions1 = [[GRPCMutableCallOptions alloc] init]; + callOptions1.transportType = GRPCTransportTypeInsecure; + GRPCMutableCallOptions *callOptions2 = [[GRPCMutableCallOptions alloc] init]; + callOptions2.transportType = GRPCTransportTypeInsecure; + callOptions2.channelID = 2; + + GRPCCall2 *call1 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata1 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion1 fulfill]; + }] + callOptions:callOptions1]; + GRPCCall2 *call2 = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + [metadata2 fulfill]; + } + messageCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + [completion2 fulfill]; + }] + callOptions:callOptions2]; + [call1 start]; + [call2 start]; + [call1 writeData:[request data]]; + [call2 writeData:[request data]]; + [self waitForExpectations:initialMetadataDone timeout:kTestTimeout]; + GRPCCall2Internal *internalCall1 = call1->_firstInterceptor; + GRPCCall2Internal *internalCall2 = call2->_firstInterceptor; + XCTAssertNotEqual( + internalCall1->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel, + internalCall2->_call->_wrappedCall->_pooledChannel->_wrappedChannel->_unmanagedChannel); + [call1 finish]; + [call2 finish]; + [self waitForExpectations:rpcDone timeout:kTestTimeout]; +} + @end diff --git a/test/cpp/interop/interop_server.cc b/test/cpp/interop/interop_server.cc index 6570bbf9696..cf55efa5409 100644 --- a/test/cpp/interop/interop_server.cc +++ b/test/cpp/interop/interop_server.cc @@ -118,7 +118,8 @@ bool CheckExpectedCompression(const ServerContext& context, "Expected compression but got uncompressed request from client."); return false; } - if (!(inspector.GetMessageFlags() & GRPC_WRITE_INTERNAL_COMPRESS)) { + if (!(inspector.GetMessageFlags() & GRPC_WRITE_INTERNAL_COMPRESS) && + received_compression != GRPC_COMPRESS_STREAM_GZIP) { gpr_log(GPR_ERROR, "Failure: Requested compression in a compressable request, but " "compression bit in message flags not set.");