123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2015 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.hpp"
- #import "RLMRealm_Private.hpp"
- #import "impl/realm_coordinator.hpp"
- #import <realm/string_data.hpp>
- #import <sys/resource.h>
- // A whole bunch of blocks don't use their RLMResults parameter
- #pragma clang diagnostic ignored "-Wunused-parameter"
- @interface ManualRefreshRealm : RLMRealm
- @end
- @implementation ManualRefreshRealm
- - (void)verifyNotificationsAreSupported:(__unused bool)isCollection {
- // The normal implementation of this will reject realms with automatic change notifications disabled
- }
- @end
- @interface AsyncTests : RLMTestCase
- @end
- @implementation AsyncTests
- - (void)createObject:(int)value {
- @autoreleasepool {
- RLMRealm *realm = [RLMRealm defaultRealm];
- [realm transactionWithBlock:^{
- [IntObject createInDefaultRealmWithValue:@[@(value)]];
- }];
- }
- }
- - (void)testInitialResultsAreDelivered {
- [self createObject:1];
- XCTestExpectation *expectation = [self expectationWithDescription:@""];
- auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertNil(e);
- XCTAssertEqualObjects(results.objectClassName, @"IntObject");
- XCTAssertEqual(results.count, 1U);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testNewResultsAreDeliveredAfterLocalCommit {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block NSUInteger expected = 0;
- auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual(results.count, expected++);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self createObject:1];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self createObject:2];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testNewResultsAreDeliveredAfterBackgroundCommit {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block NSUInteger expected = 0;
- auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual(results.count, expected++);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testResultsPerserveQuery {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block NSUInteger expected = 0;
- auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual(results.count, expected);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- ++expected;
- [self dispatchAsyncAndWait:^{
- [self createObject:1];
- [self createObject:-11];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testResultsPerserveSort {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block int expected = 0;
- auto token = [[IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:NO] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual([results.firstObject intCol], expected);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- expected = 1;
- [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:-1]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- expected = 2;
- [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testQueryingDeliveredQueryResults {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block NSUInteger expected = 0;
- auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual([results objectsWhere:@"intCol < 10"].count, expected++);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testQueryingDeliveredTableResults {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block NSUInteger expected = 0;
- auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual([results objectsWhere:@"intCol < 10"].count, expected++);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testQueryingDeliveredSortedResults {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block int expected = 0;
- auto token = [[IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:NO] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual([[results objectsWhere:@"intCol < 10"].firstObject intCol], expected++);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testSortingDeliveredResults {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block int expected = 0;
- auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertEqual([[results sortedResultsUsingKeyPath:@"intCol" ascending:NO].firstObject intCol], expected++);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testQueryingLinkList {
- RLMRealm *realm = [RLMRealm defaultRealm];
- [realm beginWriteTransaction];
- ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]];
- [realm commitWriteTransaction];
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- __block int expected = 0;
- auto token = [[array.intArray objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- XCTAssertNil(e);
- XCTAssertNotNil(results);
- XCTAssertEqual((int)results.count, expected);
- for (int i = 0; i < expected; ++i) {
- XCTAssertEqual([results[i] intCol], i + 1);
- }
- ++expected;
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- for (int i = 0; i < 3; ++i) {
- expectation = [self expectationWithDescription:@""];
- [self dispatchAsyncAndWait:^{
- RLMRealm *realm = [RLMRealm defaultRealm];
- ArrayPropertyObject *array = [[ArrayPropertyObject allObjectsInRealm:realm] firstObject];
- // Create two objects, one in the list and one not, to verify that the
- // LinkList is actually be used
- [realm beginWriteTransaction];
- [IntObject createInRealm:realm withValue:@[@(i + 1)]];
- [array.intArray addObject:[IntObject createInRealm:realm withValue:@[@(i + 1)]]];
- [realm commitWriteTransaction];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- [token invalidate];
- }
- - (RLMNotificationToken *)subscribeAndWaitForInitial:(id<RLMCollection>)query block:(void (^)(id))block {
- __block XCTestExpectation *exp = [self expectationWithDescription:@"wait for initial results"];
- auto token = [query addNotificationBlock:^(id results, RLMCollectionChange *change, NSError *e) {
- XCTAssertNotNil(results);
- XCTAssertNil(e);
- if (exp) {
- [exp fulfill];
- exp = nil;
- }
- else {
- block(results);
- }
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- return token;
- }
- - (void)testManualRefreshUsesAsyncResultsWhenPossible {
- __block bool called = false;
- auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
- called = true;
- }];
- RLMRealm *realm = [RLMRealm defaultRealm];
- realm.autorefresh = NO;
- [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{
- [self dispatchAsync:^{
- [RLMRealm.defaultRealm transactionWithBlock:^{
- [IntObject createInDefaultRealmWithValue:@[@0]];
- }];
- }];
- }];
- XCTAssertFalse(called);
- [realm refresh];
- XCTAssertTrue(called);
- [token invalidate];
- }
- - (void)testModifyingUnrelatedTableDoesNotTriggerResend {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- // will throw if called a second time
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
- RLMRealm *realm = [RLMRealm defaultRealm];
- [realm transactionWithBlock:^{
- [StringObject createInDefaultRealmWithValue:@[@""]];
- }];
- }];
- [token invalidate];
- }
- - (void)testStaleResultsAreDiscardedWhenThreadIsBlocked {
- XCTestExpectation *expectation = [self expectationWithDescription:@""];
- auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- // Will fail if this is called with the initial results
- XCTAssertEqual(1U, results.count);
- // Will fail if it's called twice
- [expectation fulfill];
- }];
- // Advance the version on a different thread, and then wait for async work
- // to complete for that new version
- [self dispatchAsyncAndWait:^{
- [RLMRealm.defaultRealm transactionWithBlock:^{
- [IntObject createInDefaultRealmWithValue:@[@0]];
- } error:nil];
- __block RLMNotificationToken *token;
- CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
- token = [IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {
- [token invalidate];
- token = nil;
- CFRunLoopStop(CFRunLoopGetCurrent());
- }];
- });
- CFRunLoopRun();
- }];
- // Only now let the main thread pick up the notifications
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- }
- - (void)testCommitInOneNotificationDoesNotCancelOtherNotifications {
- __block XCTestExpectation *exp1 = nil;
- __block XCTestExpectation *exp2 = nil;
- __block int firstBlockCalls = 0;
- __block int secondBlockCalls = 0;
- auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
- ++firstBlockCalls;
- if (firstBlockCalls == 2) {
- [exp1 fulfill];
- }
- else {
- [results.realm beginWriteTransaction];
- [IntObject createInDefaultRealmWithValue:@[@1]];
- [results.realm commitWriteTransaction];
- }
- }];
- auto token2 = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
- ++secondBlockCalls;
- if (secondBlockCalls == 2) {
- [exp2 fulfill];
- }
- }];
- exp1 = [self expectationWithDescription:@""];
- exp2 = [self expectationWithDescription:@""];
- [RLMRealm.defaultRealm transactionWithBlock:^{
- [IntObject createInDefaultRealmWithValue:@[@0]];
- } error:nil];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- XCTAssertEqual(2, firstBlockCalls);
- XCTAssertEqual(2, secondBlockCalls);
- [token invalidate];
- [token2 invalidate];
- }
- - (void)testErrorHandling {
- RLMRealm *realm = [RLMRealm defaultRealm];
- XCTestExpectation *exp = [self expectationWithDescription:@""];
- // Set the max open files to zero so that opening new files will fail
- rlimit oldrl;
- getrlimit(RLIMIT_NOFILE, &oldrl);
- rlimit rl = oldrl;
- rl.rlim_cur = 0;
- setrlimit(RLIMIT_NOFILE, &rl);
- // Will try to open another copy of the file for the pin SG
- __block bool called = false;
- auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTAssertNil(results);
- RLMValidateRealmError(error, RLMErrorFileAccess, @"Too many open files", nil);
- called = true;
- [exp fulfill];
- }];
- // Restore the old open file limit now so that we can make commits
- setrlimit(RLIMIT_NOFILE, &oldrl);
- // Block should still be called asynchronously
- XCTAssertFalse(called);
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- XCTAssertTrue(called);
- // Neither adding a new async query nor commiting a write transaction should
- // cause it to resend the error
- XCTestExpectation *exp2 = [self expectationWithDescription:@""];
- auto token2 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTAssertNil(results);
- RLMValidateRealmError(error, RLMErrorFileAccess, @"Too many open files", nil);
- [exp2 fulfill];
- }];
- [realm beginWriteTransaction];
- [IntObject createInDefaultRealmWithValue:@[@0]];
- [realm commitWriteTransaction];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [token invalidate];
- [token2 invalidate];
- }
- - (void)testRLMResultsInstanceIsReused {
- __weak __block RLMResults *prev;
- __block bool first = true;
- XCTestExpectation *expectation = [self expectationWithDescription:@""];
- auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- if (first) {
- prev = results;
- first = false;
- }
- else {
- XCTAssertEqual(prev, results); // deliberately not EqualObjects
- }
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- XCTAssertNotNil(prev);
- [token invalidate];
- }
- - (void)testCancellationTokenKeepsSubscriptionAlive {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- RLMNotificationToken *token;
- @autoreleasepool {
- token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *err) {
- XCTAssertNotNil(results);
- XCTAssertNil(err);
- [expectation fulfill];
- }];
- }
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- // at this point there are no strong references to anything other than the
- // token, so verify that things haven't magically gone away
- // this would be better as a multi-process tests with the commit done
- // from a different process
- expectation = [self expectationWithDescription:@""];
- @autoreleasepool {
- [RLMRealm.defaultRealm transactionWithBlock:^{
- [IntObject createInDefaultRealmWithValue:@[@0]];
- }];
- }
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- - (void)testCancellationTokenPreventsOpeningRealmWithMismatchedConfig {
- __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
- RLMNotificationToken *token;
- @autoreleasepool {
- token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *err) {
- XCTAssertNotNil(results);
- XCTAssertNil(err);
- [expectation fulfill];
- }];
- }
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.readOnly = true;
- @autoreleasepool {
- XCTAssertThrows([RLMRealm realmWithConfiguration:config error:nil]);
- }
- [token invalidate];
- XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
- }
- - (void)testAddAndRemoveQueries {
- RLMRealm *realm = [RLMRealm defaultRealm];
- @autoreleasepool {
- RLMResults *results = IntObject.allObjects;
- [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) {
- XCTFail(@"results delivered after removal");
- }] invalidate];
- // Readd same results at same version
- [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) {
- XCTFail(@"results delivered after removal");
- }] invalidate];
- // Add different results at same version
- [[self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *r) {
- XCTFail(@"results delivered after removal");
- }] invalidate];
- [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
- [RLMRealm.defaultRealm transactionWithBlock:^{ }];
- }];
- // Readd at later version
- [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) {
- XCTFail(@"results delivered after removal");
- }] invalidate];
- // Add different results at later version
- [[self subscribeAndWaitForInitial:[IntObject allObjectsInRealm:realm] block:^(RLMResults *r) {
- XCTFail(@"results delivered after removal");
- }] invalidate];
- }
- // Add different results after all of the previous async queries have been
- // removed entirely
- [[self subscribeAndWaitForInitial:[IntObject allObjectsInRealm:realm] block:^(RLMResults *r) {
- XCTFail(@"results delivered after removal");
- }] invalidate];
- }
- - (void)testMultipleSourceVersionsForAsyncQueries {
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.cache = false;
- // Create ten RLMRealm instances, each with a different read version
- RLMRealm *realms[10];
- for (int i = 0; i < 10; ++i) {
- RLMRealm *realm = realms[i] = [RLMRealm realmWithConfiguration:config error:nil];
- [realm transactionWithBlock:^{
- [IntObject createInRealm:realm withValue:@[@(i)]];
- }];
- }
- // Each Realm should see a different number of objects as they're on different versions
- for (NSUInteger i = 0; i < 10; ++i) {
- XCTAssertEqual(i + 1, [IntObject allObjectsInRealm:realms[i]].count);
- }
- RLMNotificationToken *tokens[10];
- // asyncify them in reverse order so that the version pin has to go backwards
- for (int i = 9; i >= 0; --i) {
- XCTestExpectation *exp = [self expectationWithDescription:@(i).stringValue];
- tokens[i] = [[IntObject allObjectsInRealm:realms[i]] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTAssertEqual(10U, results.count);
- XCTAssertNil(error);
- [exp fulfill];
- }];
- }
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- for (int i = 0; i < 10; ++i) {
- [tokens[i] invalidate];
- }
- }
- - (void)testMultipleSourceVersionsWithNotifiersRemovedBeforeRunning {
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.cache = false;
- config.config.automatic_change_notifications = false;
- // Create ten RLMRealm instances, each with a different read version
- RLMRealm *realms[10];
- for (int i = 0; i < 10; ++i) {
- RLMRealm *realm = realms[i] = [ManualRefreshRealm realmWithConfiguration:config error:nil];
- [realm transactionWithBlock:^{
- [IntObject createInRealm:realm withValue:@[@(i)]];
- }];
- }
- __block int calls = 0;
- RLMNotificationToken *tokens[10];
- @autoreleasepool {
- for (int i = 0; i < 10; ++i) {
- tokens[i] = [[IntObject allObjectsInRealm:realms[i]]
- addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {
- ++calls;
- }];
- }
- // Each Realm should see a different number of objects as they're on different versions
- for (NSUInteger i = 0; i < 10; ++i) {
- XCTAssertEqual(i + 1, [IntObject allObjectsInRealm:realms[i]].count);
- }
- // remove all but the last two so that the version pin is for a version
- // that doesn't have a notifier anymore
- for (int i = 0; i < 7; ++i) {
- [tokens[i] invalidate];
- }
- }
- // Let the background job run now
- auto coord = realm::_impl::RealmCoordinator::get_existing_coordinator(config.config.path);
- coord->on_change();
- for (int i = 7; i < 10; ++i) {
- realms[i]->_realm->notify();
- XCTAssertEqual(calls, i - 6);
- }
- for (int i = 7; i < 10; ++i) {
- [tokens[i] invalidate];
- }
- }
- - (void)testMultipleCallbacksForOneQuery {
- RLMResults *results = IntObject.allObjects;
- __block int calls1 = 0;
- auto token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
- ++calls1;
- }];
- XCTAssertEqual(calls1, 0);
- __block int calls2 = 0;
- auto token2 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
- ++calls2;
- }];
- XCTAssertEqual(calls1, 0);
- XCTAssertEqual(calls2, 0);
- [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
- [self createObject:0];
- }];
- XCTAssertEqual(calls1, 1);
- XCTAssertEqual(calls2, 1);
- [token1 invalidate];
- [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
- [self createObject:0];
- }];
- XCTAssertEqual(calls1, 1);
- XCTAssertEqual(calls2, 2);
- [token2 invalidate];
- [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
- [self createObject:0];
- }];
- XCTAssertEqual(calls1, 1);
- XCTAssertEqual(calls2, 2);
- }
- - (void)testRemovingBlockFromWithinNotificationBlock {
- RLMResults *results = IntObject.allObjects;
- __block int calls = 0;
- __block RLMNotificationToken *token1, *token2;
- token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
- [token1 invalidate];
- ++calls;
- }];
- token2 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
- [token2 invalidate];
- ++calls;
- }];
- [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
- [self createObject:0];
- }];
- [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
- [self createObject:0];
- }];
- XCTAssertEqual(calls, 2);
- }
- - (void)testAddingBlockFromWithinNotificationBlock {
- RLMResults *results = IntObject.allObjects;
- __block int calls = 0;
- __block RLMNotificationToken *token1, *token2;
- token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
- if (++calls == 1) {
- token2 = [results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- ++calls;
- }];
- }
- }];
- // Triggers one call on each block. Nested call is deferred until next refresh.
- [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
- [self createObject:0];
- }];
- XCTAssertEqual(calls, 1);
- [results.realm refresh];
- XCTAssertEqual(calls, 2);
- // Triggers one call on each block
- [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
- [self createObject:0];
- }];
- XCTAssertEqual(calls, 4);
- [token1 invalidate];
- [token2 invalidate];
- }
- - (void)testAddingNewQueryWithinNotificationBlock {
- RLMResults *results1 = IntObject.allObjects;
- RLMResults *results2 = IntObject.allObjects;
- __block int calls = 0;
- __block RLMNotificationToken *token1, *token2;
- token1 = [self subscribeAndWaitForInitial:results1 block:^(RLMResults *results) {
- ++calls;
- if (calls == 1) {
- CFRunLoopStop(CFRunLoopGetCurrent());
- token2 = [results2 addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- CFRunLoopStop(CFRunLoopGetCurrent());
- ++calls;
- }];
- }
- }];
- // Triggers one call on outer block, but inner does not get a chance to deliver
- [self dispatchAsync:^{ [self createObject:0]; }];
- CFRunLoopRun();
- XCTAssertEqual(calls, 1);
- // Pick up the initial run of the inner block
- CFRunLoopRun();
- assert(calls == 2);
- XCTAssertEqual(calls, 2);
- // Triggers a call on each block
- [self dispatchAsync:^{ [self createObject:0]; }];
- CFRunLoopRun();
- XCTAssertEqual(calls, 4);
- [token1 invalidate];
- [token2 invalidate];
- }
- - (void)testAddingNewQueryWithinRealmNotificationBlock {
- __block RLMNotificationToken *queryToken;
- __block XCTestExpectation *exp;
- auto realmToken = [RLMRealm.defaultRealm addNotificationBlock:^(RLMNotification notification, RLMRealm *realm) {
- CFRunLoopStop(CFRunLoopGetCurrent());
- exp = [self expectationWithDescription:@"query notification"];
- queryToken = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
- [exp fulfill];
- }];
- }];
- // Make a background commit to trigger a Realm notification
- [self dispatchAsync:^{ [RLMRealm.defaultRealm transactionWithBlock:^{}]; }];
- // Wait for the notification
- CFRunLoopRun();
- [realmToken invalidate];
- // Wait for the initial async query results created within the notification
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [queryToken invalidate];
- }
- - (void)testBlockedThreadWithNotificationsDoesNotPreventDeliveryOnOtherThreads {
- dispatch_semaphore_t sema = dispatch_semaphore_create(0);
- dispatch_semaphore_t sema2 = dispatch_semaphore_create(0);
- [self dispatchAsync:^{
- // Add a notification block on a background thread, run the runloop
- // until the initial results are ready, and then block the thread without
- // running the runloop until the main thread is done testing things
- __block RLMNotificationToken *token;
- CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
- token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- dispatch_semaphore_signal(sema);
- CFRunLoopStop(CFRunLoopGetCurrent());
- dispatch_semaphore_wait(sema2, DISPATCH_TIME_FOREVER);
- }];
- });
- CFRunLoopRun();
- [token invalidate];
- }];
- dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
- __block int calls = 0;
- auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
- ++calls;
- }];
- XCTAssertEqual(calls, 0);
- [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
- [self createObject:0];
- }];
- XCTAssertEqual(calls, 1);
- [token invalidate];
- dispatch_semaphore_signal(sema2);
- }
- - (void)testAddNotificationBlockFromWrongThread {
- RLMResults *results = [IntObject allObjects];
- [self dispatchAsyncAndWait:^{
- CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
- XCTAssertThrows([results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTFail(@"should not be called");
- }]);
- CFRunLoopStop(CFRunLoopGetCurrent());
- });
- CFRunLoopRun();
- }];
- }
- - (void)testRemoveNotificationBlockFromWrongThread {
- // Unlike adding this is allowed, because it can happen due to capturing
- // tokens in blocks and users are very confused by errors from deallocation
- // on the wrong thread
- RLMResults *results = [IntObject allObjects];
- auto token = [results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTFail(@"should not be called");
- }];
- [self dispatchAsyncAndWait:^{
- [token invalidate];
- }];
- }
- - (void)testSimultaneouslyRemoveCallbacksFromCallbacksForOtherResults {
- dispatch_semaphore_t sema1 = dispatch_semaphore_create(0);
- dispatch_semaphore_t sema2 = dispatch_semaphore_create(0);
- __block RLMNotificationToken *token1, *token2;
- [self dispatchAsync:^{
- CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
- __block bool first = true;
- token1 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTAssertTrue(first);
- first = false;
- dispatch_semaphore_signal(sema1);
- dispatch_semaphore_wait(sema2, DISPATCH_TIME_FOREVER);
- [token2 invalidate];
- CFRunLoopStop(CFRunLoopGetCurrent());
- }];
- });
- CFRunLoopRun();
- }];
- CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
- __block bool first = true;
- token2 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTAssertTrue(first);
- first = false;
- dispatch_semaphore_signal(sema2);
- dispatch_semaphore_wait(sema1, DISPATCH_TIME_FOREVER);
- [token1 invalidate];
- CFRunLoopStop(CFRunLoopGetCurrent());
- }];
- });
- CFRunLoopRun();
- }
- - (void)testAsyncNotSupportedForReadOnlyRealms {
- @autoreleasepool { [RLMRealm defaultRealm]; }
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.readOnly = true;
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- XCTAssertThrows([[IntObject allObjectsInRealm:realm] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTFail(@"should not be called");
- }]);
- }
- - (void)testAsyncNotSupportedInWriteTransactions {
- [RLMRealm.defaultRealm transactionWithBlock:^{
- XCTAssertThrows([IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
- XCTFail(@"should not be called");
- }]);
- }];
- }
- - (void)testTransactionsAfterDeletingLinkView {
- RLMRealm *realm = [RLMRealm defaultRealm];
- [realm beginWriteTransaction];
- IntObject *io = [IntObject createInRealm:realm withValue:@[@5]];
- ArrayPropertyObject *apo = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[io]]];
- [realm commitWriteTransaction];
- RLMNotificationToken *token1 = [self subscribeAndWaitForInitial:apo.intArray block:^(RLMArray *array) {
- XCTAssertTrue(array.invalidated);
- }];
- RLMResults *asResults = [apo.intArray objectsWhere:@"intCol = 5"];
- RLMNotificationToken *token2 = [self subscribeAndWaitForInitial:asResults block:^(RLMResults *results) {
- XCTAssertEqual(results.count, 0U);
- }];
- // Delete the object containing the RLMArray with notifiers
- [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
- RLMRealm *realm = [RLMRealm defaultRealm];
- [realm transactionWithBlock:^{
- [realm deleteObject:[ArrayPropertyObject allObjectsInRealm:realm].firstObject];
- }];
- }];
- // Perform another transaction while the notifiers are still alive as
- // transactions deleting the RLMArray and transactions with the RLMArray
- // already deleted hit different code paths
- [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
- RLMRealm *realm = [RLMRealm defaultRealm];
- [realm transactionWithBlock:^{
- [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]];
- }];
- }];
- [token1 invalidate];
- [token2 invalidate];
- }
- - (void)testInitialResultDiscardsChanges {
- auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *) {
- XCTAssertEqual(results.count, 1U);
- XCTAssertNil(changes);
- CFRunLoopStop(CFRunLoopGetCurrent());
- }];
- // Make a write on a background thread, and then wait for the notification
- // for that write to be delivered to ensure that the notification we get on
- // the main thread actually would include changes
- dispatch_semaphore_t sema = dispatch_semaphore_create(0);
- [self dispatchAsync:^{
- CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
- auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *) {
- if (changes) {
- dispatch_semaphore_signal(sema);
- CFRunLoopStop(CFRunLoopGetCurrent());
- }
- }];
- [RLMRealm.defaultRealm transactionWithBlock:^{
- [IntObject createInDefaultRealmWithValue:@[@0]];
- }];
- CFRunLoopRun();
- [token invalidate];
- CFRunLoopStop(CFRunLoopGetCurrent());
- });
- CFRunLoopRun();
- }];
- dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
- CFRunLoopRun();
- [token invalidate];
- }
- @end
|