FSTSyncEngineTestDriver.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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/SpecTests/FSTSyncEngineTestDriver.h"
  17. #import <GRPCClient/GRPCCall.h>
  18. #import "FirebaseFirestore/FIRFirestoreErrors.h"
  19. #import "Firestore/Source/Auth/FSTUser.h"
  20. #import "Firestore/Source/Core/FSTEventManager.h"
  21. #import "Firestore/Source/Core/FSTQuery.h"
  22. #import "Firestore/Source/Core/FSTSnapshotVersion.h"
  23. #import "Firestore/Source/Core/FSTSyncEngine.h"
  24. #import "Firestore/Source/Local/FSTLocalStore.h"
  25. #import "Firestore/Source/Local/FSTPersistence.h"
  26. #import "Firestore/Source/Model/FSTMutation.h"
  27. #import "Firestore/Source/Remote/FSTDatastore.h"
  28. #import "Firestore/Source/Remote/FSTWatchChange.h"
  29. #import "Firestore/Source/Util/FSTAssert.h"
  30. #import "Firestore/Source/Util/FSTDispatchQueue.h"
  31. #import "Firestore/Source/Util/FSTLogger.h"
  32. #import "Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h"
  33. #import "Firestore/Example/Tests/SpecTests/FSTMockDatastore.h"
  34. NS_ASSUME_NONNULL_BEGIN
  35. @implementation FSTQueryEvent
  36. - (NSString *)description {
  37. // The Query is also included in the view, so we skip it.
  38. return [NSString stringWithFormat:@"<FSTQueryEvent: viewSnapshot=%@, error=%@>",
  39. self.viewSnapshot, self.error];
  40. }
  41. @end
  42. @implementation FSTOutstandingWrite
  43. @end
  44. @interface FSTSyncEngineTestDriver ()
  45. #pragma mark - Parts of the Firestore system that the spec tests need to control.
  46. @property(nonatomic, strong, readonly) FSTMockDatastore *datastore;
  47. @property(nonatomic, strong, readonly) FSTEventManager *eventManager;
  48. @property(nonatomic, strong, readonly) FSTRemoteStore *remoteStore;
  49. @property(nonatomic, strong, readonly) FSTLocalStore *localStore;
  50. @property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
  51. #pragma mark - Data structures for holding events sent by the watch stream.
  52. /** A block for the FSTEventAggregator to use to report events to the test. */
  53. @property(nonatomic, strong, readonly) void (^eventHandler)(FSTQueryEvent *);
  54. /** The events received by our eventHandler and not yet retrieved via capturedEventsSinceLastCall */
  55. @property(nonatomic, strong, readonly) NSMutableArray<FSTQueryEvent *> *events;
  56. /** A dictionary for tracking the listens on queries. */
  57. @property(nonatomic, strong, readonly)
  58. NSMutableDictionary<FSTQuery *, FSTQueryListener *> *queryListeners;
  59. #pragma mark - Other data structures.
  60. @property(nonatomic, strong, readwrite) FSTUser *currentUser;
  61. @end
  62. @implementation FSTSyncEngineTestDriver {
  63. // ivar is declared as mutable.
  64. NSMutableDictionary<FSTUser *, NSMutableArray<FSTOutstandingWrite *> *> *_outstandingWrites;
  65. }
  66. - (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
  67. garbageCollector:(id<FSTGarbageCollector>)garbageCollector {
  68. return [self initWithPersistence:persistence
  69. garbageCollector:garbageCollector
  70. initialUser:[FSTUser unauthenticatedUser]
  71. outstandingWrites:@{}];
  72. }
  73. - (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
  74. garbageCollector:(id<FSTGarbageCollector>)garbageCollector
  75. initialUser:(FSTUser *)initialUser
  76. outstandingWrites:(FSTOutstandingWriteQueues *)outstandingWrites {
  77. if (self = [super init]) {
  78. // Create mutable copy of outstandingWrites.
  79. _outstandingWrites = [NSMutableDictionary dictionary];
  80. [outstandingWrites enumerateKeysAndObjectsUsingBlock:^(
  81. FSTUser *user, NSArray<FSTOutstandingWrite *> *writes, BOOL *stop) {
  82. _outstandingWrites[user] = [writes mutableCopy];
  83. }];
  84. _events = [NSMutableArray array];
  85. // Set up the sync engine and various stores.
  86. dispatch_queue_t mainQueue = dispatch_get_main_queue();
  87. FSTDispatchQueue *dispatchQueue = [FSTDispatchQueue queueWith:mainQueue];
  88. _localStore = [[FSTLocalStore alloc] initWithPersistence:persistence
  89. garbageCollector:garbageCollector
  90. initialUser:initialUser];
  91. _datastore = [FSTMockDatastore mockDatastoreWithWorkerDispatchQueue:dispatchQueue];
  92. _remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:_datastore];
  93. _syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
  94. remoteStore:_remoteStore
  95. initialUser:initialUser];
  96. _remoteStore.syncEngine = _syncEngine;
  97. _eventManager = [FSTEventManager eventManagerWithSyncEngine:_syncEngine];
  98. _remoteStore.onlineStateDelegate = self;
  99. // Set up internal event tracking for the spec tests.
  100. NSMutableArray<FSTQueryEvent *> *events = [NSMutableArray array];
  101. _eventHandler = ^(FSTQueryEvent *e) {
  102. [events addObject:e];
  103. };
  104. _events = events;
  105. _queryListeners = [NSMutableDictionary dictionary];
  106. _expectedLimboDocuments = [NSSet set];
  107. _expectedActiveTargets = [NSDictionary dictionary];
  108. _currentUser = initialUser;
  109. }
  110. return self;
  111. }
  112. - (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
  113. [self.syncEngine applyChangedOnlineState:onlineState];
  114. [self.eventManager applyChangedOnlineState:onlineState];
  115. }
  116. - (void)start {
  117. [self.localStore start];
  118. [self.remoteStore start];
  119. }
  120. - (void)validateUsage {
  121. // We could relax this if we found a reason to.
  122. FSTAssert(self.events.count == 0,
  123. @"You must clear all pending events by calling"
  124. " capturedEventsSinceLastCall before calling shutdown.");
  125. }
  126. - (void)shutdown {
  127. [self.remoteStore shutdown];
  128. [self.localStore shutdown];
  129. }
  130. - (void)validateNextWriteSent:(FSTMutation *)expectedWrite {
  131. NSArray<FSTMutation *> *request = [self.datastore nextSentWrite];
  132. // Make sure the write went through the pipe like we expected it to.
  133. FSTAssert(request.count == 1, @"Only single mutation requests are supported at the moment");
  134. FSTMutation *actualWrite = request[0];
  135. FSTAssert([actualWrite isEqual:expectedWrite],
  136. @"Mock datastore received write %@ but first outstanding mutation was %@", actualWrite,
  137. expectedWrite);
  138. FSTLog(@"A write was sent: %@", actualWrite);
  139. }
  140. - (int)sentWritesCount {
  141. return [self.datastore writesSent];
  142. }
  143. - (int)writeStreamRequestCount {
  144. return [self.datastore writeStreamRequestCount];
  145. }
  146. - (int)watchStreamRequestCount {
  147. return [self.datastore watchStreamRequestCount];
  148. }
  149. - (void)disableNetwork {
  150. // Make sure to execute all writes that are currently queued. This allows us
  151. // to assert on the total number of requests sent before shutdown.
  152. [self.remoteStore fillWritePipeline];
  153. [self.remoteStore disableNetwork];
  154. }
  155. - (void)enableNetwork {
  156. [self.remoteStore enableNetwork];
  157. }
  158. - (void)changeUser:(FSTUser *)user {
  159. self.currentUser = user;
  160. [self.syncEngine userDidChange:user];
  161. }
  162. - (FSTOutstandingWrite *)receiveWriteAckWithVersion:(FSTSnapshotVersion *)commitVersion
  163. mutationResults:
  164. (NSArray<FSTMutationResult *> *)mutationResults {
  165. FSTOutstandingWrite *write = [self currentOutstandingWrites].firstObject;
  166. [[self currentOutstandingWrites] removeObjectAtIndex:0];
  167. [self validateNextWriteSent:write.write];
  168. [self.datastore ackWriteWithVersion:commitVersion mutationResults:mutationResults];
  169. return write;
  170. }
  171. - (FSTOutstandingWrite *)receiveWriteError:(int)errorCode
  172. userInfo:(NSDictionary<NSString *, id> *)userInfo {
  173. NSError *error =
  174. [NSError errorWithDomain:FIRFirestoreErrorDomain code:errorCode userInfo:userInfo];
  175. FSTOutstandingWrite *write = [self currentOutstandingWrites].firstObject;
  176. [self validateNextWriteSent:write.write];
  177. // If this is a permanent error, the mutation is not expected to be sent again so we remove it
  178. // from currentOutstandingWrites.
  179. if ([FSTDatastore isPermanentWriteError:error]) {
  180. [[self currentOutstandingWrites] removeObjectAtIndex:0];
  181. }
  182. FSTLog(@"Failing a write.");
  183. [self.datastore failWriteWithError:error];
  184. return write;
  185. }
  186. - (NSArray<FSTQueryEvent *> *)capturedEventsSinceLastCall {
  187. NSArray<FSTQueryEvent *> *result = [self.events copy];
  188. [self.events removeAllObjects];
  189. return result;
  190. }
  191. - (FSTTargetID)addUserListenerWithQuery:(FSTQuery *)query {
  192. // TODO(dimond): Allow customizing listen options in spec tests
  193. // TODO(dimond): Change spec tests to verify isFromCache on snapshots
  194. FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
  195. includeDocumentMetadataChanges:YES
  196. waitForSyncWhenOnline:NO];
  197. FSTQueryListener *listener = [[FSTQueryListener alloc]
  198. initWithQuery:query
  199. options:options
  200. viewSnapshotHandler:^(FSTViewSnapshot *_Nullable snapshot, NSError *_Nullable error) {
  201. FSTQueryEvent *event = [[FSTQueryEvent alloc] init];
  202. event.query = query;
  203. event.viewSnapshot = snapshot;
  204. event.error = error;
  205. [self.events addObject:event];
  206. }];
  207. self.queryListeners[query] = listener;
  208. return [self.eventManager addListener:listener];
  209. }
  210. - (void)removeUserListenerWithQuery:(FSTQuery *)query {
  211. FSTQueryListener *listener = self.queryListeners[query];
  212. [self.queryListeners removeObjectForKey:query];
  213. [self.eventManager removeListener:listener];
  214. }
  215. - (void)writeUserMutation:(FSTMutation *)mutation {
  216. FSTOutstandingWrite *write = [[FSTOutstandingWrite alloc] init];
  217. write.write = mutation;
  218. [[self currentOutstandingWrites] addObject:write];
  219. FSTLog(@"sending a user write.");
  220. [self.syncEngine writeMutations:@[ mutation ]
  221. completion:^(NSError *_Nullable error) {
  222. FSTLog(@"A callback was called with error: %@", error);
  223. write.done = YES;
  224. write.error = error;
  225. }];
  226. }
  227. - (void)receiveWatchChange:(FSTWatchChange *)change
  228. snapshotVersion:(FSTSnapshotVersion *_Nullable)snapshot {
  229. [self.datastore writeWatchChange:change snapshotVersion:snapshot];
  230. }
  231. - (void)receiveWatchStreamError:(int)errorCode userInfo:(NSDictionary<NSString *, id> *)userInfo {
  232. NSError *error =
  233. [NSError errorWithDomain:FIRFirestoreErrorDomain code:errorCode userInfo:userInfo];
  234. [self.datastore failWatchStreamWithError:error];
  235. // Unlike web, stream should re-open synchronously (if we have any listeners)
  236. if (self.queryListeners.count > 0) {
  237. FSTAssert(self.datastore.isWatchStreamOpen, @"Watch stream is open");
  238. }
  239. }
  240. - (NSDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *)currentLimboDocuments {
  241. return [self.syncEngine currentLimboDocuments];
  242. }
  243. - (NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)activeTargets {
  244. return [[self.datastore activeTargets] copy];
  245. }
  246. #pragma mark - Helper Methods
  247. - (NSMutableArray<FSTOutstandingWrite *> *)currentOutstandingWrites {
  248. NSMutableArray<FSTOutstandingWrite *> *writes = _outstandingWrites[self.currentUser];
  249. if (!writes) {
  250. writes = [NSMutableArray array];
  251. _outstandingWrites[self.currentUser] = writes;
  252. }
  253. return writes;
  254. }
  255. @end
  256. NS_ASSUME_NONNULL_END