|
|
|
@ -20,16 +20,16 @@ |
|
|
|
|
#import <XCTest/XCTest.h> |
|
|
|
|
#import <grpc/grpc.h> |
|
|
|
|
|
|
|
|
|
#import <GRPCClient/GRPCCall.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> |
|
|
|
|
#import <RxLibrary/GRXBufferedPipe.h> |
|
|
|
|
|
|
|
|
|
#include <netinet/in.h> |
|
|
|
|
|
|
|
|
@ -37,10 +37,10 @@ |
|
|
|
|
|
|
|
|
|
#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 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; |
|
|
|
@ -49,15 +49,17 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
|
|
|
|
|
/** Observer class for testing that responseMetadata is KVO-compliant */ |
|
|
|
|
@interface PassthroughObserver : NSObject |
|
|
|
|
- (instancetype) initWithCallback:(void (^)(NSString*, id, NSDictionary*))callback |
|
|
|
|
- (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback |
|
|
|
|
NS_DESIGNATED_INITIALIZER; |
|
|
|
|
|
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change |
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath |
|
|
|
|
ofObject:(id)object |
|
|
|
|
change:(NSDictionary *)change |
|
|
|
|
context:(void *)context; |
|
|
|
|
@end |
|
|
|
|
|
|
|
|
|
@implementation PassthroughObserver { |
|
|
|
|
void (^_callback)(NSString*, id, NSDictionary*); |
|
|
|
|
void (^_callback)(NSString *, id, NSDictionary *); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (instancetype)init { |
|
|
|
@ -84,7 +86,7 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
|
|
|
|
|
@end |
|
|
|
|
|
|
|
|
|
# pragma mark Tests |
|
|
|
|
#pragma mark Tests |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* A few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) rather than |
|
|
|
@ -108,18 +110,14 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
[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"]; |
|
|
|
|
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 { |
|
|
|
@ -129,13 +127,15 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
@ -143,21 +143,24 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)testEmptyRPC { |
|
|
|
|
__weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; |
|
|
|
|
__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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
@ -178,18 +181,20 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
@ -210,20 +215,22 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
@ -231,38 +238,43 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)testResponseMetadataKVO { |
|
|
|
|
__weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; |
|
|
|
|
__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]; |
|
|
|
|
} |
|
|
|
|
}]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 *response = |
|
|
|
|
[self expectationWithDescription:@"Empty response received."]; |
|
|
|
|
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
|
|
|
|
|
|
|
|
|
GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
|
|
|
@ -272,46 +284,45 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
// 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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
@ -319,7 +330,8 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)testTrailers { |
|
|
|
|
__weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; |
|
|
|
|
__weak XCTestExpectation *response = |
|
|
|
|
[self expectationWithDescription:@"Empty response received."]; |
|
|
|
|
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
|
|
|
|
|
|
|
|
|
GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
|
|
|
@ -327,21 +339,22 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
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}; |
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
@ -351,15 +364,12 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
- (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]; |
|
|
|
|
(void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:nil]; |
|
|
|
|
XCTFail(@"Did not receive an exception when parameters are nil"); |
|
|
|
|
} @catch(NSException *theException) { |
|
|
|
|
} @catch (NSException *theException) { |
|
|
|
|
NSLog(@"Received exception as expected: %@", theException.name); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set state to Finished by force |
|
|
|
|
GRXWriter *requestsWriter = [GRXWriter emptyWriter]; |
|
|
|
|
[requestsWriter finishWithError:nil]; |
|
|
|
@ -368,10 +378,9 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
path:kUnaryCallMethod.HTTPPath |
|
|
|
|
requestsWriter:requestsWriter]; |
|
|
|
|
XCTFail(@"Did not receive an exception when GRXWriter has incorrect state."); |
|
|
|
|
} @catch(NSException *theException) { |
|
|
|
|
} @catch (NSException *theException) { |
|
|
|
|
NSLog(@"Received exception as expected: %@", theException.name); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)testIdempotentProtoRPC { |
|
|
|
@ -387,20 +396,24 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
[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]; |
|
|
|
|
|
|
|
|
@ -412,10 +425,12 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
|
|
|
|
request.responseSize = kPayloadSize; |
|
|
|
|
|
|
|
|
|
__weak XCTestExpectation *expectation1 = [self expectationWithDescription:@"AlternateDispatchQueue1"]; |
|
|
|
|
__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())]; |
|
|
|
|
NSString *main_queue_label = |
|
|
|
|
[NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())]; |
|
|
|
|
|
|
|
|
|
GRXWriter *requestsWriter1 = [GRXWriter writerWithValue:[request data]]; |
|
|
|
|
|
|
|
|
@ -423,20 +438,24 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
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]); |
|
|
|
|
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) { |
|
|
|
|
}]; |
|
|
|
|
[expectation1 fulfill]; |
|
|
|
|
} |
|
|
|
|
completionHandler:^(NSError *errorOrNil){ |
|
|
|
|
}]; |
|
|
|
|
|
|
|
|
|
[call1 startWithWriteable:responsesWriteable1]; |
|
|
|
|
|
|
|
|
|
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
|
|
|
|
|
|
|
|
|
// Use a custom queue |
|
|
|
|
__weak XCTestExpectation *expectation2 = [self expectationWithDescription:@"AlternateDispatchQueue2"]; |
|
|
|
|
__weak XCTestExpectation *expectation2 = |
|
|
|
|
[self expectationWithDescription:@"AlternateDispatchQueue2"]; |
|
|
|
|
|
|
|
|
|
NSString *queue_label = @"test.queue1"; |
|
|
|
|
dispatch_queue_t queue = dispatch_queue_create([queue_label UTF8String], DISPATCH_QUEUE_SERIAL); |
|
|
|
@ -449,13 +468,16 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
|
|
|
|
|
[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]); |
|
|
|
|
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) { |
|
|
|
|
}]; |
|
|
|
|
[expectation2 fulfill]; |
|
|
|
|
} |
|
|
|
|
completionHandler:^(NSError *errorOrNil){ |
|
|
|
|
}]; |
|
|
|
|
|
|
|
|
|
[call2 startWithWriteable:responsesWriteable2]; |
|
|
|
|
|
|
|
|
@ -470,13 +492,16 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
@ -490,8 +515,8 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
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(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; |
|
|
|
@ -499,21 +524,23 @@ static GRPCProtoMethod *kFullDuplexCallMethod; |
|
|
|
|
|
|
|
|
|
- (void)testErrorCode { |
|
|
|
|
int port = [self findFreePort]; |
|
|
|
|
NSString * const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port]; |
|
|
|
|
NSString *const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port]; |
|
|
|
|
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
|
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
}]; |
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
|