blob: fc09270964bfafb8bffc0fef2974687cc0e61904 [file] [log] [blame]
//
// Copyright 2019 Google LLC.
//
// 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 <XCTest/XCTest.h>
#import "Channel/Sources/EDOHostPort.h"
#import "Service/Sources/EDOClientService.h"
#import "Service/Sources/EDODeallocationTracker.h"
#import "Service/Sources/EDOHostService+Private.h"
#import "Service/Sources/EDOHostService.h"
#import "Service/Sources/EDOObject+Private.h"
#import "Service/Sources/EDOObjectReleaseMessage.h"
#import "Service/Sources/EDOServiceException.h"
#import "Service/Sources/EDOServicePort.h"
#import "Service/Sources/EDOWeakObject.h"
#import "Service/Sources/NSObject+EDOValueObject.h"
#import "Service/Sources/NSObject+EDOWeakObject.h"
#import "Service/Tests/TestsBundle/EDOTestDummy.h"
#import "Service/Tests/TestsBundle/EDOTestValueType.h"
#import <OCMock/OCMock.h>
// IWYU pragma: no_include "OCMArg.h"
// IWYU pragma: no_include "OCMFunctions.h"
// IWYU pragma: no_include "OCMLocation.h"
// IWYU pragma: no_include "OCMMacroState.h"
// IWYU pragma: no_include "OCMRecorder.h"
// IWYU pragma: no_include "OCMStubRecorder.h"
// IWYU pragma: no_include "OCMockObject.h"
@interface EDOWeakReferenceTest : XCTestCase
@end
@implementation EDOWeakReferenceTest
/**
* Tests EDOWeakObject is an NSProxy that forwards inovcation to the underlying object that both
* EDOWeakObject and underlying object behaves the same.
*/
- (void)testWeakObjectBehavesTheSameAsUnderlyingObject {
EDOTestDummy *testDummy = [[EDOTestDummy alloc] init];
// Cast to EDOTestDummy to test that weakObject is correctly forwarded using same class of
// functions.
EDOTestDummy *weakObject = (EDOTestDummy *)[[EDOWeakObject alloc] initWithWeakObject:testDummy];
XCTAssertEqual([weakObject returnSelf], [testDummy returnSelf]);
}
/**
* Tests EDOWeakObject is an NSProxy that forwards inovcation to the underlying object that both
* EDOWeakObject and underlying object behaves the same for multiple objects with the same
* underlying object.
*/
- (void)testMultipleWeakObjectBehavesTheSameAsUnderlyingObject {
int numWeakObjects = 10;
EDOTestDummy *testDummy = [[EDOTestDummy alloc] init];
// Cast to EDOTestDummy to test that weakObject is correctly forwarded using same class of
// functions.
NSArray<EDOTestDummy *> *weakObjects =
[self weakObjectsArrayWithNumberOfWeakObjects:numWeakObjects localObject:testDummy];
for (EDOTestDummy *weakObject in weakObjects) {
XCTAssertEqual([weakObject returnSelf], [testDummy returnSelf]);
}
}
/**
* Tests EDOWeakObject is an NSProxy that forwards inovcation to the underlying object.
*/
- (void)testWeakObjectCanForwardToUnderlyingObject {
EDOTestDummy *dummy = [[EDOTestDummy alloc] init];
id testDummyMock = OCMPartialMock(dummy);
// Cast to EDOTestDummy to test that weakObject is correctly forwarded using same class of
// functions.
EDOTestDummy *weakObject =
(EDOTestDummy *)[[EDOWeakObject alloc] initWithWeakObject:testDummyMock];
// Tests that invoking weakObject actually invokes the underlying object.
[testDummyMock returnData];
OCMVerify([weakObject returnData]);
[testDummyMock returnSelf];
OCMVerify([weakObject returnSelf]);
[testDummyMock stopMocking];
}
/**
* Tests multiple EDOWeakObject can forward inovcation to the same underlying object.
*/
- (void)testMultipleWeakObjectCanForwardToTheUnderlyingObject {
EDOTestDummy *dummy = [[EDOTestDummy alloc] init];
id testDummyMock = OCMPartialMock(dummy);
int numWeakObjects = 10;
NSArray<EDOTestDummy *> *weakObjects =
[self weakObjectsArrayWithNumberOfWeakObjects:numWeakObjects localObject:testDummyMock];
// Tests that invoking weakObject actually invokes the underlying object.
for (EDOTestDummy *weakObject in weakObjects) {
[testDummyMock returnData];
OCMVerify([weakObject returnData]);
[testDummyMock returnSelf];
OCMVerify([weakObject returnSelf]);
}
[testDummyMock stopMocking];
}
/**
* Tests when the underlying object is released, weakObject throws the right exception
* (EDOWeakObjectWeakReleaseException).
*/
- (void)testWeakObjectThrowWhenUnderlyingObjectIsReleased {
EDOTestDummy *weakObject;
@autoreleasepool {
EDOTestDummy *testDummy = [[EDOTestDummy alloc] init];
weakObject = (EDOTestDummy *)[[EDOWeakObject alloc] initWithWeakObject:testDummy];
}
XCTAssertThrowsSpecificNamed([weakObject returnSelf], NSException,
EDOWeakObjectWeakReleaseException);
}
/**
* Tests when the underlying object is released, underlying object becomes nil.
*/
- (void)testWeakObjectBecomeNilWhenUnderlyingObjectIsReleased {
EDOWeakObject *weakObject;
@autoreleasepool {
EDOTestDummy *testDummy = [[EDOTestDummy alloc] init];
weakObject = [[EDOWeakObject alloc] initWithWeakObject:testDummy];
}
XCTAssertNil(weakObject.weakObject);
}
/**
* Tests multiple weak object with the same underlying object should behave similarly.
*/
- (void)testMultipleWeakObjectWithSameUnderlyingObject {
int numWeakObjects = 10;
NSArray<EDOTestDummy *> *weakObjects;
@autoreleasepool {
EDOTestDummy *testDummy = [[EDOTestDummy alloc] init];
weakObjects = [self weakObjectsArrayWithNumberOfWeakObjects:numWeakObjects
localObject:testDummy];
}
for (EDOTestDummy *weakObject in weakObjects) {
XCTAssertNil(((EDOWeakObject *)weakObject).weakObject);
XCTAssertThrowsSpecificNamed([weakObject returnSelf], NSException,
EDOWeakObjectWeakReleaseException);
}
}
/**
* Tests weak object sends release messsage when underlying object is out of scope.
* EDODeallocationTracker tracks the object and sends object release request when object is out of
* scope.
*/
- (void)testWeakObjectSendReleaseMessageWhenUnderlyingObjectIsReleased {
EDOWeakObject *weakObject;
NSString *queueName = [NSString stringWithFormat:@"com.google.edotest.%@", self.name];
dispatch_queue_t queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
EDOHostService *hostService = [self serviceForQueue:queue];
id releaseMock = OCMClassMock([EDOObjectReleaseRequest class]);
@autoreleasepool {
EDOTestDummy *testDummy = [[EDOTestDummy alloc] init];
weakObject = [[EDOWeakObject alloc] initWithWeakObject:testDummy];
[EDODeallocationTracker enableTrackingForObject:weakObject hostPort:hostService.port.hostPort];
// Verify that release message is not sent if the object is in scope.
OCMVerify(never(), [releaseMock requestWithWeakRemoteAddress:(EDOPointerType)weakObject]);
}
// Verify that when object is out of scope, the release message is sent.
OCMVerify(times(1), [releaseMock requestWithWeakRemoteAddress:(EDOPointerType)weakObject]);
[releaseMock stopMocking];
[hostService invalidate];
}
/**
* Tests API for weak object reference that remoteWeak invocation and nested invocation works.
*/
- (void)testRemoteWeakAPI {
EDOTestDummy *object = [[EDOTestDummy alloc] init];
XCTAssertTrue([[[object remoteWeak] class] isEqual:[EDOWeakObject class]]);
XCTAssertEqual(((EDOWeakObject *)[object remoteWeak]).weakObject, object);
XCTAssertEqual(((EDOWeakObject *)[[object remoteWeak] remoteWeak]).weakObject, object);
}
/**
* Tests API for weak object reference that remoteWeak invocation works with EDOValueObject.
*/
- (void)testRemoteWeakWrapsWithEDOValueObject {
EDOTestValueType *object = [[EDOTestValueType alloc] init];
EDOTestValueType *valueObject = [object passByValue];
XCTAssertTrue([[[valueObject remoteWeak] class] isEqual:[EDOWeakObject class]]);
XCTAssertEqual(((EDOWeakObject *)[valueObject remoteWeak]).weakObject, object);
}
/**
* Tests API for weak object reference that remoteWeak works with PassByValue.
*/
- (void)testPassByValueWithRemoteWeak {
EDOTestDummy *dummyOnBackground = [[EDOTestDummy alloc] init];
NSArray<NSNumber *> *array = [dummyOnBackground returnArray];
XCTAssertEqual([dummyOnBackground returnCountWithArray:[[array remoteWeak] passByValue]], 4);
XCTAssertEqual([dummyOnBackground returnCountWithArray:[[array passByValue] remoteWeak]], 4);
}
/**
* Tests API for weak object reference that remoteWeak works with nested PassByValue.
*/
- (void)testPassByValueNestedWithRemoteWeak {
NSString *queueName = [NSString stringWithFormat:@"com.google.edotest.%@", self.name];
dispatch_queue_t queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
id backgroundServiceMock = [self createPartialMockServiceOnQueue:queue];
id mainServiceMock = [self createPartialMockServiceOnQueue:dispatch_get_main_queue()];
EDOTestDummy *dummyOnBackground = [self rootObjectFromService:backgroundServiceMock];
@autoreleasepool {
NSArray<NSNumber *> *array = @[ @(1), @(2), @(3), @(4) ];
XCTAssertEqual(
[dummyOnBackground returnCountWithArray:[[[array passByValue] passByValue] remoteWeak]], 4);
XCTAssertEqual(
[dummyOnBackground returnCountWithArray:[[[array remoteWeak] passByValue] passByValue]], 4);
XCTAssertEqual(
[dummyOnBackground returnCountWithArray:[[[array passByValue] remoteWeak] passByValue]], 4);
XCTAssertEqual(
[dummyOnBackground returnCountWithArray:[[[array remoteWeak] passByValue] remoteWeak]], 4);
XCTAssertEqual(
[dummyOnBackground returnCountWithArray:[[[array passByValue] remoteWeak] remoteWeak]], 4);
XCTAssertEqual(
[dummyOnBackground returnCountWithArray:[[[array remoteWeak] remoteWeak] passByValue]], 4);
}
[backgroundServiceMock stopMocking];
[mainServiceMock stopMocking];
}
/** Tests when the remoteWeak function is called on weakly referenced block object, an exception is
* thrown. */
- (void)testWeakObjectReferenceBlockObjectCatchesException {
void (^localBlock)(void) = ^{
[self awakeFromNib];
};
XCTAssertThrowsSpecificNamed([[EDOWeakObject alloc] initWithWeakObject:localBlock], NSException,
EDOWeakReferenceBlockObjectException);
}
#pragma mark - Helper methods
- (NSArray<EDOTestDummy *> *)weakObjectsArrayWithNumberOfWeakObjects:(int)numberOfWeakObjects
localObject:(EDOTestDummy *)testDummy {
NSMutableArray<EDOTestDummy *> *weakObjects =
[NSMutableArray arrayWithCapacity:numberOfWeakObjects];
for (int i = 0; i < numberOfWeakObjects; i++) {
[weakObjects addObject:(EDOTestDummy *)[[EDOWeakObject alloc] initWithWeakObject:testDummy]];
}
return weakObjects;
}
- (EDOHostService *)serviceForQueue:(dispatch_queue_t)queue {
return [EDOHostService serviceWithPort:0 rootObject:[[EDOTestDummy alloc] init] queue:queue];
}
- (EDOHostService *)createPartialMockServiceOnQueue:(dispatch_queue_t)queue {
EDOHostService *service = [EDOHostService serviceWithPort:0
rootObject:[[EDOTestDummy alloc] init]
queue:queue];
// Disable the isObjectAlive: check so the object from the background queue will not be resolved
// to the underlying object but a remote object.
id serviceMock = OCMPartialMock(service);
OCMStub([serviceMock isObjectAliveWithPort:OCMOCK_ANY remoteAddress:0])
.ignoringNonObjectArgs()
.andReturn(NO);
return serviceMock;
}
- (EDOTestDummy *)rootObjectFromService:(EDOHostService *)service {
return [EDOClientService rootObjectWithPort:service.port.hostPort.port];
}
@end