blob: 6d99f3be44f1e0dcb3a5781e439d7e630d13174d [file] [log] [blame]
/*
*
* Copyright 2018 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 <GRPCClient/GRPCCall.h>
#import <GRPCClient/version.h>
#import <ProtoRPC/ProtoMethod.h>
#import <XCTest/XCTest.h>
#import "src/objective-c/tests/RemoteTestClient/Messages.pbobjc.h"
#include <grpc/grpc.h>
#include <grpc/support/port_platform.h>
// The server address is derived from preprocessor macro, which is
// in turn derived from environment variable of the same name.
#define NSStringize_helper(x) #x
#define NSStringize(x) @NSStringize_helper(x)
static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL);
static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE);
// Package and service name of test server
static NSString *const kPackage = @"grpc.testing";
static NSString *const kService = @"TestService";
static GRPCProtoMethod *kInexistentMethod;
static GRPCProtoMethod *kEmptyCallMethod;
static GRPCProtoMethod *kUnaryCallMethod;
static GRPCProtoMethod *kOutputStreamingCallMethod;
static GRPCProtoMethod *kFullDuplexCallMethod;
static const int kSimpleDataLength = 100;
static const NSTimeInterval kTestTimeout = 8;
static const NSTimeInterval kInvertedTimeout = 2;
// Reveal the _class ivar for testing access
@interface GRPCCall2 () {
@public
GRPCCall *_call;
}
@end
// Convenience class to use blocks as callbacks
@interface ClientTestsBlockCallbacks : NSObject<GRPCResponseHandler>
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
messageCallback:(void (^)(id))messageCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
writeDataCallback:(void (^)(void))writeDataCallback;
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
messageCallback:(void (^)(id))messageCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback;
@end
@implementation ClientTestsBlockCallbacks {
void (^_initialMetadataCallback)(NSDictionary *);
void (^_messageCallback)(id);
void (^_closeCallback)(NSDictionary *, NSError *);
void (^_writeDataCallback)(void);
dispatch_queue_t _dispatchQueue;
}
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
messageCallback:(void (^)(id))messageCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
writeDataCallback:(void (^)(void))writeDataCallback {
if ((self = [super init])) {
_initialMetadataCallback = initialMetadataCallback;
_messageCallback = messageCallback;
_closeCallback = closeCallback;
_writeDataCallback = writeDataCallback;
_dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
messageCallback:(void (^)(id))messageCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
return [self initWithInitialMetadataCallback:initialMetadataCallback
messageCallback:messageCallback
closeCallback:closeCallback
writeDataCallback:nil];
}
- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
if (self->_initialMetadataCallback) {
self->_initialMetadataCallback(initialMetadata);
}
}
- (void)didReceiveRawMessage:(GPBMessage *)message {
if (self->_messageCallback) {
self->_messageCallback(message);
}
}
- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
if (self->_closeCallback) {
self->_closeCallback(trailingMetadata, error);
}
}
- (void)didWriteData {
if (self->_writeDataCallback) {
self->_writeDataCallback();
}
}
- (dispatch_queue_t)dispatchQueue {
return _dispatchQueue;
}
@end
@interface CallAPIv2Tests : XCTestCase<GRPCAuthorizationProtocol>
@end
@implementation CallAPIv2Tests
- (void)setUp {
// 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"];
kOutputStreamingCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage
service:kService
method:@"StreamingOutputCall"];
kFullDuplexCallMethod =
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"];
}
- (void)testMetadata {
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.fillUsername = YES;
request.fillOauthScope = YES;
GRPCRequestOptions *callRequest =
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kRemoteSSLHost
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
__block NSDictionary *init_md;
__block NSDictionary *trailing_md;
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.oauth2AccessToken = @"bogusToken";
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:callRequest
responseHandler:[[ClientTestsBlockCallbacks alloc]
initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) {
init_md = initialMetadata;
}
messageCallback:^(id message) {
XCTFail(@"Received unexpected response.");
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
trailing_md = trailingMetadata;
if (error) {
XCTAssertEqual(error.code, 16,
@"Finished with unexpected error: %@", error);
XCTAssertEqualObjects(init_md,
error.userInfo[kGRPCHeadersKey]);
XCTAssertEqualObjects(trailing_md,
error.userInfo[kGRPCTrailersKey]);
NSString *challengeHeader = init_md[@"www-authenticate"];
XCTAssertGreaterThan(challengeHeader.length, 0,
@"No challenge in response headers %@",
init_md);
[expectation fulfill];
}
}]
callOptions:options];
[call start];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testUserAgentPrefix {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
__weak XCTestExpectation *recvInitialMd =
[self expectationWithDescription:@"Did not receive initial md."];
GRPCRequestOptions *request = [[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kEmptyCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
NSDictionary *headers =
[NSDictionary dictionaryWithObjectsAndKeys:@"", @"x-grpc-test-echo-useragent", nil];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.userAgentPrefix = @"Foo";
options.initialMetadata = headers;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:request
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:^(
NSDictionary *initialMetadata) {
NSString *userAgent = initialMetadata[@"x-grpc-test-echo-useragent"];
// 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:@" ("];
expectedUserAgent = [expectedUserAgent stringByAppendingString:@GPR_PLATFORM_STRING];
expectedUserAgent = [expectedUserAgent stringByAppendingString:@"; chttp2; "];
expectedUserAgent = [expectedUserAgent
stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]];
expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"];
XCTAssertEqualObjects(userAgent, expectedUserAgent);
NSError *error = nil;
// 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");
[recvInitialMd fulfill];
}
messageCallback:^(id message) {
XCTAssertNotNil(message);
XCTAssertEqual([message length], 0,
@"Non-empty response received: %@", message);
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
if (error) {
XCTFail(@"Finished with unexpected error: %@", error);
} else {
[completion fulfill];
}
}]
callOptions:options];
[call writeData:[NSData data]];
[call start];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)getTokenWithHandler:(void (^)(NSString *token))handler {
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
handler(@"test-access-token");
});
}
- (void)testOAuthToken {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kEmptyCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.authTokenProvider = self;
__block GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc]
initWithInitialMetadataCallback:nil
messageCallback:nil
closeCallback:^(NSDictionary *trailingMetadata,
NSError *error) {
[completion fulfill];
}]
callOptions:options];
[call writeData:[NSData data]];
[call start];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testResponseSizeLimitExceeded {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.responseSizeLimit = kSimpleDataLength;
options.transportType = GRPCTransportTypeInsecure;
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit];
request.responseSize = (int32_t)(options.responseSizeLimit * 2);
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc]
initWithInitialMetadataCallback:nil
messageCallback:nil
closeCallback:^(NSDictionary *trailingMetadata,
NSError *error) {
XCTAssertNotNil(error,
@"Expecting non-nil error");
XCTAssertEqual(error.code,
GRPCErrorCodeResourceExhausted);
[completion fulfill];
}]
callOptions:options];
[call writeData:[request data]];
[call start];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testIdempotentProtoRPC {
__weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.responseSize = kSimpleDataLength;
request.fillUsername = YES;
request.fillOauthScope = YES;
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyIdempotentRequest];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(id message) {
NSData *data = (NSData *)message;
XCTAssertNotNil(data, @"nil value received as response.");
XCTAssertGreaterThan(data.length, 0,
@"Empty response received.");
RMTSimpleResponse *responseProto =
[RMTSimpleResponse parseFromData:data 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];
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNil(error, @"Finished with unexpected error: %@",
error);
[completion fulfill];
}]
callOptions:options];
[call start];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testTimeout {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.timeout = 0.001;
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kFullDuplexCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:
[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *data) {
XCTFail(@"Failure: response received; Expect: no response received.");
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNotNil(error,
@"Failure: no error received; Expect: receive "
@"deadline exceeded.");
XCTAssertEqual(error.code, GRPCErrorCodeDeadlineExceeded);
[completion fulfill];
}]
callOptions:options];
[call start];
[self waitForExpectationsWithTimeout:kTestTimeout 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:@"127.0.0.1:10000"];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kDummyAddress
path:@"/dummy/path"
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.connectMinTimeout = timeout;
options.connectInitialBackoff = backoff;
options.connectMaxBackoff = 0;
NSDate *startTime = [NSDate date];
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *data) {
XCTFail(@"Received message. Should not reach here.");
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNotNil(error,
@"Finished with no error; expecting error");
XCTAssertLessThan(
[[NSDate date] timeIntervalSinceDate:startTime],
maxConnectTime + kMargin);
[completion fulfill];
}]
callOptions:options];
[call start];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testTimeoutBackoff1 {
[self testTimeoutBackoffWithTimeout:0.7 Backoff:0.4];
}
- (void)testTimeoutBackoff2 {
[self testTimeoutBackoffWithTimeout:0.3 Backoff:0.8];
}
- (void)testCompression {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.expectCompressed = [RMTBoolValue message];
request.expectCompressed.value = YES;
request.responseCompressed = [RMTBoolValue message];
request.expectCompressed.value = YES;
request.responseSize = kSimpleDataLength;
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.compressionAlgorithm = GRPCCompressGzip;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *data) {
NSError *error;
RMTSimpleResponse *response =
[RMTSimpleResponse parseFromData:data error:&error];
XCTAssertNil(error, @"Error when parsing response: %@", error);
XCTAssertEqual(response.payload.body.length, kSimpleDataLength);
}
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
XCTAssertNil(error, @"Received failure: %@", error);
[completion fulfill];
}]
callOptions:options];
[call start];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testFlowControlWrite {
__weak XCTestExpectation *expectWriteData =
[self expectationWithDescription:@"Reported write data"];
RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message];
RMTResponseParameters *parameters = [RMTResponseParameters message];
parameters.size = kSimpleDataLength;
[request.responseParametersArray addObject:parameters];
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
GRPCRequestOptions *callRequest =
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.flowControlEnabled = YES;
GRPCCall2 *call =
[[GRPCCall2 alloc] initWithRequestOptions:callRequest
responseHandler:[[ClientTestsBlockCallbacks alloc]
initWithInitialMetadataCallback:nil
messageCallback:nil
closeCallback:nil
writeDataCallback:^{
[expectWriteData fulfill];
}]
callOptions:options];
[call start];
[call receiveNextMessages:1];
[call writeData:[request data]];
// Wait for 3 seconds and make sure we do not receive the response
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
[call finish];
}
- (void)testFlowControlRead {
__weak __block XCTestExpectation *expectBlockedMessage =
[self expectationWithDescription:@"Message not delivered without recvNextMessage"];
__weak __block XCTestExpectation *expectPassedMessage = nil;
__weak __block XCTestExpectation *expectBlockedClose =
[self expectationWithDescription:@"Call not closed with pending message"];
__weak __block XCTestExpectation *expectPassedClose = nil;
expectBlockedMessage.inverted = YES;
expectBlockedClose.inverted = YES;
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.responseSize = kSimpleDataLength;
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
GRPCRequestOptions *callRequest =
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.flowControlEnabled = YES;
__block int unblocked = NO;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:callRequest
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *message) {
if (!unblocked) {
[expectBlockedMessage fulfill];
} else {
[expectPassedMessage fulfill];
}
}
closeCallback:^(NSDictionary *trailers, NSError *error) {
if (!unblocked) {
[expectBlockedClose fulfill];
} else {
[expectPassedClose fulfill];
}
}]
callOptions:options];
[call start];
[call writeData:[request data]];
[call finish];
// Wait to make sure we do not receive the response
[self waitForExpectationsWithTimeout:kInvertedTimeout handler:nil];
expectPassedMessage =
[self expectationWithDescription:@"Message delivered with receiveNextMessage"];
expectPassedClose = [self expectationWithDescription:@"Close delivered after receiveNextMessage"];
unblocked = YES;
[call receiveNextMessages:1];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testFlowControlMultipleMessages {
__weak XCTestExpectation *expectPassedMessage =
[self expectationWithDescription:@"two messages delivered with receiveNextMessage"];
expectPassedMessage.expectedFulfillmentCount = 2;
__weak XCTestExpectation *expectBlockedMessage =
[self expectationWithDescription:@"Message 3 not delivered"];
expectBlockedMessage.inverted = YES;
__weak XCTestExpectation *expectWriteTwice =
[self expectationWithDescription:@"Write 2 messages done"];
expectWriteTwice.expectedFulfillmentCount = 2;
RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message];
RMTResponseParameters *parameters = [RMTResponseParameters message];
parameters.size = kSimpleDataLength;
[request.responseParametersArray addObject:parameters];
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
GRPCRequestOptions *callRequest =
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
path:kFullDuplexCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.flowControlEnabled = YES;
__block NSUInteger messageId = 0;
__block GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:callRequest
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *message) {
if (messageId <= 1) {
[expectPassedMessage fulfill];
} else {
[expectBlockedMessage fulfill];
}
messageId++;
}
closeCallback:nil
writeDataCallback:^{
[expectWriteTwice fulfill];
}]
callOptions:options];
[call receiveNextMessages:2];
[call start];
[call writeData:[request data]];
[call writeData:[request data]];
[self waitForExpectationsWithTimeout:kInvertedTimeout handler:nil];
}
- (void)testFlowControlReadReadyBeforeStart {
__weak XCTestExpectation *expectPassedMessage =
[self expectationWithDescription:@"Message delivered with receiveNextMessage"];
__weak XCTestExpectation *expectPassedClose =
[self expectationWithDescription:@"Close delivered with receiveNextMessage"];
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.responseSize = kSimpleDataLength;
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
GRPCRequestOptions *callRequest =
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.flowControlEnabled = YES;
__block BOOL closed = NO;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:callRequest
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *message) {
[expectPassedMessage fulfill];
XCTAssertFalse(closed);
}
closeCallback:^(NSDictionary *ttrailers, NSError *error) {
closed = YES;
[expectPassedClose fulfill];
}]
callOptions:options];
[call receiveNextMessages:1];
[call start];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kInvertedTimeout handler:nil];
}
- (void)testFlowControlReadReadyAfterStart {
__weak XCTestExpectation *expectPassedMessage =
[self expectationWithDescription:@"Message delivered with receiveNextMessage"];
__weak XCTestExpectation *expectPassedClose =
[self expectationWithDescription:@"Close delivered with receiveNextMessage"];
RMTStreamingOutputCallRequest *request = [RMTStreamingOutputCallRequest message];
RMTResponseParameters *parameters = [RMTResponseParameters message];
parameters.size = kSimpleDataLength;
[request.responseParametersArray addObject:parameters];
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
GRPCRequestOptions *callRequest =
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transportType = GRPCTransportTypeInsecure;
options.flowControlEnabled = YES;
__block BOOL closed = NO;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:callRequest
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
messageCallback:^(NSData *message) {
[expectPassedMessage fulfill];
XCTAssertFalse(closed);
}
closeCallback:^(NSDictionary *trailers, NSError *error) {
closed = YES;
[expectPassedClose fulfill];
}]
callOptions:options];
[call start];
[call receiveNextMessages:1];
[call writeData:[request data]];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testFlowControlReadNonBlockingFailure {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kHostAddress
path:kUnaryCallMethod.HTTPPath
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.flowControlEnabled = YES;
options.transportType = GRPCTransportTypeInsecure;
RMTSimpleRequest *request = [RMTSimpleRequest message];
request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit];
RMTEchoStatus *status = [RMTEchoStatus message];
status.code = 2;
status.message = @"test";
request.responseStatus = status;
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");
XCTAssertEqual(error.code, 2);
[completion fulfill];
}]
callOptions:options];
[call writeData:[request data]];
[call start];
[call finish];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
@end