aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/Database/Tests/Helpers
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Example/Database/Tests/Helpers
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Example/Database/Tests/Helpers')
-rw-r--r--Example/Database/Tests/Helpers/FDevice.h36
-rw-r--r--Example/Database/Tests/Helpers/FDevice.m133
-rw-r--r--Example/Database/Tests/Helpers/FEventTester.h37
-rw-r--r--Example/Database/Tests/Helpers/FEventTester.m172
-rw-r--r--Example/Database/Tests/Helpers/FIRFakeApp.h27
-rw-r--r--Example/Database/Tests/Helpers/FIRFakeApp.m48
-rw-r--r--Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.h28
-rw-r--r--Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.m61
-rw-r--r--Example/Database/Tests/Helpers/FMockStorageEngine.h23
-rw-r--r--Example/Database/Tests/Helpers/FMockStorageEngine.m168
-rw-r--r--Example/Database/Tests/Helpers/FTestAuthTokenGenerator.h23
-rw-r--r--Example/Database/Tests/Helpers/FTestAuthTokenGenerator.m90
-rw-r--r--Example/Database/Tests/Helpers/FTestBase.h38
-rw-r--r--Example/Database/Tests/Helpers/FTestBase.m170
-rw-r--r--Example/Database/Tests/Helpers/FTestCachePolicy.h27
-rw-r--r--Example/Database/Tests/Helpers/FTestCachePolicy.m65
-rw-r--r--Example/Database/Tests/Helpers/FTestClock.h28
-rw-r--r--Example/Database/Tests/Helpers/FTestClock.m33
-rw-r--r--Example/Database/Tests/Helpers/FTestContants.h23
-rw-r--r--Example/Database/Tests/Helpers/FTestExpectations.h32
-rw-r--r--Example/Database/Tests/Helpers/FTestExpectations.m88
-rw-r--r--Example/Database/Tests/Helpers/FTestHelpers.h38
-rw-r--r--Example/Database/Tests/Helpers/FTestHelpers.m132
-rw-r--r--Example/Database/Tests/Helpers/FTupleEventTypeString.h33
-rw-r--r--Example/Database/Tests/Helpers/FTupleEventTypeString.m53
-rw-r--r--Example/Database/Tests/Helpers/SenTest+FWaiter.h26
-rw-r--r--Example/Database/Tests/Helpers/SenTest+FWaiter.m57
27 files changed, 1689 insertions, 0 deletions
diff --git a/Example/Database/Tests/Helpers/FDevice.h b/Example/Database/Tests/Helpers/FDevice.h
new file mode 100644
index 0000000..c32aea0
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FDevice.h
@@ -0,0 +1,36 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@class FIRDatabaseReference;
+@class SenTest;
+
+@interface FDevice : NSObject
+- (id)initOnline;
+- (id)initOffline;
+- (id)initOnlineWithUrl:(NSString *)firebaseUrl;
+- (id)initOfflineWithUrl:(NSString *)firebaseUrl;
+- (void)goOffline;
+- (void)goOnline;
+- (void)restartOnline;
+- (void)restartOffline;
+- (void)waitForIdleUsingWaiter:(XCTest*)waiter;
+- (void)do:(void (^)(FIRDatabaseReference *))action;
+
+- (void)dispose;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FDevice.m b/Example/Database/Tests/Helpers/FDevice.m
new file mode 100644
index 0000000..f9667df
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FDevice.m
@@ -0,0 +1,133 @@
+/*
+ * 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 <XCTest/XCTest.h>
+#import "FDevice.h"
+#import "FIRDatabaseReference.h"
+#import "FRepoManager.h"
+#import "FIRDatabaseReference_Private.h"
+#import "FIRDatabaseConfig_Private.h"
+#import "SenTest+FWaiter.h"
+#import "FTestHelpers.h"
+
+@interface FDevice() {
+ FIRDatabaseConfig * config;
+ NSString *url;
+ BOOL isOnline;
+ BOOL disposed;
+}
+@end
+
+@implementation FDevice
+
+- (id)initOnline {
+ FIRDatabaseReference * ref = [FTestHelpers getRandomNode];
+ return [self initOnlineWithUrl:[ref description]];
+}
+
+- (id)initOffline {
+ FIRDatabaseReference * ref = [FTestHelpers getRandomNode];
+ return [self initOfflineWithUrl:[ref description]];
+}
+
+- (id)initOnlineWithUrl:(NSString *)firebaseUrl {
+ return [self initWithUrl:firebaseUrl andOnline:YES];
+}
+
+- (id)initOfflineWithUrl:(NSString *)firebaseUrl {
+ return [self initWithUrl:firebaseUrl andOnline:NO];
+}
+
+static NSUInteger deviceId = 0;
+
+- (id)initWithUrl:(NSString *)firebaseUrl andOnline:(BOOL)online {
+ self = [super init];
+ if (self) {
+ config = [FIRDatabaseConfig configForName:[NSString stringWithFormat:@"device-%lu", deviceId++]];
+ config.persistenceEnabled = YES;
+ url = firebaseUrl;
+ isOnline = online;
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ if (!self->disposed) {
+ [NSException raise:NSInternalInconsistencyException format:@"Forgot to dispose device"];
+ }
+}
+
+- (void) dispose {
+ // TODO: clear persistence
+ [FRepoManager disposeRepos:self->config];
+ self->disposed = YES;
+}
+
+- (void)goOffline {
+ isOnline = NO;
+ [FRepoManager interrupt:config];
+}
+
+- (void)goOnline {
+ isOnline = YES;
+ [FRepoManager resume:config];
+}
+
+- (void)restartOnline {
+ @autoreleasepool {
+ [FRepoManager disposeRepos:config];
+ isOnline = YES;
+ }
+}
+
+- (void)restartOffline {
+ @autoreleasepool {
+ [FRepoManager disposeRepos:config];
+ isOnline = NO;
+ }
+}
+
+// Waits for us to connect and then does an extra round-trip to make sure all initial state restoration is completely done.
+- (void)waitForIdleUsingWaiter:(XCTest*)waiter {
+ [self do:^(FIRDatabaseReference *ref) {
+ __block BOOL connected = NO;
+ FIRDatabaseHandle handle = [[ref.root child:@".info/connected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
+ connected = [snapshot.value boolValue];
+ }];
+ [waiter waitUntil:^BOOL { return connected; }];
+ [ref.root removeObserverWithHandle:handle];
+
+ // HACK: Do a deep setPriority (which we expect to fail because there's no data there) to do a no-op roundtrip.
+ __block BOOL done = NO;
+ [[ref.root child:@"ENTOHTNUHOE/ONTEHNUHTOE"] setPriority:@"blah" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
+ done = YES;
+ }];
+ [waiter waitUntil:^BOOL { return done; }];
+ }];
+}
+
+- (void)do:(void (^)(FIRDatabaseReference *))action {
+ @autoreleasepool {
+ FIRDatabaseReference *ref = [[[[FIRDatabaseReference alloc] initWithConfig:self->config] database] referenceFromURL:self->url];
+ if (!isOnline) {
+ [FRepoManager interrupt:config];
+ }
+ action(ref);
+ }
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FEventTester.h b/Example/Database/Tests/Helpers/FEventTester.h
new file mode 100644
index 0000000..b3503b9
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FEventTester.h
@@ -0,0 +1,37 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+
+@interface FEventTester : XCTestCase
+
+- (id)initFrom:(XCTestCase *)elsewhere;
+- (void) addLookingFor:(NSArray *)l;
+- (void) wait;
+- (void) waitForInitialization;
+- (void) unregister;
+
+@property (nonatomic, strong) NSMutableArray* lookingFor;
+@property (readwrite) int callbacksCalled;
+@property (nonatomic, strong) NSMutableDictionary* seenFirebaseLocations;
+//@property (nonatomic, strong) NSMutableDictionary* initializationEvents;
+@property (nonatomic, strong) XCTestCase* from;
+@property (nonatomic, strong) NSMutableArray* errors;
+@property (nonatomic, strong) NSMutableArray* actualPathsAndEvents;
+@property (nonatomic) int initializationEvents;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FEventTester.m b/Example/Database/Tests/Helpers/FEventTester.m
new file mode 100644
index 0000000..fa7c081
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FEventTester.m
@@ -0,0 +1,172 @@
+/*
+ * 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 "FEventTester.h"
+#import "FIRDatabaseReference.h"
+#import "FTupleBoolBlock.h"
+#import "FTupleEventTypeString.h"
+#import "FTestHelpers.h"
+#import "SenTest+FWaiter.h"
+
+@implementation FEventTester
+
+@synthesize lookingFor;
+@synthesize callbacksCalled;
+@synthesize from;
+@synthesize errors;
+@synthesize seenFirebaseLocations;
+@synthesize initializationEvents;
+@synthesize actualPathsAndEvents;
+
+- (id)initFrom:(XCTestCase *)elsewhere
+{
+ self = [super init];
+ if (self) {
+ self.seenFirebaseLocations = [[NSMutableDictionary alloc] init];
+ self.initializationEvents = 0;
+ self.lookingFor = [[NSMutableArray alloc] init];
+ self.actualPathsAndEvents = [[NSMutableArray alloc] init];
+ self.from = elsewhere;
+ self.callbacksCalled = 0;
+ }
+ return self;
+}
+
+- (void) addLookingFor:(NSArray *)l {
+
+ // expect them in the order they're given to us
+ [self.lookingFor addObjectsFromArray:l];
+
+
+ // see notes on ordering of listens in init.spec.js
+ NSArray* toListen = [l sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
+ FTupleEventTypeString* a = obj1;
+ FTupleEventTypeString* b = obj2;
+ NSUInteger lenA = [a.firebase description].length;
+ NSUInteger lenB = [b.firebase description].length;
+ if (lenA < lenB) {
+ return NSOrderedAscending;
+ } else if (lenA == lenB) {
+ return NSOrderedSame;
+ } else {
+ return NSOrderedDescending;
+ }
+ }];
+
+ for(FTupleEventTypeString* fevts in toListen) {
+ if(! [self.seenFirebaseLocations objectForKey:[fevts.firebase description]]) {
+ fevts.vvcallback = [self listenOnPath:fevts.firebase];
+ fevts.initialized = NO;
+ [self.seenFirebaseLocations setObject:fevts forKey:[fevts.firebase description]];
+ }
+ }
+}
+
+- (void) unregister {
+ for(FTupleEventTypeString* fevts in self.lookingFor) {
+ if (fevts.vvcallback) {
+ fevts.vvcallback();
+ }
+ }
+ [self.lookingFor removeAllObjects];
+}
+
+- (fbt_void_void) listenOnPath:(FIRDatabaseReference *)path {
+ FIRDatabaseHandle removedHandle = [path observeEventType:FIRDataEventTypeChildRemoved withBlock:[self makeEventCallback:FIRDataEventTypeChildRemoved]];
+ FIRDatabaseHandle addedHandle = [path observeEventType:FIRDataEventTypeChildAdded withBlock:[self makeEventCallback:FIRDataEventTypeChildAdded]];
+ FIRDatabaseHandle movedHandle = [path observeEventType:FIRDataEventTypeChildMoved withBlock:[self makeEventCallback:FIRDataEventTypeChildMoved]];
+ FIRDatabaseHandle changedHandle = [path observeEventType:FIRDataEventTypeChildChanged withBlock:[self makeEventCallback:FIRDataEventTypeChildChanged]];
+ FIRDatabaseHandle valueHandle = [path observeEventType:FIRDataEventTypeValue withBlock:[self makeEventCallback:FIRDataEventTypeValue]];
+
+ fbt_void_void cb = ^() {
+ [path removeObserverWithHandle:removedHandle];
+ [path removeObserverWithHandle:addedHandle];
+ [path removeObserverWithHandle:movedHandle];
+ [path removeObserverWithHandle:changedHandle];
+ [path removeObserverWithHandle:valueHandle];
+ };
+ return [cb copy];
+}
+
+- (void) wait {
+ [self waitUntil:^BOOL{
+ return self.actualPathsAndEvents.count >= self.lookingFor.count;
+ } timeout:kFirebaseTestTimeout];
+
+ for (int i = 0; i < self.lookingFor.count; ++i) {
+ FTupleEventTypeString* target = [self.lookingFor objectAtIndex:i];
+ FTupleEventTypeString* recvd = [self.actualPathsAndEvents objectAtIndex:i];
+ XCTAssertTrue([target isEqualTo:recvd], @"Expected %@ to match %@", target, recvd);
+ }
+
+ if (self.actualPathsAndEvents.count > self.lookingFor.count) {
+ NSLog(@"Too many events: %@", self.actualPathsAndEvents);
+ XCTFail(@"Received too many events");
+ }
+}
+
+- (void) waitForInitialization {
+ [self waitUntil:^BOOL{
+ for (FTupleEventTypeString* evt in [self.seenFirebaseLocations allValues]) {
+ if (!evt.initialized) {
+ return NO;
+ }
+ }
+
+ // splice out all of the initialization events
+ NSRange theRange;
+ theRange.location = 0;
+ theRange.length = self.initializationEvents;
+ [actualPathsAndEvents removeObjectsInRange:theRange];
+
+ return YES;
+ } timeout:kFirebaseTestTimeout];
+}
+
+- (fbt_void_datasnapshot) makeEventCallback:(FIRDataEventType)type {
+
+ fbt_void_datasnapshot cb = ^(FIRDataSnapshot * snap) {
+
+ FIRDatabaseReference * ref = snap.ref;
+ NSString* name = nil;
+ if (type != FIRDataEventTypeValue) {
+ ref = ref.parent;
+ name = snap.key;
+ }
+
+ FTupleEventTypeString* evt = [[FTupleEventTypeString alloc] initWithFirebase:ref withEvent:type withString:name];
+ [actualPathsAndEvents addObject:evt];
+
+ NSLog(@"Adding event: %@ (%@)", evt, [snap value]);
+
+ FTupleEventTypeString* targetEvt = [self.seenFirebaseLocations objectForKey:[ref description]];
+ if (targetEvt && !targetEvt.initialized) {
+ self.initializationEvents++;
+ if (type == FIRDataEventTypeValue) {
+ targetEvt.initialized = YES;
+ }
+ }
+ };
+ return [cb copy];
+}
+
+
+- (void) failWithException:(NSException *) anException {
+ //TODO: FIX
+ @throw anException;
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FIRFakeApp.h b/Example/Database/Tests/Helpers/FIRFakeApp.h
new file mode 100644
index 0000000..afe976a
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FIRFakeApp.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@class FIRFakeOptions;
+
+@interface FIRFakeApp : NSObject
+
+- (instancetype) initWithName:(NSString *)name URL:(NSString *)url;
+
+@property(nonatomic, readonly) FIRFakeOptions *options;
+@property(nonatomic, copy, readonly) NSString *name;
+@end
diff --git a/Example/Database/Tests/Helpers/FIRFakeApp.m b/Example/Database/Tests/Helpers/FIRFakeApp.m
new file mode 100644
index 0000000..b7abe81
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FIRFakeApp.m
@@ -0,0 +1,48 @@
+/*
+ * 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 "FIRFakeApp.h"
+
+@interface FIRFakeOptions: NSObject
+@property(nonatomic, readonly, copy) NSString *databaseURL;
+- (instancetype) initWithURL:(NSString *)url;
+@end
+
+@implementation FIRFakeOptions
+- (instancetype) initWithURL:(NSString *)url {
+ self = [super init];
+ if (self) {
+ self->_databaseURL = url;
+ }
+ return self;
+}
+@end
+
+@implementation FIRFakeApp
+
+- (instancetype) initWithName:(NSString *)name URL:(NSString *)url {
+ self = [super init];
+ if (self) {
+ self->_name = name;
+ self->_options = [[FIRFakeOptions alloc] initWithURL:url];
+ }
+ return self;
+}
+
+- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(void (^)(NSString *_Nullable token, NSError *_Nullable error))callback {
+ callback(nil, nil);
+}
+@end
diff --git a/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.h b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.h
new file mode 100644
index 0000000..e2a5751
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FAuthTokenProvider.h"
+
+@interface FIRTestAuthTokenProvider : NSObject <FAuthTokenProvider>
+
+@property (nonatomic, strong) NSString *token;
+@property (nonatomic, strong) NSString *nextToken;
+
+- (instancetype) initWithToken:(NSString *)token;
+- (instancetype) init NS_UNAVAILABLE;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.m b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.m
new file mode 100644
index 0000000..4719295
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.m
@@ -0,0 +1,61 @@
+/*
+ * 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 "FIRTestAuthTokenProvider.h"
+#import "FIRDatabaseQuery_Private.h"
+
+@interface FIRTestAuthTokenProvider ()
+
+@property (nonatomic, strong) NSMutableArray *listeners;
+
+@end
+
+@implementation FIRTestAuthTokenProvider
+
+- (instancetype) initWithToken:(NSString *)token {
+ self = [super init];
+ if (self != nil) {
+ self.listeners = [NSMutableArray array];
+ self.token = token;
+ }
+ return self;
+}
+
+- (void) setToken:(NSString *)token {
+ self->_token = token;
+ dispatch_async([FIRDatabaseQuery sharedQueue], ^{
+ [self.listeners enumerateObjectsUsingBlock:^(fbt_void_nsstring _Nonnull listener, NSUInteger idx, BOOL * _Nonnull stop) {
+ listener(token);
+ }];
+ });
+
+}
+
+- (void) fetchTokenForcingRefresh:(BOOL)forceRefresh withCallback:(fbt_void_nsstring_nserror)callback {
+ if (forceRefresh) {
+ self.token = self.nextToken;
+ }
+ // Simulate delay
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)), [FIRDatabaseQuery sharedQueue], ^{
+ callback(self.token, nil);
+ });
+}
+
+- (void) listenForTokenChanges:(fbt_void_nsstring)listener {
+ [self.listeners addObject:[listener copy]];
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FMockStorageEngine.h b/Example/Database/Tests/Helpers/FMockStorageEngine.h
new file mode 100644
index 0000000..98a7d84
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FMockStorageEngine.h
@@ -0,0 +1,23 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+#import "FStorageEngine.h"
+
+@interface FMockStorageEngine : NSObject<FStorageEngine>
+
+@end
diff --git a/Example/Database/Tests/Helpers/FMockStorageEngine.m b/Example/Database/Tests/Helpers/FMockStorageEngine.m
new file mode 100644
index 0000000..98cb596
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FMockStorageEngine.m
@@ -0,0 +1,168 @@
+/*
+ * 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 "FMockStorageEngine.h"
+
+#import "FWriteRecord.h"
+#import "FCompoundWrite.h"
+#import "FNode.h"
+#import "FEmptyNode.h"
+#import "FTrackedQuery.h"
+#import "FPruneForest.h"
+#import "FCompoundWrite.h"
+
+@interface FMockStorageEngine ()
+
+@property (nonatomic) BOOL closed;
+@property (nonatomic, strong) NSMutableDictionary *userWritesDict;
+@property (nonatomic, strong) FCompoundWrite *serverCache;
+@property (nonatomic, strong) NSMutableDictionary *trackedQueries;
+@property (nonatomic, strong) NSMutableDictionary *trackedQueryKeys;
+
+@end
+
+@implementation FMockStorageEngine
+
+- (id)init {
+ self = [super init];
+ if (self != nil) {
+ self->_userWritesDict = [NSMutableDictionary dictionary];
+ self->_serverCache = [FCompoundWrite emptyWrite];
+ self->_trackedQueries = [NSMutableDictionary dictionary];
+ self->_trackedQueryKeys = [NSMutableDictionary dictionary];
+ }
+ return self;
+}
+
+- (void)close {
+ self.closed = YES;
+}
+
+- (void)saveUserOverwrite:(id<FNode>)node atPath:(FPath *)path writeId:(NSUInteger)writeId {
+ FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path overwrite:node writeId:writeId visible:YES];
+ self.userWritesDict[@(writeId)] = writeRecord;
+}
+
+- (void)saveUserMerge:(FCompoundWrite *)merge atPath:(FPath *)path writeId:(NSUInteger)writeId {
+ FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path merge:merge writeId:writeId];
+ self.userWritesDict[@(writeId)] = writeRecord;
+}
+
+- (void)removeUserWrite:(NSUInteger)writeId {
+ [self.userWritesDict removeObjectForKey:@(writeId)];
+}
+
+- (void)removeAllUserWrites {
+ [self.userWritesDict removeAllObjects];
+}
+
+- (NSArray *)userWrites {
+ return [[self.userWritesDict allValues] sortedArrayUsingComparator:^NSComparisonResult(FWriteRecord *obj1, FWriteRecord *obj2) {
+ if (obj1.writeId < obj2.writeId) {
+ return NSOrderedAscending;
+ } else if (obj1.writeId > obj2.writeId) {
+ return NSOrderedDescending;
+ } else {
+ return NSOrderedSame;
+ }
+ }];
+}
+
+- (id<FNode>)serverCacheAtPath:(FPath *)path {
+ return [[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]];
+}
+
+- (id<FNode>)serverCacheForKeys:(NSSet *)keys atPath:(FPath *)path {
+ __block id<FNode> children = [FEmptyNode emptyNode];
+ id<FNode> fullNode = [[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]];
+ [keys enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
+ children = [children updateImmediateChild:key withNewChild:[fullNode getImmediateChild:key]];
+ }];
+ return children;
+}
+
+- (void)updateServerCache:(id<FNode>)node atPath:(FPath *)path merge:(BOOL)merge {
+ if (merge) {
+ [node enumerateChildrenUsingBlock:^(NSString *key, id<FNode> childNode, BOOL *stop) {
+ self.serverCache = [self.serverCache addWrite:childNode atPath:[path childFromString:key]];
+ }];
+ } else {
+ self.serverCache = [self.serverCache addWrite:node atPath:path];
+ }
+}
+
+- (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path {
+ self.serverCache = [self.serverCache addCompoundWrite:merge atPath:path];
+}
+
+- (NSUInteger)serverCacheEstimatedSizeInBytes {
+ id data = [[self.serverCache applyToNode:[FEmptyNode emptyNode]] valForExport:YES];
+ return [NSJSONSerialization dataWithJSONObject:data options:0 error:nil].length;
+}
+
+- (void)pruneCache:(FPruneForest *)pruneForest atPath:(FPath *)prunePath {
+ [self.serverCache enumerateWrites:^(FPath *absolutePath, id<FNode> node, BOOL *stop) {
+ NSAssert([prunePath isEqual:absolutePath] || ![absolutePath contains:prunePath], @"Pruning at %@ but we found data higher up!", prunePath);
+ if ([prunePath contains:absolutePath]) {
+ FPath *relativePath = [FPath relativePathFrom:prunePath to:absolutePath];
+ if ([pruneForest shouldPruneUnkeptDescendantsAtPath:relativePath]) {
+ __block FCompoundWrite *newCache = [FCompoundWrite emptyWrite];
+ [[pruneForest childAtPath:relativePath] enumarateKeptNodesUsingBlock:^(FPath *keepPath) {
+ newCache = [newCache addWrite:[node getChild:keepPath] atPath:keepPath];
+ }];
+ self.serverCache = [[self.serverCache removeWriteAtPath:absolutePath] addCompoundWrite:newCache atPath:absolutePath];
+ } else {
+ // NOTE: This is technically a valid scenario (e.g. you ask to prune at / but only want to prune
+ // 'foo' and 'bar' and ignore everything else). But currently our pruning will explicitly
+ // prune or keep everything we know about, so if we hit this it means our tracked queries and
+ // the server cache are out of sync.
+ NSAssert([pruneForest shouldKeepPath:relativePath], @"We have data at %@ that is neither pruned nor kept.", relativePath);
+ }
+ }
+ }];
+}
+
+- (NSArray *)loadTrackedQueries {
+ return self.trackedQueries.allValues;
+}
+
+- (void)removeTrackedQuery:(NSUInteger)queryId {
+ [self.trackedQueries removeObjectForKey:@(queryId)];
+ [self.trackedQueryKeys removeObjectForKey:@(queryId)];
+}
+
+- (void)saveTrackedQuery:(FTrackedQuery *)query {
+ self.trackedQueries[@(query.queryId)] = query;
+}
+
+- (void)setTrackedQueryKeys:(NSSet *)keys forQueryId:(NSUInteger)queryId {
+ self.trackedQueryKeys[@(queryId)] = keys;
+}
+
+- (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added removedKeys:(NSSet *)removed forQueryId:(NSUInteger)queryId {
+ NSSet *oldKeys = [self trackedQueryKeysForQuery:queryId];
+ NSMutableSet *newKeys = [NSMutableSet setWithSet:oldKeys];
+ [newKeys minusSet:removed];
+ [newKeys unionSet:added];
+ self.trackedQueryKeys[@(queryId)] = newKeys;
+}
+
+- (NSSet *)trackedQueryKeysForQuery:(NSUInteger)queryId {
+ NSSet *keys = self.trackedQueryKeys[@(queryId)];
+ return keys != nil ? keys : [NSSet set];
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.h b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.h
new file mode 100644
index 0000000..d6d9fd3
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.h
@@ -0,0 +1,23 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@interface FTestAuthTokenGenerator : NSObject
+
++ (NSString *) tokenWithSecret:(NSString *)secret authData:(NSDictionary *)data andOptions:(NSDictionary *)options;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.m b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.m
new file mode 100644
index 0000000..bd98e82
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.m
@@ -0,0 +1,90 @@
+/*
+ * 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 <CommonCrypto/CommonHMAC.h>
+#import "FTestAuthTokenGenerator.h"
+#import "Base64.h"
+
+@implementation FTestAuthTokenGenerator
+
++ (NSString *) jsonStringForData:(id)data {
+ NSData* jsonData = [NSJSONSerialization dataWithJSONObject:data
+ options:kNilOptions error:nil];
+
+ return [[NSString alloc] initWithData:jsonData
+ encoding:NSUTF8StringEncoding];
+}
+
++ (NSNumber *) tokenVersion {
+ return @0;
+}
+
++ (NSMutableDictionary *) createOptionsClaims:(NSDictionary *)options {
+ NSMutableDictionary* claims = [[NSMutableDictionary alloc] init];
+ if (options) {
+ NSDictionary* map = @{
+ @"expires": @"exp",
+ @"notBefore": @"nbf",
+ @"admin": @"admin",
+ @"debug": @"debug",
+ @"simulate": @"simulate"
+ };
+
+ for (NSString* claim in map) {
+ if (options[claim] != nil) {
+ NSString* claimName = [map objectForKey:claim];
+ id val = [options objectForKey:claim];
+ [claims setObject:val forKey:claimName];
+ }
+ }
+ }
+ return claims;
+}
+
++ (NSString *) webSafeBase64:(NSString *)encoded {
+ return [[[encoded stringByReplacingOccurrencesOfString:@"=" withString:@""] stringByReplacingOccurrencesOfString:@"+" withString:@"-"] stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
+}
+
++ (NSString *) base64EncodeString:(NSString *)target {
+ return [self webSafeBase64:[target base64EncodedString]];
+}
+
++ (NSString *) tokenWithClaims:(NSDictionary *)claims andSecret:(NSString *)secret {
+ NSDictionary* headerData = @{@"typ": @"JWT", @"alg": @"HS256"};
+ NSString* encodedHeader = [self base64EncodeString:[self jsonStringForData:headerData]];
+ NSString* encodedClaims = [self base64EncodeString:[self jsonStringForData:claims]];
+
+ NSString* secureBits = [NSString stringWithFormat:@"%@.%@", encodedHeader, encodedClaims];
+
+ const char *cKey = [secret cStringUsingEncoding:NSUTF8StringEncoding];
+ const char *cData = [secureBits cStringUsingEncoding:NSUTF8StringEncoding];
+ unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
+ CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
+ NSData* hmac = [NSData dataWithBytesNoCopy:cHMAC length:CC_SHA256_DIGEST_LENGTH freeWhenDone:NO];
+ NSString* encodedHmac = [self webSafeBase64:[hmac base64EncodedString]];
+ return [NSString stringWithFormat:@"%@.%@.%@", encodedHeader, encodedClaims, encodedHmac];
+}
+
++ (NSString *) tokenWithSecret:(NSString *)secret authData:(NSDictionary *)data andOptions:(NSDictionary *)options {
+ NSMutableDictionary* claims = [self createOptionsClaims:options];
+ [claims setObject:[self tokenVersion] forKey:@"v"];
+ NSNumber* now = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]];
+ [claims setObject:now forKey:@"iat"];
+ [claims setObject:data forKey:@"d"];
+ return [self tokenWithClaims:claims andSecret:secret];
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestBase.h b/Example/Database/Tests/Helpers/FTestBase.h
new file mode 100644
index 0000000..8137b94
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestBase.h
@@ -0,0 +1,38 @@
+/*
+ * 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 <XCTest/XCTest.h>
+#import "FTestHelpers.h"
+#import "SenTest+FWaiter.h"
+
+@interface FTestBase : XCTestCase {
+ BOOL runPerfTests;
+}
+
+- (void)snapWaiter:(FIRDatabaseReference *)path withBlock:(fbt_void_datasnapshot)fn;
+- (void)waitUntilConnected:(FIRDatabaseReference *)ref;
+- (void)waitForQueue:(FIRDatabaseReference *)ref;
+- (void)waitForEvents:(FIRDatabaseReference *)ref;
+- (void)waitForRoundTrip:(FIRDatabaseReference *)ref;
+- (void)waitForValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected;
+- (void)waitForExportValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected;
+- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value;
+- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value andPriority:(id)priority;
+- (void)waitForCompletionOf:(FIRDatabaseReference *)ref updateChildValues:(NSDictionary *)values;
+
+@property(nonatomic, readonly) NSString *databaseURL;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestBase.m b/Example/Database/Tests/Helpers/FTestBase.m
new file mode 100644
index 0000000..f55c73b
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestBase.m
@@ -0,0 +1,170 @@
+/*
+ * 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 "FIRApp.h"
+#import "FIROptions.h"
+#import "FTestBase.h"
+#import "FTestAuthTokenGenerator.h"
+#import "FIRDatabaseQuery_Private.h"
+#import "FIRTestAuthTokenProvider.h"
+
+@implementation FTestBase
+
++ (void)setUp
+{
+ static dispatch_once_t once;
+ dispatch_once(&once, ^ {
+ [FIRApp configure];
+ });
+}
+
+- (void)setUp
+{
+ [super setUp];
+
+ [FIRDatabase setLoggingEnabled:YES];
+ _databaseURL = [[FIRApp defaultApp] options].databaseURL;
+
+ // Disabled normally since they slow down the tests and don't actually assert anything (they just NSLog timings).
+ runPerfTests = NO;
+}
+
+- (void)snapWaiter:(FIRDatabaseReference *)path withBlock:(fbt_void_datasnapshot)fn {
+ __block BOOL done = NO;
+
+ [path observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snap) {
+ fn(snap);
+ done = YES;
+ }];
+
+ NSTimeInterval timeTaken = [self waitUntil:^BOOL{
+ return done;
+ } timeout:kFirebaseTestWaitUntilTimeout];
+
+ NSLog(@"snapWaiter:withBlock: timeTaken:%f", timeTaken);
+
+ XCTAssertTrue(done, @"Properly finished.");
+}
+
+- (void) waitUntilConnected:(FIRDatabaseReference *)ref {
+ __block BOOL connected = NO;
+ FIRDatabaseHandle handle = [[ref.root child:@".info/connected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
+ connected = [snapshot.value boolValue];
+ }];
+ WAIT_FOR(connected);
+ [ref.root removeObserverWithHandle:handle];
+}
+
+- (void) waitForRoundTrip:(FIRDatabaseReference *)ref {
+ // HACK: Do a deep setPriority (which we expect to fail because there's no data there) to do a no-op roundtrip.
+ __block BOOL done = NO;
+ [[ref.root child:@"ENTOHTNUHOE/ONTEHNUHTOE"] setPriority:@"blah" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
+ done = YES;
+ }];
+ WAIT_FOR(done);
+}
+
+- (void) waitForQueue:(FIRDatabaseReference *)ref {
+ dispatch_sync([FIRDatabaseQuery sharedQueue], ^{});
+}
+
+- (void) waitForEvents:(FIRDatabaseReference *)ref {
+ [self waitForQueue:ref];
+ __block BOOL done = NO;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ done = YES;
+ });
+ WAIT_FOR(done);
+}
+
+- (void)waitForValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected {
+ __block id value;
+ FIRDatabaseHandle handle = [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
+ value = snapshot.value;
+ }];
+
+ @try {
+ [self waitUntil:^BOOL {
+ return [value isEqual:expected];
+ }];
+ } @catch (NSException *exception) {
+ @throw [NSException exceptionWithName:@"DidNotGetValue" reason:@"Did not get expected value"
+ userInfo:@{ @"expected": (!expected ? @"nil" : expected),
+ @"actual": (!value ? @"nil" : value) }];
+ } @finally {
+ [ref removeObserverWithHandle:handle];
+ }
+}
+
+- (void)waitForExportValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected {
+ __block id value;
+ FIRDatabaseHandle handle = [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
+ value = snapshot.valueInExportFormat;
+ }];
+
+ @try {
+ [self waitUntil:^BOOL {
+ return [value isEqual:expected];
+ }];
+ } @catch (NSException *exception) {
+ if ([exception.name isEqualToString:@"Timed out"]) {
+ @throw [NSException exceptionWithName:@"DidNotGetValue" reason:@"Did not get expected value"
+ userInfo:@{ @"expected": (!expected ? @"nil" : expected),
+ @"actual": (!value ? @"nil" : value) }]; } else {
+ @throw exception;
+ }
+ } @finally {
+ [ref removeObserverWithHandle:handle];
+ }
+}
+
+- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value {
+ [self waitForCompletionOf:ref setValue:value andPriority:nil];
+}
+
+- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value andPriority:(id)priority {
+ __block BOOL done = NO;
+ [ref setValue:value andPriority:priority withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
+ done = YES;
+ }];
+
+ @try {
+ WAIT_FOR(done);
+ } @catch (NSException *exception) {
+ @throw [NSException exceptionWithName:@"DidNotSetValue" reason:@"Did not complete setting value"
+ userInfo:@{ @"ref": [ref description],
+ @"done": done ? @"true" : @"false",
+ @"value": (!value ? @"nil" : value),
+ @"priority": (!priority ? @"nil" : priority) }];
+ }
+}
+
+- (void)waitForCompletionOf:(FIRDatabaseReference *)ref updateChildValues:(NSDictionary *)values {
+ __block BOOL done = NO;
+ [ref updateChildValues:values withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
+ done = YES;
+ }];
+
+ @try {
+ WAIT_FOR(done);
+ } @catch (NSException *exception) {
+ @throw [NSException exceptionWithName:@"DidNotUpdateChildValues" reason:@"Could not finish updating child values"
+ userInfo:@{ @"ref": [ref description],
+ @"values": (!values ? @"nil" : values)}];
+ }
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestCachePolicy.h b/Example/Database/Tests/Helpers/FTestCachePolicy.h
new file mode 100644
index 0000000..688c21d
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestCachePolicy.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+#import "FCachePolicy.h"
+
+@interface FTestCachePolicy : NSObject<FCachePolicy>
+
+- (id)initWithPercent:(float)percent maxQueries:(NSUInteger)maxQueries;
+
+- (void)pruneOnNextCheck;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestCachePolicy.m b/Example/Database/Tests/Helpers/FTestCachePolicy.m
new file mode 100644
index 0000000..aacd010
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestCachePolicy.m
@@ -0,0 +1,65 @@
+/*
+ * 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 "FTestCachePolicy.h"
+
+@interface FTestCachePolicy ()
+
+
+@property (nonatomic) float percentOfQueries;
+@property (nonatomic) NSUInteger maxTrackedQueries;
+@property (nonatomic) BOOL pruneNext;
+
+@end
+
+@implementation FTestCachePolicy
+
+- (id)initWithPercent:(float)percent maxQueries:(NSUInteger)maxQueries {
+ self = [super init];
+ if (self != nil) {
+ self->_maxTrackedQueries = maxQueries;
+ self->_percentOfQueries = percent;
+ self->_pruneNext = NO;
+ }
+ return self;
+}
+
+- (void)pruneOnNextCheck {
+ self.pruneNext = YES;
+}
+
+- (BOOL)shouldPruneCacheWithSize:(NSUInteger)cacheSize numberOfTrackedQueries:(NSUInteger)numTrackedQueries {
+ if (self.pruneNext) {
+ self.pruneNext = NO;
+ return YES;
+ } else {
+ return NO;
+ }
+}
+
+- (BOOL)shouldCheckCacheSize:(NSUInteger)serverUpdatesSinceLastCheck {
+ return YES;
+}
+
+- (float)percentOfQueriesToPruneAtOnce {
+ return self.percentOfQueries;
+}
+
+- (NSUInteger)maxNumberOfQueriesToKeep {
+ return self.maxTrackedQueries;
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestClock.h b/Example/Database/Tests/Helpers/FTestClock.h
new file mode 100644
index 0000000..5520c6a
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestClock.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+#import "FClock.h"
+
+@interface FTestClock : NSObject<FClock>
+
+@property (nonatomic, readonly) NSTimeInterval currentTime;
+
+- (id)init;
+- (void)tick;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestClock.m b/Example/Database/Tests/Helpers/FTestClock.m
new file mode 100644
index 0000000..43599ac
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestClock.m
@@ -0,0 +1,33 @@
+/*
+ * 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 "FTestClock.h"
+
+@implementation FTestClock
+
+- (id)init {
+ self = [super init];
+ if (self != nil) {
+ self->_currentTime = 0.001;
+ }
+ return self;
+}
+
+- (void)tick {
+ self->_currentTime = self->_currentTime + 0.001;
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestContants.h b/Example/Database/Tests/Helpers/FTestContants.h
new file mode 100644
index 0000000..bc8dd8d
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestContants.h
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+#ifndef Firebase_FTestContants_h
+#define Firebase_FTestContants_h
+
+#define kFirebaseTestTimeout 7
+#define kFirebaseTestWaitUntilTimeout 5
+
+#endif
diff --git a/Example/Database/Tests/Helpers/FTestExpectations.h b/Example/Database/Tests/Helpers/FTestExpectations.h
new file mode 100644
index 0000000..8a797c8
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestExpectations.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+#import "FIRDatabaseQuery.h"
+
+@interface FTestExpectations : XCTestCase {
+ NSMutableArray* expectations;
+ XCTestCase* from;
+}
+
+- (id) initFrom:(XCTestCase *)other;
+- (void)addQuery:(FIRDatabaseQuery *)query withExpectation:(id)expectation;
+- (void) validate;
+
+@property (readonly) BOOL isReady;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestExpectations.m b/Example/Database/Tests/Helpers/FTestExpectations.m
new file mode 100644
index 0000000..d0f84d7
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestExpectations.m
@@ -0,0 +1,88 @@
+/*
+ * 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 "FTestExpectations.h"
+#import "FIRDataSnapshot.h"
+
+@interface FExpectation : NSObject
+
+@property (strong, nonatomic) FIRDatabaseQuery * query;
+@property (strong, nonatomic) id expectation;
+@property (strong, nonatomic) FIRDataSnapshot * snap;
+
+@end
+
+@implementation FExpectation
+
+@synthesize query;
+@synthesize expectation;
+@synthesize snap;
+
+@end
+
+@implementation FTestExpectations
+
+- (id) initFrom:(XCTestCase *)other {
+ self = [super init];
+ if (self) {
+ expectations = [[NSMutableArray alloc] init];
+ from = other;
+ }
+ return self;
+}
+
+- (void)addQuery:(FIRDatabaseQuery *)query withExpectation:(id)expectation {
+ FExpectation* exp = [[FExpectation alloc] init];
+ exp.query = query;
+ exp.expectation = expectation;
+ [query observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
+ exp.snap = snapshot;
+ }];
+ [expectations addObject:exp];
+}
+
+- (BOOL) isReady {
+ for (FExpectation* exp in expectations) {
+ if (!exp.snap) {
+ return NO;
+ }
+ // Note that a failure here will end up triggering the timeout
+ FIRDataSnapshot * snap = exp.snap;
+ NSDictionary* result = snap.value;
+ NSDictionary* expected = exp.expectation;
+ if ([result isEqual:[NSNull null]] || ![result isEqualToDictionary:expected]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (void) validate {
+ for (FExpectation* exp in expectations) {
+ FIRDataSnapshot * snap = exp.snap;
+ NSDictionary* result = [snap value];
+ NSDictionary* expected = exp.expectation;
+ XCTAssertTrue([result isEqualToDictionary:expected], @"Expectation mismatch: %@ should be %@", result, expected);
+ }
+}
+
+- (void) failWithException:(NSException *) anException {
+ @throw anException;
+ // TODO: fix
+ //[from failWithException:anException];
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestHelpers.h b/Example/Database/Tests/Helpers/FTestHelpers.h
new file mode 100644
index 0000000..679be7e
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestHelpers.h
@@ -0,0 +1,38 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+#import "FTupleFirebase.h"
+#import "FRepoManager.h"
+#import "FIRDatabaseReference_Private.h"
+#import "FTestContants.h"
+#import "FSnapshotUtilities.h"
+
+#define WAIT_FOR(x) [self waitUntil:^{ return (BOOL)(x); }];
+
+#define NODE(__node) [FSnapshotUtilities nodeFrom:(__node)]
+#define PATH(__path) [FPath pathWithString:(__path)]
+
+@interface FTestHelpers : XCTestCase
++ (FIRDatabaseReference *) getRandomNode;
++ (FIRDatabaseReference *) getRandomNodeWithoutPersistence;
++ (FTupleFirebase *) getRandomNodePair;
++ (FTupleFirebase *) getRandomNodePairWithoutPersistence;
++ (FTupleFirebase *) getRandomNodeTriple;
++ (id<FNode>)leafNodeOfSize:(NSUInteger)size;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTestHelpers.m b/Example/Database/Tests/Helpers/FTestHelpers.m
new file mode 100644
index 0000000..8ffdc7d
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTestHelpers.m
@@ -0,0 +1,132 @@
+/*
+ * 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 "FTestHelpers.h"
+#import "FConstants.h"
+#import "FIRApp.h"
+#import "FIROptions.h"
+#import "FIRDatabaseConfig_Private.h"
+#import "FTestAuthTokenGenerator.h"
+
+@implementation FTestHelpers
+
++ (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds {
+ NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
+ NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:seconds];
+ NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate];
+ NSTimeInterval currentTime;
+
+ for (currentTime = [NSDate timeIntervalSinceReferenceDate];
+ !predicate() && currentTime < timeoutTime;
+ currentTime = [NSDate timeIntervalSinceReferenceDate]) {
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
+ }
+
+ NSTimeInterval finish = [NSDate timeIntervalSinceReferenceDate];
+
+ NSAssert(currentTime <= timeoutTime, @"Timed out");
+
+ return (finish - start);
+}
+
++ (NSArray*) getRandomNodes:(int)num persistence:(BOOL)persistence {
+ static dispatch_once_t pred = 0;
+ static NSMutableArray *persistenceRefs = nil;
+ static NSMutableArray *noPersistenceRefs = nil;
+ dispatch_once(&pred, ^{
+ persistenceRefs = [[NSMutableArray alloc] init];
+ noPersistenceRefs = [[NSMutableArray alloc] init];
+ // Uncomment the following line to run tests against a background thread
+ //[Firebase setDispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
+ });
+
+ NSMutableArray *refs = (persistence) ? persistenceRefs : noPersistenceRefs;
+
+ id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:[FIRApp defaultApp]];
+
+ while (num > refs.count) {
+ NSString *sessionIdentifier = [NSString stringWithFormat:@"test-config-%@persistence-%lu", (persistence) ? @"" : @"no-", refs.count];
+ FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:sessionIdentifier authTokenProvider:authTokenProvider];
+ config.persistenceEnabled = persistence;
+ FIRDatabaseReference * ref = [[FIRDatabaseReference alloc] initWithConfig:config];
+ [refs addObject:ref];
+ }
+
+ NSMutableArray* results = [[NSMutableArray alloc] init];
+ NSString* name = nil;
+ for (int i = 0; i < num; ++i) {
+ FIRDatabaseReference * ref = [refs objectAtIndex:i];
+ if (!name) {
+ name = [ref childByAutoId].key;
+ }
+ [results addObject:[ref child:name]];
+ }
+ return results;
+}
+
+// Helpers
++ (FIRDatabaseReference *) getRandomNode {
+ NSArray* refs = [self getRandomNodes:1 persistence:YES];
+ return [refs objectAtIndex:0];
+}
+
++ (FIRDatabaseReference *) getRandomNodeWithoutPersistence {
+ NSArray* refs = [self getRandomNodes:1 persistence:NO];
+ return refs[0];
+}
+
++ (FTupleFirebase *) getRandomNodePair {
+ NSArray* refs = [self getRandomNodes:2 persistence:YES];
+
+ FTupleFirebase* tuple = [[FTupleFirebase alloc] init];
+ tuple.one = [refs objectAtIndex:0];
+ tuple.two = [refs objectAtIndex:1];
+
+ return tuple;
+}
+
++ (FTupleFirebase *) getRandomNodePairWithoutPersistence {
+ NSArray* refs = [self getRandomNodes:2 persistence:NO];
+
+ FTupleFirebase* tuple = [[FTupleFirebase alloc] init];
+ tuple.one = refs[0];
+ tuple.two = refs[1];
+
+ return tuple;
+}
+
++ (FTupleFirebase *) getRandomNodeTriple {
+ NSArray* refs = [self getRandomNodes:3 persistence:YES];
+ FTupleFirebase* triple = [[FTupleFirebase alloc] init];
+ triple.one = [refs objectAtIndex:0];
+ triple.two = [refs objectAtIndex:1];
+ triple.three = [refs objectAtIndex:2];
+
+ return triple;
+}
+
++ (id<FNode>)leafNodeOfSize:(NSUInteger)size {
+ NSMutableString *string = [NSMutableString string];
+ NSString *pattern = @"abdefghijklmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ for (NSUInteger i = 0; i < size - pattern.length; i = i + pattern.length) {
+ [string appendString:pattern];
+ }
+ NSUInteger remainingLength = size - string.length;
+ [string appendString:[pattern substringToIndex:remainingLength]];
+ return [FSnapshotUtilities nodeFrom:string];
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTupleEventTypeString.h b/Example/Database/Tests/Helpers/FTupleEventTypeString.h
new file mode 100644
index 0000000..adcb4a0
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTupleEventTypeString.h
@@ -0,0 +1,33 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "FIRDataEventType.h"
+#import "FIRDatabaseReference.h"
+#import "FTypedefs.h"
+
+@interface FTupleEventTypeString : NSObject
+
+- (id)initWithFirebase:(FIRDatabaseReference *)f withEvent:(FIRDataEventType)evt withString:(NSString *)str;
+- (BOOL) isEqualTo:(FTupleEventTypeString *)other;
+
+@property (nonatomic, strong) FIRDatabaseReference * firebase;
+@property (readwrite) FIRDataEventType eventType;
+@property (nonatomic, strong) NSString* string;
+@property (nonatomic, copy) fbt_void_void vvcallback;
+@property (nonatomic) BOOL initialized;
+
+@end
diff --git a/Example/Database/Tests/Helpers/FTupleEventTypeString.m b/Example/Database/Tests/Helpers/FTupleEventTypeString.m
new file mode 100644
index 0000000..4cb3df2
--- /dev/null
+++ b/Example/Database/Tests/Helpers/FTupleEventTypeString.m
@@ -0,0 +1,53 @@
+/*
+ * 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 "FTupleEventTypeString.h"
+
+@implementation FTupleEventTypeString
+
+@synthesize firebase;
+@synthesize eventType;
+@synthesize string;
+@synthesize vvcallback;
+@synthesize initialized;
+
+- (id)initWithFirebase:(FIRDatabaseReference *)f withEvent:(FIRDataEventType)evt withString:(NSString *)str;
+{
+ self = [super init];
+ if (self) {
+ self.firebase = f;
+ self.eventType = evt;
+ self.string = str;
+ self.initialized = NO;
+ }
+ return self;
+}
+
+- (NSString *) description {
+ return [NSString stringWithFormat:@"%@ %@ (%zd)", self.firebase, self.string, self.eventType];
+}
+
+- (BOOL) isEqualTo:(FTupleEventTypeString *)other {
+ BOOL stringsEqual = NO;
+ if (self.string == nil && other.string == nil) {
+ stringsEqual = YES;
+ } else if (self.string != nil && other.string != nil) {
+ stringsEqual = [self.string isEqualToString:other.string];
+ }
+ return self.eventType == other.eventType && stringsEqual && [[self.firebase description] isEqualToString:[other.firebase description]];
+}
+
+@end
diff --git a/Example/Database/Tests/Helpers/SenTest+FWaiter.h b/Example/Database/Tests/Helpers/SenTest+FWaiter.h
new file mode 100644
index 0000000..81556df
--- /dev/null
+++ b/Example/Database/Tests/Helpers/SenTest+FWaiter.h
@@ -0,0 +1,26 @@
+/*
+ * 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 <XCTest/XCTest.h>
+
+@interface XCTest (FWaiter)
+
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate;
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate description:(NSString*)desc;
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds;
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds description:(NSString*)desc;
+
+@end
diff --git a/Example/Database/Tests/Helpers/SenTest+FWaiter.m b/Example/Database/Tests/Helpers/SenTest+FWaiter.m
new file mode 100644
index 0000000..4c5c854
--- /dev/null
+++ b/Example/Database/Tests/Helpers/SenTest+FWaiter.m
@@ -0,0 +1,57 @@
+/*
+ * 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 "SenTest+FWaiter.h"
+#import "FTestContants.h"
+
+@implementation XCTestCase (FWaiter)
+
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate {
+ return [self waitUntil:predicate timeout:kFirebaseTestWaitUntilTimeout description:nil];
+}
+
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate description:(NSString*)desc {
+ return [self waitUntil:predicate timeout:kFirebaseTestWaitUntilTimeout description:desc];
+}
+
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds {
+ return [self waitUntil:predicate timeout:seconds description:nil];
+}
+
+- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds description:(NSString*)desc {
+ NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
+ NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:seconds];
+ NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate];
+ NSTimeInterval currentTime;
+
+ for (currentTime = [NSDate timeIntervalSinceReferenceDate];
+ !predicate() && currentTime < timeoutTime;
+ currentTime = [NSDate timeIntervalSinceReferenceDate]) {
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
+ }
+
+ NSTimeInterval finish = [NSDate timeIntervalSinceReferenceDate];
+ if (currentTime > timeoutTime) {
+ if (desc != nil) {
+ XCTFail("Timed out on: %@", desc);
+ } else {
+ XCTFail("Timed out");
+ }
+ }
+ return (finish - start);
+}
+
+@end