FSTTransactionTests.mm 31 KB

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