Files
firebase-ios-sdk/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
T
Gil 729b8d176c Move all Firestore Objective-C to Objective-C++ (#734)
* Move all Firestore files to Objective-C++
* Update project file references
* Don't use module imports from Objective-C++
* Use extern "C" for C-accessible globals
* Work around more stringent type checking in Objective-C++
  * NSMutableDictionary ivars aren't implicitly casted to NSDictionary
  * FSTMaybeDocument callback can't be passed a function that accepts
    FSTDocument
  * NSComparisonResult can't be multiplied by -1 without casting
* Add a #include <inttypes.h> where needed
* Avoid using C++ keywords as variables
* Remove #if __cplusplus guards
2018-01-31 11:23:55 -08:00

323 lines
12 KiB
Plaintext

/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h"
#import <FirebaseFirestore/FIRFirestoreErrors.h>
#import <GRPCClient/GRPCCall.h>
#import "Firestore/Source/Auth/FSTUser.h"
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTSyncEngine.h"
#import "Firestore/Source/Local/FSTLocalStore.h"
#import "Firestore/Source/Local/FSTPersistence.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Source/Util/FSTLogger.h"
#import "Firestore/Example/Tests/Core/FSTSyncEngine+Testing.h"
#import "Firestore/Example/Tests/SpecTests/FSTMockDatastore.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FSTQueryEvent
- (NSString *)description {
// The Query is also included in the view, so we skip it.
return [NSString stringWithFormat:@"<FSTQueryEvent: viewSnapshot=%@, error=%@>",
self.viewSnapshot, self.error];
}
@end
@implementation FSTOutstandingWrite
@end
@interface FSTSyncEngineTestDriver ()
#pragma mark - Parts of the Firestore system that the spec tests need to control.
@property(nonatomic, strong, readonly) FSTMockDatastore *datastore;
@property(nonatomic, strong, readonly) FSTEventManager *eventManager;
@property(nonatomic, strong, readonly) FSTRemoteStore *remoteStore;
@property(nonatomic, strong, readonly) FSTLocalStore *localStore;
@property(nonatomic, strong, readonly) FSTSyncEngine *syncEngine;
#pragma mark - Data structures for holding events sent by the watch stream.
/** A block for the FSTEventAggregator to use to report events to the test. */
@property(nonatomic, strong, readonly) void (^eventHandler)(FSTQueryEvent *);
/** The events received by our eventHandler and not yet retrieved via capturedEventsSinceLastCall */
@property(nonatomic, strong, readonly) NSMutableArray<FSTQueryEvent *> *events;
/** A dictionary for tracking the listens on queries. */
@property(nonatomic, strong, readonly)
NSMutableDictionary<FSTQuery *, FSTQueryListener *> *queryListeners;
#pragma mark - Other data structures.
@property(nonatomic, strong, readwrite) FSTUser *currentUser;
@end
@implementation FSTSyncEngineTestDriver {
// ivar is declared as mutable.
NSMutableDictionary<FSTUser *, NSMutableArray<FSTOutstandingWrite *> *> *_outstandingWrites;
}
- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
garbageCollector:(id<FSTGarbageCollector>)garbageCollector {
return [self initWithPersistence:persistence
garbageCollector:garbageCollector
initialUser:[FSTUser unauthenticatedUser]
outstandingWrites:@{}];
}
- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
garbageCollector:(id<FSTGarbageCollector>)garbageCollector
initialUser:(FSTUser *)initialUser
outstandingWrites:(FSTOutstandingWriteQueues *)outstandingWrites {
if (self = [super init]) {
// Create mutable copy of outstandingWrites.
_outstandingWrites = [NSMutableDictionary dictionary];
[outstandingWrites enumerateKeysAndObjectsUsingBlock:^(
FSTUser *user, NSArray<FSTOutstandingWrite *> *writes, BOOL *stop) {
_outstandingWrites[user] = [writes mutableCopy];
}];
_events = [NSMutableArray array];
// Set up the sync engine and various stores.
dispatch_queue_t mainQueue = dispatch_get_main_queue();
FSTDispatchQueue *dispatchQueue = [FSTDispatchQueue queueWith:mainQueue];
_localStore = [[FSTLocalStore alloc] initWithPersistence:persistence
garbageCollector:garbageCollector
initialUser:initialUser];
_datastore = [FSTMockDatastore mockDatastoreWithWorkerDispatchQueue:dispatchQueue];
_remoteStore = [FSTRemoteStore remoteStoreWithLocalStore:_localStore datastore:_datastore];
_syncEngine = [[FSTSyncEngine alloc] initWithLocalStore:_localStore
remoteStore:_remoteStore
initialUser:initialUser];
_remoteStore.syncEngine = _syncEngine;
_eventManager = [FSTEventManager eventManagerWithSyncEngine:_syncEngine];
_remoteStore.onlineStateDelegate = self;
// Set up internal event tracking for the spec tests.
NSMutableArray<FSTQueryEvent *> *events = [NSMutableArray array];
_eventHandler = ^(FSTQueryEvent *e) {
[events addObject:e];
};
_events = events;
_queryListeners = [NSMutableDictionary dictionary];
_expectedLimboDocuments = [NSSet set];
_expectedActiveTargets = [NSDictionary dictionary];
_currentUser = initialUser;
}
return self;
}
- (NSDictionary<FSTUser *, NSMutableArray<FSTOutstandingWrite *> *> *)outstandingWrites {
return static_cast<NSDictionary<FSTUser *, NSMutableArray<FSTOutstandingWrite *> *> *>(
_outstandingWrites);
}
- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
[self.syncEngine applyChangedOnlineState:onlineState];
[self.eventManager applyChangedOnlineState:onlineState];
}
- (void)start {
[self.localStore start];
[self.remoteStore start];
}
- (void)validateUsage {
// We could relax this if we found a reason to.
FSTAssert(self.events.count == 0,
@"You must clear all pending events by calling"
" capturedEventsSinceLastCall before calling shutdown.");
}
- (void)shutdown {
[self.remoteStore shutdown];
[self.localStore shutdown];
}
- (void)validateNextWriteSent:(FSTMutation *)expectedWrite {
NSArray<FSTMutation *> *request = [self.datastore nextSentWrite];
// Make sure the write went through the pipe like we expected it to.
FSTAssert(request.count == 1, @"Only single mutation requests are supported at the moment");
FSTMutation *actualWrite = request[0];
FSTAssert([actualWrite isEqual:expectedWrite],
@"Mock datastore received write %@ but first outstanding mutation was %@", actualWrite,
expectedWrite);
FSTLog(@"A write was sent: %@", actualWrite);
}
- (int)sentWritesCount {
return [self.datastore writesSent];
}
- (int)writeStreamRequestCount {
return [self.datastore writeStreamRequestCount];
}
- (int)watchStreamRequestCount {
return [self.datastore watchStreamRequestCount];
}
- (void)disableNetwork {
// Make sure to execute all writes that are currently queued. This allows us
// to assert on the total number of requests sent before shutdown.
[self.remoteStore fillWritePipeline];
[self.remoteStore disableNetwork];
}
- (void)enableNetwork {
[self.remoteStore enableNetwork];
}
- (void)changeUser:(FSTUser *)user {
self.currentUser = user;
[self.syncEngine userDidChange:user];
}
- (FSTOutstandingWrite *)receiveWriteAckWithVersion:(FSTSnapshotVersion *)commitVersion
mutationResults:
(NSArray<FSTMutationResult *> *)mutationResults {
FSTOutstandingWrite *write = [self currentOutstandingWrites].firstObject;
[[self currentOutstandingWrites] removeObjectAtIndex:0];
[self validateNextWriteSent:write.write];
[self.datastore ackWriteWithVersion:commitVersion mutationResults:mutationResults];
return write;
}
- (FSTOutstandingWrite *)receiveWriteError:(int)errorCode
userInfo:(NSDictionary<NSString *, id> *)userInfo {
NSError *error =
[NSError errorWithDomain:FIRFirestoreErrorDomain code:errorCode userInfo:userInfo];
FSTOutstandingWrite *write = [self currentOutstandingWrites].firstObject;
[self validateNextWriteSent:write.write];
// If this is a permanent error, the mutation is not expected to be sent again so we remove it
// from currentOutstandingWrites.
if ([FSTDatastore isPermanentWriteError:error]) {
[[self currentOutstandingWrites] removeObjectAtIndex:0];
}
FSTLog(@"Failing a write.");
[self.datastore failWriteWithError:error];
return write;
}
- (NSArray<FSTQueryEvent *> *)capturedEventsSinceLastCall {
NSArray<FSTQueryEvent *> *result = [self.events copy];
[self.events removeAllObjects];
return result;
}
- (FSTTargetID)addUserListenerWithQuery:(FSTQuery *)query {
// TODO(dimond): Allow customizing listen options in spec tests
// TODO(dimond): Change spec tests to verify isFromCache on snapshots
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
includeDocumentMetadataChanges:YES
waitForSyncWhenOnline:NO];
FSTQueryListener *listener = [[FSTQueryListener alloc]
initWithQuery:query
options:options
viewSnapshotHandler:^(FSTViewSnapshot *_Nullable snapshot, NSError *_Nullable error) {
FSTQueryEvent *event = [[FSTQueryEvent alloc] init];
event.query = query;
event.viewSnapshot = snapshot;
event.error = error;
[self.events addObject:event];
}];
self.queryListeners[query] = listener;
return [self.eventManager addListener:listener];
}
- (void)removeUserListenerWithQuery:(FSTQuery *)query {
FSTQueryListener *listener = self.queryListeners[query];
[self.queryListeners removeObjectForKey:query];
[self.eventManager removeListener:listener];
}
- (void)writeUserMutation:(FSTMutation *)mutation {
FSTOutstandingWrite *write = [[FSTOutstandingWrite alloc] init];
write.write = mutation;
[[self currentOutstandingWrites] addObject:write];
FSTLog(@"sending a user write.");
[self.syncEngine writeMutations:@[ mutation ]
completion:^(NSError *_Nullable error) {
FSTLog(@"A callback was called with error: %@", error);
write.done = YES;
write.error = error;
}];
}
- (void)receiveWatchChange:(FSTWatchChange *)change
snapshotVersion:(FSTSnapshotVersion *_Nullable)snapshot {
[self.datastore writeWatchChange:change snapshotVersion:snapshot];
}
- (void)receiveWatchStreamError:(int)errorCode userInfo:(NSDictionary<NSString *, id> *)userInfo {
NSError *error =
[NSError errorWithDomain:FIRFirestoreErrorDomain code:errorCode userInfo:userInfo];
[self.datastore failWatchStreamWithError:error];
// Unlike web, stream should re-open synchronously (if we have any listeners)
if (self.queryListeners.count > 0) {
FSTAssert(self.datastore.isWatchStreamOpen, @"Watch stream is open");
}
}
- (NSDictionary<FSTDocumentKey *, FSTBoxedTargetID *> *)currentLimboDocuments {
return [self.syncEngine currentLimboDocuments];
}
- (NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)activeTargets {
return [[self.datastore activeTargets] copy];
}
#pragma mark - Helper Methods
- (NSMutableArray<FSTOutstandingWrite *> *)currentOutstandingWrites {
NSMutableArray<FSTOutstandingWrite *> *writes = _outstandingWrites[self.currentUser];
if (!writes) {
writes = [NSMutableArray array];
_outstandingWrites[self.currentUser] = writes;
}
return writes;
}
@end
NS_ASSUME_NONNULL_END