FLevelDBStorageEngineTests.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  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 "FirebaseDatabase/Sources/Core/FQueryParams.h"
  18. #import "FirebaseDatabase/Sources/Core/FWriteRecord.h"
  19. #import "FirebaseDatabase/Sources/FPathIndex.h"
  20. #import "FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.h"
  21. #import "FirebaseDatabase/Sources/Persistence/FTrackedQuery.h"
  22. #import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
  23. #import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
  24. #import "FirebaseDatabase/Tests/Helpers/FTestHelpers.h"
  25. @interface FLevelDBStorageEngineTests : XCTestCase
  26. @end
  27. @implementation FLevelDBStorageEngineTests
  28. - (FLevelDBStorageEngine *)cleanStorageEngine {
  29. NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test-db"];
  30. FLevelDBStorageEngine *db = [[FLevelDBStorageEngine alloc] initWithPath:path];
  31. [db purgeEverything];
  32. return db;
  33. }
  34. #define SAMPLE_NODE \
  35. ([FSnapshotUtilities \
  36. nodeFrom:@{@"foo" : @{@"bar" : @YES, @"baz" : @"string"}, @"qux" : @2, @"quu" : @1.2}])
  37. #define ONE_MEG_NODE ([FTestHelpers leafNodeOfSize:1024 * 1024])
  38. #define FIVE_MEG_NODE ([FTestHelpers leafNodeOfSize:5 * 1024 * 1024])
  39. #define TEN_MEG_NODE ([FTestHelpers leafNodeOfSize:10 * 1024 * 1024])
  40. #define TEN_MEG_MINUS_ONE_NODE ([FTestHelpers leafNodeOfSize:10 * 1024 * 1024 - 1])
  41. #define SAMPLE_PARAMS \
  42. ([[[[[FQueryParams defaultInstance] orderBy:[[FPathIndex alloc] initWithPath:PATH(@"child")]] \
  43. startAt:[FSnapshotUtilities nodeFrom:@"startVal"] \
  44. childKey:@"startKey"] endAt:[FSnapshotUtilities nodeFrom:@"endVal"] \
  45. childKey:@"endKey"] limitToLast:5])
  46. #define SAMPLE_QUERY \
  47. ([[FQuerySpec alloc] initWithPath:[FPath pathWithString:@"foo"] params:SAMPLE_PARAMS])
  48. #define DEFAULT_FOO_QUERY \
  49. ([[FQuerySpec alloc] initWithPath:[FPath pathWithString:@"foo"] \
  50. params:[FQueryParams defaultInstance]])
  51. #define SAMPLE_TRACKED_QUERY \
  52. ([[FTrackedQuery alloc] initWithId:1 \
  53. query:SAMPLE_QUERY \
  54. isPinned:NO \
  55. lastUse:100 \
  56. Active:NO \
  57. isComplete:NO])
  58. #define OVERWRITE_RECORD(__path, __node, __writeId) \
  59. ([[FWriteRecord alloc] initWithPath:[FPath pathWithString:__path] \
  60. overwrite:__node \
  61. writeId:__writeId \
  62. visible:YES])
  63. #define MERGE_RECORD(__path, __merge, __writeId) \
  64. ([[FWriteRecord alloc] initWithPath:[FPath pathWithString:__path] \
  65. merge:__merge \
  66. writeId:__writeId])
  67. - (void)testRecocversFromBadCache {
  68. NSString *dbPath = @"corrupted-db";
  69. NSString *serverData = [[FLevelDBStorageEngine firebaseDir]
  70. stringByAppendingPathComponent:@"corrupted-db/server_data/CURRENT"];
  71. [@"Corrupted" writeToFile:serverData atomically:YES encoding:NSUTF8StringEncoding error:nil];
  72. NSString *userData = [[FLevelDBStorageEngine firebaseDir]
  73. stringByAppendingPathComponent:@"corrupted-db/writes/CURRENT"];
  74. [@"Corrupted" writeToFile:userData atomically:YES encoding:NSUTF8StringEncoding error:nil];
  75. FLevelDBStorageEngine *db = [[FLevelDBStorageEngine alloc] initWithPath:dbPath];
  76. XCTAssertNotNil(db);
  77. }
  78. - (void)testUserWriteIsPersisted {
  79. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  80. [engine saveUserOverwrite:SAMPLE_NODE atPath:[FPath pathWithString:@"foo/bar"] writeId:1];
  81. XCTAssertEqualObjects(engine.userWrites, @[ OVERWRITE_RECORD(@"foo/bar", SAMPLE_NODE, 1) ]);
  82. }
  83. - (void)testUserMergeIsPersisted {
  84. FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:@{
  85. @"foo" : @{@"bar" : @1, @"baz" : @"string"},
  86. @"quu" : @YES
  87. }];
  88. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  89. [engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:1];
  90. XCTAssertEqualObjects(engine.userWrites, @[ MERGE_RECORD(@"foo/bar", merge, 1) ]);
  91. }
  92. - (void)testDeepUserMergeIsPersisted {
  93. FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:@{
  94. @"foo/bar" : @1,
  95. @"foo/baz" : @"string",
  96. @"quu/qux" : @YES,
  97. @"shallow" : @2
  98. }];
  99. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  100. [engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:1];
  101. XCTAssertEqualObjects(engine.userWrites, @[ MERGE_RECORD(@"foo/bar", merge, 1) ]);
  102. }
  103. - (void)testSameWriteIdOverwritesOldWrite {
  104. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  105. [engine saveUserOverwrite:NODE(@"first") atPath:PATH(@"foo/bar") writeId:1];
  106. [engine saveUserOverwrite:NODE(@"second") atPath:PATH(@"other/path") writeId:1];
  107. XCTAssertEqualObjects(engine.userWrites,
  108. @[ OVERWRITE_RECORD(@"other/path", NODE(@"second"), 1) ]);
  109. }
  110. - (void)testHugeWriteWorks {
  111. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  112. [engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/bar") writeId:1];
  113. FCompoundWrite *merge = [[FCompoundWrite emptyWrite] addWrite:TEN_MEG_NODE atKey:@"update"];
  114. [engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:2];
  115. NSArray *expected =
  116. @[ OVERWRITE_RECORD(@"foo/bar", TEN_MEG_NODE, 1), MERGE_RECORD(@"foo/bar", merge, 2) ];
  117. XCTAssertEqualObjects(engine.userWrites, expected);
  118. }
  119. - (void)testHugeWritesCanBeDeleted {
  120. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  121. [engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/bar") writeId:1];
  122. [engine removeUserWrite:1];
  123. XCTAssertTrue(engine.userWrites.count == 0);
  124. }
  125. - (void)testHugeWritesCanBeInterleavedWithSmallWrites {
  126. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  127. [engine saveUserOverwrite:NODE(@"node-1") atPath:PATH(@"foo/1") writeId:1];
  128. [engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/2") writeId:2];
  129. [engine saveUserOverwrite:NODE(@"node-3") atPath:PATH(@"foo/3") writeId:3];
  130. [engine saveUserOverwrite:FIVE_MEG_NODE atPath:PATH(@"foo/4") writeId:4];
  131. NSArray *expected = @[
  132. OVERWRITE_RECORD(@"foo/1", NODE(@"node-1"), 1), OVERWRITE_RECORD(@"foo/2", TEN_MEG_NODE, 2),
  133. OVERWRITE_RECORD(@"foo/3", NODE(@"node-3"), 3), OVERWRITE_RECORD(@"foo/4", FIVE_MEG_NODE, 4)
  134. ];
  135. XCTAssertEqualObjects(engine.userWrites, expected);
  136. }
  137. // This is ported from the Android client and doesn't really make sense since we don't have multi
  138. // part writes, but It's always good to have tests, so what the heck...
  139. - (void)testSameWriteIdOverwritesOldMultiPartWrite {
  140. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  141. [engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/bar") writeId:1];
  142. [engine saveUserOverwrite:NODE(@"second") atPath:PATH(@"other/path") writeId:1];
  143. XCTAssertEqualObjects(engine.userWrites,
  144. @[ OVERWRITE_RECORD(@"other/path", NODE(@"second"), 1) ]);
  145. }
  146. - (void)testWritesAreReturnedInOrder {
  147. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  148. NSUInteger count = 20;
  149. for (NSUInteger i = count - 1; i > 0; i--) {
  150. NSString *path = [NSString stringWithFormat:@"foo/%lu", (unsigned long)i];
  151. [engine saveUserOverwrite:NODE(@(i)) atPath:PATH(path) writeId:i];
  152. }
  153. NSString *path = [NSString stringWithFormat:@"foo/%lu", (unsigned long)count];
  154. [engine saveUserOverwrite:NODE(@(count)) atPath:PATH(path) writeId:count];
  155. NSArray *userWrites = engine.userWrites;
  156. XCTAssertEqual(userWrites.count, count);
  157. for (NSUInteger i = 1; i <= count; i++) {
  158. NSString *path = [NSString stringWithFormat:@"foo/%lu", (unsigned long)i];
  159. XCTAssertEqualObjects(userWrites[i - 1], OVERWRITE_RECORD(path, NODE(@(i)), i));
  160. }
  161. }
  162. - (void)testRemoveAllUserWrites {
  163. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  164. [engine saveUserOverwrite:NODE(@"node-1") atPath:PATH(@"foo/1") writeId:1];
  165. [engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/2") writeId:2];
  166. FCompoundWrite *merge = [[FCompoundWrite emptyWrite] addWrite:TEN_MEG_NODE atKey:@"update"];
  167. [engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:3];
  168. [engine removeAllUserWrites];
  169. XCTAssertEqualObjects(engine.userWrites, @[]);
  170. }
  171. - (void)testCacheSavedIsReturned {
  172. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  173. [engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
  174. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], SAMPLE_NODE);
  175. }
  176. - (void)testCacheSavedIsReturnedAtRoot {
  177. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  178. [engine updateServerCache:SAMPLE_NODE atPath:PATH(@"") merge:NO];
  179. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"")], SAMPLE_NODE);
  180. }
  181. - (void)testLaterCacheWritesOverwriteOlderWrites {
  182. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  183. [engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
  184. [engine updateServerCache:NODE(@"later-bar") atPath:PATH(@"foo/bar") merge:NO];
  185. // this does not affect the node
  186. [engine updateServerCache:NODE(@"unaffected") atPath:PATH(@"unaffected") merge:NO];
  187. [engine updateServerCache:NODE(@"later-qux") atPath:PATH(@"foo/later-qux") merge:NO];
  188. [engine updateServerCache:NODE(@"latest-bar") atPath:PATH(@"foo/bar") merge:NO];
  189. id<FNode> expected = [[SAMPLE_NODE updateImmediateChild:@"bar" withNewChild:NODE(@"latest-bar")]
  190. updateImmediateChild:@"later-qux"
  191. withNewChild:NODE(@"later-qux")];
  192. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
  193. }
  194. - (void)testLaterCacheWritesOverwriteOlderDeeperWrites {
  195. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  196. [engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
  197. [engine updateServerCache:NODE(@"later-bar") atPath:PATH(@"foo/bar") merge:NO];
  198. // this does not affect the node
  199. [engine updateServerCache:NODE(@"unaffected") atPath:PATH(@"unaffected") merge:NO];
  200. [engine updateServerCache:NODE(@"later-qux") atPath:PATH(@"foo/later-qux") merge:NO];
  201. [engine updateServerCache:NODE(@"latest-bar") atPath:PATH(@"foo/bar") merge:NO];
  202. [engine updateServerCache:NODE(@"latest-foo") atPath:PATH(@"foo") merge:NO];
  203. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@"latest-foo"));
  204. }
  205. - (void)testLaterCacheWritesDontAffectEarlierWritesAtUnaffectedPath {
  206. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  207. [engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
  208. // this does not affect the node
  209. [engine updateServerCache:NODE(@"unaffected") atPath:PATH(@"unaffected") merge:NO];
  210. [engine updateServerCache:NODE(@"latest-foo") atPath:PATH(@"foo") merge:NO];
  211. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"unaffected")], NODE(@"unaffected"));
  212. }
  213. - (void)testMergeOnEmptyCacheGivesResults {
  214. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  215. NSDictionary *mergeData = @{@"foo" : @"foo-value", @"bar" : @"bar-value"};
  216. FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:mergeData];
  217. [engine updateServerCacheWithMerge:merge atPath:PATH(@"foo")];
  218. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(mergeData));
  219. }
  220. - (void)testMergePartlyOverwritingPreviousWrite {
  221. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  222. id<FNode> existingNode = NODE((@{@"foo" : @"foo-value", @"bar" : @"bar-value"}));
  223. [engine updateServerCache:existingNode atPath:PATH(@"foo") merge:NO];
  224. FCompoundWrite *merge = [FCompoundWrite
  225. compoundWriteWithValueDictionary:@{@"foo" : @"new-foo-value", @"baz" : @"baz-value"}];
  226. [engine updateServerCacheWithMerge:merge atPath:PATH(@"foo")];
  227. id<FNode> expected =
  228. NODE((@{@"foo" : @"new-foo-value", @"bar" : @"bar-value", @"baz" : @"baz-value"}));
  229. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
  230. }
  231. - (void)testDeepMergePartlyOverwritingPreviousWrite {
  232. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  233. id<FNode> existingNode =
  234. NODE((@{@"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"}, @"qux" : @"qux-value"}));
  235. [engine updateServerCache:existingNode atPath:PATH(@"foo") merge:NO];
  236. FCompoundWrite *merge = [FCompoundWrite
  237. compoundWriteWithValueDictionary:@{@"foo/bar" : @"new-bar-value", @"quu" : @"quu-value"}];
  238. [engine updateServerCacheWithMerge:merge atPath:PATH(@"foo")];
  239. id<FNode> expected = NODE((@{
  240. @"foo" : @{@"bar" : @"new-bar-value", @"baz" : @"baz-value"},
  241. @"qux" : @"qux-value",
  242. @"quu" : @"quu-value"
  243. }));
  244. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
  245. }
  246. - (void)testMergePartlyOverwritingPreviousMerge {
  247. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  248. FCompoundWrite *merge1 = [FCompoundWrite
  249. compoundWriteWithValueDictionary:@{@"foo" : @"foo-value", @"bar" : @"bar-value"}];
  250. [engine updateServerCacheWithMerge:merge1 atPath:PATH(@"foo")];
  251. FCompoundWrite *merge2 = [FCompoundWrite
  252. compoundWriteWithValueDictionary:@{@"foo" : @"new-foo-value", @"baz" : @"baz-value"}];
  253. [engine updateServerCacheWithMerge:merge2 atPath:PATH(@"foo")];
  254. id<FNode> expected =
  255. NODE((@{@"foo" : @"new-foo-value", @"bar" : @"bar-value", @"baz" : @"baz-value"}));
  256. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
  257. }
  258. - (void)testOverwriteRemovesPreviousMerge {
  259. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  260. id<FNode> initial = NODE((@{@"foo" : @"foo-value", @"bar" : @"bar-value"}));
  261. [engine updateServerCache:initial atPath:PATH(@"foo") merge:NO];
  262. FCompoundWrite *merge2 = [FCompoundWrite
  263. compoundWriteWithValueDictionary:@{@"foo" : @"new-foo-value", @"baz" : @"baz-value"}];
  264. [engine updateServerCacheWithMerge:merge2 atPath:PATH(@"foo")];
  265. id<FNode> replacingNode = NODE((@{@"qux" : @"qux-value", @"quu" : @"quu-value"}));
  266. [engine updateServerCache:replacingNode atPath:PATH(@"foo") merge:NO];
  267. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], replacingNode);
  268. }
  269. - (void)testEmptyOverwriteDeletesNodeFromHigherWrite {
  270. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  271. id<FNode> initial = NODE((@{@"foo" : @"foo-value", @"bar" : @"bar-value"}));
  272. [engine updateServerCache:initial atPath:PATH(@"foo") merge:NO];
  273. // delete bar
  274. [engine updateServerCache:NODE(nil) atPath:PATH(@"foo/bar") merge:NO];
  275. id<FNode> expected = NODE((@{@"foo" : @"foo-value"}));
  276. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
  277. }
  278. - (void)testDeeperReadFromHigherSet {
  279. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  280. id<FNode> initial = NODE((@{@"foo" : @"foo-value", @"bar" : @"bar-value"}));
  281. [engine updateServerCache:initial atPath:PATH(@"foo") merge:NO];
  282. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/bar")], NODE(@"bar-value"));
  283. }
  284. - (void)testDeeperLeafNodeSetRemovesHigherLeafNodes {
  285. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  286. [engine updateServerCache:NODE(@"level-0") atPath:PATH(@"") merge:NO];
  287. [engine updateServerCache:NODE(@"level-1") atPath:PATH(@"lvl1") merge:NO];
  288. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"")], NODE((@{@"lvl1" : @"level-1"})));
  289. [engine updateServerCache:NODE(@"level-2") atPath:PATH(@"lvl1/lvl2") merge:NO];
  290. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"lvl1")], NODE((@{@"lvl2" : @"level-2"})));
  291. [engine updateServerCache:NODE(@"level-4") atPath:PATH(@"lvl1/lvl2/lvl3/lvl4") merge:NO];
  292. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"lvl1")],
  293. NODE((@{@"lvl2" : @{@"lvl3" : @{@"lvl4" : @"level-4"}}})));
  294. }
  295. // This test causes a split on Android so it doesn't really make sense here, but why not test
  296. // anyways...
  297. - (void)testHugeNodeWithSplit {
  298. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  299. id<FNode> outer = [FEmptyNode emptyNode];
  300. // This structure ensures splits at various depths
  301. for (NSUInteger i = 0; i < 100; i++) { // Outer
  302. id<FNode> inner = [FEmptyNode emptyNode];
  303. for (NSUInteger j = 0; j < i; j++) { // Inner
  304. id<FNode> innerMost = [FEmptyNode emptyNode];
  305. for (NSUInteger k = 0; k < j; k++) {
  306. NSString *key = [NSString stringWithFormat:@"key-%lu", (unsigned long)k];
  307. id<FNode> node = NODE(([NSString stringWithFormat:@"leaf-%lu", (unsigned long)k]));
  308. innerMost = [innerMost updateImmediateChild:key withNewChild:node];
  309. }
  310. NSString *innerKey = [NSString stringWithFormat:@"key-%lu", (unsigned long)j];
  311. inner = [inner updateImmediateChild:innerKey withNewChild:innerMost];
  312. }
  313. NSString *outerKey = [NSString stringWithFormat:@"key-%lu", (unsigned long)i];
  314. outer = [outer updateImmediateChild:outerKey withNewChild:inner];
  315. }
  316. [engine updateServerCache:outer atPath:PATH(@"foo") merge:NO];
  317. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], outer);
  318. }
  319. - (void)testManyLargeLeafNodes {
  320. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  321. id<FNode> outer = [FEmptyNode emptyNode];
  322. for (NSUInteger i = 0; i < 30; i++) {
  323. NSString *outerKey = [NSString stringWithFormat:@"key-%lu", (unsigned long)i];
  324. outer = [outer updateImmediateChild:outerKey withNewChild:ONE_MEG_NODE];
  325. }
  326. [engine updateServerCache:outer atPath:PATH(@"foo") merge:NO];
  327. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], outer);
  328. }
  329. - (void)testPriorityWorks {
  330. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  331. [engine updateServerCache:NODE(@"bar-value") atPath:PATH(@"foo/bar") merge:NO];
  332. [engine updateServerCache:NODE(@"prio-value") atPath:PATH(@"foo/.priority") merge:NO];
  333. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")],
  334. NODE((@{@".priority" : @"prio-value", @"bar" : @"bar-value"})));
  335. }
  336. - (void)testSimilarSiblingsAreNotLoaded {
  337. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  338. [engine updateServerCache:NODE(@"value") atPath:PATH(@"foo/123") merge:NO];
  339. [engine updateServerCache:NODE(@"sibling-value") atPath:PATH(@"foo/1230") merge:NO];
  340. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/123")], NODE(@"value"));
  341. }
  342. // TODO: this test fails, but it is a rare edge case around priorities which would require a bunch
  343. // of code Fix whenever we have too much time on our hands
  344. - (void)priorityIsCleared {
  345. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  346. [engine updateServerCache:NODE((@{@"bar" : @"bar-value"})) atPath:PATH(@"foo") merge:NO];
  347. [engine updateServerCache:NODE(@"prio-value") atPath:PATH(@"foo/.priority") merge:NO];
  348. [engine updateServerCache:NODE(nil) atPath:PATH(@"foo/bar") merge:NO];
  349. [engine updateServerCache:NODE(@"baz-value") atPath:PATH(@"foo/baz") merge:NO];
  350. // Priority should have been cleaned out
  351. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@{@"baz" : @"baz-value"}));
  352. }
  353. - (void)testHugeLeafNode {
  354. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  355. [engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
  356. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], TEN_MEG_NODE);
  357. }
  358. - (void)testHugeLeafNodeSiblings {
  359. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  360. [engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo/one") merge:NO];
  361. [engine updateServerCache:TEN_MEG_MINUS_ONE_NODE atPath:PATH(@"foo/two") merge:NO];
  362. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/one")], TEN_MEG_NODE);
  363. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/two")], TEN_MEG_MINUS_ONE_NODE);
  364. }
  365. - (void)testHugeLeafNodeThenTinyLeafNode {
  366. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  367. [engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
  368. [engine updateServerCache:NODE(@"tiny") atPath:PATH(@"foo") merge:NO];
  369. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@"tiny"));
  370. }
  371. - (void)testHugeLeafNodeThenSmallerLeafNode {
  372. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  373. [engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
  374. [engine updateServerCache:FIVE_MEG_NODE atPath:PATH(@"foo") merge:NO];
  375. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], FIVE_MEG_NODE);
  376. }
  377. - (void)testHugeLeafNodeThenDeeperSet {
  378. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  379. [engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
  380. [engine updateServerCache:NODE(@"deep-value") atPath:PATH(@"foo/deep") merge:NO];
  381. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")],
  382. NODE((@{@"deep" : @"deep-value"})));
  383. }
  384. // Well this is awkward, but NSJSONSerialization fails to deserialize JSON with tiny/huge doubles
  385. // It is kind of bad we raise "invalid" data, but at least we don't crash *trollface*
  386. - (void)testExtremeDoublesAsServerCache {
  387. #ifdef TARGET_OS_IOS
  388. if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion >= 11) {
  389. // NSJSONSerialization on iOS 11 correctly serializes small and large doubles.
  390. return;
  391. }
  392. #endif
  393. #if TARGET_OS_MACCATALYST || TARGET_OS_OSX || TARGET_OS_WATCH
  394. return;
  395. #endif
  396. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  397. [engine updateServerCache:NODE((@{@"works" : @"value", @"fails" : @(2.225073858507201e-308)}))
  398. atPath:PATH(@"foo")
  399. merge:NO];
  400. // Will drop the tiny double
  401. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@{@"works" : @"value"}));
  402. XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/fails")], [FEmptyNode emptyNode]);
  403. }
  404. - (void)testLongValuesDontLosePrecision {
  405. id longValue = @1542405709418655810;
  406. id floatValue = @2.47;
  407. id<FNode> expectedData = NODE((@{@"long" : longValue, @"float" : floatValue}));
  408. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  409. [engine updateServerCache:expectedData atPath:PATH(@"foo") merge:NO];
  410. id<FNode> actualData = [engine serverCacheAtPath:PATH(@"foo")];
  411. NSDictionary *value = [actualData val];
  412. XCTAssertEqualObjects([value[@"long"] stringValue], [longValue stringValue]);
  413. XCTAssertEqualObjects([value[@"float"] stringValue], [floatValue stringValue]);
  414. }
  415. // NSJSONSerialization has a bug in which it rounds doubles wrongly so hashes end up not matching on
  416. // the server for some doubles (including 2.47). Make sure LevelDB has the correct hash for that
  417. - (void)testDoublesAreRoundedProperly {
  418. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  419. [engine updateServerCache:NODE(@(2.47)) atPath:PATH(@"foo") merge:NO];
  420. // Expected hash for 2.47 parsed correctly
  421. NSString *hashFor247 = @"EsibHXKcBp2/b/bn/a0C5WffcUU=";
  422. XCTAssertEqualObjects([[engine serverCacheAtPath:PATH(@"foo")] dataHash], hashFor247);
  423. }
  424. // NOTE: This deals with part of the NSDecimalNumber issue: namely that
  425. // [NSDecimalNumber longLongValue] is completely bonkers for decimals with
  426. // high precision. The decimal value below (close to 1000) is returned as -844!
  427. // http://www.openradar.me/radar?id=5007005597040640
  428. // This does not deal with the fact that this is not the same behavior as the
  429. // RTDB server. Given an NSDecimalNumber, the server stores decimals with the
  430. // full precision and returns these as NSDecimalNumbers too.
  431. // This means that the RTDB server can store the double value 2.47 as 2.47.
  432. // But if the double is wrapped in an NSDecimalNumber, the server apparently
  433. // stores the value 2.470000000000000512.
  434. // This means that the persistence layer will have a hard time determining whether
  435. // rounding is appropriate or not.
  436. // Similarly, as an NSDecimalNumber, the RTDB gladly stores and returns
  437. // 999.9999999999999487 without any rounding, although considered as a double,
  438. // the value will be 1000.
  439. - (void)testNSDecimalsAreRoundedProperly {
  440. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  441. id decimalValue = [NSDecimalNumber decimalNumberWithString:@"999.9999999999999487"];
  442. id expectedDecimalValue = [NSDecimalNumber decimalNumberWithString:@"1000"];
  443. [engine updateServerCache:NODE(decimalValue) atPath:PATH(@"foo") merge:NO];
  444. id<FNode> actualData = [engine serverCacheAtPath:PATH(@"foo")];
  445. NSNumber *actualDecimal = [actualData val];
  446. XCTAssertEqualObjects([actualDecimal stringValue], [expectedDecimalValue stringValue]);
  447. XCTAssertEqual(CFNumberGetType((CFNumberRef)actualDecimal), kCFNumberFloat64Type);
  448. }
  449. - (void)testIntegersAreReturnedsAsIntegers {
  450. id intValue = @247;
  451. id longValue = @1542405709418655810;
  452. id doubleValue = @0xFFFFFFFFFFFFFFFFUL; // This number can't be represented as a signed long.
  453. id<FNode> expectedData =
  454. NODE((@{@"int" : intValue, @"long" : longValue, @"double" : doubleValue}));
  455. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  456. [engine updateServerCache:expectedData atPath:PATH(@"foo") merge:NO];
  457. id<FNode> actualData = [engine serverCacheAtPath:PATH(@"foo")];
  458. NSNumber *actualInt = [actualData val][@"int"];
  459. NSNumber *actualLong = [actualData val][@"long"];
  460. NSNumber *actualDouble = [actualData val][@"double"];
  461. XCTAssertEqualObjects([actualInt stringValue], [intValue stringValue]);
  462. XCTAssertEqual(CFNumberGetType((CFNumberRef)actualInt), kCFNumberSInt64Type);
  463. XCTAssertEqualObjects([actualLong stringValue], [longValue stringValue]);
  464. XCTAssertEqual(CFNumberGetType((CFNumberRef)actualLong), kCFNumberSInt64Type);
  465. XCTAssertEqual(CFNumberGetType((CFNumberRef)actualDouble), kCFNumberSInt64Type);
  466. }
  467. // TODO[offline]: Somehow test estimated server size?
  468. // TODO[offline]: Test pruning!
  469. - (void)testSaveAndLoadTrackedQueries {
  470. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  471. NSArray *queries = @[
  472. [[FTrackedQuery alloc] initWithId:1 query:SAMPLE_QUERY lastUse:100 isActive:NO isComplete:NO],
  473. [[FTrackedQuery alloc] initWithId:2
  474. query:[FQuerySpec defaultQueryAtPath:PATH(@"a")]
  475. lastUse:200
  476. isActive:NO
  477. isComplete:NO],
  478. [[FTrackedQuery alloc] initWithId:3
  479. query:[FQuerySpec defaultQueryAtPath:PATH(@"b")]
  480. lastUse:300
  481. isActive:YES
  482. isComplete:NO],
  483. [[FTrackedQuery alloc] initWithId:4
  484. query:[FQuerySpec defaultQueryAtPath:PATH(@"c")]
  485. lastUse:400
  486. isActive:NO
  487. isComplete:YES],
  488. [[FTrackedQuery alloc] initWithId:5
  489. query:[FQuerySpec defaultQueryAtPath:PATH(@"foo")]
  490. lastUse:500
  491. isActive:NO
  492. isComplete:NO]
  493. ];
  494. [queries enumerateObjectsUsingBlock:^(FTrackedQuery *query, NSUInteger idx, BOOL *stop) {
  495. [engine saveTrackedQuery:query];
  496. }];
  497. XCTAssertEqualObjects([engine loadTrackedQueries], queries);
  498. }
  499. - (void)testOverwriteTrackedQueryById {
  500. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  501. FTrackedQuery *first = [[FTrackedQuery alloc] initWithId:1
  502. query:SAMPLE_QUERY
  503. lastUse:100
  504. isActive:NO
  505. isComplete:NO];
  506. FTrackedQuery *second = [[FTrackedQuery alloc] initWithId:1
  507. query:DEFAULT_FOO_QUERY
  508. lastUse:200
  509. isActive:YES
  510. isComplete:YES];
  511. [engine saveTrackedQuery:first];
  512. [engine saveTrackedQuery:second];
  513. XCTAssertEqualObjects([engine loadTrackedQueries], @[ second ]);
  514. }
  515. - (void)testDeleteTrackedQuery {
  516. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  517. FTrackedQuery *query1 =
  518. [[FTrackedQuery alloc] initWithId:1
  519. query:[FQuerySpec defaultQueryAtPath:PATH(@"a")]
  520. lastUse:100
  521. isActive:NO
  522. isComplete:NO];
  523. FTrackedQuery *query2 =
  524. [[FTrackedQuery alloc] initWithId:2
  525. query:[FQuerySpec defaultQueryAtPath:PATH(@"b")]
  526. lastUse:200
  527. isActive:YES
  528. isComplete:NO];
  529. FTrackedQuery *query3 =
  530. [[FTrackedQuery alloc] initWithId:3
  531. query:[FQuerySpec defaultQueryAtPath:PATH(@"c")]
  532. lastUse:300
  533. isActive:NO
  534. isComplete:YES];
  535. [engine saveTrackedQuery:query1];
  536. [engine saveTrackedQuery:query2];
  537. [engine saveTrackedQuery:query3];
  538. [engine removeTrackedQuery:2];
  539. XCTAssertEqualObjects([engine loadTrackedQueries], (@[ query1, query3 ]));
  540. }
  541. - (void)testSaveAndLoadTrackedQueryKeys {
  542. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  543. NSSet *keys = [NSSet setWithArray:@[ @"foo", @"☁", @"10", @"٩(͡๏̯͡๏)۶" ]];
  544. [engine setTrackedQueryKeys:keys forQueryId:1];
  545. [engine setTrackedQueryKeys:[NSSet setWithArray:@[ @"not", @"included" ]] forQueryId:2];
  546. XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1], keys);
  547. }
  548. - (void)testSaveOverwritesTrackedQueryKeys {
  549. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  550. [engine setTrackedQueryKeys:[NSSet setWithArray:@[ @"a", @"b", @"c" ]] forQueryId:1];
  551. [engine setTrackedQueryKeys:[NSSet setWithArray:@[ @"c", @"d", @"e" ]] forQueryId:1];
  552. XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1],
  553. ([NSSet setWithArray:@[ @"c", @"d", @"e" ]]));
  554. }
  555. - (void)testUpdateTrackedQueryKeys {
  556. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  557. [engine setTrackedQueryKeys:[NSSet setWithArray:@[ @"a", @"b", @"c" ]] forQueryId:1];
  558. [engine updateTrackedQueryKeysWithAddedKeys:[NSSet setWithArray:@[ @"c", @"d", @"e" ]]
  559. removedKeys:[NSSet setWithArray:@[ @"a", @"b" ]]
  560. forQueryId:1];
  561. XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1],
  562. ([NSSet setWithArray:@[ @"c", @"d", @"e" ]]));
  563. }
  564. - (void)testRemoveTrackedQueryRemovesTrackedQueryKeys {
  565. FLevelDBStorageEngine *engine = [self cleanStorageEngine];
  566. FTrackedQuery *query1 =
  567. [[FTrackedQuery alloc] initWithId:1
  568. query:[FQuerySpec defaultQueryAtPath:PATH(@"a")]
  569. lastUse:100
  570. isActive:NO
  571. isComplete:NO];
  572. FTrackedQuery *query2 =
  573. [[FTrackedQuery alloc] initWithId:2
  574. query:[FQuerySpec defaultQueryAtPath:PATH(@"b")]
  575. lastUse:200
  576. isActive:NO
  577. isComplete:NO];
  578. [engine saveTrackedQuery:query1];
  579. [engine saveTrackedQuery:query2];
  580. [engine setTrackedQueryKeys:[NSSet setWithArray:@[ @"a", @"b" ]] forQueryId:1];
  581. [engine setTrackedQueryKeys:[NSSet setWithArray:@[ @"b", @"c" ]] forQueryId:2];
  582. XCTAssertEqualObjects([engine loadTrackedQueries], (@[ query1, query2 ]));
  583. XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1],
  584. ([NSSet setWithArray:@[ @"a", @"b" ]]));
  585. XCTAssertEqualObjects([engine trackedQueryKeysForQuery:2],
  586. ([NSSet setWithArray:@[ @"b", @"c" ]]));
  587. [engine removeTrackedQuery:1];
  588. XCTAssertEqualObjects([engine loadTrackedQueries], (@[ query2 ]));
  589. XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1], [NSSet set]);
  590. XCTAssertEqualObjects([engine trackedQueryKeysForQuery:2],
  591. ([NSSet setWithArray:@[ @"b", @"c" ]]));
  592. }
  593. @end