blob: cd6162371562a30b9e06326734a2ecc21c03aeb4 [file] [log] [blame]
//
// Copyright 2018 Google Inc.
//
// 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 <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Channel/Sources/EDOHostPort.h"
#import "Service/Sources/EDOClientService+Private.h"
#import "Service/Sources/EDOClientService.h"
#import "Service/Sources/EDOHostNamingService+Private.h"
#import "Service/Sources/EDOHostService+Private.h"
#import "Service/Sources/EDOObjectMessage.h"
#import "Service/Sources/EDORemoteException.h"
#import "Service/Sources/EDOServiceError.h"
#import "Service/Sources/EDOServicePort.h"
#import "Service/Sources/EDOServiceRequest.h"
#import "Service/Sources/NSObject+EDOValueObject.h"
#import "Service/Tests/TestsBundle/EDOTestClassDummy.h"
#import "Service/Tests/TestsBundle/EDOTestDummy.h"
#import "Service/Tests/TestsBundle/EDOTestNonNSCodingType.h"
#import "Service/Tests/TestsBundle/EDOTestProtocol.h"
#import "Service/Tests/TestsBundle/EDOTestValueType.h"
static NSString *const kTestServiceName = @"com.google.edotest.service";
@interface EDOServiceTest : XCTestCase
@property(readonly) id serviceBackgroundMock;
@property(readonly) id serviceMainMock;
@property(readonly) EDOHostService *serviceOnBackground;
@property(readonly) EDOHostService *serviceOnMain;
@property(readonly) EDOTestDummy *rootObject;
@property(readonly) dispatch_queue_t executionQueue;
@property(readonly) EDOTestDummy *rootObjectOnBackground;
@property(weak) EDOHostService *weakService;
@property(weak) dispatch_queue_t weakQueue;
@end
@implementation EDOServiceTest
- (void)setUp {
[super setUp];
NSString *queueName = [NSString stringWithFormat:@"com.google.edotest.%@", self.name];
_executionQueue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
_rootObject = [[EDOTestDummy alloc] init];
_serviceOnMain = [EDOHostService serviceWithRegisteredName:kTestServiceName
rootObject:[[EDOTestDummy alloc] init]
queue:dispatch_get_main_queue()];
_serviceMainMock = OCMPartialMock(_serviceOnMain);
OCMStub([_serviceMainMock isObjectAlive:OCMOCK_ANY]).andReturn(NO);
[self resetBackgroundService];
}
- (void)tearDown {
[self.serviceOnMain invalidate];
[self.serviceOnBackground invalidate];
_executionQueue = nil;
_serviceOnMain = nil;
_serviceOnBackground = nil;
_rootObject = nil;
[self.serviceMainMock stopMocking];
[self.serviceBackgroundMock stopMocking];
_serviceMainMock = nil;
_serviceBackgroundMock = nil;
[super tearDown];
}
- (void)testServiceAssociatedQueue {
dispatch_queue_t testQueue = dispatch_queue_create("com.google.edotest", DISPATCH_QUEUE_SERIAL);
EDOHostService *hostService = [EDOHostService serviceWithPort:0 rootObject:self queue:testQueue];
dispatch_sync(testQueue, ^{
XCTAssertEqual(hostService, [EDOHostService serviceForCurrentOriginatingQueue]);
XCTAssertEqual(hostService, [EDOHostService serviceForOriginatingQueue:testQueue]);
});
XCTAssertEqual(hostService, [EDOHostService serviceForOriginatingQueue:testQueue]);
}
- (void)testServiceWithNonassignedeExecutingQueue {
EDOHostService *hostService = [EDOHostService serviceWithPort:0 rootObject:self queue:nil];
// TODO(haowoo): This should be 1 once we fix the ownership.
XCTAssertEqual(hostService.originatingQueues.count, 0);
}
- (void)testExecutingQueueIsOriginatingQueue {
dispatch_queue_t testQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
EDOHostService *hostService = [EDOHostService serviceWithPort:0 rootObject:self queue:testQueue];
XCTAssertTrue([hostService.originatingQueues containsObject:testQueue]);
}
- (void)testServiceCanHaveOtherExecutingQueue {
dispatch_queue_t testQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
EDOHostService *hostService = [EDOHostService serviceWithPort:0 rootObject:self queue:testQueue];
@autoreleasepool {
dispatch_queue_t testQueue2 = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
hostService.originatingQueues = @[ testQueue2 ];
XCTAssertTrue([hostService.originatingQueues containsObject:testQueue]);
XCTAssertTrue([hostService.originatingQueues containsObject:testQueue2]);
}
// Service doesn't hold the originating queue.
XCTAssertEqual(hostService.originatingQueues.count, 1);
}
- (void)testServiceCanGetFromOriginatingQueue {
dispatch_queue_t testQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_queue_t testQueue2 = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
EDOHostService *hostService = [EDOHostService serviceWithPort:0 rootObject:self queue:testQueue];
XCTAssertEqual(hostService, [EDOHostService serviceForOriginatingQueue:testQueue]);
hostService.originatingQueues = @[ testQueue2 ];
XCTAssertEqual(hostService, [EDOHostService serviceForOriginatingQueue:testQueue]);
XCTAssertEqual(hostService, [EDOHostService serviceForOriginatingQueue:testQueue2]);
dispatch_sync(testQueue2, ^{
XCTAssertEqual(hostService, [EDOHostService serviceForCurrentOriginatingQueue]);
});
}
- (void)testServiceCanGetFromExecutingQueue {
dispatch_queue_t testQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
EDOHostService *hostService = [EDOHostService serviceWithPort:0 rootObject:self queue:testQueue];
dispatch_sync(testQueue, ^{
XCTAssertEqual(hostService, [EDOHostService serviceForCurrentOriginatingQueue]);
XCTAssertEqual(hostService, [EDOHostService serviceForCurrentExecutingQueue]);
});
}
- (void)testServiceLifecycleIsBoundToQueue {
dispatch_queue_t testQueue = dispatch_queue_create("com.google.edotest", DISPATCH_QUEUE_SERIAL);
self.weakQueue = testQueue;
self.weakService = [EDOHostService serviceWithPort:0 rootObject:self queue:testQueue];
XCTAssertNotNil([EDOHostService serviceForOriginatingQueue:testQueue]);
XCTAssertEqual(self.weakService, [EDOHostService serviceForOriginatingQueue:testQueue]);
// The dispatch queue library may delegate its dealloc and callback to a different queue/thread.
XCTKVOExpectation *expectServiceNil = [[XCTKVOExpectation alloc] initWithKeyPath:@"weakService"
object:self
expectedValue:nil];
XCTKVOExpectation *expectQueueNil = [[XCTKVOExpectation alloc] initWithKeyPath:@"weakQueue"
object:self
expectedValue:nil];
[self waitForExpectations:@[ expectServiceNil, expectQueueNil ] timeout:10];
}
- (void)testTemporaryServiceLazilyCreatedIfNoServiceAvailable {
dispatch_queue_t testQueue = dispatch_queue_create("com.google.edotest", DISPATCH_QUEUE_SERIAL);
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
EDOHostService *service = [EDOHostService serviceWithPort:0 rootObject:nil queue:nil];
XCTAssertFalse(service.valid);
id serviceMock = OCMPartialMock(service);
[serviceMock setExpectationOrderMatters:YES];
OCMStub([[serviceMock classMethod] serviceWithPort:0 rootObject:nil queue:nil])
.andReturn(serviceMock);
OCMExpect([serviceMock distantObjectForLocalObject:OCMOCK_ANY hostPort:[OCMArg isNil]])
.andForwardToRealObject();
OCMExpect([(EDOHostService *)serviceMock port]).andForwardToRealObject();
dispatch_sync(testQueue, ^{
XCTAssertNil([EDOHostService serviceForCurrentOriginatingQueue]);
// eDO will wrap the block into a remote object which would create a temporary service.
// The service shouldn't initialize the listen socket (by calling -port), until it is later to
// wrap the block object (by calling -distantObjectForLocalObject:hostPort:), in the
// respective order.
[dummyOnBackground voidWithBlock:^{
}];
});
XCTAssertTrue(service.valid);
OCMVerifyAll(serviceMock);
[serviceMock stopMocking];
}
- (void)testTemporaryServiceNotListenedIfNoRemoteObject {
dispatch_queue_t testQueue = dispatch_queue_create("com.google.edotest", DISPATCH_QUEUE_SERIAL);
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
EDOHostService *service = [EDOHostService serviceWithPort:0 rootObject:nil queue:nil];
id serviceMock = OCMPartialMock(service);
OCMStub([[serviceMock classMethod] serviceWithPort:0 rootObject:nil queue:nil])
.andReturn(serviceMock);
OCMReject([(EDOHostService *)serviceMock port]);
dispatch_sync(testQueue, ^{
XCTAssertNil([EDOHostService serviceForCurrentOriginatingQueue]);
// Whether eDO creates a temporary service or not, it shouldn't initialize the listen socket by
// calling -port.
[dummyOnBackground voidWithInt:0];
});
OCMVerifyAll(serviceMock);
[serviceMock stopMocking];
}
- (void)testClassMethodsAndInit {
Class remoteClass = EDO_REMOTE_CLASS(EDOTestDummy, self.serviceOnBackground.port.hostPort.port);
EDOTestDummy *dummy;
@autoreleasepool {
EDOTestDummy *dummyAlloc;
XCTAssertNoThrow(dummyAlloc = [remoteClass alloc]);
XCTAssertNoThrow(dummy = [dummyAlloc initWithValue:10]);
XCTAssertEqual(dummy, dummyAlloc);
XCTAssertEqual(dummy.value, 10);
}
@autoreleasepool {
XCTAssertNoThrow(dummy = [remoteClass classMethodWithNumber:@10]);
XCTAssertEqual(dummy.value, 10);
}
}
- (void)testEDOBlock {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
double doubleReturn = [dummyOnBackground returnWithBlockDouble:^double {
return 100.0;
}];
XCTAssertEqual(doubleReturn, 100.0);
EDOTestDummyStruct dummyStruct =
[dummyOnBackground returnStructWithBlockStret:^EDOTestDummyStruct {
return (EDOTestDummyStruct){.value = 100, .a = 30.0, .x = 50, .z = 200};
}];
XCTAssertEqual(dummyStruct.value, 100);
XCTAssertEqual(dummyStruct.a, 30);
XCTAssertEqual(dummyStruct.x, 50);
XCTAssertEqual(dummyStruct.z, 200);
dummyOnBackground.value = 10;
EDOTestDummy *returnDummy = [dummyOnBackground returnWithBlockObject:^id(EDOTestDummy *dummy) {
dummy.value += 10;
return dummy;
}];
XCTAssertEqual(returnDummy.value, 20);
EDOTestDummy * (^complexBlock)(EDOTestDummyStruct, int, EDOTestDummy *) =
^(EDOTestDummyStruct dummy, int i, EDOTestDummy *test) {
test.value = i + dummy.value + 4;
return test;
};
returnDummy = [dummyOnBackground returnWithInt:5
dummyStruct:(EDOTestDummyStruct){.value = 150}
blockComplex:complexBlock];
;
XCTAssertEqual(returnDummy.value, 159);
}
- (void)testReturnUniqueEDOForSameUnderlyingObjects {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
Class remoteClass = EDO_REMOTE_CLASS(EDOTestDummy, self.serviceOnBackground.port.hostPort.port);
XCTAssertEqual([dummyOnBackground returnClass], [dummyOnBackground returnClass]);
XCTAssertEqual([dummyOnBackground returnClass], remoteClass);
EDOTestDummy *dummySelf = [dummyOnBackground returnSelf];
XCTAssertEqual(dummySelf, dummyOnBackground);
XCTAssertEqual([self class], [dummyOnBackground classsWithClass:[self class]]);
XCTAssertEqual(remoteClass, [dummyOnBackground classsWithClass:remoteClass]);
XCTAssertEqual(remoteClass,
EDO_REMOTE_CLASS(EDOTestDummy, self.serviceOnBackground.port.hostPort.port));
[self resetBackgroundService];
// Even the underlying objects are the same, it should return a different remote object because
// the old service is gone, and the local cache should be invalidated.
XCTAssertNotEqual(remoteClass,
EDO_REMOTE_CLASS(EDOTestDummy, self.serviceOnBackground.port.hostPort.port));
// Update the background object after resetting the service.
dummyOnBackground = self.rootObjectOnBackground;
EDOTestDummy *dummyOut;
self.rootObject.value = 19;
XCTAssertNoThrow([dummyOnBackground voidWithValue:23 outSelf:&dummyOut]);
XCTAssertEqual(dummyOut, dummyOnBackground);
XCTAssertEqual(dummyOut.value, 42);
self.rootObject.value = 19;
XCTAssertNoThrow([dummyOnBackground voidWithValue:11 outSelf:&dummyOut]);
XCTAssertEqual(dummyOut, dummyOnBackground);
XCTAssertEqual(dummyOut.value, 49);
EDOTestDummy *dummyOutAgain;
dummyOut = nil;
// voidWithOutObject: returns
// 1) a new object if dummyOut is nil
// 2) the same object if dummyOut is already pointing to something
XCTAssertNoThrow([dummyOnBackground voidWithOutObject:&dummyOut]);
XCTAssertNoThrow([dummyOnBackground voidWithOutObject:&dummyOutAgain]);
XCTAssertNotEqual(dummyOutAgain, dummyOut);
XCTAssertEqual(dummyOut.value, dummyOutAgain.value);
EDOTestDummy *dummyOutOriginal = dummyOut;
XCTAssertNoThrow([dummyOnBackground voidWithOutObject:&dummyOut]);
XCTAssertEqual(dummyOutOriginal, dummyOut);
}
- (void)testUnderlyingObjectShouldReturnFromBackgroundQueueInTheSameProcess {
dispatch_queue_t testQueue = dispatch_queue_create("com.google.edotest", DISPATCH_QUEUE_SERIAL);
dispatch_sync(testQueue, ^{
XCTAssertNotEqual([[self.rootObjectOnBackground returnSelf] class], [EDOTestDummy class],
@"The returned object should be a remote object.");
});
// The service on the background queue will resolve to the local address.
[self.serviceBackgroundMock stopMocking];
dispatch_sync(testQueue, ^{
XCTAssertEqual([[self.rootObjectOnBackground returnSelf] class], [EDOTestDummy class],
@"The returned object should be a local object.");
});
}
- (void)testResolveToSameUnderlyingObjectInArgument {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
// The argument dummyOnBackground should be resolved to the same remote object.
XCTAssertEqual([dummyOnBackground memoryAddressFromObject:dummyOnBackground],
[dummyOnBackground memoryAddressFromObject:dummyOnBackground]);
XCTAssertEqual([dummyOnBackground memoryAddressFromObjectRef:&dummyOnBackground],
[dummyOnBackground memoryAddressFromObjectRef:&dummyOnBackground]);
}
- (void)testResolveToUnderlyingInstanceIfInTheSameProcess {
EDOTestDummy *dummyOut;
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
// The service on the background queue will resolve to the local address.
[self.serviceBackgroundMock stopMocking];
XCTAssertEqual([[dummyOnBackground returnSelf] class], [EDOTestDummy class]);
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:self], @"EDOObject");
XCTAssertNoThrow(({
dummyOut = nil;
[dummyOnBackground voidWithOutObject:&dummyOut];
}));
XCTAssertEqual([dummyOut class], [EDOTestDummy class]);
// The services on both the main queue and the background queue will resolve to the local address.
[self.serviceMainMock stopMocking];
XCTAssertEqual([[dummyOnBackground returnSelf] class], [EDOTestDummy class]);
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:self],
NSStringFromClass([self class]));
XCTAssertNoThrow(({
dummyOut = nil;
[dummyOnBackground voidWithOutObject:&dummyOut];
}));
XCTAssertEqual([dummyOut class], [EDOTestDummy class]);
// The service on the main queue will resolve to the local address.
_serviceBackgroundMock = OCMPartialMock(self.serviceOnBackground);
OCMStub([_serviceBackgroundMock isObjectAlive:OCMOCK_ANY]).andReturn(NO);
XCTAssertEqual([[dummyOnBackground returnSelf] class], NSClassFromString(@"EDOObject"));
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:self],
NSStringFromClass([self class]));
XCTAssertNoThrow(({
dummyOut = nil;
[dummyOnBackground voidWithOutObject:&dummyOut];
}));
XCTAssertEqual([dummyOut class], NSClassFromString(@"EDOObject"));
}
- (void)testVoidReturnWithParametersOfDifferentKinds {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
self.rootObject.value = 10;
XCTAssertNoThrow([dummyOnBackground voidWithValuePlusOne]);
XCTAssertEqual(self.rootObject.value, 11);
XCTAssertNoThrow([dummyOnBackground voidWithInt:5]);
XCTAssertEqual(self.rootObject.value, 16);
XCTAssertNoThrow([dummyOnBackground voidWithNumber:@(7)]);
XCTAssertEqual(self.rootObject.value, 23);
XCTAssertNoThrow([dummyOnBackground voidWithString:@"12345" data:NSData.data]);
XCTAssertEqual(self.rootObject.value, 28);
XCTAssertNoThrow([dummyOnBackground voidWithStruct:(EDOTestDummyStruct){.value = 11}]);
XCTAssertEqual(self.rootObject.value, 39);
XCTAssertThrowsSpecificNamed([dummyOnBackground voidWithId:nil], EDORemoteException,
@"Dummy NilArg 39");
XCTAssertNoThrow([dummyOnBackground voidWithClass:self.class]);
XCTAssertEqualObjects([[dummyOnBackground returnDictionary] class],
NSClassFromString(@"EDOObject"));
XCTAssertEqualObjects([[dummyOnBackground returnArray] class], NSClassFromString(@"EDOObject"));
XCTAssertEqualObjects([[dummyOnBackground returnSet] class], NSClassFromString(@"EDOObject"));
XCTAssertEqualObjects([self fastEnumerateDictionary:dummyOnBackground],
[self fastEnumerateDictionary:self.rootObject]);
XCTAssertEqualObjects([self fastEnumerateArray:dummyOnBackground],
[self fastEnumerateArray:self.rootObject]);
XCTAssertEqualObjects([self fastEnumerateSet:dummyOnBackground],
[self fastEnumerateSet:self.rootObject]);
XCTAssertNoThrow([self fastEnumerateCustom:self.rootObject]);
XCTAssertThrowsSpecificNamed([self fastEnumerateCustom:dummyOnBackground], NSException,
NSInternalInconsistencyException);
XCTAssertNoThrow([dummyOnBackground voidWithBlock:nil]);
XCTAssertNoThrow([dummyOnBackground voidWithBlock:^{
}]);
}
- (void)testOutParameterCanResolveToLocalWhenDereferencing {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
EDOTestDummy *remoteDummy = [dummyOnBackground returnSelf];
EDOTestDummy *localDummy = [[EDOTestDummy alloc] initWithValue:5];
EDOTestDummy *emptyDummy;
XCTAssertNil([dummyOnBackground returnClassNameWithObjectRef:nil]);
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObjectRef:&emptyDummy], @"");
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObjectRef:&localDummy], @"EDOObject");
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObjectRef:&remoteDummy],
@"EDOTestDummy");
}
- (void)testOutParameters {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
self.rootObject.value = 13;
NSNumber *numberOut;
XCTAssertNoThrow([dummyOnBackground voidWithValueOut:&numberOut]);
XCTAssertEqualObjects(numberOut, @(13));
XCTAssertNoThrow([dummyOnBackground voidWithValueOut:&numberOut]);
XCTAssertEqualObjects(numberOut, @(26));
NSError *errorOut;
XCTAssertNoThrow([dummyOnBackground voidWithErrorOut:&errorOut]);
XCTAssertEqualObjects([errorOut class], NSClassFromString(@"EDOObject"));
EDOTestDummy *dummyOut;
XCTAssertNoThrow([dummyOnBackground voidWithOutObject:&dummyOut]);
XCTAssertEqual(dummyOut.value, 18);
XCTAssertEqualObjects([dummyOut class], NSClassFromString(@"EDOObject"));
XCTAssertNoThrow([dummyOnBackground voidWithOutObject:&dummyOut]);
XCTAssertEqual(dummyOut.value, 23);
dummyOut = [[EDOTestDummy alloc] initWithValue:17];
EDOTestDummy *dummyOriginal = dummyOut;
XCTAssertNoThrow([dummyOnBackground voidWithOutObject:&dummyOut]);
XCTAssertEqual(dummyOut, dummyOriginal);
XCTAssertEqual(dummyOut.value, 22);
self.rootObject.value = 13;
XCTAssertNoThrow([dummyOnBackground voidWithValue:5 outSelf:&dummyOut]);
XCTAssertEqualObjects([dummyOut class], NSClassFromString(@"EDOObject"));
XCTAssertEqual(dummyOut.value, 40);
}
- (void)testDifferentThrows {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
self.rootObject.value = 13;
XCTAssertThrowsSpecificNamed([dummyOnBackground selWithThrow], EDORemoteException,
@"Dummy Just Throw 13");
XCTAssertThrowsSpecificNamed([dummyOnBackground voidWithId:nil], EDORemoteException,
@"Dummy NilArg 13");
XCTAssertThrowsSpecificNamed([dummyOnBackground voidWithId:dummyOnBackground], EDORemoteException,
@"Dummy NonNilArg 13");
XCTAssertThrowsSpecificNamed([dummyOnBackground voidWithId:self.rootObject], EDORemoteException,
@"Dummy EDOArg 13");
XCTAssertThrowsSpecificNamed([dummyOnBackground voidWithValueOut:nil], EDORemoteException,
@"Dummy NilOutArg 13");
XCTAssertThrowsSpecificNamed([dummyOnBackground voidWithErrorOut:nil], EDORemoteException,
@"Dummy NilErrorOut 13");
XCTAssertThrowsSpecificNamed([dummyOnBackground voidWithOutObject:nil], EDORemoteException,
@"Dummy dummyOut is nil 13");
}
- (void)testNoParametersWithReturnsOfDifferentKinds {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
self.rootObject.value = 17;
XCTAssertEqualObjects([dummyOnBackground class], NSClassFromString(@"EDOObject"));
XCTAssertEqual([dummyOnBackground returnInt], [self.rootObject returnInt]);
XCTAssertEqual([dummyOnBackground returnStruct].value, [self.rootObject returnStruct].value);
XCTAssertEqualObjects([dummyOnBackground returnNumber], [self.rootObject returnNumber]);
XCTAssertEqualObjects([dummyOnBackground returnString], [self.rootObject returnString]);
XCTAssertEqualObjects([dummyOnBackground returnData], [self.rootObject returnData]);
XCTAssertEqual([dummyOnBackground returnSelf].value, self.rootObject.value);
XCTAssertNil([dummyOnBackground returnIdNil]);
XCTAssertNoThrow([dummyOnBackground returnClass]);
}
- (void)testReturnsWithParameters {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
self.rootObject.value = 23;
XCTAssertEqual([dummyOnBackground structWithStruct:(EDOTestDummyStruct){.value = 7}].value, 30);
XCTAssertEqual(self.rootObject.value, 30);
XCTAssertEqual([dummyOnBackground returnIdWithInt:11].value, 41);
XCTAssertEqualObjects([dummyOnBackground classsWithClass:self.class], self.class);
XCTAssertEqualObjects([dummyOnBackground returnNumberWithInt:3 value:@5],
@(self.rootObject.value + 8));
XCTAssertFalse([dummyOnBackground returnBoolWithError:nil]);
NSError *errorOut;
XCTAssertTrue([dummyOnBackground returnBoolWithError:&errorOut]);
XCTAssertEqual(self.rootObject.error.code, errorOut.code);
XCTAssertEqualObjects(self.rootObject.error.domain, errorOut.domain);
}
- (void)testSelectorParameterAndReturns {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
XCTAssertEqual([dummyOnBackground selectorFromName:nil], nil);
XCTAssertEqual([dummyOnBackground selectorFromName:@"selectorFromName:"],
@selector(selectorFromName:));
XCTAssertEqualObjects([dummyOnBackground nameFromSelector:@selector(nameFromSelector:)],
@"nameFromSelector:");
XCTAssertNil([dummyOnBackground nameFromSelector:nil]);
}
- (void)testMockAndNSProxyParameters {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
// The real object on the background queue.
EDOTestDummy *objectMock = OCMPartialMock(self.rootObject);
[(OCMockObject *)objectMock setExpectationOrderMatters:YES];
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:objectMock], @"EDOObject");
XCTAssertNoThrow(dummyOnBackground.value);
XCTAssertNoThrow([dummyOnBackground voidWithInt:10]);
OCMVerify([objectMock returnClassNameWithObject:[OCMArg isNotNil]]);
OCMVerify([objectMock value]);
OCMVerify([objectMock voidWithInt:10]);
Class helperClass =
EDO_REMOTE_CLASS(EDOProtocolMockTestHelper, self.serviceOnBackground.port.hostPort.port);
Class ocmArgClass = EDO_REMOTE_CLASS(OCMArg, self.serviceOnBackground.port.hostPort.port);
id testProtocol = [helperClass createTestProtocol];
[[testProtocol expect] methodWithNothing];
[[testProtocol expect] methodWithObject:[ocmArgClass isNil]];
[[testProtocol expect] returnWithNothing];
[[testProtocol expect] returnWithObject:[ocmArgClass isNotNil]];
[helperClass invokeMethodsWithProtocol:testProtocol];
OCMVerifyAll(testProtocol);
// Let it resolve otherwise -[isEqual:] causes infinite loop from mocking.
[self.serviceBackgroundMock stopMocking];
[self.serviceMainMock stopMocking];
}
- (void)testEDODescription {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
// Assign an arbitrary non-zero number to avoid test flakiness.
dummyOnBackground.value = 19;
XCTAssertEqualObjects(dummyOnBackground.description, @"Test Dummy 19");
}
- (void)testEDOEqual {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSSet<NSNumber *> *returnSetA = [dummyOnBackground returnSet];
NSSet<NSNumber *> *returnSetB = [dummyOnBackground returnSet];
XCTAssertEqual([returnSetA class], NSClassFromString(@"EDOObject"));
XCTAssertEqual([returnSetB class], NSClassFromString(@"EDOObject"));
XCTAssertTrue([returnSetA isEqual:returnSetB]);
XCTAssertTrue([returnSetA isEqual:returnSetA]);
XCTAssertEqual(returnSetB.hash, returnSetA.hash);
XCTAssertEqual([dummyOnBackground returnSelf].hash, [dummyOnBackground returnSelf].hash);
XCTAssertEqual(returnSetB.hash, [dummyOnBackground returnArray].hash);
XCTAssertFalse([returnSetA isEqual:[self.rootObject returnSet]]);
}
- (void)testEDOAsDictKey {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSSet *returnSet = [dummyOnBackground returnSet];
NSArray *returnArray = [dummyOnBackground returnArray];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
XCTAssertNil(dictionary[returnSet]);
XCTAssertNoThrow(dictionary[returnSet] = @1);
XCTAssertNil(dictionary[returnArray]);
XCTAssertEqualObjects(dictionary[returnSet], @1);
XCTAssertNoThrow([dictionary removeObjectForKey:returnSet]);
XCTAssertNil(dictionary[returnSet]);
}
- (void)testEDOAsCFDictKey {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSSet *returnSet = [dummyOnBackground returnSet];
NSArray *returnArray = [dummyOnBackground returnArray];
CFMutableDictionaryRef cfDictionary = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(cfDictionary, (void *)returnSet, CFSTR("Set"));
XCTAssertNoThrow(XCTAssertNotNil(CFDictionaryGetValue(cfDictionary, (void *)returnSet)));
NSMutableDictionary *tolledDict = (__bridge NSMutableDictionary *)cfDictionary;
XCTAssertNoThrow(tolledDict[returnArray] = @"Array");
XCTAssertEqualObjects(tolledDict[returnSet], @"Set");
NSMutableSet *allValues = [[NSMutableSet alloc] init];
XCTAssertNoThrow(({
for (NSSet *key in tolledDict) {
[allValues addObject:tolledDict[key]];
}
}));
XCTAssertEqualObjects(allValues, [NSSet setWithArray:tolledDict.allValues]);
}
// Test enable value type for custom class conforming to NSCoding.
- (void)testEnableCustomClassToBeValueType {
EDOTestValueType *object = [[EDOTestValueType alloc] init];
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:object], @"EDOObject");
[EDOTestValueType edo_enableValueType];
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:object], @"EDOTestValueType");
// Enable again to guarantee idempotency
[EDOTestValueType edo_enableValueType];
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:object], @"EDOTestValueType");
}
// Test enable value type for custom class not conforming to NSCoding.
- (void)testFailToEnableNonNSCodingTypetoBeValueType {
EDOTestNonNSCodingType *object = [[EDOTestNonNSCodingType alloc] init];
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
XCTAssertThrows([EDOTestNonNSCodingType edo_enableValueType]);
XCTAssertEqualObjects([dummyOnBackground returnClassNameWithObject:object], @"EDOObject");
}
- (void)testEDOReturnsAsValueType {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSArray *returnArray = [[dummyOnBackground returnByValue] returnArray];
NSArray *localArray = @[ @1, @2, @3, @4 ];
XCTAssertEqual([returnArray class], [localArray class]);
XCTAssertTrue([returnArray isEqualToArray:localArray]);
}
- (void)testEDOReturnsByValueError {
NSArray *localArray = @[ @1, @2, @3 ];
XCTAssertThrowsSpecificNamed([[localArray returnByValue] count], NSException,
NSObjectNotAvailableException);
}
- (void)testEDOPassByValue {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSArray *array = @[ @1, @2, @3, @4 ];
XCTAssertEqual([dummyOnBackground returnSumWithArrayAndProxyCheck:[array passByValue]], 10);
}
- (void)testEDOPassByValueWithRemoteObject {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSArray *array = [dummyOnBackground returnArray];
XCTAssertEqual([dummyOnBackground returnCountWithArray:[array passByValue]], 4);
}
- (void)testEDOPassByValueNestedWithReturnByValue {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSArray *array = [dummyOnBackground returnArray];
XCTAssertEqual([dummyOnBackground returnCountWithArray:[[array returnByValue] passByValue]], 4);
}
- (void)testEDOPassByValueNestedWithPassByValue {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSArray *array = [dummyOnBackground returnArray];
XCTAssertEqual([dummyOnBackground returnCountWithArray:[[array passByValue] passByValue]], 4);
}
- (void)testEDOReturnByValueNestedWithPassByValue {
EDOTestDummy *dummyOnBackground = self.rootObjectOnBackground;
NSArray *array = [dummyOnBackground returnArray];
XCTAssertEqual([dummyOnBackground returnCountWithArray:[[array passByValue] returnByValue]], 4);
}
- (void)testEDOHostServiceTrackedByNamingService {
EDOHostNamingService *namingServiceObject = EDOHostNamingService.sharedService;
XCTAssertFalse([namingServiceObject portForServiceWithName:kTestServiceName] == 0);
}
- (void)testClientErrorHandlerNotNil {
EDOSetClientErrorHandler(nil);
EDOClientErrorHandler oldErrorHandler = EDOSetClientErrorHandler(nil);
XCTAssertNotNil(oldErrorHandler);
}
- (void)testClientErrorHandlerInvoked {
XCTestExpectation *expectInvoke = [self expectationWithDescription:@"Error handler is invoked."];
EDOSetClientErrorHandler(^(NSError *error) {
[expectInvoke fulfill];
});
// The port 0 is reserved and should always fail to connect to it.
[EDOClientService rootObjectWithPort:0];
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testServicePopulateExecutorHandlingError {
EDOHostService *hostService = [EDOHostService serviceWithPort:2233 rootObject:self queue:nil];
EDOHostPort *port = [EDOHostPort hostPortWithLocalPort:2233];
XCTAssertThrows([EDOClientService
sendSynchronousRequest:[EDOObjectRequest requestWithHostPort:port]
onPort:port]);
[hostService invalidate];
}
- (void)testServiceRecordProcessTime {
dispatch_queue_t testQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
EDOHostService *hostService = [EDOHostService serviceWithPort:2234
rootObject:self
queue:testQueue];
EDOHostPort *port = [EDOHostPort hostPortWithLocalPort:2234];
EDOServiceResponse *response =
[EDOClientService sendSynchronousRequest:[EDOObjectRequest requestWithHostPort:port]
onPort:port];
[hostService invalidate];
XCTAssertTrue([response isKindOfClass:[EDOObjectResponse class]]);
// Assert the duration is within the reasonable range (0ms, 1000ms].
XCTAssertTrue(response.duration > 0 && response.duration <= 1000);
}
#pragma mark - Helper methods
- (EDOTestDummy *)rootObjectOnBackground {
return [EDOClientService rootObjectWithPort:self.serviceOnBackground.port.hostPort.port];
}
- (NSArray<NSString *> *)fastEnumerateDictionary:(EDOTestDummy *)dummy {
NSMutableArray<NSString *> *allKeys = [[NSMutableArray alloc] init];
for (NSString *key in [dummy returnDictionary]) {
[allKeys addObject:key];
}
return [allKeys copy];
}
- (NSArray<NSNumber *> *)fastEnumerateArray:(EDOTestDummy *)dummy {
NSMutableArray *allElements = [[NSMutableArray alloc] init];
for (NSNumber *obj in [dummy returnArray]) {
[allElements addObject:obj];
}
return [allElements copy];
}
- (NSSet<NSNumber *> *)fastEnumerateSet:(EDOTestDummy *)dummy {
NSMutableSet *allElements = [[NSMutableSet alloc] init];
for (NSNumber *obj in [dummy returnSet]) {
[allElements addObject:obj];
}
return [allElements copy];
}
- (void)fastEnumerateCustom:(EDOTestDummy *)dummy {
for (id obj in dummy) {
NSLog(@"%@", obj);
}
}
- (void)resetBackgroundService {
[_serviceBackgroundMock stopMocking];
[_serviceOnBackground invalidate];
_serviceOnBackground = [EDOHostService serviceWithPort:0
rootObject:self.rootObject
queue:self.executionQueue];
// Disable the isObjectAlive: check so the object from the background queue will not be resolved
// to the underlying object but a remote object.
_serviceBackgroundMock = OCMPartialMock(_serviceOnBackground);
OCMStub([_serviceBackgroundMock isObjectAlive:OCMOCK_ANY]).andReturn(NO);
}
@end