FLevelDBStorageEngineTests.m 28 KB

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