You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
14 KiB
281 lines
14 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright 2016 Realm 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 "RLMTestCase.h"
|
|
|
|
#import "RLMRealmConfiguration_Private.h"
|
|
#import "RLMThreadSafeReference.h"
|
|
|
|
@interface ThreadSafeReferenceTests : RLMTestCase
|
|
|
|
@end
|
|
|
|
@implementation ThreadSafeReferenceTests
|
|
|
|
/// Resolve a thread-safe reference confirming that you can't resolve it a second time.
|
|
- (id)assertResolve:(RLMRealm *)realm reference:(RLMThreadSafeReference *)reference {
|
|
XCTAssertFalse(reference.isInvalidated);
|
|
id object = [realm resolveThreadSafeReference:reference];
|
|
XCTAssertTrue(reference.isInvalidated);
|
|
RLMAssertThrowsWithReasonMatching([realm resolveThreadSafeReference:reference],
|
|
@"Can only resolve a thread safe reference once");
|
|
return object;
|
|
}
|
|
|
|
- (void)testInvalidThreadSafeReferenceConstruction {
|
|
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
|
|
configuration.cache = false;
|
|
RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
|
|
|
|
StringObject *stringObject = [[StringObject alloc] init];
|
|
ArrayPropertyObject *arrayParent = [[ArrayPropertyObject alloc] initWithValue:@[@"arrayObject", @[@[@"a"]], @[]]];
|
|
RLMArray *arrayObject = arrayParent.array;
|
|
|
|
RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:stringObject],
|
|
@"Cannot construct reference to unmanaged object");
|
|
RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:arrayObject],
|
|
@"Cannot construct reference to unmanaged object");
|
|
|
|
[realm beginWriteTransaction];
|
|
[realm addObject:stringObject];
|
|
[realm addObject:arrayParent];
|
|
arrayObject = arrayParent.array;
|
|
[realm deleteAllObjects];
|
|
[realm commitWriteTransaction];
|
|
|
|
RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:stringObject],
|
|
@"Cannot construct reference to invalidated object");
|
|
RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:arrayObject],
|
|
@"Cannot construct reference to invalidated object");
|
|
}
|
|
|
|
- (void)testInvalidThreadSafeReferenceUsage {
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
[realm beginWriteTransaction];
|
|
StringObject *stringObject = [StringObject createInDefaultRealmWithValue:@{@"stringCol": @"hello"}];
|
|
RLMAssertThrowsWithReasonMatching([RLMThreadSafeReference referenceWithThreadConfined:stringObject],
|
|
@"Cannot obtain thread safe reference during a write transaction");
|
|
[realm commitWriteTransaction];
|
|
|
|
RLMThreadSafeReference *ref1 = [RLMThreadSafeReference referenceWithThreadConfined:stringObject];
|
|
RLMThreadSafeReference *ref2 = [RLMThreadSafeReference referenceWithThreadConfined:stringObject];
|
|
RLMThreadSafeReference *ref3 = [RLMThreadSafeReference referenceWithThreadConfined:stringObject];
|
|
[self dispatchAsyncAndWait:^{
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
RLMAssertThrowsWithReasonMatching([[self realmWithTestPath] resolveThreadSafeReference:ref1],
|
|
@"Cannot resolve thread safe reference in Realm with different configuration than the source Realm");
|
|
[realm beginWriteTransaction];
|
|
RLMAssertThrowsWithReasonMatching([realm resolveThreadSafeReference:ref2],
|
|
@"Cannot resolve thread safe reference during a write transaction");
|
|
RLMAssertThrowsWithReasonMatching([realm resolveThreadSafeReference:ref2],
|
|
@"Can only resolve a thread safe reference once");
|
|
[realm cancelWriteTransaction];
|
|
RLMAssertThrowsWithReasonMatching([realm resolveThreadSafeReference:ref2],
|
|
@"Can only resolve a thread safe reference once");
|
|
// Assert that we can resolve a different reference to the same object.
|
|
XCTAssertEqualObjects([self assertResolve:realm reference:ref3][@"stringCol"], @"hello");
|
|
}];
|
|
}
|
|
|
|
- (void)testPassThreadSafeReferenceToDeletedObject {
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
IntObject *intObject = [[IntObject alloc] init];
|
|
[realm transactionWithBlock:^{
|
|
[realm addObject:intObject];
|
|
}];
|
|
|
|
RLMThreadSafeReference *ref1 = [RLMThreadSafeReference referenceWithThreadConfined:intObject];
|
|
RLMThreadSafeReference *ref2 = [RLMThreadSafeReference referenceWithThreadConfined:intObject];
|
|
XCTAssertEqual(0, intObject.intCol);
|
|
[realm transactionWithBlock:^{
|
|
[realm deleteObject:intObject];
|
|
}];
|
|
[self dispatchAsyncAndWait:^{
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
XCTAssertEqualObjects([self assertResolve:realm reference:ref1][@"intCol"], @0);
|
|
[realm refresh];
|
|
XCTAssertNil([self assertResolve:realm reference:ref2]);
|
|
}];
|
|
}
|
|
|
|
- (void)testPassThreadSafeReferencesToMultipleObjects {
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
StringObject *stringObject = [[StringObject alloc] init];
|
|
IntObject *intObject = [[IntObject alloc] init];
|
|
[realm transactionWithBlock:^{
|
|
[realm addObject:stringObject];
|
|
[realm addObject:intObject];
|
|
}];
|
|
|
|
RLMThreadSafeReference *stringObjectRef = [RLMThreadSafeReference referenceWithThreadConfined:stringObject];
|
|
RLMThreadSafeReference *intObjectRef = [RLMThreadSafeReference referenceWithThreadConfined:intObject];
|
|
XCTAssertEqualObjects(nil, stringObject.stringCol);
|
|
XCTAssertEqual(0, intObject.intCol);
|
|
[self dispatchAsyncAndWait:^{
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
StringObject *stringObject = [self assertResolve:realm reference:stringObjectRef];
|
|
IntObject *intObject = [self assertResolve:realm reference:intObjectRef];
|
|
|
|
[realm transactionWithBlock:^{
|
|
stringObject.stringCol = @"the meaning of life";
|
|
intObject.intCol = 42;
|
|
}];
|
|
}];
|
|
XCTAssertEqualObjects(nil, stringObject.stringCol);
|
|
XCTAssertEqual(0, intObject.intCol);
|
|
[realm refresh];
|
|
XCTAssertEqualObjects(@"the meaning of life", stringObject.stringCol);
|
|
XCTAssertEqual(42, intObject.intCol);
|
|
}
|
|
|
|
- (void)testPassThreadSafeReferenceToArray {
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
DogArrayObject *object = [[DogArrayObject alloc] init];
|
|
[realm transactionWithBlock:^{
|
|
[realm addObject:object];
|
|
DogObject *friday = [DogObject createInDefaultRealmWithValue:@{@"dogName": @"Friday", @"age": @15}];
|
|
[object.dogs addObject:friday];
|
|
}];
|
|
RLMThreadSafeReference *dogsArrayRef = [RLMThreadSafeReference referenceWithThreadConfined:object.dogs];
|
|
XCTAssertEqual(1ul, object.dogs.count);
|
|
XCTAssertEqualObjects(@"Friday", object.dogs[0].dogName);
|
|
[self dispatchAsyncAndWait:^{
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
RLMArray<DogObject *> *dogs = [self assertResolve:realm reference:dogsArrayRef];
|
|
XCTAssertEqual(1ul, dogs.count);
|
|
XCTAssertEqualObjects(@"Friday", dogs[0].dogName);
|
|
|
|
[realm transactionWithBlock:^{
|
|
[dogs removeAllObjects];
|
|
DogObject *cookie = [DogObject createInDefaultRealmWithValue:@{@"dogName": @"Cookie", @"age": @8}];
|
|
DogObject *breezy = [DogObject createInDefaultRealmWithValue:@{@"dogName": @"Breezy", @"age": @6}];
|
|
[dogs addObjects:@[cookie, breezy]];
|
|
}];
|
|
XCTAssertEqual(2ul, dogs.count);
|
|
XCTAssertEqualObjects(@"Cookie", dogs[0].dogName);
|
|
XCTAssertEqualObjects(@"Breezy", dogs[1].dogName);
|
|
}];
|
|
XCTAssertEqual(1ul, object.dogs.count);
|
|
XCTAssertEqualObjects(@"Friday", object.dogs[0].dogName);
|
|
[realm refresh];
|
|
XCTAssertEqual(2ul, object.dogs.count);
|
|
XCTAssertEqualObjects(@"Cookie", object.dogs[0].dogName);
|
|
XCTAssertEqualObjects(@"Breezy", object.dogs[1].dogName);
|
|
}
|
|
|
|
- (void)testPassThreadSafeReferenceToResults {
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
RLMResults<StringObject *> *allObjects = [StringObject allObjects];
|
|
RLMResults<StringObject *> *results = [[StringObject objectsWhere:@"stringCol != 'C'"]
|
|
sortedResultsUsingKeyPath:@"stringCol" ascending:NO];
|
|
RLMThreadSafeReference *resultsRef = [RLMThreadSafeReference referenceWithThreadConfined:results];
|
|
[realm transactionWithBlock:^{
|
|
[StringObject createInDefaultRealmWithValue:@[@"A"]];
|
|
[StringObject createInDefaultRealmWithValue:@[@"B"]];
|
|
[StringObject createInDefaultRealmWithValue:@[@"C"]];
|
|
[StringObject createInDefaultRealmWithValue:@[@"D"]];
|
|
}];
|
|
XCTAssertEqual(4ul, allObjects.count);
|
|
XCTAssertEqual(3ul, results.count);
|
|
XCTAssertEqualObjects(@"D", results[0].stringCol);
|
|
XCTAssertEqualObjects(@"B", results[1].stringCol);
|
|
XCTAssertEqualObjects(@"A", results[2].stringCol);
|
|
[self dispatchAsyncAndWait:^{
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
RLMResults<StringObject *> *results = [self assertResolve:realm reference:resultsRef];
|
|
RLMResults<StringObject *> *allObjects = [StringObject allObjects];
|
|
XCTAssertEqual(0ul, [StringObject allObjects].count);
|
|
XCTAssertEqual(0ul, results.count);
|
|
[realm refresh];
|
|
XCTAssertEqual(4ul, allObjects.count);
|
|
XCTAssertEqual(3ul, results.count);
|
|
XCTAssertEqualObjects(@"D", results[0].stringCol);
|
|
XCTAssertEqualObjects(@"B", results[1].stringCol);
|
|
XCTAssertEqualObjects(@"A", results[2].stringCol);
|
|
[realm transactionWithBlock:^{
|
|
[realm deleteObject:results[2]];
|
|
[realm deleteObject:results[0]];
|
|
[StringObject createInDefaultRealmWithValue:@[@"E"]];
|
|
}];
|
|
XCTAssertEqual(3ul, allObjects.count);
|
|
XCTAssertEqual(2ul, results.count);
|
|
XCTAssertEqualObjects(@"E", results[0].stringCol);
|
|
XCTAssertEqualObjects(@"B", results[1].stringCol);
|
|
}];
|
|
XCTAssertEqual(4ul, allObjects.count);
|
|
XCTAssertEqual(3ul, results.count);
|
|
XCTAssertEqualObjects(@"D", results[0].stringCol);
|
|
XCTAssertEqualObjects(@"B", results[1].stringCol);
|
|
XCTAssertEqualObjects(@"A", results[2].stringCol);
|
|
[realm refresh];
|
|
XCTAssertEqual(3ul, allObjects.count);
|
|
XCTAssertEqual(2ul, results.count);
|
|
XCTAssertEqualObjects(@"E", results[0].stringCol);
|
|
XCTAssertEqualObjects(@"B", results[1].stringCol);
|
|
}
|
|
|
|
- (void)testPassThreadSafeReferenceToLinkingObjects {
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
DogObject *dogA = [[DogObject alloc] initWithValue:@{@"dogName": @"Cookie", @"age": @10}];
|
|
DogObject *unaccessedDogB = [[DogObject alloc] initWithValue:@{@"dogName": @"Skipper", @"age": @7}];
|
|
// Ensures that an `RLMLinkingObjects` without cached results can be handed over
|
|
|
|
[realm transactionWithBlock:^{
|
|
[realm addObject:[[OwnerObject alloc] initWithValue:@{@"name": @"Andrea", @"dog": dogA}]];
|
|
[realm addObject:[[OwnerObject alloc] initWithValue:@{@"name": @"Mike", @"dog": unaccessedDogB}]];
|
|
}];
|
|
XCTAssertEqual(1ul, dogA.owners.count);
|
|
XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)dogA.owners[0]).name);
|
|
RLMThreadSafeReference *ownersARef = [RLMThreadSafeReference referenceWithThreadConfined:dogA.owners];
|
|
RLMThreadSafeReference *ownersBRef = [RLMThreadSafeReference referenceWithThreadConfined:unaccessedDogB.owners];
|
|
[self dispatchAsyncAndWait:^{
|
|
RLMRealm *realm = [RLMRealm defaultRealm];
|
|
RLMLinkingObjects<OwnerObject *> *ownersA = [self assertResolve:realm reference:ownersARef];
|
|
RLMLinkingObjects<OwnerObject *> *ownersB = [self assertResolve:realm reference:ownersBRef];
|
|
|
|
XCTAssertEqual(1ul, ownersA.count);
|
|
XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)ownersA[0]).name);
|
|
XCTAssertEqual(1ul, ownersB.count);
|
|
XCTAssertEqualObjects(@"Mike", ((OwnerObject *)ownersB[0]).name);
|
|
|
|
[realm transactionWithBlock:^{
|
|
// Swap dogs
|
|
OwnerObject *ownerA = ownersA[0];
|
|
OwnerObject *ownerB = ownersB[0];
|
|
DogObject *dogA = ownerA.dog;
|
|
DogObject *dogB = ownerB.dog;
|
|
ownerA.dog = dogB;
|
|
ownerB.dog = dogA;
|
|
}];
|
|
XCTAssertEqual(1ul, ownersA.count);
|
|
XCTAssertEqualObjects(@"Mike", ((OwnerObject *)ownersA[0]).name);
|
|
XCTAssertEqual(1ul, ownersB.count);
|
|
XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)ownersB[0]).name);
|
|
}];
|
|
XCTAssertEqual(1ul, dogA.owners.count);
|
|
XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)dogA.owners[0]).name);
|
|
XCTAssertEqual(1ul, unaccessedDogB.owners.count);
|
|
XCTAssertEqualObjects(@"Mike", ((OwnerObject *)unaccessedDogB.owners[0]).name);
|
|
[realm refresh];
|
|
XCTAssertEqual(1ul, dogA.owners.count);
|
|
XCTAssertEqualObjects(@"Mike", ((OwnerObject *)dogA.owners[0]).name);
|
|
XCTAssertEqual(1ul, unaccessedDogB.owners.count);
|
|
XCTAssertEqualObjects(@"Andrea", ((OwnerObject *)unaccessedDogB.owners[0]).name);
|
|
}
|
|
|
|
@end
|
|
|