FLevelDBStorageEngineTests.m 28 KB

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