FPersist.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FPersist.h"
  17. #import <Foundation/Foundation.h>
  18. #import <XCTest/XCTest.h>
  19. #import "FDevice.h"
  20. #import "FIRDatabaseQuery_Private.h"
  21. #import "FIRDatabaseReference.h"
  22. #import "FIRDatabaseReference_Private.h"
  23. #import "FRepo_Private.h"
  24. #import "FTestHelpers.h"
  25. @implementation FPersist
  26. - (void)setUp {
  27. [super setUp];
  28. NSFileManager *fileManager = [NSFileManager defaultManager];
  29. NSString *baseDir = [FPersist getFirebaseDir];
  30. // HACK: We want to clean up old persistence files from previous test runs, but on OSX, baseDir is
  31. // going to be something like /Users/michael/Documents/firebase, and we probably shouldn't blindly
  32. // delete it, since somebody might have actual documents there. We should probably change the
  33. // directory where we store persistence on OSX to .firebase or something to avoid colliding with
  34. // real files, but for now, we'll leave it and just manually delete each of the /0, /1, /2, etc.
  35. // directories that may exist from previous test runs. As of now (2014/09/07), these directories
  36. // only go up to ~50, but if we add a ton more tests, we may need to increase the 100. But I'm
  37. // guessing we'll rewrite persistence and move the persistence folder before then though.
  38. for (int i = 0; i < 100; i++) {
  39. // TODO: This hack is uneffective because the format now follows different rules. Persistence
  40. // really needs a purge option
  41. NSString *dir = [NSString stringWithFormat:@"%@/%d", baseDir, i];
  42. if ([fileManager fileExistsAtPath:dir]) {
  43. NSError *error;
  44. [[NSFileManager defaultManager] removeItemAtPath:dir error:&error];
  45. if (error) {
  46. XCTFail(@"Failed to clear persisted data at %@: %@", dir, error);
  47. }
  48. }
  49. }
  50. }
  51. - (void)testSetIsResentAfterRestart {
  52. FIRDatabaseReference *readerRef = [FTestHelpers getRandomNode];
  53. NSString *url = [readerRef description];
  54. FDevice *device = [[FDevice alloc] initOfflineWithUrl:url];
  55. // Monitor the data at this location.
  56. __block FIRDataSnapshot *readSnapshot = nil;
  57. [readerRef observeEventType:FIRDataEventTypeValue
  58. withBlock:^(FIRDataSnapshot *snapshot) {
  59. readSnapshot = snapshot;
  60. }];
  61. // Do some sets while offline and then "kill" the app, so it doesn't get sent to Firebase.
  62. [device do:^(FIRDatabaseReference *ref) {
  63. [ref setValue:@{
  64. @"a" : @42,
  65. @"b" : @3.1415,
  66. @"c" : @"hello",
  67. @"d" : @{@"dd" : @"dd-val", @".priority" : @"d-pri"}
  68. }];
  69. [[ref child:@"a"] setValue:@"a-val"];
  70. [[ref child:@"c"] setPriority:@"c-pri"];
  71. [ref updateChildValues:@{@"b" : @"b-val"}];
  72. }];
  73. // restart and wait for "idle" (so all pending puts should have been sent).
  74. [device restartOnline];
  75. [device waitForIdleUsingWaiter:self];
  76. // Pending sets should have gone through.
  77. id expected = @{
  78. @"a" : @"a-val",
  79. @"b" : @"b-val",
  80. @"c" : @{@".value" : @"hello", @".priority" : @"c-pri"},
  81. @"d" : @{@"dd" : @"dd-val", @".priority" : @"d-pri"}
  82. };
  83. [self waitForExportValueOf:readerRef toBe:expected];
  84. // Set the value to something else (12).
  85. [readerRef setValue:@12];
  86. // "restart" the app again and make sure it doesn't set it to 42 again.
  87. [device restartOnline];
  88. [device waitForIdleUsingWaiter:self];
  89. // Make sure data is still 12.
  90. [self waitForRoundTrip:readerRef];
  91. XCTAssertEqual(readSnapshot.value, @12, @"Read data should still be 12.");
  92. [device dispose];
  93. }
  94. - (void)testSetIsReappliedAfterRestart {
  95. FDevice *device = [[FDevice alloc] initOffline];
  96. // Do some sets while offline and then "kill" the app, so it doesn't get sent to Firebase.
  97. [device do:^(FIRDatabaseReference *ref) {
  98. [ref setValue:@{@"a" : @42, @"b" : @3.1415, @"c" : @"hello"}];
  99. [[ref child:@"a"] setValue:@"a-val"];
  100. [[ref child:@"c"] setPriority:@"c-pri"];
  101. [ref updateChildValues:@{@"b" : @"b-val"}];
  102. }];
  103. // restart the app offline and observe the data.
  104. [device restartOffline];
  105. // Pending sets should be visible
  106. id expected =
  107. @{@"a" : @"a-val", @"b" : @"b-val", @"c" : @{@".value" : @"hello", @".priority" : @"c-pri"}};
  108. [device do:^(FIRDatabaseReference *ref) {
  109. [self waitForExportValueOf:ref toBe:expected];
  110. }];
  111. [device dispose];
  112. }
  113. - (void)testServerDataCachedOffline1 {
  114. FIRDatabaseReference *writerRef = [FTestHelpers getRandomNode];
  115. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writerRef description]];
  116. __block BOOL done = NO;
  117. id data = @{@"a" : @1, @"b" : @2};
  118. [writerRef setValue:data
  119. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  120. done = YES;
  121. }];
  122. WAIT_FOR(done);
  123. // Wait for the data to get it cached.
  124. [device do:^(FIRDatabaseReference *ref) {
  125. [self waitForValueOf:ref toBe:data];
  126. }];
  127. // Should still be there after restart, offline.
  128. [device restartOffline];
  129. [device do:^(FIRDatabaseReference *ref) {
  130. [self waitForValueOf:ref toBe:data];
  131. }];
  132. // Children should be there too.
  133. [device restartOffline];
  134. [device do:^(FIRDatabaseReference *ref) {
  135. [self waitForValueOf:[ref child:@"a"] toBe:@1];
  136. }];
  137. [device dispose];
  138. }
  139. - (void)testServerDataCompleteness1 {
  140. FIRDatabaseReference *writerRef = [FTestHelpers getRandomNode];
  141. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writerRef description]];
  142. id data = @{@"child" : @{@"a" : @1, @"b" : @2}, @"other" : @"blah"};
  143. [self waitForCompletionOf:writerRef setValue:data];
  144. // Wait for each child to get it cached (but not the parent).
  145. [device do:^(FIRDatabaseReference *ref) {
  146. [self waitForValueOf:[ref child:@"child/a"] toBe:@1];
  147. [self waitForValueOf:[ref child:@"child/b"] toBe:@2];
  148. [self waitForValueOf:[ref child:@"other"] toBe:@"blah"];
  149. }];
  150. // Restart, offline, should get child_added events, but not value.
  151. [device restartOffline];
  152. __block BOOL gotA, gotB;
  153. [device do:^(FIRDatabaseReference *ref) {
  154. FIRDatabaseReference *childRef = [ref child:@"child"];
  155. [childRef observeEventType:FIRDataEventTypeChildAdded
  156. withBlock:^(FIRDataSnapshot *snapshot) {
  157. if ([snapshot.key isEqualToString:@"a"]) {
  158. XCTAssertEqualObjects(snapshot.value, @1, @"Got a");
  159. gotA = YES;
  160. } else if ([snapshot.key isEqualToString:@"b"]) {
  161. XCTAssertEqualObjects(snapshot.value, @2, @"Got a");
  162. gotB = YES;
  163. } else {
  164. XCTFail(@"Unexpected child event.");
  165. }
  166. }];
  167. // Listen for value events (which we should *not* get).
  168. [childRef observeEventType:FIRDataEventTypeValue
  169. withBlock:^(FIRDataSnapshot *snapshot) {
  170. XCTFail(@"Got a value event with incomplete data!");
  171. }];
  172. // Wait for another location just to make sure we wait long enough that we /would/ get a value
  173. // event if it was coming.
  174. [self waitForValueOf:[ref child:@"other"] toBe:@"blah"];
  175. }];
  176. XCTAssertTrue(gotA && gotB, @"Got a and b.");
  177. [device dispose];
  178. }
  179. - (void)testServerDataCompleteness2 {
  180. FIRDatabaseReference *writerRef = [FTestHelpers getRandomNode];
  181. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writerRef description]];
  182. id data = @{@"a" : @1, @"b" : @2};
  183. [self waitForCompletionOf:writerRef setValue:data];
  184. // Wait for the children individually.
  185. [device do:^(FIRDatabaseReference *ref) {
  186. [self waitForValueOf:[ref child:@"a"] toBe:@1];
  187. [self waitForValueOf:[ref child:@"b"] toBe:@2];
  188. }];
  189. // Should still be there after restart, offline.
  190. [device restartOffline];
  191. [device do:^(FIRDatabaseReference *ref) {
  192. [ref observeEventType:FIRDataEventTypeValue
  193. withBlock:^(FIRDataSnapshot *snapshot){
  194. // No-op. Just triggering a listen at this location.
  195. }];
  196. [self waitForValueOf:[ref child:@"a"] toBe:@1];
  197. [self waitForValueOf:[ref child:@"b"] toBe:@2];
  198. }];
  199. [device dispose];
  200. }
  201. - (void)testServerDataLimit {
  202. FIRDatabaseReference *writerRef = [FTestHelpers getRandomNode];
  203. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writerRef description]];
  204. [self waitForCompletionOf:writerRef setValue:@{@"a" : @1, @"b" : @2, @"c" : @3}];
  205. // Cache limit(2) of the data.
  206. [device do:^(FIRDatabaseReference *ref) {
  207. FIRDatabaseQuery *limitRef = [ref queryLimitedToLast:2];
  208. [self waitForValueOf:limitRef toBe:@{@"b" : @2, @"c" : @3}];
  209. }];
  210. // We should be able to get limit(2) data offline, but not the whole node.
  211. [device restartOffline];
  212. [device do:^(FIRDatabaseReference *ref) {
  213. [ref observeSingleEventOfType:FIRDataEventTypeValue
  214. withBlock:^(FIRDataSnapshot *snapshot) {
  215. XCTFail(@"Got value event for whole node!");
  216. }];
  217. FIRDatabaseQuery *limitRef = [ref queryLimitedToLast:2];
  218. [self waitForValueOf:limitRef toBe:@{@"b" : @2, @"c" : @3}];
  219. }];
  220. [device dispose];
  221. }
  222. - (void)testRemoveWhileOfflineAndRestart {
  223. FIRDatabaseReference *writerRef = [FTestHelpers getRandomNode];
  224. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writerRef description]];
  225. [[writerRef child:@"test"] setValue:@"test"];
  226. [device do:^(FIRDatabaseReference *ref) {
  227. // Cache this location.
  228. __block id val = nil;
  229. [ref observeEventType:FIRDataEventTypeValue
  230. withBlock:^(FIRDataSnapshot *snapshot) {
  231. val = snapshot.value;
  232. }];
  233. [self waitUntil:^BOOL {
  234. return [val isEqual:@{@"test" : @"test"}];
  235. }];
  236. }];
  237. [device restartOffline];
  238. __block BOOL done = NO;
  239. [writerRef removeValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  240. done = YES;
  241. }];
  242. WAIT_FOR(done);
  243. [device goOnline];
  244. [device waitForIdleUsingWaiter:self];
  245. [device do:^(FIRDatabaseReference *ref) {
  246. [self waitForValueOf:ref toBe:[NSNull null]];
  247. }];
  248. [device dispose];
  249. }
  250. - (void)testDeltaSyncAfterRestart {
  251. FIRDatabaseReference *writerRef = [FTestHelpers getRandomNode];
  252. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writerRef description]];
  253. [writerRef setValue:@"test"];
  254. [device do:^(FIRDatabaseReference *ref) {
  255. // Cache this location.
  256. __block id val = nil;
  257. [ref observeEventType:FIRDataEventTypeValue
  258. withBlock:^(FIRDataSnapshot *snapshot) {
  259. val = snapshot.value;
  260. }];
  261. [self waitUntil:^BOOL {
  262. return [val isEqual:@"test"];
  263. }];
  264. XCTAssertEqual(ref.repo.dataUpdateCount, 1L, @"Should have gotten one update.");
  265. }];
  266. [device restartOnline];
  267. [device waitForIdleUsingWaiter:self];
  268. [device do:^(FIRDatabaseReference *ref) {
  269. [self waitForValueOf:ref toBe:@"test"];
  270. XCTAssertEqual(ref.repo.dataUpdateCount, 0L, @"Should have gotten no updates.");
  271. }];
  272. [device dispose];
  273. }
  274. - (void)testDeltaSyncWorksWithUnfilteredQuery {
  275. FIRDatabaseReference *writerRef = [FTestHelpers getRandomNode];
  276. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writerRef description]];
  277. // List must be large enough to trigger delta sync.
  278. NSMutableDictionary *longList = [[NSMutableDictionary alloc] init];
  279. for (NSInteger i = 0; i < 50; i++) {
  280. NSString *key = [[writerRef childByAutoId] key];
  281. longList[key] = @{@"order" : @1, @"text" : @"This is an awesome message!"};
  282. }
  283. [writerRef setValue:longList];
  284. [device do:^(FIRDatabaseReference *ref) {
  285. // Cache this location.
  286. [self waitForValueOf:[ref queryOrderedByChild:@"order"] toBe:longList];
  287. XCTAssertEqual(ref.repo.dataUpdateCount, 1L, @"Should have gotten one update.");
  288. }];
  289. [device restartOffline];
  290. // Add a new child while the device is offline.
  291. FIRDatabaseReference *newChildRef = [writerRef childByAutoId];
  292. NSDictionary *newChild = @{@"order" : @50, @"text" : @"This is a new appended child!"};
  293. [self waitForCompletionOf:newChildRef setValue:newChild];
  294. longList[[newChildRef key]] = newChild;
  295. [device goOnline];
  296. [device do:^(FIRDatabaseReference *ref) {
  297. // Wait for updated value with new child.
  298. [self waitForValueOf:[ref queryOrderedByChild:@"order"] toBe:longList];
  299. XCTAssertEqual(ref.repo.rangeMergeUpdateCount, 1L, @"Should have gotten a range merge update.");
  300. }];
  301. [device dispose];
  302. }
  303. - (void)testPutsAreRestoredInOrder {
  304. FDevice *device = [[FDevice alloc] initOffline];
  305. // Store puts which should have a putId with 10 which is lexiographical small than 9
  306. [device do:^(FIRDatabaseReference *ref) {
  307. for (int i = 0; i < 11; i++) {
  308. [ref setValue:[NSNumber numberWithInt:i]];
  309. }
  310. }];
  311. // restart the app offline and observe the data.
  312. [device restartOffline];
  313. // Make sure that the write with putId 10 wins, not 9
  314. id expected = @10;
  315. [device do:^(FIRDatabaseReference *ref) {
  316. [self waitForExportValueOf:ref toBe:expected];
  317. }];
  318. [device dispose];
  319. }
  320. - (void)testStoreSetsPerf1 {
  321. if (!runPerfTests) return;
  322. // Disable persistence in FDevice for comparison without persistence
  323. FDevice *device = [[FDevice alloc] initOnline];
  324. __block BOOL done = NO;
  325. [device do:^(FIRDatabaseReference *ref) {
  326. NSDate *start = [NSDate date];
  327. [self writeChildren:ref count:1000 size:100 waitForComplete:NO];
  328. [self waitForQueue:ref];
  329. NSLog(@"Elapsed: %f", [[NSDate date] timeIntervalSinceDate:start]);
  330. done = YES;
  331. }];
  332. WAIT_FOR(done);
  333. [device dispose];
  334. }
  335. - (void)testStoreListenPerf1 {
  336. if (!runPerfTests) return;
  337. // Disable persistence in FDevice for comparison without persistence
  338. // Write 1000 x 100-byte children, to read back.
  339. unsigned int count = 1000;
  340. FIRDatabaseReference *writer = [FTestHelpers getRandomNode];
  341. [self writeChildren:writer count:count size:100];
  342. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writer description]];
  343. __block BOOL done = NO;
  344. [device do:^(FIRDatabaseReference *ref) {
  345. NSDate *start = [NSDate date];
  346. [ref observeSingleEventOfType:FIRDataEventTypeValue
  347. withBlock:^(FIRDataSnapshot *snapshot) {
  348. // Wait to make sure we're done persisting everything.
  349. [self waitForQueue:ref];
  350. XCTAssertEqual(snapshot.childrenCount, count, @"Got correct data.");
  351. NSLog(@"Elapsed: %f", [[NSDate date] timeIntervalSinceDate:start]);
  352. done = YES;
  353. }];
  354. }];
  355. WAIT_FOR(done);
  356. [device dispose];
  357. }
  358. - (void)testRestoreListenPerf1 {
  359. if (!runPerfTests) return;
  360. // NOTE: Since this is testing restoration of data from cache after restarting, it only works with
  361. // persistence on.
  362. // Write 1000 * 100-byte children, to read back.
  363. unsigned int count = 1000;
  364. FIRDatabaseReference *writer = [FTestHelpers getRandomNode];
  365. [self writeChildren:writer count:count size:100];
  366. FDevice *device = [[FDevice alloc] initOnlineWithUrl:[writer description]];
  367. // Get the data cached.
  368. __block BOOL done = NO;
  369. [device do:^(FIRDatabaseReference *ref) {
  370. [ref observeSingleEventOfType:FIRDataEventTypeValue
  371. withBlock:^(FIRDataSnapshot *snapshot) {
  372. XCTAssertEqual(snapshot.childrenCount, count, @"Got correct data.");
  373. done = YES;
  374. }];
  375. }];
  376. WAIT_FOR(done);
  377. // Restart offline and see how long it takes to restore the data from cache.
  378. [device restartOffline];
  379. done = NO;
  380. [device do:^(FIRDatabaseReference *ref) {
  381. NSDate *start = [NSDate date];
  382. [ref observeSingleEventOfType:FIRDataEventTypeValue
  383. withBlock:^(FIRDataSnapshot *snapshot) {
  384. // Wait to make sure we're done persisting everything.
  385. XCTAssertEqual(snapshot.childrenCount, count, @"Got correct data.");
  386. [self waitForQueue:ref];
  387. NSLog(@"Elapsed: %f", [[NSDate date] timeIntervalSinceDate:start]);
  388. done = YES;
  389. }];
  390. }];
  391. WAIT_FOR(done);
  392. [device dispose];
  393. }
  394. - (void)writeChildren:(FIRDatabaseReference *)writer
  395. count:(unsigned int)count
  396. size:(unsigned int)size {
  397. [self writeChildren:writer count:count size:size waitForComplete:YES];
  398. }
  399. - (void)writeChildren:(FIRDatabaseReference *)writer
  400. count:(unsigned int)count
  401. size:(unsigned int)size
  402. waitForComplete:(BOOL)waitForComplete {
  403. __block BOOL done = NO;
  404. NSString *data = [self randomStringOfLength:size];
  405. for (int i = 0; i < count; i++) {
  406. [[writer childByAutoId] setValue:data
  407. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  408. if (i == (count - 1)) {
  409. done = YES;
  410. }
  411. }];
  412. }
  413. if (waitForComplete) {
  414. WAIT_FOR(done);
  415. }
  416. }
  417. NSString *letters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  418. - (NSString *)randomStringOfLength:(unsigned int)len {
  419. NSMutableString *randomString = [NSMutableString stringWithCapacity:len];
  420. for (int i = 0; i < len; i++) {
  421. [randomString appendFormat:@"%C", [letters characterAtIndex:arc4random() % [letters length]]];
  422. }
  423. return randomString;
  424. }
  425. + (NSString *)getFirebaseDir {
  426. NSArray *dirPaths =
  427. NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  428. NSString *documentsDir = [dirPaths objectAtIndex:0];
  429. NSString *firebaseDir = [documentsDir stringByAppendingPathComponent:@"firebase"];
  430. return firebaseDir;
  431. }
  432. @end