FSTQueryCacheTests.mm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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 "Firestore/Example/Tests/Local/FSTQueryCacheTests.h"
  17. #include <set>
  18. #import "Firestore/Source/Core/FSTQuery.h"
  19. #import "Firestore/Source/Local/FSTPersistence.h"
  20. #import "Firestore/Source/Local/FSTQueryData.h"
  21. #import "Firestore/Source/Util/FSTClasses.h"
  22. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  23. #import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.h"
  24. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  25. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  26. namespace testutil = firebase::firestore::testutil;
  27. using firebase::firestore::model::DocumentKey;
  28. using firebase::firestore::model::SnapshotVersion;
  29. using firebase::firestore::model::DocumentKeySet;
  30. NS_ASSUME_NONNULL_BEGIN
  31. @implementation FSTQueryCacheTests {
  32. FSTQuery *_queryRooms;
  33. FSTListenSequenceNumber _previousSequenceNumber;
  34. FSTTargetID _previousTargetID;
  35. FSTTestSnapshotVersion _previousSnapshotVersion;
  36. }
  37. - (void)setUp {
  38. [super setUp];
  39. _queryRooms = FSTTestQuery("rooms");
  40. _previousSequenceNumber = 1000;
  41. _previousTargetID = 500;
  42. _previousSnapshotVersion = 100;
  43. }
  44. - (void)tearDown {
  45. [self.persistence shutdown];
  46. }
  47. /**
  48. * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
  49. * FSTSpecTests since it is incomplete without the implementations supplied by its subclasses.
  50. */
  51. - (BOOL)isTestBaseClass {
  52. return [self class] == [FSTQueryCacheTests class];
  53. }
  54. - (void)testReadQueryNotInCache {
  55. if ([self isTestBaseClass]) return;
  56. self.persistence.run("testReadQueryNotInCache",
  57. [&]() { XCTAssertNil([self.queryCache queryDataForQuery:_queryRooms]); });
  58. }
  59. - (void)testSetAndReadAQuery {
  60. if ([self isTestBaseClass]) return;
  61. self.persistence.run("testSetAndReadAQuery", [&]() {
  62. FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms];
  63. [self.queryCache addQueryData:queryData];
  64. FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
  65. XCTAssertEqualObjects(result.query, queryData.query);
  66. XCTAssertEqual(result.targetID, queryData.targetID);
  67. XCTAssertEqualObjects(result.resumeToken, queryData.resumeToken);
  68. });
  69. }
  70. - (void)testCanonicalIDCollision {
  71. if ([self isTestBaseClass]) return;
  72. self.persistence.run("testCanonicalIDCollision", [&]() {
  73. // Type information is currently lost in our canonicalID implementations so this currently an
  74. // easy way to force colliding canonicalIDs
  75. FSTQuery *q1 = [FSTTestQuery("a") queryByAddingFilter:FSTTestFilter("foo", @"==", @(1))];
  76. FSTQuery *q2 = [FSTTestQuery("a") queryByAddingFilter:FSTTestFilter("foo", @"==", @"1")];
  77. XCTAssertEqualObjects(q1.canonicalID, q2.canonicalID);
  78. FSTQueryData *data1 = [self queryDataWithQuery:q1];
  79. [self.queryCache addQueryData:data1];
  80. // Using the other query should not return the query cache entry despite equal canonicalIDs.
  81. XCTAssertNil([self.queryCache queryDataForQuery:q2]);
  82. XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
  83. FSTQueryData *data2 = [self queryDataWithQuery:q2];
  84. [self.queryCache addQueryData:data2];
  85. XCTAssertEqual([self.queryCache count], 2);
  86. XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
  87. XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2);
  88. [self.queryCache removeQueryData:data1];
  89. XCTAssertNil([self.queryCache queryDataForQuery:q1]);
  90. XCTAssertEqualObjects([self.queryCache queryDataForQuery:q2], data2);
  91. XCTAssertEqual([self.queryCache count], 1);
  92. [self.queryCache removeQueryData:data2];
  93. XCTAssertNil([self.queryCache queryDataForQuery:q1]);
  94. XCTAssertNil([self.queryCache queryDataForQuery:q2]);
  95. XCTAssertEqual([self.queryCache count], 0);
  96. });
  97. }
  98. - (void)testSetQueryToNewValue {
  99. if ([self isTestBaseClass]) return;
  100. self.persistence.run("testSetQueryToNewValue", [&]() {
  101. FSTQueryData *queryData1 =
  102. [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:1];
  103. [self.queryCache addQueryData:queryData1];
  104. FSTQueryData *queryData2 =
  105. [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:2];
  106. [self.queryCache addQueryData:queryData2];
  107. FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
  108. XCTAssertNotEqualObjects(queryData2.resumeToken, queryData1.resumeToken);
  109. XCTAssertNotEqual(queryData2.snapshotVersion, queryData1.snapshotVersion);
  110. XCTAssertEqualObjects(result.resumeToken, queryData2.resumeToken);
  111. XCTAssertEqual(result.snapshotVersion, queryData2.snapshotVersion);
  112. });
  113. }
  114. - (void)testRemoveQuery {
  115. if ([self isTestBaseClass]) return;
  116. self.persistence.run("testRemoveQuery", [&]() {
  117. FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms];
  118. [self.queryCache addQueryData:queryData1];
  119. [self.queryCache removeQueryData:queryData1];
  120. FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
  121. XCTAssertNil(result);
  122. });
  123. }
  124. - (void)testRemoveNonExistentQuery {
  125. if ([self isTestBaseClass]) return;
  126. self.persistence.run("testRemoveNonExistentQuery", [&]() {
  127. FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms];
  128. // no-op, but make sure it doesn't throw.
  129. XCTAssertNoThrow([self.queryCache removeQueryData:queryData]);
  130. });
  131. }
  132. - (void)testRemoveQueryRemovesMatchingKeysToo {
  133. if ([self isTestBaseClass]) return;
  134. self.persistence.run("testRemoveQueryRemovesMatchingKeysToo", [&]() {
  135. FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms];
  136. [self.queryCache addQueryData:rooms];
  137. DocumentKey key1 = testutil::Key("rooms/foo");
  138. DocumentKey key2 = testutil::Key("rooms/bar");
  139. [self addMatchingKey:key1 forTargetID:rooms.targetID];
  140. [self addMatchingKey:key2 forTargetID:rooms.targetID];
  141. XCTAssertTrue([self.queryCache containsKey:key1]);
  142. XCTAssertTrue([self.queryCache containsKey:key2]);
  143. [self.queryCache removeQueryData:rooms];
  144. XCTAssertFalse([self.queryCache containsKey:key1]);
  145. XCTAssertFalse([self.queryCache containsKey:key2]);
  146. });
  147. }
  148. - (void)testAddOrRemoveMatchingKeys {
  149. if ([self isTestBaseClass]) return;
  150. self.persistence.run("testAddOrRemoveMatchingKeys", [&]() {
  151. DocumentKey key = testutil::Key("foo/bar");
  152. XCTAssertFalse([self.queryCache containsKey:key]);
  153. [self addMatchingKey:key forTargetID:1];
  154. XCTAssertTrue([self.queryCache containsKey:key]);
  155. [self addMatchingKey:key forTargetID:2];
  156. XCTAssertTrue([self.queryCache containsKey:key]);
  157. [self removeMatchingKey:key forTargetID:1];
  158. XCTAssertTrue([self.queryCache containsKey:key]);
  159. [self removeMatchingKey:key forTargetID:2];
  160. XCTAssertFalse([self.queryCache containsKey:key]);
  161. });
  162. }
  163. - (void)testRemoveMatchingKeysForTargetID {
  164. if ([self isTestBaseClass]) return;
  165. self.persistence.run("testRemoveMatchingKeysForTargetID", [&]() {
  166. DocumentKey key1 = testutil::Key("foo/bar");
  167. DocumentKey key2 = testutil::Key("foo/baz");
  168. DocumentKey key3 = testutil::Key("foo/blah");
  169. [self addMatchingKey:key1 forTargetID:1];
  170. [self addMatchingKey:key2 forTargetID:1];
  171. [self addMatchingKey:key3 forTargetID:2];
  172. XCTAssertTrue([self.queryCache containsKey:key1]);
  173. XCTAssertTrue([self.queryCache containsKey:key2]);
  174. XCTAssertTrue([self.queryCache containsKey:key3]);
  175. [self.queryCache removeMatchingKeysForTargetID:1];
  176. XCTAssertFalse([self.queryCache containsKey:key1]);
  177. XCTAssertFalse([self.queryCache containsKey:key2]);
  178. XCTAssertTrue([self.queryCache containsKey:key3]);
  179. [self.queryCache removeMatchingKeysForTargetID:2];
  180. XCTAssertFalse([self.queryCache containsKey:key1]);
  181. XCTAssertFalse([self.queryCache containsKey:key2]);
  182. XCTAssertFalse([self.queryCache containsKey:key3]);
  183. });
  184. }
  185. - (void)testMatchingKeysForTargetID {
  186. if ([self isTestBaseClass]) return;
  187. self.persistence.run("testMatchingKeysForTargetID", [&]() {
  188. DocumentKey key1 = testutil::Key("foo/bar");
  189. DocumentKey key2 = testutil::Key("foo/baz");
  190. DocumentKey key3 = testutil::Key("foo/blah");
  191. [self addMatchingKey:key1 forTargetID:1];
  192. [self addMatchingKey:key2 forTargetID:1];
  193. [self addMatchingKey:key3 forTargetID:2];
  194. XCTAssertEqual([self.queryCache matchingKeysForTargetID:1], (DocumentKeySet{key1, key2}));
  195. XCTAssertEqual([self.queryCache matchingKeysForTargetID:2], (DocumentKeySet{key3}));
  196. [self addMatchingKey:key1 forTargetID:2];
  197. XCTAssertEqual([self.queryCache matchingKeysForTargetID:1], (DocumentKeySet{key1, key2}));
  198. XCTAssertEqual([self.queryCache matchingKeysForTargetID:2], (DocumentKeySet{key1, key3}));
  199. });
  200. }
  201. - (void)testHighestListenSequenceNumber {
  202. if ([self isTestBaseClass]) return;
  203. self.persistence.run("testHighestListenSequenceNumber", [&]() {
  204. FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("rooms")
  205. targetID:1
  206. listenSequenceNumber:10
  207. purpose:FSTQueryPurposeListen];
  208. [self.queryCache addQueryData:query1];
  209. FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("halls")
  210. targetID:2
  211. listenSequenceNumber:20
  212. purpose:FSTQueryPurposeListen];
  213. [self.queryCache addQueryData:query2];
  214. XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20);
  215. // Sequence numbers never come down.
  216. [self.queryCache removeQueryData:query2];
  217. XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20);
  218. FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("garages")
  219. targetID:42
  220. listenSequenceNumber:100
  221. purpose:FSTQueryPurposeListen];
  222. [self.queryCache addQueryData:query3];
  223. XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
  224. [self.queryCache removeQueryData:query1];
  225. XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
  226. [self.queryCache removeQueryData:query3];
  227. XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
  228. });
  229. }
  230. - (void)testHighestTargetID {
  231. if ([self isTestBaseClass]) return;
  232. self.persistence.run("testHighestTargetID", [&]() {
  233. XCTAssertEqual([self.queryCache highestTargetID], 0);
  234. FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("rooms")
  235. targetID:1
  236. listenSequenceNumber:10
  237. purpose:FSTQueryPurposeListen];
  238. DocumentKey key1 = testutil::Key("rooms/bar");
  239. DocumentKey key2 = testutil::Key("rooms/foo");
  240. [self.queryCache addQueryData:query1];
  241. [self addMatchingKey:key1 forTargetID:1];
  242. [self addMatchingKey:key2 forTargetID:1];
  243. FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("halls")
  244. targetID:2
  245. listenSequenceNumber:20
  246. purpose:FSTQueryPurposeListen];
  247. DocumentKey key3 = testutil::Key("halls/foo");
  248. [self.queryCache addQueryData:query2];
  249. [self addMatchingKey:key3 forTargetID:2];
  250. XCTAssertEqual([self.queryCache highestTargetID], 2);
  251. // TargetIDs never come down.
  252. [self.queryCache removeQueryData:query2];
  253. XCTAssertEqual([self.queryCache highestTargetID], 2);
  254. // A query with an empty result set still counts.
  255. FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery("garages")
  256. targetID:42
  257. listenSequenceNumber:100
  258. purpose:FSTQueryPurposeListen];
  259. [self.queryCache addQueryData:query3];
  260. XCTAssertEqual([self.queryCache highestTargetID], 42);
  261. [self.queryCache removeQueryData:query1];
  262. XCTAssertEqual([self.queryCache highestTargetID], 42);
  263. [self.queryCache removeQueryData:query3];
  264. XCTAssertEqual([self.queryCache highestTargetID], 42);
  265. });
  266. }
  267. - (void)testLastRemoteSnapshotVersion {
  268. if ([self isTestBaseClass]) return;
  269. self.persistence.run("testLastRemoteSnapshotVersion", [&]() {
  270. XCTAssertEqual([self.queryCache lastRemoteSnapshotVersion], SnapshotVersion::None());
  271. // Can set the snapshot version.
  272. [self.queryCache setLastRemoteSnapshotVersion:testutil::Version(42)];
  273. XCTAssertEqual([self.queryCache lastRemoteSnapshotVersion], testutil::Version(42));
  274. });
  275. }
  276. #pragma mark - Helpers
  277. /**
  278. * Creates a new FSTQueryData object from the given parameters, synthesizing a resume token from
  279. * the snapshot version.
  280. */
  281. - (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query {
  282. return [self queryDataWithQuery:query
  283. targetID:++_previousTargetID
  284. listenSequenceNumber:++_previousSequenceNumber
  285. version:++_previousSnapshotVersion];
  286. }
  287. - (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query
  288. targetID:(FSTTargetID)targetID
  289. listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
  290. version:(FSTTestSnapshotVersion)version {
  291. NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(version);
  292. return [[FSTQueryData alloc] initWithQuery:query
  293. targetID:targetID
  294. listenSequenceNumber:sequenceNumber
  295. purpose:FSTQueryPurposeListen
  296. snapshotVersion:testutil::Version(version)
  297. resumeToken:resumeToken];
  298. }
  299. - (void)addMatchingKey:(const DocumentKey &)key forTargetID:(FSTTargetID)targetID {
  300. DocumentKeySet keys{key};
  301. [self.queryCache addMatchingKeys:keys forTargetID:targetID];
  302. }
  303. - (void)removeMatchingKey:(const DocumentKey &)key forTargetID:(FSTTargetID)targetID {
  304. DocumentKeySet keys{key};
  305. [self.queryCache removeMatchingKeys:keys forTargetID:targetID];
  306. }
  307. @end
  308. NS_ASSUME_NONNULL_END