FPersist.m 18 KB

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