FSTTransactionTests.mm 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  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 <FirebaseFirestore/FirebaseFirestore.h>
  17. #import <XCTest/XCTest.h>
  18. #include <atomic>
  19. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  20. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  21. using firebase::firestore::util::TimerId;
  22. @interface FSTTransactionTests : FSTIntegrationTestCase
  23. @end
  24. /**
  25. * This category is to handle the use of assertions in `FSTTransactionTester`, since XCTest
  26. * assertions do not work in classes that don't extend XCTestCase.
  27. */
  28. @interface FSTTransactionTests (Assertions)
  29. - (void)assertExistsWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error;
  30. - (void)assertDoesNotExistWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error;
  31. - (void)assertNilError:(NSError *)error message:(NSString *)message;
  32. - (void)assertError:(NSError *)error message:(NSString *)message;
  33. - (void)assertSnapshot:(FIRDocumentSnapshot *)snapshot
  34. equalsObject:(NSObject *)expected
  35. error:(NSError *)error;
  36. @end
  37. @implementation FSTTransactionTests (Assertions)
  38. - (void)assertExistsWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error {
  39. XCTAssertNil(error);
  40. XCTAssertTrue(snapshot.exists);
  41. }
  42. - (void)assertDoesNotExistWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error {
  43. XCTAssertNil(error);
  44. XCTAssertFalse(snapshot.exists);
  45. }
  46. - (void)assertNilError:(NSError *)error message:(NSString *)message {
  47. XCTAssertNil(error, @"%@", message);
  48. }
  49. - (void)assertError:(NSError *)error message:(NSString *)message {
  50. XCTAssertNotNil(error, @"%@", message);
  51. }
  52. - (void)assertSnapshot:(FIRDocumentSnapshot *)snapshot
  53. equalsObject:(NSObject *)expected
  54. error:(NSError *)error {
  55. XCTAssertNil(error);
  56. XCTAssertTrue(snapshot.exists);
  57. XCTAssertEqualObjects(expected, snapshot.data);
  58. }
  59. @end
  60. typedef void (^TransactionStage)(FIRTransaction *, FIRDocumentReference *);
  61. /**
  62. * The transaction stages that follow are postfixed by numbers to indicate the calling order. For
  63. * example, calling `set1` followed by `set2` should result in the document being set to the value
  64. * specified by `set2`.
  65. */
  66. TransactionStage delete1 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  67. [transaction deleteDocument:doc];
  68. };
  69. TransactionStage update1 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  70. [transaction updateData:@{@"foo" : @"bar1"} forDocument:doc];
  71. };
  72. TransactionStage update2 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  73. [transaction updateData:@{@"foo" : @"bar2"} forDocument:doc];
  74. };
  75. TransactionStage set1 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  76. [transaction setData:@{@"foo" : @"bar1"} forDocument:doc];
  77. };
  78. TransactionStage set2 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  79. [transaction setData:@{@"foo" : @"bar2"} forDocument:doc];
  80. };
  81. TransactionStage get = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  82. NSError *error = nil;
  83. [transaction getDocument:doc error:&error];
  84. };
  85. /**
  86. * Used for testing that all possible combinations of executing transactions result in the desired
  87. * document value or error.
  88. *
  89. * `runWithStages`, `withExistingDoc`, and `withNonexistentDoc` don't actually do anything except
  90. * assign variables into `FSTTransactionTester`.
  91. *
  92. * `expectDoc`, `expectNoDoc`, and `expectError` will trigger the transaction to run and assert
  93. * that the end result matches the input.
  94. */
  95. @interface FSTTransactionTester : NSObject
  96. - (FSTTransactionTester *)withExistingDoc;
  97. - (FSTTransactionTester *)withNonexistentDoc;
  98. - (FSTTransactionTester *)runWithStages:(NSArray<TransactionStage> *)stages;
  99. - (void)expectDoc:(NSObject *)expected;
  100. - (void)expectNoDoc;
  101. - (void)expectError:(FIRFirestoreErrorCode)expected;
  102. @end
  103. @implementation FSTTransactionTester {
  104. FIRFirestore *_db;
  105. FIRDocumentReference *_docRef;
  106. BOOL _fromExistingDoc;
  107. NSArray<TransactionStage> *_stages;
  108. FSTTransactionTests *_testCase;
  109. NSMutableArray<XCTestExpectation *> *_testExpectations;
  110. }
  111. - (instancetype)initWithDb:(FIRFirestore *)db testCase:(FSTTransactionTests *)testCase {
  112. self = [super init];
  113. if (self) {
  114. _db = db;
  115. _stages = [NSArray array];
  116. _testCase = testCase;
  117. _testExpectations = [NSMutableArray array];
  118. }
  119. return self;
  120. }
  121. - (FSTTransactionTester *)withExistingDoc {
  122. _fromExistingDoc = YES;
  123. return self;
  124. }
  125. - (FSTTransactionTester *)withNonexistentDoc {
  126. _fromExistingDoc = NO;
  127. return self;
  128. }
  129. - (FSTTransactionTester *)runWithStages:(NSArray<TransactionStage> *)stages {
  130. _stages = stages;
  131. return self;
  132. }
  133. - (void)expectDoc:(NSObject *)expected {
  134. [self prepareDoc];
  135. [self runSuccessfulTransaction];
  136. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"expectDoc"];
  137. [_docRef getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  138. [self->_testCase assertSnapshot:snapshot equalsObject:expected error:error];
  139. [expectation fulfill];
  140. }];
  141. [_testCase awaitExpectations];
  142. [self cleanupTester];
  143. }
  144. - (void)expectNoDoc {
  145. [self prepareDoc];
  146. [self runSuccessfulTransaction];
  147. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"expectNoDoc"];
  148. [_docRef getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  149. [self->_testCase assertDoesNotExistWithSnapshot:snapshot error:error];
  150. [expectation fulfill];
  151. }];
  152. [_testCase awaitExpectations];
  153. [self cleanupTester];
  154. }
  155. - (void)expectError:(FIRFirestoreErrorCode)expected {
  156. [self prepareDoc];
  157. [self runFailingTransactionWithError:expected];
  158. [self cleanupTester];
  159. }
  160. - (void)prepareDoc {
  161. _docRef = [[_db collectionWithPath:@"nonexistent"] documentWithAutoID];
  162. if (_fromExistingDoc) {
  163. NSError *setError = [self writeDocumentRef:_docRef data:@{@"foo" : @"bar"}];
  164. NSString *message = [NSString stringWithFormat:@"Failed set at %@", [self stageNames]];
  165. [_testCase assertNilError:setError message:message];
  166. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"prepareDoc:get"];
  167. [_docRef getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  168. [self->_testCase assertExistsWithSnapshot:snapshot error:error];
  169. [expectation fulfill];
  170. }];
  171. [_testCase awaitExpectations];
  172. }
  173. }
  174. - (NSError *)writeDocumentRef:(FIRDocumentReference *)ref
  175. data:(NSDictionary<NSString *, id> *)data {
  176. __block NSError *errorResult;
  177. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"prepareDoc:set"];
  178. [_docRef setData:data
  179. completion:^(NSError *error) {
  180. errorResult = error;
  181. [expectation fulfill];
  182. }];
  183. [_testCase awaitExpectations];
  184. return errorResult;
  185. }
  186. - (void)runSuccessfulTransaction {
  187. XCTestExpectation *expectation =
  188. [_testCase expectationWithDescription:@"runSuccessfulTransaction"];
  189. [_db
  190. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  191. for (TransactionStage stage in self->_stages) {
  192. stage(transaction, self->_docRef);
  193. }
  194. return @YES;
  195. }
  196. completion:^(id _Nullable result, NSError *_Nullable error) {
  197. [expectation fulfill];
  198. NSString *message =
  199. [NSString stringWithFormat:@"Expected the sequence %@, to succeed, but got %ld.",
  200. [self stageNames], [error code]];
  201. [self->_testCase assertNilError:error message:message];
  202. }];
  203. [_testCase awaitExpectations];
  204. }
  205. - (void)runFailingTransactionWithError:(FIRFirestoreErrorCode)expected {
  206. XCTestExpectation *expectation =
  207. [_testCase expectationWithDescription:@"runFailingTransactionWithError"];
  208. [_db
  209. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  210. for (TransactionStage stage in self->_stages) {
  211. stage(transaction, self->_docRef);
  212. }
  213. return @YES;
  214. }
  215. completion:^(id _Nullable result, NSError *_Nullable error) {
  216. [expectation fulfill];
  217. NSString *message =
  218. [NSString stringWithFormat:@"Expected the sequence (%@), to fail, but it didn't.",
  219. [self stageNames]];
  220. [self->_testCase assertError:error message:message];
  221. }];
  222. [_testCase awaitExpectations];
  223. }
  224. - (void)cleanupTester {
  225. _stages = [NSArray array];
  226. // Set the docRef to something else to lose the original reference.
  227. _docRef = [[self->_db collectionWithPath:@"reset"] documentWithAutoID];
  228. }
  229. - (NSString *)stageNames {
  230. NSMutableArray<NSString *> *seqList = [NSMutableArray array];
  231. for (TransactionStage stage in _stages) {
  232. if (stage == delete1) {
  233. [seqList addObject:@"delete"];
  234. } else if (stage == update1 || stage == update2) {
  235. [seqList addObject:@"update"];
  236. } else if (stage == set1 || stage == set2) {
  237. [seqList addObject:@"set"];
  238. } else if (stage == get) {
  239. [seqList addObject:@"get"];
  240. }
  241. }
  242. return [seqList description];
  243. }
  244. @end
  245. @implementation FSTTransactionTests
  246. - (void)testRunsTransactionsAfterGettingExistingDoc {
  247. FIRFirestore *firestore = [self firestore];
  248. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  249. [[[tt withExistingDoc] runWithStages:@[ get, delete1, delete1 ]] expectNoDoc];
  250. [[[tt withExistingDoc] runWithStages:@[ get, delete1, update2 ]]
  251. expectError:FIRFirestoreErrorCodeInvalidArgument];
  252. [[[tt withExistingDoc] runWithStages:@[ get, delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  253. [[[tt withExistingDoc] runWithStages:@[ get, update1, delete1 ]] expectNoDoc];
  254. [[[tt withExistingDoc] runWithStages:@[ get, update1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  255. [[[tt withExistingDoc] runWithStages:@[ get, update1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  256. [[[tt withExistingDoc] runWithStages:@[ get, set1, delete1 ]] expectNoDoc];
  257. [[[tt withExistingDoc] runWithStages:@[ get, set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  258. [[[tt withExistingDoc] runWithStages:@[ get, set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  259. }
  260. - (void)testRunsTransactionsAfterGettingNonexistentDoc {
  261. FIRFirestore *firestore = [self firestore];
  262. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  263. [[[tt withNonexistentDoc] runWithStages:@[ get, delete1, delete1 ]] expectNoDoc];
  264. [[[tt withNonexistentDoc] runWithStages:@[ get, delete1, update2 ]]
  265. expectError:FIRFirestoreErrorCodeInvalidArgument];
  266. [[[tt withNonexistentDoc] runWithStages:@[ get, delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  267. [[[tt withNonexistentDoc] runWithStages:@[ get, update1, delete1 ]]
  268. expectError:FIRFirestoreErrorCodeInvalidArgument];
  269. [[[tt withNonexistentDoc] runWithStages:@[ get, update1, update2 ]]
  270. expectError:FIRFirestoreErrorCodeInvalidArgument];
  271. [[[tt withNonexistentDoc] runWithStages:@[ get, update1, set2 ]]
  272. expectError:FIRFirestoreErrorCodeInvalidArgument];
  273. [[[tt withNonexistentDoc] runWithStages:@[ get, set1, delete1 ]] expectNoDoc];
  274. [[[tt withNonexistentDoc] runWithStages:@[ get, set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  275. [[[tt withNonexistentDoc] runWithStages:@[ get, set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  276. }
  277. - (void)testRunsTransactionOnExistingDoc {
  278. FIRFirestore *firestore = [self firestore];
  279. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  280. [[[tt withExistingDoc] runWithStages:@[ delete1, delete1 ]] expectNoDoc];
  281. [[[tt withExistingDoc] runWithStages:@[ delete1, update2 ]]
  282. expectError:FIRFirestoreErrorCodeInvalidArgument];
  283. [[[tt withExistingDoc] runWithStages:@[ delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  284. [[[tt withExistingDoc] runWithStages:@[ update1, delete1 ]] expectNoDoc];
  285. [[[tt withExistingDoc] runWithStages:@[ update1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  286. [[[tt withExistingDoc] runWithStages:@[ update1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  287. [[[tt withExistingDoc] runWithStages:@[ set1, delete1 ]] expectNoDoc];
  288. [[[tt withExistingDoc] runWithStages:@[ set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  289. [[[tt withExistingDoc] runWithStages:@[ set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  290. }
  291. - (void)testRunsTransactionsOnNonexistentDoc {
  292. FIRFirestore *firestore = [self firestore];
  293. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  294. [[[tt withNonexistentDoc] runWithStages:@[ delete1, delete1 ]] expectNoDoc];
  295. [[[tt withNonexistentDoc] runWithStages:@[ delete1, update2 ]]
  296. expectError:FIRFirestoreErrorCodeInvalidArgument];
  297. [[[tt withNonexistentDoc] runWithStages:@[ delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  298. [[[tt withNonexistentDoc] runWithStages:@[ update1, delete1 ]]
  299. expectError:FIRFirestoreErrorCodeNotFound];
  300. [[[tt withNonexistentDoc] runWithStages:@[ update1, update2 ]]
  301. expectError:FIRFirestoreErrorCodeNotFound];
  302. [[[tt withNonexistentDoc] runWithStages:@[ update1, set2 ]]
  303. expectError:FIRFirestoreErrorCodeNotFound];
  304. [[[tt withNonexistentDoc] runWithStages:@[ set1, delete1 ]] expectNoDoc];
  305. [[[tt withNonexistentDoc] runWithStages:@[ set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  306. [[[tt withNonexistentDoc] runWithStages:@[ set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  307. }
  308. - (void)testGetDocuments {
  309. FIRFirestore *firestore = [self firestore];
  310. FIRDocumentReference *doc = [[firestore collectionWithPath:@"spaces"] documentWithAutoID];
  311. [self writeDocumentRef:doc data:@{@"foo" : @1, @"desc" : @"Stuff", @"owner" : @"Jonny"}];
  312. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  313. [firestore
  314. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  315. [transaction getDocument:doc error:error];
  316. XCTAssertNil(*error);
  317. return @YES;
  318. }
  319. completion:^(id _Nullable result, NSError *_Nullable error) {
  320. XCTAssertNil(result);
  321. // We currently require every document read to also be written.
  322. // TODO(b/34879758): Fix this check once we drop that requirement.
  323. XCTAssertNotNil(error);
  324. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  325. [expectation fulfill];
  326. }];
  327. [self awaitExpectations];
  328. }
  329. - (void)testSetDocumentWithMerge {
  330. FIRFirestore *firestore = [self firestore];
  331. FIRDocumentReference *doc = [[firestore collectionWithPath:@"towns"] documentWithAutoID];
  332. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  333. [firestore
  334. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  335. [transaction setData:@{@"a" : @"b", @"nested" : @{@"a" : @"b"}} forDocument:doc];
  336. [transaction setData:@{@"c" : @"d", @"nested" : @{@"c" : @"d"}} forDocument:doc merge:YES];
  337. return @YES;
  338. }
  339. completion:^(id _Nullable result, NSError *_Nullable error) {
  340. XCTAssertEqualObjects(@YES, result);
  341. XCTAssertNil(error);
  342. [expectation fulfill];
  343. }];
  344. [self awaitExpectations];
  345. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  346. XCTAssertEqualObjects(snapshot.data,
  347. (@{@"a" : @"b", @"c" : @"d", @"nested" : @{@"a" : @"b", @"c" : @"d"}}));
  348. }
  349. - (void)testIncrementTransactionally {
  350. // A barrier to make sure every transaction reaches the same spot.
  351. dispatch_semaphore_t writeBarrier = dispatch_semaphore_create(0);
  352. auto counter_unique = absl::make_unique<std::atomic_int32_t>(0);
  353. auto counter = counter_unique.get();
  354. FIRFirestore *firestore = [self firestore];
  355. FIRDocumentReference *doc = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  356. [self writeDocumentRef:doc data:@{@"count" : @(5.0)}];
  357. // Skip backoff delays.
  358. [firestore workerQueue] -> SkipDelaysForTimerId(TimerId::RetryTransaction);
  359. // Make 3 transactions that will all increment.
  360. int total = 3;
  361. for (int i = 0; i < total; i++) {
  362. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  363. [firestore
  364. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  365. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc error:error];
  366. XCTAssertNil(*error);
  367. (*counter)++;
  368. // Once all of the transactions have read, allow the first write.
  369. if (*counter == total) {
  370. dispatch_semaphore_signal(writeBarrier);
  371. }
  372. dispatch_semaphore_wait(writeBarrier, DISPATCH_TIME_FOREVER);
  373. // Refill the barrier so that the other transactions and retries succeed.
  374. dispatch_semaphore_signal(writeBarrier);
  375. double newCount = ((NSNumber *)snapshot[@"count"]).doubleValue + 1.0;
  376. [transaction setData:@{@"count" : @(newCount)} forDocument:doc];
  377. return @YES;
  378. }
  379. completion:^(id _Nullable result, NSError *_Nullable error) {
  380. [expectation fulfill];
  381. }];
  382. }
  383. [self awaitExpectations];
  384. // Now all transaction should be completed, so check the result.
  385. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  386. XCTAssertEqualObjects(@(5.0 + total), snapshot[@"count"]);
  387. }
  388. - (void)testUpdateTransactionally {
  389. // A barrier to make sure every transaction reaches the same spot.
  390. dispatch_semaphore_t writeBarrier = dispatch_semaphore_create(0);
  391. auto counter_unique = absl::make_unique<std::atomic_int32_t>(0);
  392. auto counter = counter_unique.get();
  393. FIRFirestore *firestore = [self firestore];
  394. FIRDocumentReference *doc = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  395. [self writeDocumentRef:doc data:@{@"count" : @(5.0), @"other" : @"yes"}];
  396. // Skip backoff delays.
  397. [firestore workerQueue] -> SkipDelaysForTimerId(TimerId::RetryTransaction);
  398. // Make 3 transactions that will all increment.
  399. int total = 3;
  400. for (int i = 0; i < total; i++) {
  401. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  402. [firestore
  403. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  404. int32_t nowStarted = ++(*counter);
  405. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc error:error];
  406. XCTAssertNil(*error);
  407. // Once all of the transactions have read, allow the first write. There should be 3
  408. // initial transaction runs.
  409. if (nowStarted == total) {
  410. XCTAssertEqual(3, (int)(*counter));
  411. dispatch_semaphore_signal(writeBarrier);
  412. }
  413. dispatch_semaphore_wait(writeBarrier, DISPATCH_TIME_FOREVER);
  414. // Refill the barrier so that the other transactions and retries succeed.
  415. dispatch_semaphore_signal(writeBarrier);
  416. double newCount = ((NSNumber *)snapshot[@"count"]).doubleValue + 1.0;
  417. [transaction updateData:@{@"count" : @(newCount)} forDocument:doc];
  418. return @YES;
  419. }
  420. completion:^(id _Nullable result, NSError *_Nullable error) {
  421. [expectation fulfill];
  422. }];
  423. }
  424. [self awaitExpectations];
  425. // There should be a maximum of 3 retries: once for the 2nd update, and twice for the 3rd update.
  426. XCTAssertLessThanOrEqual((int)(*counter), 6);
  427. // Now all transaction should be completed, so check the result.
  428. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  429. XCTAssertEqualObjects(@(5.0 + total), snapshot[@"count"]);
  430. XCTAssertEqualObjects(@"yes", snapshot[@"other"]);
  431. }
  432. - (void)testHandleReadingOneDocAndWritingAnother {
  433. FIRFirestore *firestore = [self firestore];
  434. FIRDocumentReference *doc1 = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  435. FIRDocumentReference *doc2 = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  436. [self writeDocumentRef:doc1 data:@{@"count" : @(15.0)}];
  437. // Skip backoff delays.
  438. [firestore workerQueue] -> SkipDelaysForTimerId(TimerId::RetryTransaction);
  439. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  440. [firestore
  441. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  442. // Get the first doc.
  443. [transaction getDocument:doc1 error:error];
  444. XCTAssertNil(*error);
  445. // Do a write outside of the transaction. The first time the
  446. // transaction is tried, this will bump the version, which
  447. // will cause the write to doc2 to fail. The second time, it
  448. // will be a no-op and not bump the version.
  449. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  450. [doc1 setData:@{
  451. @"count" : @(1234)
  452. }
  453. completion:^(NSError *_Nullable error) {
  454. dispatch_semaphore_signal(writeSemaphore);
  455. }];
  456. // We can block on it, because transactions run on a background queue.
  457. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  458. // Now try to update the other doc from within the transaction.
  459. // This should fail once, because we read 15 earlier.
  460. [transaction setData:@{@"count" : @(16)} forDocument:doc2];
  461. return nil;
  462. }
  463. completion:^(id _Nullable result, NSError *_Nullable error) {
  464. // We currently require every document read to also be written.
  465. // TODO(b/34879758): Add this check back once we drop that.
  466. // NSError *error = nil;
  467. // FIRDocument *snapshot = [transaction getDocument:doc1 error:&error];
  468. // XCTAssertNil(error);
  469. // XCTAssertEquals(0, tries);
  470. // XCTAssertEqualObjects(@(1234), snapshot[@"count"]);
  471. // snapshot = [transaction getDocument:doc2 error:&error];
  472. // XCTAssertNil(error);
  473. // XCTAssertEqualObjects(@(16), snapshot[@"count"]);
  474. XCTAssertNotNil(error);
  475. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  476. [expectation fulfill];
  477. }];
  478. [self awaitExpectations];
  479. }
  480. - (void)testReadingADocTwiceWithDifferentVersions {
  481. FIRFirestore *firestore = [self firestore];
  482. FIRDocumentReference *doc = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  483. auto counter_unique = absl::make_unique<std::atomic_int32_t>(0);
  484. auto counter = counter_unique.get();
  485. [self writeDocumentRef:doc data:@{@"count" : @(15.0)}];
  486. // Skip backoff delays.
  487. [firestore workerQueue] -> SkipDelaysForTimerId(TimerId::RetryTransaction);
  488. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  489. [firestore
  490. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  491. ++(*counter);
  492. // Get the doc once.
  493. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc error:error];
  494. XCTAssertNil(*error);
  495. // Do a write outside of the transaction. Because the transaction will retry, set the
  496. // document to a different value each time.
  497. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  498. [doc setData:@{
  499. @"count" : @(1234 + (int)(*counter))
  500. }
  501. completion:^(NSError *_Nullable error) {
  502. dispatch_semaphore_signal(writeSemaphore);
  503. }];
  504. // We can block on it, because transactions run on a background queue.
  505. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  506. // Get the doc again in the transaction with the new version.
  507. snapshot = [transaction getDocument:doc error:error];
  508. // The get itself will fail, because we already read an earlier version of this document.
  509. // TODO(klimt): Perhaps we shouldn't fail reads for this, but should wait and fail the
  510. // whole transaction? It's an edge-case anyway, as developers shouldn't be reading the same
  511. // doc multiple times. But they need to handle read errors anyway.
  512. XCTAssertNotNil(*error);
  513. return nil;
  514. }
  515. completion:^(id _Nullable result, NSError *_Nullable error) {
  516. [expectation fulfill];
  517. XCTAssertNotNil(error);
  518. XCTAssertEqual(error.code, FIRFirestoreErrorCodeAborted);
  519. }];
  520. [self awaitExpectations];
  521. }
  522. - (void)testReadAndUpdateNonExistentDocumentWithExternalWrite {
  523. FIRFirestore *firestore = [self firestore];
  524. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  525. [firestore
  526. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  527. // Get and update a document that doesn't exist so that the transaction fails.
  528. FIRDocumentReference *doc =
  529. [[firestore collectionWithPath:@"nonexistent"] documentWithAutoID];
  530. [transaction getDocument:doc error:error];
  531. XCTAssertNil(*error);
  532. // Do a write outside of the transaction.
  533. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  534. [doc setData:@{
  535. @"count" : @(1234)
  536. }
  537. completion:^(NSError *_Nullable error) {
  538. dispatch_semaphore_signal(writeSemaphore);
  539. }];
  540. // We can block on it, because transactions run on a background queue.
  541. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  542. // Now try to update the other doc from within the transaction.
  543. // This should fail, because the document didn't exist at the
  544. // start of the transaction.
  545. [transaction updateData:@{@"count" : @(16)} forDocument:doc];
  546. return nil;
  547. }
  548. completion:^(id _Nullable result, NSError *_Nullable error) {
  549. [expectation fulfill];
  550. XCTAssertNotNil(error);
  551. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  552. }];
  553. [self awaitExpectations];
  554. }
  555. - (void)testCannotHaveAGetWithoutMutations {
  556. FIRFirestore *firestore = [self firestore];
  557. FIRDocumentReference *doc = [[firestore collectionWithPath:@"foo"] documentWithAutoID];
  558. [self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
  559. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  560. [firestore
  561. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  562. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc error:error];
  563. XCTAssertTrue(snapshot.exists);
  564. XCTAssertNil(*error);
  565. return nil;
  566. }
  567. completion:^(id _Nullable result, NSError *_Nullable error) {
  568. // We currently require every document read to also be written.
  569. // TODO(b/34879758): Fix this check once we drop that requirement.
  570. XCTAssertNotNil(error);
  571. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  572. [expectation fulfill];
  573. }];
  574. [self awaitExpectations];
  575. }
  576. - (void)testDoesNotRetryOnPermanentError {
  577. FIRFirestore *firestore = [self firestore];
  578. auto counter_unique = absl::make_unique<std::atomic_int32_t>(0);
  579. auto counter = counter_unique.get();
  580. // Make a transaction that should fail with a permanent error
  581. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  582. [firestore
  583. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  584. ++(*counter);
  585. // Get and update a document that doesn't exist so that the transaction fails.
  586. FIRDocumentReference *doc =
  587. [[firestore collectionWithPath:@"nonexistent"] documentWithAutoID];
  588. [transaction getDocument:doc error:error];
  589. [transaction updateData:@{@"count" : @(16)} forDocument:doc];
  590. return nil;
  591. }
  592. completion:^(id _Nullable result, NSError *_Nullable error) {
  593. [expectation fulfill];
  594. XCTAssertNotNil(error);
  595. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  596. XCTAssertEqual(1, (int)(*counter));
  597. }];
  598. [self awaitExpectations];
  599. }
  600. - (void)testSuccessWithNoTransactionOperations {
  601. FIRFirestore *firestore = [self firestore];
  602. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  603. [firestore
  604. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  605. return @"yes";
  606. }
  607. completion:^(id _Nullable result, NSError *_Nullable error) {
  608. XCTAssertEqualObjects(@"yes", result);
  609. XCTAssertNil(error);
  610. [expectation fulfill];
  611. }];
  612. [self awaitExpectations];
  613. }
  614. - (void)testCancellationOnError {
  615. FIRFirestore *firestore = [self firestore];
  616. FIRDocumentReference *doc = [[firestore collectionWithPath:@"towns"] documentWithAutoID];
  617. auto counter_unique = absl::make_unique<std::atomic_int32_t>(0);
  618. auto counter = counter_unique.get();
  619. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  620. [firestore
  621. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  622. ++(*counter);
  623. [transaction setData:@{@"foo" : @"bar"} forDocument:doc];
  624. if (error) {
  625. *error = [NSError errorWithDomain:NSCocoaErrorDomain code:35 userInfo:@{}];
  626. }
  627. return nil;
  628. }
  629. completion:^(id _Nullable result, NSError *_Nullable error) {
  630. XCTAssertNil(result);
  631. XCTAssertNotNil(error);
  632. XCTAssertEqual(35, error.code);
  633. [expectation fulfill];
  634. }];
  635. [self awaitExpectations];
  636. XCTAssertEqual(1, (int)(*counter));
  637. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  638. XCTAssertFalse(snapshot.exists);
  639. }
  640. - (void)testUpdateFieldsWithDotsTransactionally {
  641. FIRDocumentReference *doc = [self documentRef];
  642. XCTestExpectation *expectation =
  643. [self expectationWithDescription:@"testUpdateFieldsWithDotsTransactionally"];
  644. [doc.firestore
  645. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  646. XCTAssertNil(*error);
  647. [transaction setData:@{@"a.b" : @"old", @"c.d" : @"old"} forDocument:doc];
  648. [transaction updateData:@{
  649. [[FIRFieldPath alloc] initWithFields:@[ @"a.b" ]] : @"new"
  650. }
  651. forDocument:doc];
  652. return nil;
  653. }
  654. completion:^(id result, NSError *error) {
  655. XCTAssertNil(error);
  656. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  657. XCTAssertNil(error);
  658. XCTAssertEqualObjects(snapshot.data, (@{@"a.b" : @"new", @"c.d" : @"old"}));
  659. }];
  660. [expectation fulfill];
  661. }];
  662. [self awaitExpectations];
  663. }
  664. - (void)testUpdateNestedFieldsTransactionally {
  665. FIRDocumentReference *doc = [self documentRef];
  666. XCTestExpectation *expectation =
  667. [self expectationWithDescription:@"testUpdateNestedFieldsTransactionally"];
  668. [doc.firestore
  669. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  670. XCTAssertNil(*error);
  671. [transaction setData:@{
  672. @"a" : @{@"b" : @"old"},
  673. @"c" : @{@"d" : @"old"},
  674. @"e" : @{@"f" : @"old"}
  675. }
  676. forDocument:doc];
  677. [transaction updateData:@{
  678. @"a.b" : @"new",
  679. [[FIRFieldPath alloc] initWithFields:@[ @"c", @"d" ]] : @"new"
  680. }
  681. forDocument:doc];
  682. return nil;
  683. }
  684. completion:^(id result, NSError *error) {
  685. XCTAssertNil(error);
  686. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  687. XCTAssertNil(error);
  688. XCTAssertEqualObjects(snapshot.data, (@{
  689. @"a" : @{@"b" : @"new"},
  690. @"c" : @{@"d" : @"new"},
  691. @"e" : @{@"f" : @"old"}
  692. }));
  693. }];
  694. [expectation fulfill];
  695. }];
  696. [self awaitExpectations];
  697. }
  698. @end