FSTMutationQueueTests.mm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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/FSTMutationQueueTests.h"
  17. #import <FirebaseFirestore/FIRTimestamp.h>
  18. #import "Firestore/Source/Core/FSTQuery.h"
  19. #import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
  20. #import "Firestore/Source/Local/FSTMutationQueue.h"
  21. #import "Firestore/Source/Local/FSTPersistence.h"
  22. #import "Firestore/Source/Model/FSTMutation.h"
  23. #import "Firestore/Source/Model/FSTMutationBatch.h"
  24. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  25. #include "Firestore/core/src/firebase/firestore/auth/user.h"
  26. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  27. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  28. namespace testutil = firebase::firestore::testutil;
  29. using firebase::firestore::auth::User;
  30. using firebase::firestore::model::DocumentKey;
  31. NS_ASSUME_NONNULL_BEGIN
  32. @implementation FSTMutationQueueTests
  33. - (void)tearDown {
  34. [self.persistence shutdown];
  35. [super tearDown];
  36. }
  37. /**
  38. * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
  39. * FSTMutationQueueTests since it is incomplete without the implementations supplied by its
  40. * subclasses.
  41. */
  42. - (BOOL)isTestBaseClass {
  43. return [self class] == [FSTMutationQueueTests class];
  44. }
  45. - (void)testCountBatches {
  46. if ([self isTestBaseClass]) return;
  47. self.persistence.run("testCountBatches", [&]() {
  48. XCTAssertEqual(0, [self batchCount]);
  49. XCTAssertTrue([self.mutationQueue isEmpty]);
  50. FSTMutationBatch *batch1 = [self addMutationBatch];
  51. XCTAssertEqual(1, [self batchCount]);
  52. XCTAssertFalse([self.mutationQueue isEmpty]);
  53. FSTMutationBatch *batch2 = [self addMutationBatch];
  54. XCTAssertEqual(2, [self batchCount]);
  55. [self.mutationQueue removeMutationBatches:@[ batch2 ]];
  56. XCTAssertEqual(1, [self batchCount]);
  57. [self.mutationQueue removeMutationBatches:@[ batch1 ]];
  58. XCTAssertEqual(0, [self batchCount]);
  59. XCTAssertTrue([self.mutationQueue isEmpty]);
  60. });
  61. }
  62. - (void)testAcknowledgeBatchID {
  63. if ([self isTestBaseClass]) return;
  64. // Initial state of an empty queue
  65. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
  66. // Adding mutation batches should not change the highest acked batchID.
  67. self.persistence.run("testAcknowledgeBatchID", [&]() {
  68. FSTMutationBatch *batch1 = [self addMutationBatch];
  69. FSTMutationBatch *batch2 = [self addMutationBatch];
  70. FSTMutationBatch *batch3 = [self addMutationBatch];
  71. XCTAssertGreaterThan(batch1.batchID, kFSTBatchIDUnknown);
  72. XCTAssertGreaterThan(batch2.batchID, batch1.batchID);
  73. XCTAssertGreaterThan(batch3.batchID, batch2.batchID);
  74. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
  75. [self.mutationQueue acknowledgeBatch:batch1 streamToken:nil];
  76. [self.mutationQueue acknowledgeBatch:batch2 streamToken:nil];
  77. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
  78. [self.mutationQueue removeMutationBatches:@[ batch1 ]];
  79. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
  80. [self.mutationQueue removeMutationBatches:@[ batch2 ]];
  81. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
  82. // Batch 3 never acknowledged.
  83. [self.mutationQueue removeMutationBatches:@[ batch3 ]];
  84. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
  85. });
  86. }
  87. - (void)testAcknowledgeThenRemove {
  88. if ([self isTestBaseClass]) return;
  89. self.persistence.run("testAcknowledgeThenRemove", [&]() {
  90. FSTMutationBatch *batch1 = [self addMutationBatch];
  91. [self.mutationQueue acknowledgeBatch:batch1 streamToken:nil];
  92. [self.mutationQueue removeMutationBatches:@[ batch1 ]];
  93. XCTAssertEqual([self batchCount], 0);
  94. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch1.batchID);
  95. });
  96. }
  97. - (void)testHighestAcknowledgedBatchIDNeverExceedsNextBatchID {
  98. if ([self isTestBaseClass]) return;
  99. FSTMutationBatch *batch1 =
  100. self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID batch1",
  101. [&]() -> FSTMutationBatch * { return [self addMutationBatch]; });
  102. FSTMutationBatch *batch2 =
  103. self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID batch2",
  104. [&]() -> FSTMutationBatch * { return [self addMutationBatch]; });
  105. self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID", [&]() {
  106. [self.mutationQueue acknowledgeBatch:batch1 streamToken:nil];
  107. [self.mutationQueue acknowledgeBatch:batch2 streamToken:nil];
  108. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
  109. [self.mutationQueue removeMutationBatches:@[ batch1, batch2 ]];
  110. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], batch2.batchID);
  111. });
  112. // Restart the queue so that nextBatchID will be reset.
  113. FSTMutationBatch *batch = self.persistence.run(
  114. "testHighestAcknowledgedBatchIDNeverExceedsNextBatchID restart", [&]() -> FSTMutationBatch * {
  115. self.mutationQueue = [self.persistence mutationQueueForUser:User("user")];
  116. [self.mutationQueue start];
  117. // Verify that on restart with an empty queue, nextBatchID falls to a lower value.
  118. XCTAssertLessThan(self.mutationQueue.nextBatchID, batch2.batchID);
  119. // As a result highestAcknowledgedBatchID must also reset lower.
  120. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
  121. // The mutation queue will reset the next batchID after all mutations are removed so adding
  122. // another mutation will cause a collision.
  123. FSTMutationBatch *newBatch = [self addMutationBatch];
  124. XCTAssertEqual(newBatch.batchID, batch1.batchID);
  125. return newBatch;
  126. });
  127. self.persistence.run("testHighestAcknowledgedBatchIDNeverExceedsNextBatchID restart2", [&]() {
  128. // Restart the queue with one unacknowledged batch in it.
  129. [self.mutationQueue start];
  130. XCTAssertEqual([self.mutationQueue nextBatchID], batch.batchID + 1);
  131. // highestAcknowledgedBatchID must still be kFSTBatchIDUnknown.
  132. XCTAssertEqual([self.mutationQueue highestAcknowledgedBatchID], kFSTBatchIDUnknown);
  133. });
  134. }
  135. - (void)testLookupMutationBatch {
  136. if ([self isTestBaseClass]) return;
  137. // Searching on an empty queue should not find a non-existent batch
  138. self.persistence.run("testLookupMutationBatch", [&]() {
  139. FSTMutationBatch *notFound = [self.mutationQueue lookupMutationBatch:42];
  140. XCTAssertNil(notFound);
  141. NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
  142. NSArray<FSTMutationBatch *> *removed = [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
  143. // After removing, a batch should not be found
  144. for (NSUInteger i = 0; i < removed.count; i++) {
  145. notFound = [self.mutationQueue lookupMutationBatch:removed[i].batchID];
  146. XCTAssertNil(notFound);
  147. }
  148. // Remaining entries should still be found
  149. for (FSTMutationBatch *batch in batches) {
  150. FSTMutationBatch *found = [self.mutationQueue lookupMutationBatch:batch.batchID];
  151. XCTAssertEqual(found.batchID, batch.batchID);
  152. }
  153. // Even on a nonempty queue searching should not find a non-existent batch
  154. notFound = [self.mutationQueue lookupMutationBatch:42];
  155. XCTAssertNil(notFound);
  156. });
  157. }
  158. - (void)testNextMutationBatchAfterBatchID {
  159. if ([self isTestBaseClass]) return;
  160. self.persistence.run("testNextMutationBatchAfterBatchID", [&]() {
  161. NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
  162. // This is an array of successors assuming the removals below will happen:
  163. NSArray<FSTMutationBatch *> *afters = @[ batches[3], batches[8], batches[8] ];
  164. NSArray<FSTMutationBatch *> *removed = [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
  165. for (NSUInteger i = 0; i < batches.count - 1; i++) {
  166. FSTMutationBatch *current = batches[i];
  167. FSTMutationBatch *next = batches[i + 1];
  168. FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:current.batchID];
  169. XCTAssertEqual(found.batchID, next.batchID);
  170. }
  171. for (NSUInteger i = 0; i < removed.count; i++) {
  172. FSTMutationBatch *current = removed[i];
  173. FSTMutationBatch *next = afters[i];
  174. FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:current.batchID];
  175. XCTAssertEqual(found.batchID, next.batchID);
  176. }
  177. FSTMutationBatch *first = batches[0];
  178. FSTMutationBatch *found = [self.mutationQueue nextMutationBatchAfterBatchID:first.batchID - 42];
  179. XCTAssertEqual(found.batchID, first.batchID);
  180. FSTMutationBatch *last = batches[batches.count - 1];
  181. FSTMutationBatch *notFound = [self.mutationQueue nextMutationBatchAfterBatchID:last.batchID];
  182. XCTAssertNil(notFound);
  183. });
  184. }
  185. - (void)testNextMutationBatchAfterBatchIDSkipsAcknowledgedBatches {
  186. if ([self isTestBaseClass]) return;
  187. NSMutableArray<FSTMutationBatch *> *batches = self.persistence.run(
  188. "testNextMutationBatchAfterBatchIDSkipsAcknowledgedBatches newBatches",
  189. [&]() -> NSMutableArray<FSTMutationBatch *> * {
  190. NSMutableArray<FSTMutationBatch *> *newBatches = [self createBatches:3];
  191. XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:kFSTBatchIDUnknown],
  192. newBatches[0]);
  193. return newBatches;
  194. });
  195. self.persistence.run("testNextMutationBatchAfterBatchIDSkipsAcknowledgedBatches", [&]() {
  196. [self.mutationQueue acknowledgeBatch:batches[0] streamToken:nil];
  197. XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:kFSTBatchIDUnknown],
  198. batches[1]);
  199. XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:batches[0].batchID],
  200. batches[1]);
  201. XCTAssertEqualObjects([self.mutationQueue nextMutationBatchAfterBatchID:batches[1].batchID],
  202. batches[2]);
  203. });
  204. }
  205. - (void)testAllMutationBatchesThroughBatchID {
  206. if ([self isTestBaseClass]) return;
  207. self.persistence.run("testAllMutationBatchesThroughBatchID", [&]() {
  208. NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
  209. [self makeHoles:@[ @2, @6, @7 ] inBatches:batches];
  210. NSArray<FSTMutationBatch *> *found, *expected;
  211. found = [self.mutationQueue allMutationBatchesThroughBatchID:batches[0].batchID - 1];
  212. XCTAssertEqualObjects(found, (@[]));
  213. for (NSUInteger i = 0; i < batches.count; i++) {
  214. found = [self.mutationQueue allMutationBatchesThroughBatchID:batches[i].batchID];
  215. expected = [batches subarrayWithRange:NSMakeRange(0, i + 1)];
  216. XCTAssertEqualObjects(found, expected, @"for index %lu", (unsigned long)i);
  217. }
  218. });
  219. }
  220. - (void)testAllMutationBatchesAffectingDocumentKey {
  221. if ([self isTestBaseClass]) return;
  222. self.persistence.run("testAllMutationBatchesAffectingDocumentKey", [&]() {
  223. NSArray<FSTMutation *> *mutations = @[
  224. FSTTestSetMutation(@"fob/bar",
  225. @{ @"a" : @1 }),
  226. FSTTestSetMutation(@"foo/bar",
  227. @{ @"a" : @1 }),
  228. FSTTestPatchMutation("foo/bar",
  229. @{ @"b" : @1 }, {}),
  230. FSTTestSetMutation(@"foo/bar/suffix/key",
  231. @{ @"a" : @1 }),
  232. FSTTestSetMutation(@"foo/baz",
  233. @{ @"a" : @1 }),
  234. FSTTestSetMutation(@"food/bar",
  235. @{ @"a" : @1 })
  236. ];
  237. // Store all the mutations.
  238. NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
  239. for (FSTMutation *mutation in mutations) {
  240. FSTMutationBatch *batch =
  241. [self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
  242. mutations:@[ mutation ]];
  243. [batches addObject:batch];
  244. }
  245. NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2] ];
  246. NSArray<FSTMutationBatch *> *matches =
  247. [self.mutationQueue allMutationBatchesAffectingDocumentKey:testutil::Key("foo/bar")];
  248. XCTAssertEqualObjects(matches, expected);
  249. });
  250. }
  251. - (void)testAllMutationBatchesAffectingQuery {
  252. if ([self isTestBaseClass]) return;
  253. self.persistence.run("testAllMutationBatchesAffectingQuery", [&]() {
  254. NSArray<FSTMutation *> *mutations = @[
  255. FSTTestSetMutation(@"fob/bar",
  256. @{ @"a" : @1 }),
  257. FSTTestSetMutation(@"foo/bar",
  258. @{ @"a" : @1 }),
  259. FSTTestPatchMutation("foo/bar",
  260. @{ @"b" : @1 }, {}),
  261. FSTTestSetMutation(@"foo/bar/suffix/key",
  262. @{ @"a" : @1 }),
  263. FSTTestSetMutation(@"foo/baz",
  264. @{ @"a" : @1 }),
  265. FSTTestSetMutation(@"food/bar",
  266. @{ @"a" : @1 })
  267. ];
  268. // Store all the mutations.
  269. NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
  270. for (FSTMutation *mutation in mutations) {
  271. FSTMutationBatch *batch =
  272. [self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
  273. mutations:@[ mutation ]];
  274. [batches addObject:batch];
  275. }
  276. NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2], batches[4] ];
  277. FSTQuery *query = FSTTestQuery("foo");
  278. NSArray<FSTMutationBatch *> *matches =
  279. [self.mutationQueue allMutationBatchesAffectingQuery:query];
  280. XCTAssertEqualObjects(matches, expected);
  281. });
  282. }
  283. - (void)testRemoveMutationBatches {
  284. if ([self isTestBaseClass]) return;
  285. self.persistence.run("testRemoveMutationBatches", [&]() {
  286. NSMutableArray<FSTMutationBatch *> *batches = [self createBatches:10];
  287. [self.mutationQueue removeMutationBatches:@[ batches[0] ]];
  288. [batches removeObjectAtIndex:0];
  289. FSTMutationBatch *last = batches[batches.count - 1];
  290. XCTAssertEqual([self batchCount], 9);
  291. NSArray<FSTMutationBatch *> *found;
  292. found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
  293. XCTAssertEqualObjects(found, batches);
  294. XCTAssertEqual(found.count, 9);
  295. [self.mutationQueue removeMutationBatches:@[ batches[0], batches[1], batches[2] ]];
  296. [batches removeObjectsInRange:NSMakeRange(0, 3)];
  297. XCTAssertEqual([self batchCount], 6);
  298. found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
  299. XCTAssertEqualObjects(found, batches);
  300. XCTAssertEqual(found.count, 6);
  301. [self.mutationQueue removeMutationBatches:@[ batches[batches.count - 1] ]];
  302. [batches removeObjectAtIndex:batches.count - 1];
  303. XCTAssertEqual([self batchCount], 5);
  304. found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
  305. XCTAssertEqualObjects(found, batches);
  306. XCTAssertEqual(found.count, 5);
  307. [self.mutationQueue removeMutationBatches:@[ batches[3] ]];
  308. [batches removeObjectAtIndex:3];
  309. XCTAssertEqual([self batchCount], 4);
  310. [self.mutationQueue removeMutationBatches:@[ batches[1] ]];
  311. [batches removeObjectAtIndex:1];
  312. XCTAssertEqual([self batchCount], 3);
  313. found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
  314. XCTAssertEqualObjects(found, batches);
  315. XCTAssertEqual(found.count, 3);
  316. XCTAssertFalse([self.mutationQueue isEmpty]);
  317. [self.mutationQueue removeMutationBatches:batches];
  318. found = [self.mutationQueue allMutationBatchesThroughBatchID:last.batchID];
  319. XCTAssertEqualObjects(found, @[]);
  320. XCTAssertEqual(found.count, 0);
  321. XCTAssertTrue([self.mutationQueue isEmpty]);
  322. });
  323. }
  324. - (void)testRemoveMutationBatchesEmitsGarbageEvents {
  325. if ([self isTestBaseClass]) return;
  326. FSTEagerGarbageCollector *garbageCollector = [[FSTEagerGarbageCollector alloc] init];
  327. [garbageCollector addGarbageSource:self.mutationQueue];
  328. NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
  329. self.persistence.run("testRemoveMutationBatchesEmitsGarbageEvents", [&]() {
  330. [batches addObjectsFromArray:@[
  331. [self addMutationBatchWithKey:@"foo/bar"],
  332. [self addMutationBatchWithKey:@"foo/ba"],
  333. [self addMutationBatchWithKey:@"foo/bar2"],
  334. [self addMutationBatchWithKey:@"foo/bar"],
  335. [self addMutationBatchWithKey:@"foo/bar/suffix/baz"],
  336. [self addMutationBatchWithKey:@"bar/baz"],
  337. ]];
  338. [self.mutationQueue removeMutationBatches:@[ batches[0] ]];
  339. std::set<DocumentKey> garbage = [garbageCollector collectGarbage];
  340. XCTAssertEqual(garbage, std::set<DocumentKey>({}));
  341. [self.mutationQueue removeMutationBatches:@[ batches[1] ]];
  342. garbage = [garbageCollector collectGarbage];
  343. XCTAssertEqual(garbage, std::set<DocumentKey>({testutil::Key("foo/ba")}));
  344. [self.mutationQueue removeMutationBatches:@[ batches[5] ]];
  345. garbage = [garbageCollector collectGarbage];
  346. XCTAssertEqual(garbage, std::set<DocumentKey>({testutil::Key("bar/baz")}));
  347. [self.mutationQueue removeMutationBatches:@[ batches[2], batches[3] ]];
  348. garbage = [garbageCollector collectGarbage];
  349. XCTAssertEqual(garbage,
  350. std::set<DocumentKey>({testutil::Key("foo/bar"), testutil::Key("foo/bar2")}));
  351. [batches addObject:[self addMutationBatchWithKey:@"foo/bar/suffix/baz"]];
  352. garbage = [garbageCollector collectGarbage];
  353. XCTAssertEqual(garbage, std::set<DocumentKey>({}));
  354. [self.mutationQueue removeMutationBatches:@[ batches[4], batches[6] ]];
  355. garbage = [garbageCollector collectGarbage];
  356. XCTAssertEqual(garbage, std::set<DocumentKey>({testutil::Key("foo/bar/suffix/baz")}));
  357. });
  358. }
  359. - (void)testStreamToken {
  360. if ([self isTestBaseClass]) return;
  361. NSData *streamToken1 = [@"token1" dataUsingEncoding:NSUTF8StringEncoding];
  362. NSData *streamToken2 = [@"token2" dataUsingEncoding:NSUTF8StringEncoding];
  363. self.persistence.run("testStreamToken", [&]() {
  364. [self.mutationQueue setLastStreamToken:streamToken1];
  365. FSTMutationBatch *batch1 = [self addMutationBatch];
  366. [self addMutationBatch];
  367. XCTAssertEqualObjects([self.mutationQueue lastStreamToken], streamToken1);
  368. [self.mutationQueue acknowledgeBatch:batch1 streamToken:streamToken2];
  369. XCTAssertEqual(self.mutationQueue.highestAcknowledgedBatchID, batch1.batchID);
  370. XCTAssertEqualObjects([self.mutationQueue lastStreamToken], streamToken2);
  371. });
  372. }
  373. #pragma mark - Helpers
  374. /** Creates a new FSTMutationBatch with the next batch ID and a set of dummy mutations. */
  375. - (FSTMutationBatch *)addMutationBatch {
  376. return [self addMutationBatchWithKey:@"foo/bar"];
  377. }
  378. /**
  379. * Creates a new FSTMutationBatch with the given key, the next batch ID and a set of dummy
  380. * mutations.
  381. */
  382. - (FSTMutationBatch *)addMutationBatchWithKey:(NSString *)key {
  383. FSTSetMutation *mutation = FSTTestSetMutation(key, @{ @"a" : @1 });
  384. FSTMutationBatch *batch =
  385. [self.mutationQueue addMutationBatchWithWriteTime:[FIRTimestamp timestamp]
  386. mutations:@[ mutation ]];
  387. return batch;
  388. }
  389. /**
  390. * Creates an array of batches containing @a number dummy FSTMutationBatches. Each has a different
  391. * batchID.
  392. */
  393. - (NSMutableArray<FSTMutationBatch *> *)createBatches:(int)number {
  394. NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
  395. for (int i = 0; i < number; i++) {
  396. FSTMutationBatch *batch = [self addMutationBatch];
  397. [batches addObject:batch];
  398. }
  399. return batches;
  400. }
  401. /** Returns the number of mutation batches in the mutation queue. */
  402. - (NSUInteger)batchCount {
  403. return [self.mutationQueue allMutationBatches].count;
  404. }
  405. /**
  406. * Removes entries from from the given @a batches and returns them.
  407. *
  408. * @param holes An array of indexes in the batches array; in increasing order. Indexes are relative
  409. * to the original state of the batches array, not any intermediate state that might occur.
  410. * @param batches The array to mutate, removing entries from it.
  411. * @return A new array containing all the entries that were removed from @a batches.
  412. */
  413. - (NSArray<FSTMutationBatch *> *)makeHoles:(NSArray<NSNumber *> *)holes
  414. inBatches:(NSMutableArray<FSTMutationBatch *> *)batches {
  415. NSMutableArray<FSTMutationBatch *> *removed = [NSMutableArray array];
  416. for (NSUInteger i = 0; i < holes.count; i++) {
  417. NSUInteger index = holes[i].unsignedIntegerValue - i;
  418. FSTMutationBatch *batch = batches[index];
  419. [self.mutationQueue removeMutationBatches:@[ batch ]];
  420. [batches removeObjectAtIndex:index];
  421. [removed addObject:batch];
  422. }
  423. return removed;
  424. }
  425. @end
  426. NS_ASSUME_NONNULL_END