AsyncTests.mm 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2015 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMTestCase.h"
  19. #import "RLMRealmConfiguration_Private.hpp"
  20. #import "RLMRealm_Private.hpp"
  21. #import "impl/realm_coordinator.hpp"
  22. #import <realm/string_data.hpp>
  23. #import <sys/resource.h>
  24. // A whole bunch of blocks don't use their RLMResults parameter
  25. #pragma clang diagnostic ignored "-Wunused-parameter"
  26. @interface ManualRefreshRealm : RLMRealm
  27. @end
  28. @implementation ManualRefreshRealm
  29. - (void)verifyNotificationsAreSupported:(__unused bool)isCollection {
  30. // The normal implementation of this will reject realms with automatic change notifications disabled
  31. }
  32. @end
  33. @interface AsyncTests : RLMTestCase
  34. @end
  35. @implementation AsyncTests
  36. - (void)createObject:(int)value {
  37. @autoreleasepool {
  38. RLMRealm *realm = [RLMRealm defaultRealm];
  39. [realm transactionWithBlock:^{
  40. [IntObject createInDefaultRealmWithValue:@[@(value)]];
  41. }];
  42. }
  43. }
  44. - (void)testInitialResultsAreDelivered {
  45. [self createObject:1];
  46. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  47. auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  48. XCTAssertNil(e);
  49. XCTAssertEqualObjects(results.objectClassName, @"IntObject");
  50. XCTAssertEqual(results.count, 1U);
  51. [expectation fulfill];
  52. }];
  53. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  54. [token invalidate];
  55. }
  56. - (void)testNewResultsAreDeliveredAfterLocalCommit {
  57. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  58. __block NSUInteger expected = 0;
  59. auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  60. XCTAssertEqual(results.count, expected++);
  61. [expectation fulfill];
  62. }];
  63. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  64. expectation = [self expectationWithDescription:@""];
  65. [self createObject:1];
  66. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  67. expectation = [self expectationWithDescription:@""];
  68. [self createObject:2];
  69. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  70. [token invalidate];
  71. }
  72. - (void)testNewResultsAreDeliveredAfterBackgroundCommit {
  73. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  74. __block NSUInteger expected = 0;
  75. auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  76. XCTAssertEqual(results.count, expected++);
  77. [expectation fulfill];
  78. }];
  79. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  80. expectation = [self expectationWithDescription:@""];
  81. [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
  82. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  83. expectation = [self expectationWithDescription:@""];
  84. [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
  85. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  86. [token invalidate];
  87. }
  88. - (void)testResultsPerserveQuery {
  89. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  90. __block NSUInteger expected = 0;
  91. auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  92. XCTAssertEqual(results.count, expected);
  93. [expectation fulfill];
  94. }];
  95. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  96. expectation = [self expectationWithDescription:@""];
  97. ++expected;
  98. [self dispatchAsyncAndWait:^{
  99. [self createObject:1];
  100. [self createObject:-11];
  101. }];
  102. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  103. [token invalidate];
  104. }
  105. - (void)testResultsPerserveSort {
  106. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  107. __block int expected = 0;
  108. auto token = [[IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:NO] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  109. XCTAssertEqual([results.firstObject intCol], expected);
  110. [expectation fulfill];
  111. }];
  112. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  113. expectation = [self expectationWithDescription:@""];
  114. expected = 1;
  115. [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
  116. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  117. expectation = [self expectationWithDescription:@""];
  118. [self dispatchAsyncAndWait:^{ [self createObject:-1]; }];
  119. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  120. expectation = [self expectationWithDescription:@""];
  121. expected = 2;
  122. [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
  123. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  124. [token invalidate];
  125. }
  126. - (void)testQueryingDeliveredQueryResults {
  127. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  128. __block NSUInteger expected = 0;
  129. auto token = [[IntObject objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  130. XCTAssertEqual([results objectsWhere:@"intCol < 10"].count, expected++);
  131. [expectation fulfill];
  132. }];
  133. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  134. expectation = [self expectationWithDescription:@""];
  135. [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
  136. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  137. expectation = [self expectationWithDescription:@""];
  138. [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
  139. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  140. [token invalidate];
  141. }
  142. - (void)testQueryingDeliveredTableResults {
  143. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  144. __block NSUInteger expected = 0;
  145. auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  146. XCTAssertEqual([results objectsWhere:@"intCol < 10"].count, expected++);
  147. [expectation fulfill];
  148. }];
  149. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  150. expectation = [self expectationWithDescription:@""];
  151. [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
  152. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  153. expectation = [self expectationWithDescription:@""];
  154. [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
  155. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  156. [token invalidate];
  157. }
  158. - (void)testQueryingDeliveredSortedResults {
  159. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  160. __block int expected = 0;
  161. auto token = [[IntObject.allObjects sortedResultsUsingKeyPath:@"intCol" ascending:NO] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  162. XCTAssertEqual([[results objectsWhere:@"intCol < 10"].firstObject intCol], expected++);
  163. [expectation fulfill];
  164. }];
  165. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  166. expectation = [self expectationWithDescription:@""];
  167. [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
  168. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  169. expectation = [self expectationWithDescription:@""];
  170. [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
  171. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  172. [token invalidate];
  173. }
  174. - (void)testSortingDeliveredResults {
  175. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  176. __block int expected = 0;
  177. auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  178. XCTAssertEqual([[results sortedResultsUsingKeyPath:@"intCol" ascending:NO].firstObject intCol], expected++);
  179. [expectation fulfill];
  180. }];
  181. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  182. expectation = [self expectationWithDescription:@""];
  183. [self dispatchAsyncAndWait:^{ [self createObject:1]; }];
  184. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  185. expectation = [self expectationWithDescription:@""];
  186. [self dispatchAsyncAndWait:^{ [self createObject:2]; }];
  187. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  188. [token invalidate];
  189. }
  190. - (void)testQueryingLinkList {
  191. RLMRealm *realm = [RLMRealm defaultRealm];
  192. [realm beginWriteTransaction];
  193. ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]];
  194. [realm commitWriteTransaction];
  195. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  196. __block int expected = 0;
  197. auto token = [[array.intArray objectsWhere:@"intCol > 0"] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  198. XCTAssertNil(e);
  199. XCTAssertNotNil(results);
  200. XCTAssertEqual((int)results.count, expected);
  201. for (int i = 0; i < expected; ++i) {
  202. XCTAssertEqual([results[i] intCol], i + 1);
  203. }
  204. ++expected;
  205. [expectation fulfill];
  206. }];
  207. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  208. for (int i = 0; i < 3; ++i) {
  209. expectation = [self expectationWithDescription:@""];
  210. [self dispatchAsyncAndWait:^{
  211. RLMRealm *realm = [RLMRealm defaultRealm];
  212. ArrayPropertyObject *array = [[ArrayPropertyObject allObjectsInRealm:realm] firstObject];
  213. // Create two objects, one in the list and one not, to verify that the
  214. // LinkList is actually be used
  215. [realm beginWriteTransaction];
  216. [IntObject createInRealm:realm withValue:@[@(i + 1)]];
  217. [array.intArray addObject:[IntObject createInRealm:realm withValue:@[@(i + 1)]]];
  218. [realm commitWriteTransaction];
  219. }];
  220. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  221. }
  222. [token invalidate];
  223. }
  224. - (RLMNotificationToken *)subscribeAndWaitForInitial:(id<RLMCollection>)query block:(void (^)(id))block {
  225. __block XCTestExpectation *exp = [self expectationWithDescription:@"wait for initial results"];
  226. auto token = [query addNotificationBlock:^(id results, RLMCollectionChange *change, NSError *e) {
  227. XCTAssertNotNil(results);
  228. XCTAssertNil(e);
  229. if (exp) {
  230. [exp fulfill];
  231. exp = nil;
  232. }
  233. else {
  234. block(results);
  235. }
  236. }];
  237. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  238. return token;
  239. }
  240. - (void)testManualRefreshUsesAsyncResultsWhenPossible {
  241. __block bool called = false;
  242. auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
  243. called = true;
  244. }];
  245. RLMRealm *realm = [RLMRealm defaultRealm];
  246. realm.autorefresh = NO;
  247. [self waitForNotification:RLMRealmRefreshRequiredNotification realm:realm block:^{
  248. [self dispatchAsync:^{
  249. [RLMRealm.defaultRealm transactionWithBlock:^{
  250. [IntObject createInDefaultRealmWithValue:@[@0]];
  251. }];
  252. }];
  253. }];
  254. XCTAssertFalse(called);
  255. [realm refresh];
  256. XCTAssertTrue(called);
  257. [token invalidate];
  258. }
  259. - (void)testModifyingUnrelatedTableDoesNotTriggerResend {
  260. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  261. auto token = [[IntObject allObjects] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  262. // will throw if called a second time
  263. [expectation fulfill];
  264. }];
  265. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  266. [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
  267. RLMRealm *realm = [RLMRealm defaultRealm];
  268. [realm transactionWithBlock:^{
  269. [StringObject createInDefaultRealmWithValue:@[@""]];
  270. }];
  271. }];
  272. [token invalidate];
  273. }
  274. - (void)testStaleResultsAreDiscardedWhenThreadIsBlocked {
  275. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  276. auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  277. // Will fail if this is called with the initial results
  278. XCTAssertEqual(1U, results.count);
  279. // Will fail if it's called twice
  280. [expectation fulfill];
  281. }];
  282. // Advance the version on a different thread, and then wait for async work
  283. // to complete for that new version
  284. [self dispatchAsyncAndWait:^{
  285. [RLMRealm.defaultRealm transactionWithBlock:^{
  286. [IntObject createInDefaultRealmWithValue:@[@0]];
  287. } error:nil];
  288. __block RLMNotificationToken *token;
  289. CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
  290. token = [IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {
  291. [token invalidate];
  292. token = nil;
  293. CFRunLoopStop(CFRunLoopGetCurrent());
  294. }];
  295. });
  296. CFRunLoopRun();
  297. }];
  298. // Only now let the main thread pick up the notifications
  299. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  300. [token invalidate];
  301. }
  302. - (void)testCommitInOneNotificationDoesNotCancelOtherNotifications {
  303. __block XCTestExpectation *exp1 = nil;
  304. __block XCTestExpectation *exp2 = nil;
  305. __block int firstBlockCalls = 0;
  306. __block int secondBlockCalls = 0;
  307. auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
  308. ++firstBlockCalls;
  309. if (firstBlockCalls == 2) {
  310. [exp1 fulfill];
  311. }
  312. else {
  313. [results.realm beginWriteTransaction];
  314. [IntObject createInDefaultRealmWithValue:@[@1]];
  315. [results.realm commitWriteTransaction];
  316. }
  317. }];
  318. auto token2 = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
  319. ++secondBlockCalls;
  320. if (secondBlockCalls == 2) {
  321. [exp2 fulfill];
  322. }
  323. }];
  324. exp1 = [self expectationWithDescription:@""];
  325. exp2 = [self expectationWithDescription:@""];
  326. [RLMRealm.defaultRealm transactionWithBlock:^{
  327. [IntObject createInDefaultRealmWithValue:@[@0]];
  328. } error:nil];
  329. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  330. XCTAssertEqual(2, firstBlockCalls);
  331. XCTAssertEqual(2, secondBlockCalls);
  332. [token invalidate];
  333. [token2 invalidate];
  334. }
  335. - (void)testErrorHandling {
  336. RLMRealm *realm = [RLMRealm defaultRealm];
  337. XCTestExpectation *exp = [self expectationWithDescription:@""];
  338. // Set the max open files to zero so that opening new files will fail
  339. rlimit oldrl;
  340. getrlimit(RLIMIT_NOFILE, &oldrl);
  341. rlimit rl = oldrl;
  342. rl.rlim_cur = 0;
  343. setrlimit(RLIMIT_NOFILE, &rl);
  344. // Will try to open another copy of the file for the pin SG
  345. __block bool called = false;
  346. auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  347. XCTAssertNil(results);
  348. RLMValidateRealmError(error, RLMErrorFileAccess, @"Too many open files", nil);
  349. called = true;
  350. [exp fulfill];
  351. }];
  352. // Restore the old open file limit now so that we can make commits
  353. setrlimit(RLIMIT_NOFILE, &oldrl);
  354. // Block should still be called asynchronously
  355. XCTAssertFalse(called);
  356. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  357. XCTAssertTrue(called);
  358. // Neither adding a new async query nor commiting a write transaction should
  359. // cause it to resend the error
  360. XCTestExpectation *exp2 = [self expectationWithDescription:@""];
  361. auto token2 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  362. XCTAssertNil(results);
  363. RLMValidateRealmError(error, RLMErrorFileAccess, @"Too many open files", nil);
  364. [exp2 fulfill];
  365. }];
  366. [realm beginWriteTransaction];
  367. [IntObject createInDefaultRealmWithValue:@[@0]];
  368. [realm commitWriteTransaction];
  369. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  370. [token invalidate];
  371. [token2 invalidate];
  372. }
  373. - (void)testRLMResultsInstanceIsReused {
  374. __weak __block RLMResults *prev;
  375. __block bool first = true;
  376. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  377. auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  378. if (first) {
  379. prev = results;
  380. first = false;
  381. }
  382. else {
  383. XCTAssertEqual(prev, results); // deliberately not EqualObjects
  384. }
  385. [expectation fulfill];
  386. }];
  387. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  388. XCTAssertNotNil(prev);
  389. [token invalidate];
  390. }
  391. - (void)testCancellationTokenKeepsSubscriptionAlive {
  392. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  393. RLMNotificationToken *token;
  394. @autoreleasepool {
  395. token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *err) {
  396. XCTAssertNotNil(results);
  397. XCTAssertNil(err);
  398. [expectation fulfill];
  399. }];
  400. }
  401. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  402. // at this point there are no strong references to anything other than the
  403. // token, so verify that things haven't magically gone away
  404. // this would be better as a multi-process tests with the commit done
  405. // from a different process
  406. expectation = [self expectationWithDescription:@""];
  407. @autoreleasepool {
  408. [RLMRealm.defaultRealm transactionWithBlock:^{
  409. [IntObject createInDefaultRealmWithValue:@[@0]];
  410. }];
  411. }
  412. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  413. }
  414. - (void)testCancellationTokenPreventsOpeningRealmWithMismatchedConfig {
  415. __block XCTestExpectation *expectation = [self expectationWithDescription:@""];
  416. RLMNotificationToken *token;
  417. @autoreleasepool {
  418. token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *err) {
  419. XCTAssertNotNil(results);
  420. XCTAssertNil(err);
  421. [expectation fulfill];
  422. }];
  423. }
  424. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  425. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  426. config.readOnly = true;
  427. @autoreleasepool {
  428. XCTAssertThrows([RLMRealm realmWithConfiguration:config error:nil]);
  429. }
  430. [token invalidate];
  431. XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
  432. }
  433. - (void)testAddAndRemoveQueries {
  434. RLMRealm *realm = [RLMRealm defaultRealm];
  435. @autoreleasepool {
  436. RLMResults *results = IntObject.allObjects;
  437. [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) {
  438. XCTFail(@"results delivered after removal");
  439. }] invalidate];
  440. // Readd same results at same version
  441. [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) {
  442. XCTFail(@"results delivered after removal");
  443. }] invalidate];
  444. // Add different results at same version
  445. [[self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *r) {
  446. XCTFail(@"results delivered after removal");
  447. }] invalidate];
  448. [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
  449. [RLMRealm.defaultRealm transactionWithBlock:^{ }];
  450. }];
  451. // Readd at later version
  452. [[self subscribeAndWaitForInitial:results block:^(RLMResults *r) {
  453. XCTFail(@"results delivered after removal");
  454. }] invalidate];
  455. // Add different results at later version
  456. [[self subscribeAndWaitForInitial:[IntObject allObjectsInRealm:realm] block:^(RLMResults *r) {
  457. XCTFail(@"results delivered after removal");
  458. }] invalidate];
  459. }
  460. // Add different results after all of the previous async queries have been
  461. // removed entirely
  462. [[self subscribeAndWaitForInitial:[IntObject allObjectsInRealm:realm] block:^(RLMResults *r) {
  463. XCTFail(@"results delivered after removal");
  464. }] invalidate];
  465. }
  466. - (void)testMultipleSourceVersionsForAsyncQueries {
  467. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  468. config.cache = false;
  469. // Create ten RLMRealm instances, each with a different read version
  470. RLMRealm *realms[10];
  471. for (int i = 0; i < 10; ++i) {
  472. RLMRealm *realm = realms[i] = [RLMRealm realmWithConfiguration:config error:nil];
  473. [realm transactionWithBlock:^{
  474. [IntObject createInRealm:realm withValue:@[@(i)]];
  475. }];
  476. }
  477. // Each Realm should see a different number of objects as they're on different versions
  478. for (NSUInteger i = 0; i < 10; ++i) {
  479. XCTAssertEqual(i + 1, [IntObject allObjectsInRealm:realms[i]].count);
  480. }
  481. RLMNotificationToken *tokens[10];
  482. // asyncify them in reverse order so that the version pin has to go backwards
  483. for (int i = 9; i >= 0; --i) {
  484. XCTestExpectation *exp = [self expectationWithDescription:@(i).stringValue];
  485. tokens[i] = [[IntObject allObjectsInRealm:realms[i]] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  486. XCTAssertEqual(10U, results.count);
  487. XCTAssertNil(error);
  488. [exp fulfill];
  489. }];
  490. }
  491. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  492. for (int i = 0; i < 10; ++i) {
  493. [tokens[i] invalidate];
  494. }
  495. }
  496. - (void)testMultipleSourceVersionsWithNotifiersRemovedBeforeRunning {
  497. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  498. config.cache = false;
  499. config.config.automatic_change_notifications = false;
  500. // Create ten RLMRealm instances, each with a different read version
  501. RLMRealm *realms[10];
  502. for (int i = 0; i < 10; ++i) {
  503. RLMRealm *realm = realms[i] = [ManualRefreshRealm realmWithConfiguration:config error:nil];
  504. [realm transactionWithBlock:^{
  505. [IntObject createInRealm:realm withValue:@[@(i)]];
  506. }];
  507. }
  508. __block int calls = 0;
  509. RLMNotificationToken *tokens[10];
  510. @autoreleasepool {
  511. for (int i = 0; i < 10; ++i) {
  512. tokens[i] = [[IntObject allObjectsInRealm:realms[i]]
  513. addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {
  514. ++calls;
  515. }];
  516. }
  517. // Each Realm should see a different number of objects as they're on different versions
  518. for (NSUInteger i = 0; i < 10; ++i) {
  519. XCTAssertEqual(i + 1, [IntObject allObjectsInRealm:realms[i]].count);
  520. }
  521. // remove all but the last two so that the version pin is for a version
  522. // that doesn't have a notifier anymore
  523. for (int i = 0; i < 7; ++i) {
  524. [tokens[i] invalidate];
  525. }
  526. }
  527. // Let the background job run now
  528. auto coord = realm::_impl::RealmCoordinator::get_existing_coordinator(config.config.path);
  529. coord->on_change();
  530. for (int i = 7; i < 10; ++i) {
  531. realms[i]->_realm->notify();
  532. XCTAssertEqual(calls, i - 6);
  533. }
  534. for (int i = 7; i < 10; ++i) {
  535. [tokens[i] invalidate];
  536. }
  537. }
  538. - (void)testMultipleCallbacksForOneQuery {
  539. RLMResults *results = IntObject.allObjects;
  540. __block int calls1 = 0;
  541. auto token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
  542. ++calls1;
  543. }];
  544. XCTAssertEqual(calls1, 0);
  545. __block int calls2 = 0;
  546. auto token2 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
  547. ++calls2;
  548. }];
  549. XCTAssertEqual(calls1, 0);
  550. XCTAssertEqual(calls2, 0);
  551. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
  552. [self createObject:0];
  553. }];
  554. XCTAssertEqual(calls1, 1);
  555. XCTAssertEqual(calls2, 1);
  556. [token1 invalidate];
  557. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
  558. [self createObject:0];
  559. }];
  560. XCTAssertEqual(calls1, 1);
  561. XCTAssertEqual(calls2, 2);
  562. [token2 invalidate];
  563. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
  564. [self createObject:0];
  565. }];
  566. XCTAssertEqual(calls1, 1);
  567. XCTAssertEqual(calls2, 2);
  568. }
  569. - (void)testRemovingBlockFromWithinNotificationBlock {
  570. RLMResults *results = IntObject.allObjects;
  571. __block int calls = 0;
  572. __block RLMNotificationToken *token1, *token2;
  573. token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
  574. [token1 invalidate];
  575. ++calls;
  576. }];
  577. token2 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
  578. [token2 invalidate];
  579. ++calls;
  580. }];
  581. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
  582. [self createObject:0];
  583. }];
  584. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
  585. [self createObject:0];
  586. }];
  587. XCTAssertEqual(calls, 2);
  588. }
  589. - (void)testAddingBlockFromWithinNotificationBlock {
  590. RLMResults *results = IntObject.allObjects;
  591. __block int calls = 0;
  592. __block RLMNotificationToken *token1, *token2;
  593. token1 = [self subscribeAndWaitForInitial:results block:^(RLMResults *results) {
  594. if (++calls == 1) {
  595. token2 = [results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  596. ++calls;
  597. }];
  598. }
  599. }];
  600. // Triggers one call on each block. Nested call is deferred until next refresh.
  601. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
  602. [self createObject:0];
  603. }];
  604. XCTAssertEqual(calls, 1);
  605. [results.realm refresh];
  606. XCTAssertEqual(calls, 2);
  607. // Triggers one call on each block
  608. [self waitForNotification:RLMRealmDidChangeNotification realm:results.realm block:^{
  609. [self createObject:0];
  610. }];
  611. XCTAssertEqual(calls, 4);
  612. [token1 invalidate];
  613. [token2 invalidate];
  614. }
  615. - (void)testAddingNewQueryWithinNotificationBlock {
  616. RLMResults *results1 = IntObject.allObjects;
  617. RLMResults *results2 = IntObject.allObjects;
  618. __block int calls = 0;
  619. __block RLMNotificationToken *token1, *token2;
  620. token1 = [self subscribeAndWaitForInitial:results1 block:^(RLMResults *results) {
  621. ++calls;
  622. if (calls == 1) {
  623. CFRunLoopStop(CFRunLoopGetCurrent());
  624. token2 = [results2 addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  625. CFRunLoopStop(CFRunLoopGetCurrent());
  626. ++calls;
  627. }];
  628. }
  629. }];
  630. // Triggers one call on outer block, but inner does not get a chance to deliver
  631. [self dispatchAsync:^{ [self createObject:0]; }];
  632. CFRunLoopRun();
  633. XCTAssertEqual(calls, 1);
  634. // Pick up the initial run of the inner block
  635. CFRunLoopRun();
  636. assert(calls == 2);
  637. XCTAssertEqual(calls, 2);
  638. // Triggers a call on each block
  639. [self dispatchAsync:^{ [self createObject:0]; }];
  640. CFRunLoopRun();
  641. XCTAssertEqual(calls, 4);
  642. [token1 invalidate];
  643. [token2 invalidate];
  644. }
  645. - (void)testAddingNewQueryWithinRealmNotificationBlock {
  646. __block RLMNotificationToken *queryToken;
  647. __block XCTestExpectation *exp;
  648. auto realmToken = [RLMRealm.defaultRealm addNotificationBlock:^(RLMNotification notification, RLMRealm *realm) {
  649. CFRunLoopStop(CFRunLoopGetCurrent());
  650. exp = [self expectationWithDescription:@"query notification"];
  651. queryToken = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *e) {
  652. [exp fulfill];
  653. }];
  654. }];
  655. // Make a background commit to trigger a Realm notification
  656. [self dispatchAsync:^{ [RLMRealm.defaultRealm transactionWithBlock:^{}]; }];
  657. // Wait for the notification
  658. CFRunLoopRun();
  659. [realmToken invalidate];
  660. // Wait for the initial async query results created within the notification
  661. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  662. [queryToken invalidate];
  663. }
  664. - (void)testBlockedThreadWithNotificationsDoesNotPreventDeliveryOnOtherThreads {
  665. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  666. dispatch_semaphore_t sema2 = dispatch_semaphore_create(0);
  667. [self dispatchAsync:^{
  668. // Add a notification block on a background thread, run the runloop
  669. // until the initial results are ready, and then block the thread without
  670. // running the runloop until the main thread is done testing things
  671. __block RLMNotificationToken *token;
  672. CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
  673. token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  674. dispatch_semaphore_signal(sema);
  675. CFRunLoopStop(CFRunLoopGetCurrent());
  676. dispatch_semaphore_wait(sema2, DISPATCH_TIME_FOREVER);
  677. }];
  678. });
  679. CFRunLoopRun();
  680. [token invalidate];
  681. }];
  682. dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  683. __block int calls = 0;
  684. auto token = [self subscribeAndWaitForInitial:IntObject.allObjects block:^(RLMResults *results) {
  685. ++calls;
  686. }];
  687. XCTAssertEqual(calls, 0);
  688. [self waitForNotification:RLMRealmDidChangeNotification realm:RLMRealm.defaultRealm block:^{
  689. [self createObject:0];
  690. }];
  691. XCTAssertEqual(calls, 1);
  692. [token invalidate];
  693. dispatch_semaphore_signal(sema2);
  694. }
  695. - (void)testAddNotificationBlockFromWrongThread {
  696. RLMResults *results = [IntObject allObjects];
  697. [self dispatchAsyncAndWait:^{
  698. CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
  699. XCTAssertThrows([results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  700. XCTFail(@"should not be called");
  701. }]);
  702. CFRunLoopStop(CFRunLoopGetCurrent());
  703. });
  704. CFRunLoopRun();
  705. }];
  706. }
  707. - (void)testRemoveNotificationBlockFromWrongThread {
  708. // Unlike adding this is allowed, because it can happen due to capturing
  709. // tokens in blocks and users are very confused by errors from deallocation
  710. // on the wrong thread
  711. RLMResults *results = [IntObject allObjects];
  712. auto token = [results addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  713. XCTFail(@"should not be called");
  714. }];
  715. [self dispatchAsyncAndWait:^{
  716. [token invalidate];
  717. }];
  718. }
  719. - (void)testSimultaneouslyRemoveCallbacksFromCallbacksForOtherResults {
  720. dispatch_semaphore_t sema1 = dispatch_semaphore_create(0);
  721. dispatch_semaphore_t sema2 = dispatch_semaphore_create(0);
  722. __block RLMNotificationToken *token1, *token2;
  723. [self dispatchAsync:^{
  724. CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
  725. __block bool first = true;
  726. token1 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  727. XCTAssertTrue(first);
  728. first = false;
  729. dispatch_semaphore_signal(sema1);
  730. dispatch_semaphore_wait(sema2, DISPATCH_TIME_FOREVER);
  731. [token2 invalidate];
  732. CFRunLoopStop(CFRunLoopGetCurrent());
  733. }];
  734. });
  735. CFRunLoopRun();
  736. }];
  737. CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
  738. __block bool first = true;
  739. token2 = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  740. XCTAssertTrue(first);
  741. first = false;
  742. dispatch_semaphore_signal(sema2);
  743. dispatch_semaphore_wait(sema1, DISPATCH_TIME_FOREVER);
  744. [token1 invalidate];
  745. CFRunLoopStop(CFRunLoopGetCurrent());
  746. }];
  747. });
  748. CFRunLoopRun();
  749. }
  750. - (void)testAsyncNotSupportedForReadOnlyRealms {
  751. @autoreleasepool { [RLMRealm defaultRealm]; }
  752. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  753. config.readOnly = true;
  754. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  755. XCTAssertThrows([[IntObject allObjectsInRealm:realm] addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  756. XCTFail(@"should not be called");
  757. }]);
  758. }
  759. - (void)testAsyncNotSupportedInWriteTransactions {
  760. [RLMRealm.defaultRealm transactionWithBlock:^{
  761. XCTAssertThrows([IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
  762. XCTFail(@"should not be called");
  763. }]);
  764. }];
  765. }
  766. - (void)testTransactionsAfterDeletingLinkView {
  767. RLMRealm *realm = [RLMRealm defaultRealm];
  768. [realm beginWriteTransaction];
  769. IntObject *io = [IntObject createInRealm:realm withValue:@[@5]];
  770. ArrayPropertyObject *apo = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[io]]];
  771. [realm commitWriteTransaction];
  772. RLMNotificationToken *token1 = [self subscribeAndWaitForInitial:apo.intArray block:^(RLMArray *array) {
  773. XCTAssertTrue(array.invalidated);
  774. }];
  775. RLMResults *asResults = [apo.intArray objectsWhere:@"intCol = 5"];
  776. RLMNotificationToken *token2 = [self subscribeAndWaitForInitial:asResults block:^(RLMResults *results) {
  777. XCTAssertEqual(results.count, 0U);
  778. }];
  779. // Delete the object containing the RLMArray with notifiers
  780. [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
  781. RLMRealm *realm = [RLMRealm defaultRealm];
  782. [realm transactionWithBlock:^{
  783. [realm deleteObject:[ArrayPropertyObject allObjectsInRealm:realm].firstObject];
  784. }];
  785. }];
  786. // Perform another transaction while the notifiers are still alive as
  787. // transactions deleting the RLMArray and transactions with the RLMArray
  788. // already deleted hit different code paths
  789. [self waitForNotification:RLMRealmDidChangeNotification realm:realm block:^{
  790. RLMRealm *realm = [RLMRealm defaultRealm];
  791. [realm transactionWithBlock:^{
  792. [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]];
  793. }];
  794. }];
  795. [token1 invalidate];
  796. [token2 invalidate];
  797. }
  798. - (void)testInitialResultDiscardsChanges {
  799. auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *) {
  800. XCTAssertEqual(results.count, 1U);
  801. XCTAssertNil(changes);
  802. CFRunLoopStop(CFRunLoopGetCurrent());
  803. }];
  804. // Make a write on a background thread, and then wait for the notification
  805. // for that write to be delivered to ensure that the notification we get on
  806. // the main thread actually would include changes
  807. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  808. [self dispatchAsync:^{
  809. CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
  810. auto token = [IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *changes, NSError *) {
  811. if (changes) {
  812. dispatch_semaphore_signal(sema);
  813. CFRunLoopStop(CFRunLoopGetCurrent());
  814. }
  815. }];
  816. [RLMRealm.defaultRealm transactionWithBlock:^{
  817. [IntObject createInDefaultRealmWithValue:@[@0]];
  818. }];
  819. CFRunLoopRun();
  820. [token invalidate];
  821. CFRunLoopStop(CFRunLoopGetCurrent());
  822. });
  823. CFRunLoopRun();
  824. }];
  825. dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  826. CFRunLoopRun();
  827. [token invalidate];
  828. }
  829. @end