diff --git a/.travis.yml b/.travis.yml index 5a089fe03f6..6748698c5b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -112,15 +112,15 @@ jobs: # The travis_wait is necessary because the command takes more than 10 minutes. - travis_wait 45 ./scripts/if_cron.sh ./scripts/pod_lib_lint.sh FirebaseFirestore.podspec --use-libraries --allow-warnings --no-subspecs - # GoogleDataLogger unit tests and pod linting using the default Xcode version. + # GoogleDataTransport unit tests and pod linting using the default Xcode version. - stage: test env: - - PROJECT=GoogleDataLogger PLATFORM=iOS METHOD=xcodebuild + - PROJECT=GoogleDataTransport PLATFORM=iOS METHOD=xcodebuild before_install: - ./scripts/if_changed.sh ./scripts/install_prereqs.sh script: - ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM - - ./scripts/if_changed.sh ./scripts/pod_lib_lint.sh GoogleDataLogger.podspec + - ./scripts/if_changed.sh ./scripts/pod_lib_lint.sh GoogleDataTransport.podspec # Daily test for symbol collisions between Firebase and CocoaPods. - stage: test diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m deleted file mode 100644 index 71b7cc2d540..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2018 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 - -#import "GDLAssert.h" -#import "GDLLogEvent_Private.h" - -@implementation GDLLogEvent - -- (instancetype)initWithLogMapID:(NSString *)logMapID logTarget:(NSInteger)logTarget { - GDLAssert(logMapID.length > 0, @"Please give a valid log map ID"); - GDLAssert(logTarget > 0, @"A log target cannot be negative or 0"); - self = [super init]; - if (self) { - _logMapID = logMapID; - _logTarget = logTarget; - _qosTier = GDLLogQosDefault; - } - return self; -} - -- (instancetype)copy { - GDLLogEvent *copy = [[GDLLogEvent alloc] initWithLogMapID:_logMapID logTarget:_logTarget]; - copy.extension = _extension; - copy.extensionBytes = _extensionBytes; - copy.qosTier = _qosTier; - copy.clockSnapshot = _clockSnapshot; - copy.customPrioritizationParams = _customPrioritizationParams; - return copy; -} - -- (NSUInteger)hash { - // This loses some precision, but it's probably fine. - NSUInteger logMapIDHash = [_logMapID hash]; - NSUInteger timeHash = [_clockSnapshot hash]; - NSUInteger extensionBytesHash = [_extensionBytes hash]; - return logMapIDHash ^ _logTarget ^ extensionBytesHash ^ _qosTier ^ timeHash; -} - -- (void)setExtension:(id)extension { - // If you're looking here because of a performance issue in -transportBytes slowing the assignment - // of extension, one way to address this is to add a queue to this class, - // dispatch_(barrier_ if concurrent)async here, and implement the getter with a dispatch_sync. - if (extension != _extension) { - _extension = extension; - _extensionBytes = [extension transportBytes]; - } -} - -#pragma mark - NSSecureCoding and NSCoding Protocols - -/** NSCoding key for logMapID property. */ -static NSString *logMapIDKey = @"_logMapID"; - -/** NSCoding key for logTarget property. */ -static NSString *logTargetKey = @"_logTarget"; - -/** NSCoding key for extensionBytes property. */ -static NSString *extensionBytesKey = @"_extensionBytes"; - -/** NSCoding key for qosTier property. */ -static NSString *qosTierKey = @"_qosTier"; - -/** NSCoding key for clockSnapshot property. */ -static NSString *clockSnapshotKey = @"_clockSnapshot"; - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - NSString *logMapID = [aDecoder decodeObjectOfClass:[NSObject class] forKey:logMapIDKey]; - NSInteger logTarget = [aDecoder decodeIntegerForKey:logTargetKey]; - self = [self initWithLogMapID:logMapID logTarget:logTarget]; - if (self) { - _extensionBytes = [aDecoder decodeObjectOfClass:[NSData class] forKey:extensionBytesKey]; - _qosTier = [aDecoder decodeIntegerForKey:qosTierKey]; - _clockSnapshot = [aDecoder decodeObjectOfClass:[GDLClock class] forKey:clockSnapshotKey]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:_logMapID forKey:logMapIDKey]; - [aCoder encodeInteger:_logTarget forKey:logTargetKey]; - [aCoder encodeObject:_extensionBytes forKey:extensionBytesKey]; - [aCoder encodeInteger:_qosTier forKey:qosTierKey]; - [aCoder encodeObject:_clockSnapshot forKey:clockSnapshotKey]; -} - -@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogStorage.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogStorage.m deleted file mode 100644 index ff707ce3d12..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogStorage.m +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2018 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 "GDLLogStorage.h" -#import "GDLLogStorage_Private.h" - -#import - -#import "GDLAssert.h" -#import "GDLConsoleLogger.h" -#import "GDLLogEvent_Private.h" -#import "GDLRegistrar_Private.h" -#import "GDLUploadCoordinator.h" - -/** Creates and/or returns a singleton NSString that is the shared logging path. - * - * @return The SDK logging path. - */ -static NSString *GDLStoragePath() { - static NSString *archivePath; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *cachePath = - NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; - archivePath = [NSString stringWithFormat:@"%@/google-sdks-logs", cachePath]; - }); - return archivePath; -} - -@implementation GDLLogStorage - -+ (instancetype)sharedInstance { - static GDLLogStorage *sharedStorage; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedStorage = [[GDLLogStorage alloc] init]; - }); - return sharedStorage; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _storageQueue = dispatch_queue_create("com.google.GDLLogStorage", DISPATCH_QUEUE_SERIAL); - _logHashToLogFile = [[NSMutableDictionary alloc] init]; - _logTargetToLogHashSet = [[NSMutableDictionary alloc] init]; - _uploader = [GDLUploadCoordinator sharedInstance]; - } - return self; -} - -- (void)storeLog:(GDLLogEvent *)log { - [self createLogDirectoryIfNotExists]; - - // This is done to ensure that log is deallocated at the end of the ensuing block. - __block GDLLogEvent *shortLivedLog = log; - __weak GDLLogEvent *weakShortLivedLog = log; - log = nil; - - dispatch_async(_storageQueue, ^{ - // Check that a backend implementation is available for this logTarget. - NSInteger logTarget = shortLivedLog.logTarget; - - // Check that a log prioritizer is available for this logTarget. - id logPrioritizer = - [GDLRegistrar sharedInstance].logTargetToPrioritizer[@(logTarget)]; - GDLAssert(logPrioritizer, @"There's no prioritizer registered for the given logTarget."); - - // Write the extension bytes to disk, get a filename. - GDLAssert(shortLivedLog.extensionBytes, @"The log should have been serialized to bytes"); - NSURL *logFile = [self saveLogProtoToDisk:shortLivedLog.extensionBytes - logHash:shortLivedLog.hash]; - - // Add log to tracking collections. - [self addLogToTrackingCollections:shortLivedLog logFile:logFile]; - - // Check the QoS, if it's high priority, notify the log target that it has a high priority log. - if (shortLivedLog.qosTier == GDLLogQoSFast) { - NSSet *allLogsForLogTarget = self.logTargetToLogHashSet[@(logTarget)]; - [self.uploader forceUploadLogs:allLogsForLogTarget target:logTarget]; - } - - // Have the prioritizer prioritize the log, enforcing that they do not retain it. - @autoreleasepool { - [logPrioritizer prioritizeLog:shortLivedLog]; - shortLivedLog = nil; - } - if (weakShortLivedLog) { - GDLLogError(GDLMCELogEventWasIllegallyRetained, @"%@", - @"A LogEvent should not be retained outside of storage."); - }; - }); -} - -- (void)removeLogs:(NSSet *)logHashes logTarget:(NSNumber *)logTarget { - dispatch_sync(_storageQueue, ^{ - for (NSNumber *logHash in logHashes) { - [self removeLog:logHash logTarget:logTarget]; - } - }); -} - -- (NSSet *)logHashesToFiles:(NSSet *)logHashes { - NSMutableSet *logFiles = [[NSMutableSet alloc] init]; - dispatch_sync(_storageQueue, ^{ - for (NSNumber *hashNumber in logHashes) { - NSURL *logURL = self.logHashToLogFile[hashNumber]; - GDLAssert(logURL, @"A log file URL couldn't be found for the given hash"); - [logFiles addObject:logURL]; - } - }); - return logFiles; -} - -#pragma mark - Private helper methods - -/** Removes the corresponding log file from disk. - * - * @param logHash The hash value of the original log. - * @param logTarget The logTarget of the original log. - */ -- (void)removeLog:(NSNumber *)logHash logTarget:(NSNumber *)logTarget { - NSURL *logFile = self.logHashToLogFile[logHash]; - - // Remove from disk, first and foremost. - NSError *error; - [[NSFileManager defaultManager] removeItemAtURL:logFile error:&error]; - GDLAssert(error == nil, @"There was an error removing a logFile: %@", error); - - // Remove from the tracking collections. - [self.logHashToLogFile removeObjectForKey:logHash]; - NSMutableSet *logHashes = self.logTargetToLogHashSet[logTarget]; - GDLAssert(logHashes, @"There wasn't a logSet for this logTarget."); - [logHashes removeObject:logHash]; - // It's fine to not remove the set if it's empty. - - // Check that a log prioritizer is available for this logTarget. - id logPrioritizer = - [GDLRegistrar sharedInstance].logTargetToPrioritizer[logTarget]; - GDLAssert(logPrioritizer, @"There's no prioritizer registered for the given logTarget."); - [logPrioritizer unprioritizeLog:logHash]; -} - -/** Creates the log directory if it does not exist. */ -- (void)createLogDirectoryIfNotExists { - NSError *error; - BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:GDLStoragePath() - withIntermediateDirectories:YES - attributes:0 - error:&error]; - if (!result || error) { - GDLLogError(GDLMCEDirectoryCreationError, @"Error creating the directory: %@", error); - } -} - -/** Saves the log's extensionBytes to a file using NSData mechanisms. - * - * @note This method should only be called from a method within a block on _storageQueue to maintain - * thread safety. - * - * @param logTransportBytes The extensionBytes of the log, presumably proto bytes. - * @param logHash The hash value of the log. - * @return The filename - */ -- (NSURL *)saveLogProtoToDisk:(NSData *)logTransportBytes logHash:(NSUInteger)logHash { - NSString *storagePath = GDLStoragePath(); - NSString *logFile = [NSString stringWithFormat:@"log-%lu", (unsigned long)logHash]; - NSURL *logFilePath = [NSURL fileURLWithPath:[storagePath stringByAppendingPathComponent:logFile]]; - - BOOL writingSuccess = [logTransportBytes writeToURL:logFilePath atomically:YES]; - if (!writingSuccess) { - GDLLogError(GDLMCEFileWriteError, @"A log file could not be written: %@", logFilePath); - } - - return logFilePath; -} - -/** Adds the log to internal collections in order to help track the log. - * - * @note This method should only be called from a method within a block on _storageQueue to maintain - * thread safety. - * - * @param log The log to track. - * @param logFile The file the log has been saved to. - */ -- (void)addLogToTrackingCollections:(GDLLogEvent *)log logFile:(NSURL *)logFile { - NSInteger logTarget = log.logTarget; - NSNumber *logHash = @(log.hash); - NSNumber *logTargetNumber = @(logTarget); - self.logHashToLogFile[logHash] = logFile; - NSMutableSet *logs = self.logTargetToLogHashSet[logTargetNumber]; - if (logs) { - [logs addObject:logHash]; - } else { - NSMutableSet *logSet = [NSMutableSet setWithObject:logHash]; - self.logTargetToLogHashSet[logTargetNumber] = logSet; - } -} - -#pragma mark - NSSecureCoding - -/** The NSKeyedCoder key for the logHashToFile property. */ -static NSString *const kGDLLogHashToLogFileKey = @"logHashToLogFileKey"; - -/** The NSKeyedCoder key for the logTargetToLogHashSet property. */ -static NSString *const kGDLLogTargetToLogHashSetKey = @"logTargetToLogHashSetKey"; - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - // Create the singleton and populate its ivars. - GDLLogStorage *sharedInstance = [self.class sharedInstance]; - dispatch_sync(sharedInstance.storageQueue, ^{ - Class NSMutableDictionaryClass = [NSMutableDictionary class]; - sharedInstance->_logHashToLogFile = [aDecoder decodeObjectOfClass:NSMutableDictionaryClass - forKey:kGDLLogHashToLogFileKey]; - sharedInstance->_logTargetToLogHashSet = - [aDecoder decodeObjectOfClass:NSMutableDictionaryClass forKey:kGDLLogTargetToLogHashSetKey]; - }); - return sharedInstance; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - GDLLogStorage *sharedInstance = [self.class sharedInstance]; - dispatch_sync(sharedInstance.storageQueue, ^{ - [aCoder encodeObject:sharedInstance->_logHashToLogFile forKey:kGDLLogHashToLogFileKey]; - [aCoder encodeObject:sharedInstance->_logTargetToLogHashSet - forKey:kGDLLogTargetToLogHashSetKey]; - }); -} - -@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.h b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.h deleted file mode 100644 index 99f386826f1..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2018 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 - -@class GDLLogEvent; - -@protocol GDLLogTransformer; - -NS_ASSUME_NONNULL_BEGIN - -/** Manages the writing and log-time transforming of logs. */ -@interface GDLLogWriter : NSObject - -/** Instantiates or returns the log writer singleton. - * - * @return The singleton instance of the log writer. - */ -+ (instancetype)sharedInstance; - -/** Writes the result of applying the given transformers' -transform method on the given log. - * - * @param log The log to apply transformers on. - * @param logTransformers The list of transformers to apply. - */ -- (void)writeLog:(GDLLogEvent *)log - afterApplyingTransformers:(nullable NSArray> *)logTransformers; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m deleted file mode 100644 index 25b3fe00086..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2018 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 "GDLLogWriter.h" -#import "GDLLogWriter_Private.h" - -#import - -#import "GDLAssert.h" -#import "GDLConsoleLogger.h" -#import "GDLLogEvent_Private.h" -#import "GDLLogStorage.h" - -@implementation GDLLogWriter - -// This class doesn't have to be a singleton, but allocating an instance for every logger could be -// wasteful. -+ (instancetype)sharedInstance { - static GDLLogWriter *logWriter; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - logWriter = [[self alloc] init]; - }); - return logWriter; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _logWritingQueue = dispatch_queue_create("com.google.GDLLogWriter", DISPATCH_QUEUE_SERIAL); - _storageInstance = [GDLLogStorage sharedInstance]; - } - return self; -} - -- (void)writeLog:(GDLLogEvent *)log - afterApplyingTransformers:(NSArray> *)logTransformers { - GDLAssert(log, @"You can't write a nil log"); - dispatch_async(_logWritingQueue, ^{ - GDLLogEvent *transformedLog = log; - for (id transformer in logTransformers) { - if ([transformer respondsToSelector:@selector(transform:)]) { - transformedLog = [transformer transform:transformedLog]; - if (!transformedLog) { - return; - } - } else { - GDLLogError(GDLMCETransformerDoesntImplementTransform, - @"Transformer doesn't implement transform: %@", transformer); - return; - } - } - [self.storageInstance storeLog:transformedLog]; - }); -} - -@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogger.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogger.m deleted file mode 100644 index 97c998004b9..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogger.m +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2018 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 "GDLLogger.h" -#import "GDLLogger_Private.h" - -#import "GDLAssert.h" -#import "GDLLogEvent.h" -#import "GDLLogEvent_Private.h" -#import "GDLLogWriter.h" - -@implementation GDLLogger - -- (instancetype)initWithLogMapID:(NSString *)logMapID - logTransformers:(nullable NSArray> *)logTransformers - logTarget:(NSInteger)logTarget { - self = [super init]; - if (self) { - GDLAssert(logMapID.length > 0, @"A log mapping ID cannot be nil or empty"); - GDLAssert(logTarget > 0, @"A log target cannot be negative or 0"); - _logMapID = logMapID; - _logTransformers = logTransformers; - _logTarget = logTarget; - _logWriterInstance = [GDLLogWriter sharedInstance]; - } - return self; -} - -- (void)logTelemetryEvent:(GDLLogEvent *)logEvent { - // TODO: Determine if logging an event before registration is allowed. - GDLAssert(logEvent, @"You can't log a nil event"); - GDLLogEvent *copiedLog = [logEvent copy]; - copiedLog.qosTier = GDLLogQoSTelemetry; - copiedLog.clockSnapshot = [GDLClock snapshot]; - [self.logWriterInstance writeLog:copiedLog afterApplyingTransformers:_logTransformers]; -} - -- (void)logDataEvent:(GDLLogEvent *)logEvent { - // TODO: Determine if logging an event before registration is allowed. - GDLAssert(logEvent, @"You can't log a nil event"); - GDLAssert(logEvent.qosTier != GDLLogQoSTelemetry, @"Use -logTelemetryEvent, please."); - GDLLogEvent *copiedLog = [logEvent copy]; - copiedLog.clockSnapshot = [GDLClock snapshot]; - [self.logWriterInstance writeLog:copiedLog afterApplyingTransformers:_logTransformers]; -} - -- (GDLLogEvent *)newEvent { - return [[GDLLogEvent alloc] initWithLogMapID:_logMapID logTarget:_logTarget]; -} - -@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLRegistrar.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLRegistrar.m deleted file mode 100644 index b7d0eda780f..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLRegistrar.m +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2018 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 "GDLRegistrar.h" - -#import "GDLRegistrar_Private.h" - -@implementation GDLRegistrar { - /** Backing ivar for logTargetToUploader property. */ - NSMutableDictionary> *_logTargetToUploader; - - /** Backing ivar for logTargetToPrioritizer property. */ - NSMutableDictionary> *_logTargetToPrioritizer; -} - -+ (instancetype)sharedInstance { - static GDLRegistrar *sharedInstance; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [[GDLRegistrar alloc] init]; - }); - return sharedInstance; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _registrarQueue = dispatch_queue_create("com.google.GDLRegistrar", DISPATCH_QUEUE_CONCURRENT); - _logTargetToPrioritizer = [[NSMutableDictionary alloc] init]; - _logTargetToUploader = [[NSMutableDictionary alloc] init]; - } - return self; -} - -- (void)registerUploader:(id)backend logTarget:(GDLLogTarget)logTarget { - __weak GDLRegistrar *weakSelf = self; - dispatch_barrier_async(_registrarQueue, ^{ - GDLRegistrar *strongSelf = weakSelf; - if (strongSelf) { - strongSelf->_logTargetToUploader[@(logTarget)] = backend; - } - }); -} - -- (void)registerPrioritizer:(id)prioritizer logTarget:(GDLLogTarget)logTarget { - __weak GDLRegistrar *weakSelf = self; - dispatch_barrier_async(_registrarQueue, ^{ - GDLRegistrar *strongSelf = weakSelf; - if (strongSelf) { - strongSelf->_logTargetToPrioritizer[@(logTarget)] = prioritizer; - } - }); -} - -- (NSMutableDictionary> *)logTargetToUploader { - __block NSMutableDictionary> *logTargetToUploader; - __weak GDLRegistrar *weakSelf = self; - dispatch_sync(_registrarQueue, ^{ - GDLRegistrar *strongSelf = weakSelf; - if (strongSelf) { - logTargetToUploader = strongSelf->_logTargetToUploader; - } - }); - return logTargetToUploader; -} - -- (NSMutableDictionary> *)logTargetToPrioritizer { - __block NSMutableDictionary> *logTargetToPrioritizer; - __weak GDLRegistrar *weakSelf = self; - dispatch_sync(_registrarQueue, ^{ - GDLRegistrar *strongSelf = weakSelf; - if (strongSelf) { - logTargetToPrioritizer = strongSelf->_logTargetToPrioritizer; - } - }); - return logTargetToPrioritizer; -} - -@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLUploadCoordinator.m b/GoogleDataLogger/GoogleDataLogger/Classes/GDLUploadCoordinator.m deleted file mode 100644 index e4fe24e18ac..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLUploadCoordinator.m +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2018 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 "GDLUploadCoordinator.h" -#import "GDLUploadCoordinator_Private.h" - -#import "GDLAssert.h" -#import "GDLClock.h" -#import "GDLConsoleLogger.h" -#import "GDLLogStorage.h" -#import "GDLRegistrar_Private.h" - -@implementation GDLUploadCoordinator - -+ (instancetype)sharedInstance { - static GDLUploadCoordinator *sharedUploader; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedUploader = [[GDLUploadCoordinator alloc] init]; - [sharedUploader startTimer]; - }); - return sharedUploader; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _coordinationQueue = - dispatch_queue_create("com.google.GDLUploadCoordinator", DISPATCH_QUEUE_SERIAL); - _registrar = [GDLRegistrar sharedInstance]; - _logTargetToNextUploadTimes = [[NSMutableDictionary alloc] init]; - _logTargetToInFlightLogSet = [[NSMutableDictionary alloc] init]; - _forcedUploadQueue = [[NSMutableArray alloc] init]; - _timerInterval = 30 * NSEC_PER_SEC; - _timerLeeway = 5 * NSEC_PER_SEC; - } - return self; -} - -- (void)forceUploadLogs:(NSSet *)logHashes target:(GDLLogTarget)logTarget { - dispatch_async(_coordinationQueue, ^{ - NSNumber *logTargetNumber = @(logTarget); - GDLRegistrar *registrar = self->_registrar; - GDLUploadCoordinatorForceUploadBlock forceUploadBlock = ^{ - GDLAssert(logHashes.count, @"It doesn't make sense to force upload of 0 logs"); - id uploader = registrar.logTargetToUploader[logTargetNumber]; - NSSet *logFiles = [self.logStorage logHashesToFiles:logHashes]; - GDLAssert(uploader, @"log target '%@' is missing an implementation", logTargetNumber); - [uploader uploadLogs:logFiles onComplete:self.onCompleteBlock]; - self->_logTargetToInFlightLogSet[logTargetNumber] = logHashes; - }; - - // Enqueue the force upload block if there's an in-flight upload for that target already. - if (self->_logTargetToInFlightLogSet[logTargetNumber]) { - [self->_forcedUploadQueue insertObject:forceUploadBlock atIndex:0]; - } else { - forceUploadBlock(); - } - }); -} - -#pragma mark - Property overrides - -// GDLLogStorage and GDLUploadCoordinator +sharedInstance methods call each other, so this breaks -// the loop. -- (GDLLogStorage *)logStorage { - if (!_logStorage) { - _logStorage = [GDLLogStorage sharedInstance]; - } - return _logStorage; -} - -// This should always be called in a thread-safe manner. -- (GDLUploaderCompletionBlock)onCompleteBlock { - __weak GDLUploadCoordinator *weakSelf = self; - static GDLUploaderCompletionBlock onCompleteBlock; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - onCompleteBlock = ^(GDLLogTarget target, GDLClock *nextUploadAttemptUTC, NSError *error) { - GDLUploadCoordinator *strongSelf = weakSelf; - if (strongSelf) { - dispatch_async(strongSelf.coordinationQueue, ^{ - NSNumber *logTarget = @(target); - if (error) { - GDLLogWarning(GDLMCWUploadFailed, @"Error during upload: %@", error); - [strongSelf->_logTargetToInFlightLogSet removeObjectForKey:logTarget]; - return; - } - strongSelf->_logTargetToNextUploadTimes[logTarget] = nextUploadAttemptUTC; - NSSet *logHashSet = - [strongSelf->_logTargetToInFlightLogSet objectForKey:logTarget]; - GDLAssert(logHashSet, @"There should be an in-flight log set to remove."); - [strongSelf.logStorage removeLogs:logHashSet logTarget:logTarget]; - [strongSelf->_logTargetToInFlightLogSet removeObjectForKey:logTarget]; - if (strongSelf->_forcedUploadQueue.count) { - GDLUploadCoordinatorForceUploadBlock queuedBlock = - [strongSelf->_forcedUploadQueue lastObject]; - if (queuedBlock) { - queuedBlock(); - } - [strongSelf->_forcedUploadQueue removeLastObject]; - } - }); - } - }; - }); - return onCompleteBlock; -} - -#pragma mark - Private helper methods - -/** Starts a timer that checks whether or not logs can be uploaded at regular intervals. It will - * check the next-upload clocks of all log targets to determine if an upload attempt can be made. - */ -- (void)startTimer { - __weak GDLUploadCoordinator *weakSelf = self; - dispatch_sync(_coordinationQueue, ^{ - GDLUploadCoordinator *strongSelf = weakSelf; - GDLAssert(strongSelf, @"self must be real to start a timer."); - strongSelf->_timer = - dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, strongSelf->_coordinationQueue); - dispatch_source_set_timer(strongSelf->_timer, DISPATCH_TIME_NOW, strongSelf->_timerInterval, - strongSelf->_timerLeeway); - dispatch_source_set_event_handler(strongSelf->_timer, ^{ - [self checkPrioritizersAndUploadLogs]; - }); - dispatch_resume(strongSelf->_timer); - }); -} - -/** Checks the next upload time for each log target and makes a determination on whether to upload - * logs for that target or not. If so, queries the prioritizers - */ -- (void)checkPrioritizersAndUploadLogs { - __weak GDLUploadCoordinator *weakSelf = self; - dispatch_async(_coordinationQueue, ^{ - static int count = 0; - count++; - GDLUploadCoordinator *strongSelf = weakSelf; - if (strongSelf) { - NSArray *logTargetsReadyForUpload = [self logTargetsReadyForUpload]; - for (NSNumber *logTarget in logTargetsReadyForUpload) { - id prioritizer = - strongSelf->_registrar.logTargetToPrioritizer[logTarget]; - id uploader = strongSelf->_registrar.logTargetToUploader[logTarget]; - GDLAssert(prioritizer && uploader, @"log target '%@' is missing an implementation", - logTarget); - GDLUploadConditions conds = [self uploadConditions]; - NSSet *logHashesToUpload = - [[prioritizer logsToUploadGivenConditions:conds] copy]; - if (logHashesToUpload && logHashesToUpload.count > 0) { - NSAssert(logHashesToUpload.count > 0, @""); - NSSet *logFilesToUpload = - [strongSelf.logStorage logHashesToFiles:logHashesToUpload]; - NSAssert(logFilesToUpload.count == logHashesToUpload.count, - @"There should be the same number of files to logs"); - strongSelf->_logTargetToInFlightLogSet[logTarget] = logHashesToUpload; - [uploader uploadLogs:logFilesToUpload onComplete:self.onCompleteBlock]; - } - } - } - }); -} - -/** */ -- (GDLUploadConditions)uploadConditions { - // TODO: Compute the real upload conditions. - return GDLUploadConditionMobileData; -} - -/** Checks the next upload time for each log target and returns an array of log targets that are - * able to make an upload attempt. - * - * @return An array of log targets wrapped in NSNumbers that are ready for upload attempts. - */ -- (NSArray *)logTargetsReadyForUpload { - NSMutableArray *logTargetsReadyForUpload = [[NSMutableArray alloc] init]; - GDLClock *currentTime = [GDLClock snapshot]; - for (NSNumber *logTarget in self.registrar.logTargetToPrioritizer) { - // Log targets in flight are not ready. - if (_logTargetToInFlightLogSet[logTarget]) { - continue; - } - GDLClock *nextUploadTime = _logTargetToNextUploadTimes[logTarget]; - - // If no next upload time was specified or if the currentTime > nextUpload time, mark as ready. - if (!nextUploadTime || [currentTime isAfter:nextUploadTime]) { - [logTargetsReadyForUpload addObject:logTarget]; - } - } - return logTargetsReadyForUpload; -} - -@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogEvent.h b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogEvent.h deleted file mode 100644 index 00815a194da..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogEvent.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2018 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 - -#import "GDLLogProto.h" - -NS_ASSUME_NONNULL_BEGIN - -/** The different possible log quality of service specifiers. High values indicate high priority. */ -typedef NS_ENUM(NSInteger, GDLLogQoS) { - /** The QoS tier wasn't set, and won't ever be sent. */ - GDLLogQoSUnknown = 0, - - /** This log is internal telemetry data that should not be sent on its own if possible. */ - GDLLogQoSTelemetry = 1, - - /** This log should be sent, but in a batch only roughly once per day. */ - GDLLogQoSDaily = 2, - - /** This log should be sent when requested by the uploader. */ - GDLLogQosDefault = 3, - - /** This log should be sent immediately along with any other data that can be batched. */ - GDLLogQoSFast = 4, - - /** This log should only be uploaded on wifi. */ - GDLLogQoSWifiOnly = 5, -}; - -@interface GDLLogEvent : NSObject - -/** The log map identifier, to allow backends to map the extension property to a proto. */ -@property(readonly, nonatomic) NSString *logMapID; - -/** The identifier for the backend this log will eventually be sent to. */ -@property(readonly, nonatomic) NSInteger logTarget; - -/** The log object itself, encapsulated in the transport of your choice, as long as it implements - * the GDLLogProto protocol. */ -@property(nullable, nonatomic) id extension; - -/** The quality of service tier this log belongs to. */ -@property(nonatomic) GDLLogQoS qosTier; - -/** A dictionary provided to aid prioritizers by allowing the passing of arbitrary data. It will be - * retained by a copy in -copy, but not used for -hash. - */ -@property(nullable, nonatomic) NSDictionary *customPrioritizationParams; - -// Please use the designated initializer. -- (instancetype)init NS_UNAVAILABLE; - -/** Initializes an instance using the given logMapID. - * - * @param logMapID The log map identifier. - * @param logTarget The log's target identifier. - * @return An instance of this class. - */ -- (instancetype)initWithLogMapID:(NSString *)logMapID - logTarget:(NSInteger)logTarget NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogPrioritizer.h b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogPrioritizer.h deleted file mode 100644 index a490523f766..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogPrioritizer.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2018 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 - -@class GDLLogEvent; - -NS_ASSUME_NONNULL_BEGIN - -/** Options that define a set of upload conditions. This is used to help minimize end user data - * consumption impact. - */ -typedef NS_OPTIONS(NSInteger, GDLUploadConditions) { - - /** An upload would likely use mobile data. */ - GDLUploadConditionMobileData, - - /** An upload would likely use wifi data. */ - GDLUploadConditionWifiData, -}; - -/** This protocol defines the common interface of a log prioritization. Log prioritizers are - * stateful objects that prioritize logs upon insertion into storage and remain prepared to return a - * set of log filenames to the storage system. - */ -@protocol GDLLogPrioritizer - -@required - -/** Accepts a logEvent and uses the log metadata to make choices on how to prioritize the log. This - * method exists as a way to help prioritize which logs should be sent, which is dependent on the - * request proto structure of your backend. - * - * @note Three things: 1. the logEvent cannot be retained for longer than the execution time of - * this method. 2. The extension should be nil by this point and should not be used to prioritize - * logs. 3. You should retain the logEvent hashes, because those are returned in logsForNextUpload. - * - * @param logEvent The log event to prioritize. - */ -- (void)prioritizeLog:(GDLLogEvent *)logEvent; - -/** Unprioritizes a log. This method is called when a log has been removed from storage and should - * no longer be given as a log to upload. - */ -- (void)unprioritizeLog:(NSNumber *)logHash; - -/** Returns a set of logs to upload given a set of conditions. - * - * @param conditions A bit mask specifying the current upload conditions. - * @return A set of logs to upload with respect to the current conditions. - */ -- (NSSet *)logsToUploadGivenConditions:(GDLUploadConditions)conditions; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogger.h b/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogger.h deleted file mode 100644 index ea52804dc38..00000000000 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogger.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2018 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 - -#import "GDLLogTransformer.h" - -@class GDLLogEvent; - -NS_ASSUME_NONNULL_BEGIN - -@interface GDLLogger : NSObject - -// Please use the designated initializer. -- (instancetype)init NS_UNAVAILABLE; - -/** Initializes a new logger that will log events to the given target backend. - * - * @param logMapID The mapping identifier used by the backend to map the extension to a proto. - * @param logTransformers A list of transformers to be applied to log events that are logged. - * @param logTarget The target backend of this logger. - * @return A logger that will log events. - */ -- (instancetype)initWithLogMapID:(NSString *)logMapID - logTransformers:(nullable NSArray> *)logTransformers - logTarget:(NSInteger)logTarget NS_DESIGNATED_INITIALIZER; - -/** Copies and logs an internal telemetry event. Logs sent using this API are lower in priority, and - * sometimes won't be sent on their own. - * - * @note This will convert the log event's extension proto to data and release the original log. - * - * @param logEvent The log event to log. - */ -- (void)logTelemetryEvent:(GDLLogEvent *)logEvent; - -/** Copies and logs an SDK service data event. Logs send using this API are higher in priority, and - * will cause a network request at some point in the relative near future. - * - * @note This will convert the log event's extension proto to data and release the original log. - * - * @param logEvent The log event to log. - */ -- (void)logDataEvent:(GDLLogEvent *)logEvent; - -/** Creates a log event for use by this logger. - * - * @return A log event that is suited for use by this logger. - */ -- (GDLLogEvent *)newEvent; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/Tests/Integration/GDLIntegrationTest.m b/GoogleDataLogger/Tests/Integration/GDLIntegrationTest.m deleted file mode 100644 index c2b818dc6a0..00000000000 --- a/GoogleDataLogger/Tests/Integration/GDLIntegrationTest.m +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2019 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 - -#import - -#import "GDLIntegrationTestPrioritizer.h" -#import "GDLIntegrationTestUploader.h" -#import "GDLTestServer.h" - -#import "GDLLogStorage_Private.h" -#import "GDLUploadCoordinator+Testing.h" - -/** A test-only log object used in this integration test. */ -@interface GDLIntegrationTestLog : NSObject - -@end - -@implementation GDLIntegrationTestLog - -- (NSData *)transportBytes { - // In real usage, protobuf's -data method or a custom implementation using nanopb are used. - return [[NSString stringWithFormat:@"%@", [NSDate date]] dataUsingEncoding:NSUTF8StringEncoding]; -} - -@end - -/** A test-only log transformer. */ -@interface GDLIntegrationTestTransformer : NSObject - -@end - -@implementation GDLIntegrationTestTransformer - -- (GDLLogEvent *)transform:(GDLLogEvent *)logEvent { - // drop half the logs during transforming. - if (arc4random_uniform(2) == 1) { - logEvent = nil; - } - return logEvent; -} - -@end - -@interface GDLIntegrationTest : XCTestCase - -/** A test prioritizer. */ -@property(nonatomic) GDLIntegrationTestPrioritizer *prioritizer; - -/** A test uploader. */ -@property(nonatomic) GDLIntegrationTestUploader *uploader; - -/** The first test logger. */ -@property(nonatomic) GDLLogger *logger1; - -/** The second test logger. */ -@property(nonatomic) GDLLogger *logger2; - -@end - -@implementation GDLIntegrationTest - -- (void)tearDown { - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertEqual([GDLLogStorage sharedInstance].logHashToLogFile.count, 0); - }); -} - -- (void)testEndToEndLog { - XCTestExpectation *expectation = [self expectationWithDescription:@"server got the request"]; - expectation.assertForOverFulfill = NO; - - // Create the server. - GDLTestServer *testServer = [[GDLTestServer alloc] init]; - [testServer setResponseCompletedBlock:^(GCDWebServerRequest *_Nonnull request, - GCDWebServerResponse *_Nonnull response) { - [expectation fulfill]; - }]; - [testServer registerTestPaths]; - [testServer start]; - - // Create loggers. - self.logger1 = [[GDLLogger alloc] initWithLogMapID:@"logMap1" - logTransformers:nil - logTarget:kGDLIntegrationTestTarget]; - - self.logger2 = [[GDLLogger alloc] initWithLogMapID:@"logMap2" - logTransformers:nil - logTarget:kGDLIntegrationTestTarget]; - - // Create a prioritizer and uploader. - self.prioritizer = [[GDLIntegrationTestPrioritizer alloc] init]; - self.uploader = [[GDLIntegrationTestUploader alloc] initWithServerURL:testServer.serverURL]; - - // Set the interval to be much shorter than the standard timer. - [GDLUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC * 0.1; - [GDLUploadCoordinator sharedInstance].timerLeeway = NSEC_PER_SEC * 0.01; - - // Confirm no logs are in disk. - XCTAssertEqual([GDLLogStorage sharedInstance].logHashToLogFile.count, 0); - XCTAssertEqual([GDLLogStorage sharedInstance].logTargetToLogHashSet.count, 0); - - // Generate some logs data. - [self generateLogs]; - - // Confirm logs are on disk. - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertGreaterThan([GDLLogStorage sharedInstance].logHashToLogFile.count, 0); - XCTAssertGreaterThan([GDLLogStorage sharedInstance].logTargetToLogHashSet.count, 0); - }); - - // Confirm logs were sent and received. - [self waitForExpectations:@[ expectation ] timeout:10.0]; - - // Generate logs for a bit. - NSUInteger lengthOfTestToRunInSeconds = 30; - [GDLUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC * 5; - [GDLUploadCoordinator sharedInstance].timerLeeway = NSEC_PER_SEC * 1; - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); - dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC); - dispatch_source_set_event_handler(timer, ^{ - static int numberOfTimesCalled = 0; - numberOfTimesCalled++; - if (numberOfTimesCalled < lengthOfTestToRunInSeconds) { - [self generateLogs]; - } else { - dispatch_source_cancel(timer); - } - }); - dispatch_resume(timer); - - // Run for a bit, a couple seconds longer than the previous bit. - [[NSRunLoop currentRunLoop] - runUntilDate:[NSDate dateWithTimeIntervalSinceNow:lengthOfTestToRunInSeconds + 2]]; - - [testServer stop]; -} - -/** Generates and logs a bunch of random logs. */ -- (void)generateLogs { - for (int i = 0; i < 50; i++) { - // Choose a random logger, and randomly choose if it's a telemetry log. - GDLLogger *logger = arc4random_uniform(2) ? self.logger1 : self.logger2; - BOOL isTelemetryLog = arc4random_uniform(2); - - // Create a log - GDLLogEvent *logEvent = [logger newEvent]; - logEvent.extension = [[GDLIntegrationTestLog alloc] init]; - - if (isTelemetryLog) { - [logger logTelemetryEvent:logEvent]; - } else { - [logger logDataEvent:logEvent]; - } - } -} - -@end diff --git a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestPrioritizer.m b/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestPrioritizer.m deleted file mode 100644 index a8b4d94fd21..00000000000 --- a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestPrioritizer.m +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019 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 "GDLIntegrationTestPrioritizer.h" - -@interface GDLIntegrationTestPrioritizer () - -/** Logs that are only supposed to be uploaded whilst on wifi. */ -@property(nonatomic) NSMutableSet *wifiOnlyLogs; - -/** Logs that can be uploaded on any type of connection. */ -@property(nonatomic) NSMutableSet *nonWifiLogs; - -/** The queue on which this prioritizer operates. */ -@property(nonatomic) dispatch_queue_t queue; - -@end - -@implementation GDLIntegrationTestPrioritizer - -- (instancetype)init { - self = [super init]; - if (self) { - _queue = - dispatch_queue_create("com.google.GDLIntegrationTestPrioritizer", DISPATCH_QUEUE_SERIAL); - _wifiOnlyLogs = [[NSMutableSet alloc] init]; - _nonWifiLogs = [[NSMutableSet alloc] init]; - [[GDLRegistrar sharedInstance] registerPrioritizer:self logTarget:kGDLIntegrationTestTarget]; - } - return self; -} - -- (void)prioritizeLog:(GDLLogEvent *)logEvent { - dispatch_sync(_queue, ^{ - if (logEvent.qosTier == GDLLogQoSWifiOnly) { - [self.wifiOnlyLogs addObject:@(logEvent.hash)]; - } else { - [self.nonWifiLogs addObject:@(logEvent.hash)]; - } - }); -} - -- (void)unprioritizeLog:(NSNumber *)logHash { - dispatch_sync(_queue, ^{ - [self.wifiOnlyLogs removeObject:logHash]; - [self.nonWifiLogs removeObject:logHash]; - }); -} - -- (nonnull NSSet *)logsToUploadGivenConditions:(GDLUploadConditions)conditions { - __block NSSet *logs; - dispatch_sync(_queue, ^{ - if ((conditions & GDLUploadConditionWifiData) == GDLUploadConditionWifiData) { - logs = self.wifiOnlyLogs; - } else { - logs = self.nonWifiLogs; - } - }); - return logs; -} - -@end diff --git a/GoogleDataLogger/Tests/Unit/GDLLogEventTest.m b/GoogleDataLogger/Tests/Unit/GDLLogEventTest.m deleted file mode 100644 index de3f11eba8b..00000000000 --- a/GoogleDataLogger/Tests/Unit/GDLLogEventTest.m +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2018 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 "GDLTestCase.h" - -#import - -#import "GDLLogEvent_Private.h" - -@interface GDLLogEventTest : GDLTestCase - -@end - -@implementation GDLLogEventTest - -/** Tests the designated initializer. */ -- (void)testInit { - XCTAssertNotNil([[GDLLogEvent alloc] initWithLogMapID:@"1" logTarget:1]); - XCTAssertThrows([[GDLLogEvent alloc] initWithLogMapID:@"" logTarget:1]); -} - -/** Tests NSKeyedArchiver encoding and decoding. */ -- (void)testArchiving { - XCTAssertTrue([GDLLogEvent supportsSecureCoding]); - GDLClock *clockSnapshot = [GDLClock snapshot]; - int64_t timeMillis = clockSnapshot.timeMillis; - int64_t timezoneOffsetSeconds = clockSnapshot.timezoneOffsetSeconds; - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"testID" logTarget:42]; - logEvent.extensionBytes = [@"someData" dataUsingEncoding:NSUTF8StringEncoding]; - logEvent.qosTier = GDLLogQoSTelemetry; - logEvent.clockSnapshot = clockSnapshot; - - NSData *archiveData = [NSKeyedArchiver archivedDataWithRootObject:logEvent]; - - // To ensure that all the objects being retained by the original logEvent are dealloc'd. - logEvent = nil; - - GDLLogEvent *decodedLogEvent = [NSKeyedUnarchiver unarchiveObjectWithData:archiveData]; - XCTAssertEqualObjects(decodedLogEvent.logMapID, @"testID"); - XCTAssertEqual(decodedLogEvent.logTarget, 42); - XCTAssertEqualObjects(decodedLogEvent.extensionBytes, - [@"someData" dataUsingEncoding:NSUTF8StringEncoding]); - XCTAssertEqual(decodedLogEvent.qosTier, GDLLogQoSTelemetry); - XCTAssertEqual(decodedLogEvent.clockSnapshot.timeMillis, timeMillis); - XCTAssertEqual(decodedLogEvent.clockSnapshot.timezoneOffsetSeconds, timezoneOffsetSeconds); -} - -@end diff --git a/GoogleDataLogger/Tests/Unit/GDLLogStorageTest.m b/GoogleDataLogger/Tests/Unit/GDLLogStorageTest.m deleted file mode 100644 index 9b3f28adb00..00000000000 --- a/GoogleDataLogger/Tests/Unit/GDLLogStorageTest.m +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2018 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 "GDLTestCase.h" - -#import - -#import "GDLLogEvent_Private.h" -#import "GDLLogStorage.h" -#import "GDLLogStorage_Private.h" -#import "GDLRegistrar.h" -#import "GDLRegistrar_Private.h" - -#import "GDLTestPrioritizer.h" -#import "GDLTestUploader.h" - -#import "GDLAssertHelper.h" -#import "GDLLogStorage+Testing.h" -#import "GDLRegistrar+Testing.h" -#import "GDLUploadCoordinatorFake.h" - -static NSInteger logTarget = 1337; - -@interface GDLLogStorageTest : GDLTestCase - -/** The test backend implementation. */ -@property(nullable, nonatomic) GDLTestUploader *testBackend; - -/** The test prioritizer implementation. */ -@property(nullable, nonatomic) GDLTestPrioritizer *testPrioritizer; - -/** The uploader fake. */ -@property(nonatomic) GDLUploadCoordinatorFake *uploaderFake; - -@end - -@implementation GDLLogStorageTest - -- (void)setUp { - [super setUp]; - self.testBackend = [[GDLTestUploader alloc] init]; - self.testPrioritizer = [[GDLTestPrioritizer alloc] init]; - [[GDLRegistrar sharedInstance] registerUploader:_testBackend logTarget:logTarget]; - [[GDLRegistrar sharedInstance] registerPrioritizer:_testPrioritizer logTarget:logTarget]; - self.uploaderFake = [[GDLUploadCoordinatorFake alloc] init]; - [GDLLogStorage sharedInstance].uploader = self.uploaderFake; -} - -- (void)tearDown { - [super tearDown]; - // Destroy these objects before the next test begins. - self.testBackend = nil; - self.testPrioritizer = nil; - [[GDLRegistrar sharedInstance] reset]; - [[GDLLogStorage sharedInstance] reset]; - [GDLLogStorage sharedInstance].uploader = [GDLUploadCoordinator sharedInstance]; - self.uploaderFake = nil; -} - -/** Tests the singleton pattern. */ -- (void)testInit { - XCTAssertEqual([GDLLogStorage sharedInstance], [GDLLogStorage sharedInstance]); -} - -/** Tests storing a log. */ -- (void)testStoreLog { - NSUInteger logHash; - // logEvent is autoreleased, and the pool needs to drain. - @autoreleasepool { - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; - logHash = logEvent.hash; - XCTAssertNoThrow([[GDLLogStorage sharedInstance] storeLog:logEvent]); - } - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertEqual([GDLLogStorage sharedInstance].logHashToLogFile.count, 1); - XCTAssertEqual([GDLLogStorage sharedInstance].logTargetToLogHashSet[@(logTarget)].count, 1); - NSURL *logFile = [GDLLogStorage sharedInstance].logHashToLogFile[@(logHash)]; - XCTAssertNotNil(logFile); - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:logFile.path]); - NSError *error; - XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:logFile error:&error]); - XCTAssertNil(error, @"There was an error deleting the logFile: %@", error); - }); -} - -/** Tests removing a log. */ -- (void)testRemoveLog { - NSUInteger logHash; - // logEvent is autoreleased, and the pool needs to drain. - @autoreleasepool { - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; - logHash = logEvent.hash; - XCTAssertNoThrow([[GDLLogStorage sharedInstance] storeLog:logEvent]); - } - __block NSURL *logFile; - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - logFile = [GDLLogStorage sharedInstance].logHashToLogFile[@(logHash)]; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:logFile.path]); - }); - [[GDLLogStorage sharedInstance] removeLogs:[NSSet setWithObject:@(logHash)] - logTarget:@(logTarget)]; - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:logFile.path]); - XCTAssertEqual([GDLLogStorage sharedInstance].logHashToLogFile.count, 0); - XCTAssertEqual([GDLLogStorage sharedInstance].logTargetToLogHashSet[@(logTarget)].count, 0); - }); -} - -/** Tests removing a set of logs */ -- (void)testRemoveLogs { - GDLLogStorage *storage = [GDLLogStorage sharedInstance]; - NSUInteger log1Hash, log2Hash, log3Hash; - - // logEvents are autoreleased, and the pool needs to drain. - @autoreleasepool { - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding]; - log1Hash = logEvent.hash; - XCTAssertNoThrow([storage storeLog:logEvent]); - - logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"100" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; - log2Hash = logEvent.hash; - XCTAssertNoThrow([storage storeLog:logEvent]); - - logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding]; - log3Hash = logEvent.hash; - XCTAssertNoThrow([storage storeLog:logEvent]); - } - NSSet *logHashSet = [NSSet setWithObjects:@(log1Hash), @(log2Hash), @(log3Hash), nil]; - NSSet *logFiles = [storage logHashesToFiles:logHashSet]; - [storage removeLogs:logHashSet logTarget:@(logTarget)]; - dispatch_sync(storage.storageQueue, ^{ - XCTAssertNil(storage.logHashToLogFile[@(log1Hash)]); - XCTAssertNil(storage.logHashToLogFile[@(log2Hash)]); - XCTAssertNil(storage.logHashToLogFile[@(log3Hash)]); - XCTAssertEqual(storage.logTargetToLogHashSet[@(logTarget)].count, 0); - for (NSURL *logFile in logFiles) { - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:logFile.path]); - } - }); -} - -/** Tests storing a few different logs. */ -- (void)testStoreMultipleLogs { - NSUInteger log1Hash, log2Hash, log3Hash; - - // logEvents are autoreleased, and the pool needs to drain. - @autoreleasepool { - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding]; - log1Hash = logEvent.hash; - XCTAssertNoThrow([[GDLLogStorage sharedInstance] storeLog:logEvent]); - - logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"100" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; - log2Hash = logEvent.hash; - XCTAssertNoThrow([[GDLLogStorage sharedInstance] storeLog:logEvent]); - - logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding]; - log3Hash = logEvent.hash; - XCTAssertNoThrow([[GDLLogStorage sharedInstance] storeLog:logEvent]); - } - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertEqual([GDLLogStorage sharedInstance].logHashToLogFile.count, 3); - XCTAssertEqual([GDLLogStorage sharedInstance].logTargetToLogHashSet[@(logTarget)].count, 3); - - NSURL *log1File = [GDLLogStorage sharedInstance].logHashToLogFile[@(log1Hash)]; - XCTAssertNotNil(log1File); - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:log1File.path]); - NSError *error; - XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:log1File error:&error]); - XCTAssertNil(error, @"There was an error deleting the logFile: %@", error); - - NSURL *log2File = [GDLLogStorage sharedInstance].logHashToLogFile[@(log2Hash)]; - XCTAssertNotNil(log2File); - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:log2File.path]); - error = nil; - XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:log2File error:&error]); - XCTAssertNil(error, @"There was an error deleting the logFile: %@", error); - - NSURL *log3File = [GDLLogStorage sharedInstance].logHashToLogFile[@(log3Hash)]; - XCTAssertNotNil(log3File); - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:log3File.path]); - error = nil; - XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:log3File error:&error]); - XCTAssertNil(error, @"There was an error deleting the logFile: %@", error); - }); -} - -/** Tests enforcing that a log prioritizer does not retain a log in memory. */ -- (void)testLogEventDeallocationIsEnforced { - XCTestExpectation *errorExpectation = [self expectationWithDescription:@"log retain error"]; - [GDLAssertHelper setAssertionBlock:^{ - [errorExpectation fulfill]; - }]; - - // logEvent is referenced past -storeLog, ensuring it's retained, which should assert. - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; - - // Store the log and wait for the expectation. - [[GDLLogStorage sharedInstance] storeLog:logEvent]; - [self waitForExpectations:@[ errorExpectation ] timeout:5.0]; - - NSURL *logFile; - logFile = [GDLLogStorage sharedInstance].logHashToLogFile[@(logEvent.hash)]; - - // This isn't strictly necessary because of the -waitForExpectations above. - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:logFile.path]); - }); - - // Ensure log was removed. - NSNumber *logHash = @(logEvent.hash); - [[GDLLogStorage sharedInstance] removeLogs:[NSSet setWithObject:logHash] logTarget:@(logTarget)]; - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:logFile.path]); - XCTAssertEqual([GDLLogStorage sharedInstance].logHashToLogFile.count, 0); - XCTAssertEqual([GDLLogStorage sharedInstance].logTargetToLogHashSet[@(logTarget)].count, 0); - }); -} - -/** Tests encoding and decoding the storage singleton correctly. */ -- (void)testNSSecureCoding { - XCTAssertTrue([GDLLogStorage supportsSecureCoding]); - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; - NSUInteger logHash = logEvent.hash; - XCTAssertNoThrow([[GDLLogStorage sharedInstance] storeLog:logEvent]); - logEvent = nil; - NSData *storageData = [NSKeyedArchiver archivedDataWithRootObject:[GDLLogStorage sharedInstance]]; - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertNotNil([GDLLogStorage sharedInstance].logHashToLogFile[@(logHash)]); - }); - [[GDLLogStorage sharedInstance] removeLogs:[NSSet setWithObject:@(logHash)] - logTarget:@(logTarget)]; - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertNil([GDLLogStorage sharedInstance].logHashToLogFile[@(logHash)]); - }); - - // TODO(mikehaney24): Ensure that the object created by alloc is discarded? - [NSKeyedUnarchiver unarchiveObjectWithData:storageData]; - XCTAssertNotNil([GDLLogStorage sharedInstance].logHashToLogFile[@(logHash)]); -} - -/** Tests logging a fast log causes an upload attempt. */ -- (void)testQoSTierFast { - NSUInteger logHash; - // logEvent is autoreleased, and the pool needs to drain. - @autoreleasepool { - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; - logEvent.qosTier = GDLLogQoSFast; - logHash = logEvent.hash; - XCTAssertFalse(self.uploaderFake.forceUploadCalled); - XCTAssertNoThrow([[GDLLogStorage sharedInstance] storeLog:logEvent]); - } - dispatch_sync([GDLLogStorage sharedInstance].storageQueue, ^{ - XCTAssertTrue(self.uploaderFake.forceUploadCalled); - XCTAssertEqual([GDLLogStorage sharedInstance].logHashToLogFile.count, 1); - XCTAssertEqual([GDLLogStorage sharedInstance].logTargetToLogHashSet[@(logTarget)].count, 1); - NSURL *logFile = [GDLLogStorage sharedInstance].logHashToLogFile[@(logHash)]; - XCTAssertNotNil(logFile); - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:logFile.path]); - NSError *error; - XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:logFile error:&error]); - XCTAssertNil(error, @"There was an error deleting the logFile: %@", error); - }); -} - -/** Tests convert a set of log hashes to a set of log file URLS. */ -- (void)testLogHashesToFiles { - GDLLogStorage *storage = [GDLLogStorage sharedInstance]; - NSUInteger log1Hash, log2Hash, log3Hash; - - // logEvents are autoreleased, and the pool needs to drain. - @autoreleasepool { - GDLLogEvent *logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding]; - log1Hash = logEvent.hash; - XCTAssertNoThrow([storage storeLog:logEvent]); - - logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"100" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; - log2Hash = logEvent.hash; - XCTAssertNoThrow([storage storeLog:logEvent]); - - logEvent = [[GDLLogEvent alloc] initWithLogMapID:@"404" logTarget:logTarget]; - logEvent.extensionBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding]; - log3Hash = logEvent.hash; - XCTAssertNoThrow([storage storeLog:logEvent]); - } - NSSet *logHashSet = [NSSet setWithObjects:@(log1Hash), @(log2Hash), @(log3Hash), nil]; - NSSet *logFiles = [storage logHashesToFiles:logHashSet]; - dispatch_sync(storage.storageQueue, ^{ - XCTAssertEqual(logFiles.count, 3); - XCTAssertTrue([logFiles containsObject:storage.logHashToLogFile[@(log1Hash)]]); - XCTAssertTrue([logFiles containsObject:storage.logHashToLogFile[@(log2Hash)]]); - XCTAssertTrue([logFiles containsObject:storage.logHashToLogFile[@(log3Hash)]]); - }); -} - -@end diff --git a/GoogleDataLogger/Tests/Unit/GDLLogWriterTest.m b/GoogleDataLogger/Tests/Unit/GDLLogWriterTest.m deleted file mode 100644 index e5081d29b56..00000000000 --- a/GoogleDataLogger/Tests/Unit/GDLLogWriterTest.m +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2018 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 "GDLTestCase.h" - -#import - -#import "GDLLogEvent.h" -#import "GDLLogExtensionTesterClasses.h" -#import "GDLLogStorage.h" -#import "GDLLogWriter.h" -#import "GDLLogWriter_Private.h" - -#import "GDLAssertHelper.h" -#import "GDLLogStorageFake.h" - -@interface GDLLogWriterTestNilingTransformer : NSObject - -@end - -@implementation GDLLogWriterTestNilingTransformer - -- (GDLLogEvent *)transform:(GDLLogEvent *)logEvent { - return nil; -} - -@end - -@interface GDLLogWriterTestNewLogTransformer : NSObject - -@end - -@implementation GDLLogWriterTestNewLogTransformer - -- (GDLLogEvent *)transform:(GDLLogEvent *)logEvent { - return [[GDLLogEvent alloc] initWithLogMapID:@"new" logTarget:1]; -} - -@end - -@interface GDLLogWriterTest : GDLTestCase - -@end - -@implementation GDLLogWriterTest - -- (void)setUp { - [super setUp]; - dispatch_sync([GDLLogWriter sharedInstance].logWritingQueue, ^{ - [GDLLogWriter sharedInstance].storageInstance = [[GDLLogStorageFake alloc] init]; - }); -} - -- (void)tearDown { - [super tearDown]; - dispatch_sync([GDLLogWriter sharedInstance].logWritingQueue, ^{ - [GDLLogWriter sharedInstance].storageInstance = [GDLLogStorage sharedInstance]; - }); -} - -/** Tests the default initializer. */ -- (void)testInit { - XCTAssertNotNil([[GDLLogWriter alloc] init]); -} - -/** Tests the pointer equality of result of the -sharedInstance method. */ -- (void)testSharedInstance { - XCTAssertEqual([GDLLogWriter sharedInstance], [GDLLogWriter sharedInstance]); -} - -/** Tests writing a log without a transformer. */ -- (void)testWriteLogWithoutTransformers { - GDLLogWriter *writer = [GDLLogWriter sharedInstance]; - GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"1" logTarget:1]; - log.extension = [[GDLLogExtensionTesterSimple alloc] init]; - XCTAssertNoThrow([writer writeLog:log afterApplyingTransformers:nil]); -} - -/** Tests writing a log with a transformer that nils out the log. */ -- (void)testWriteLogWithTransformersThatNilTheLog { - GDLLogWriter *writer = [GDLLogWriter sharedInstance]; - GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"2" logTarget:1]; - log.extension = [[GDLLogExtensionTesterSimple alloc] init]; - NSArray> *transformers = - @[ [[GDLLogWriterTestNilingTransformer alloc] init] ]; - XCTAssertNoThrow([writer writeLog:log afterApplyingTransformers:transformers]); -} - -/** Tests writing a log with a transformer that creates a new log. */ -- (void)testWriteLogWithTransformersThatCreateANewLog { - GDLLogWriter *writer = [GDLLogWriter sharedInstance]; - GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"2" logTarget:1]; - log.extension = [[GDLLogExtensionTesterSimple alloc] init]; - NSArray> *transformers = - @[ [[GDLLogWriterTestNewLogTransformer alloc] init] ]; - XCTAssertNoThrow([writer writeLog:log afterApplyingTransformers:transformers]); -} - -/** Tests that using a transformer without transform: implemented throws. */ -- (void)testWriteLogWithBadTransformer { - GDLLogWriter *writer = [GDLLogWriter sharedInstance]; - GDLLogEvent *log = [[GDLLogEvent alloc] initWithLogMapID:@"2" logTarget:1]; - log.extension = [[GDLLogExtensionTesterSimple alloc] init]; - NSArray *transformers = @[ [[NSObject alloc] init] ]; - - XCTestExpectation *errorExpectation = [self expectationWithDescription:@"transform: is missing"]; - [GDLAssertHelper setAssertionBlock:^{ - [errorExpectation fulfill]; - }]; - [writer writeLog:log afterApplyingTransformers:transformers]; - [self waitForExpectations:@[ errorExpectation ] timeout:5.0]; -} - -@end diff --git a/GoogleDataLogger/Tests/Unit/GDLLoggerTest.m b/GoogleDataLogger/Tests/Unit/GDLLoggerTest.m deleted file mode 100644 index d77757b992d..00000000000 --- a/GoogleDataLogger/Tests/Unit/GDLLoggerTest.m +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2018 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 "GDLTestCase.h" - -#import -#import - -#import "GDLLogger_Private.h" - -#import "GDLLogExtensionTesterClasses.h" -#import "GDLLogWriterFake.h" - -@interface GDLLoggerTest : GDLTestCase - -@end - -@implementation GDLLoggerTest - -/** Tests the default initializer. */ -- (void)testInit { - XCTAssertNotNil([[GDLLogger alloc] initWithLogMapID:@"1" logTransformers:nil logTarget:1]); - XCTAssertThrows([[GDLLogger alloc] initWithLogMapID:@"" logTransformers:nil logTarget:1]); -} - -/** Tests logging a telemetry event. */ -- (void)testLogTelemetryEvent { - GDLLogger *logger = [[GDLLogger alloc] initWithLogMapID:@"1" logTransformers:nil logTarget:1]; - logger.logWriterInstance = [[GDLLogWriterFake alloc] init]; - GDLLogEvent *event = [logger newEvent]; - event.extension = [[GDLLogExtensionTesterSimple alloc] init]; - XCTAssertNoThrow([logger logTelemetryEvent:event]); -} - -/** Tests logging a data event. */ -- (void)testLogDataEvent { - GDLLogger *logger = [[GDLLogger alloc] initWithLogMapID:@"1" logTransformers:nil logTarget:1]; - logger.logWriterInstance = [[GDLLogWriterFake alloc] init]; - GDLLogEvent *event = [logger newEvent]; - event.extension = [[GDLLogExtensionTesterSimple alloc] init]; - XCTAssertNoThrow([logger logDataEvent:event]); -} - -@end diff --git a/GoogleDataLogger/Tests/Unit/GDLRegistrarTest.m b/GoogleDataLogger/Tests/Unit/GDLRegistrarTest.m deleted file mode 100644 index 68a0dea3d77..00000000000 --- a/GoogleDataLogger/Tests/Unit/GDLRegistrarTest.m +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2018 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 "GDLTestCase.h" - -#import - -#import "GDLRegistrar_Private.h" -#import "GDLTestPrioritizer.h" -#import "GDLTestUploader.h" - -@interface GDLRegistrarTest : GDLTestCase - -@property(nonatomic) GDLLogTarget logTarget; - -@end - -@implementation GDLRegistrarTest - -- (void)setUp { - [super setUp]; - _logTarget = 23; -} - -/** Tests the default initializer. */ -- (void)testInit { - XCTAssertNotNil([[GDLRegistrarTest alloc] init]); -} - -/** Test registering an uploader. */ -- (void)testRegisterUpload { - GDLRegistrar *registrar = [GDLRegistrar sharedInstance]; - GDLTestUploader *uploader = [[GDLTestUploader alloc] init]; - XCTAssertNoThrow([registrar registerUploader:uploader logTarget:self.logTarget]); - XCTAssertEqual(uploader, registrar.logTargetToUploader[@(_logTarget)]); -} - -/** Test registering a prioritizer. */ -- (void)testRegisterPrioritizer { - GDLRegistrar *registrar = [GDLRegistrar sharedInstance]; - GDLTestPrioritizer *prioritizer = [[GDLTestPrioritizer alloc] init]; - XCTAssertNoThrow([registrar registerPrioritizer:prioritizer logTarget:self.logTarget]); - XCTAssertEqual(prioritizer, registrar.logTargetToPrioritizer[@(_logTarget)]); -} - -@end diff --git a/GoogleDataLogger/Tests/Unit/GDLUploadCoordinatorTest.m b/GoogleDataLogger/Tests/Unit/GDLUploadCoordinatorTest.m deleted file mode 100644 index f375ee7f6f2..00000000000 --- a/GoogleDataLogger/Tests/Unit/GDLUploadCoordinatorTest.m +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2018 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 "GDLTestCase.h" - -#import "GDLUploadCoordinator.h" -#import "GDLUploadCoordinator_Private.h" - -#import "GDLLogStorageFake.h" -#import "GDLRegistrar+Testing.h" -#import "GDLTestPrioritizer.h" -#import "GDLTestUploader.h" -#import "GDLUploadCoordinator+Testing.h" - -@interface GDLUploadCoordinatorTest : GDLTestCase - -/** A log storage fake to inject into GDLUploadCoordinator. */ -@property(nonatomic) GDLLogStorageFake *storageFake; - -/** A test prioritizer. */ -@property(nonatomic) GDLTestPrioritizer *prioritizer; - -/** A test uploader. */ -@property(nonatomic) GDLTestUploader *uploader; - -/** A log target for the prioritizer and uploader to use. */ -@property(nonatomic) GDLLogTarget logTarget; - -@end - -@implementation GDLUploadCoordinatorTest - -- (void)setUp { - [super setUp]; - self.storageFake = [[GDLLogStorageFake alloc] init]; - self.logTarget = 42; - self.prioritizer = [[GDLTestPrioritizer alloc] init]; - self.uploader = [[GDLTestUploader alloc] init]; - - [[GDLRegistrar sharedInstance] registerPrioritizer:_prioritizer logTarget:_logTarget]; - [[GDLRegistrar sharedInstance] registerUploader:_uploader logTarget:_logTarget]; - - GDLUploadCoordinator *uploadCoordinator = [GDLUploadCoordinator sharedInstance]; - uploadCoordinator.logStorage = self.storageFake; - uploadCoordinator.timerInterval = NSEC_PER_SEC; - uploadCoordinator.timerLeeway = 0; -} - -- (void)tearDown { - [super tearDown]; - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - [[GDLUploadCoordinator sharedInstance] reset]; - }); - [[GDLRegistrar sharedInstance] reset]; - self.storageFake = nil; - self.prioritizer = nil; - self.uploader = nil; -} - -/** Tests the default initializer. */ -- (void)testSharedInstance { - XCTAssertEqual([GDLUploadCoordinator sharedInstance], [GDLUploadCoordinator sharedInstance]); -} - -/** Tests that forcing a log upload works. */ -- (void)testForceUploadLogs { - XCTestExpectation *expectation = [self expectationWithDescription:@"uploader will upload"]; - self.uploader.uploadLogsBlock = - ^(NSSet *_Nonnull logFiles, GDLUploaderCompletionBlock _Nonnull completionBlock) { - [expectation fulfill]; - }; - NSSet *fakeLogSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; - self.storageFake.logsToReturnFromLogHashesToFiles = fakeLogSet; - NSSet *logSet = [NSSet setWithObjects:@(1234), nil]; - XCTAssertNoThrow([[GDLUploadCoordinator sharedInstance] forceUploadLogs:logSet - target:_logTarget]); - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - [self waitForExpectations:@[ expectation ] timeout:0.1]; - }); -} - -/** Tests forcing an upload while that log target currently has a request in flight queues. */ -- (void)testForceUploadLogsEnqueuesIfLogTargetAlreadyHasLogsInFlight { - [GDLUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 100; - [GDLUploadCoordinator sharedInstance].timerLeeway = NSEC_PER_SEC / 1000; - XCTestExpectation *expectation = [self expectationWithDescription:@"uploader will upload"]; - self.uploader.uploadLogsBlock = - ^(NSSet *_Nonnull logFiles, GDLUploaderCompletionBlock _Nonnull completionBlock) { - [expectation fulfill]; - }; - NSSet *fakeLogSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; - self.storageFake.logsToReturnFromLogHashesToFiles = fakeLogSet; - NSSet *logSet = [NSSet setWithObjects:@(1234), nil]; - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - [GDLUploadCoordinator sharedInstance].logTargetToInFlightLogSet[@(self->_logTarget)] = - [[NSSet alloc] init]; - }); - XCTAssertNoThrow([[GDLUploadCoordinator sharedInstance] forceUploadLogs:logSet - target:_logTarget]); - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - XCTAssertEqual([GDLUploadCoordinator sharedInstance].forcedUploadQueue.count, 1); - [GDLUploadCoordinator sharedInstance].onCompleteBlock( - self.logTarget, [GDLClock clockSnapshotInTheFuture:1000], nil); - }); - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - [self waitForExpectations:@[ expectation ] timeout:0.1]; - }); -} - -/** Tests the timer is running at the desired frequency. */ -- (void)testTimerIsRunningAtDesiredFrequency { - __block int numberOfTimesCalled = 0; - self.prioritizer.logsForNextUploadBlock = ^{ - numberOfTimesCalled++; - }; - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - // Timer should fire 10 times a second. - [GDLUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 10; - [GDLUploadCoordinator sharedInstance].timerLeeway = 0; - }); - [[GDLUploadCoordinator sharedInstance] startTimer]; - - // Run for 1 second. - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - - // It's expected that the timer called the prioritizer 10 times +/- 3 during that 1 second + the - // coordinator running before that. - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - XCTAssertEqualWithAccuracy(numberOfTimesCalled, 10, 3); - }); -} - -/** Tests uploading logs via the coordinator timer. */ -- (void)testUploadingLogsViaTimer { - NSSet *fakeLogSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; - self.storageFake.logsToReturnFromLogHashesToFiles = fakeLogSet; - __block int uploadAttempts = 0; - __weak GDLUploadCoordinatorTest *weakSelf = self; - self.prioritizer.logsForNextUploadFake = [NSSet setWithObjects:@(1234), nil]; - self.uploader.uploadLogsBlock = - ^(NSSet *_Nonnull logFiles, GDLUploaderCompletionBlock _Nonnull completionBlock) { - GDLUploadCoordinatorTest *strongSelf = weakSelf; - completionBlock(strongSelf->_logTarget, [GDLClock clockSnapshotInTheFuture:100], nil); - uploadAttempts++; - }; - [GDLUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 10; - [GDLUploadCoordinator sharedInstance].timerLeeway = 0; - - [[GDLUploadCoordinator sharedInstance] startTimer]; - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - // More than two attempts should have been made. - XCTAssertGreaterThan(uploadAttempts, 2); - }); -} - -/** Tests the situation in which the uploader failed to upload the logs for some reason. */ -- (void)testThatAFailedUploadResultsInAnEventualRetry { - NSSet *fakeLogSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; - self.storageFake.logsToReturnFromLogHashesToFiles = fakeLogSet; - __block int uploadAttempts = 0; - __weak GDLUploadCoordinatorTest *weakSelf = self; - self.prioritizer.logsForNextUploadFake = [NSSet setWithObjects:@(1234), nil]; - self.uploader.uploadLogsBlock = - ^(NSSet *_Nonnull logFiles, GDLUploaderCompletionBlock _Nonnull completionBlock) { - GDLUploadCoordinatorTest *strongSelf = weakSelf; - NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:1337 userInfo:nil]; - completionBlock(strongSelf->_logTarget, [GDLClock clockSnapshotInTheFuture:100], error); - uploadAttempts++; - }; - [GDLUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 10; - [GDLUploadCoordinator sharedInstance].timerLeeway = 0; - - [[GDLUploadCoordinator sharedInstance] startTimer]; - - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; - dispatch_sync([GDLUploadCoordinator sharedInstance].coordinationQueue, ^{ - // More than two attempts should have been made. - XCTAssertGreaterThan(uploadAttempts, 2); - }); -} - -@end diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestPrioritizer.h b/GoogleDataLogger/Tests/Unit/Helpers/GDLTestPrioritizer.h deleted file mode 100644 index 3775af06ac7..00000000000 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestPrioritizer.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2018 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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** This class implements the log prioritizer protocol for testing purposes, providing APIs to allow - * tests to alter the prioritizer behavior without creating a bunch of specialized classes. - */ -@interface GDLTestPrioritizer : NSObject - -/** The return value of -logsForNextUpload. */ -@property(nullable, nonatomic) NSSet *logsForNextUploadFake; - -/** Allows the running of a block of code during -prioritizeLog. */ -@property(nullable, nonatomic) void (^prioritizeLogBlock)(GDLLogEvent *logEvent); - -/** A block that can run before -logsForNextUpload completes. */ -@property(nullable, nonatomic) void (^logsForNextUploadBlock)(void); - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger.podspec b/GoogleDataTransport.podspec similarity index 56% rename from GoogleDataLogger.podspec rename to GoogleDataTransport.podspec index 9985b41e5e2..22243dbffa5 100644 --- a/GoogleDataLogger.podspec +++ b/GoogleDataTransport.podspec @@ -1,10 +1,10 @@ Pod::Spec.new do |s| - s.name = 'GoogleDataLogger' + s.name = 'GoogleDataTransport' s.version = '0.1.0' - s.summary = 'Google Data Logging iOS SDK.' + s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC -Shared library for iOS SDK data logging needs. +Shared library for iOS SDK data transport needs. DESC s.homepage = 'https://developers.google.com/' @@ -12,7 +12,7 @@ Shared library for iOS SDK data logging needs. s.authors = 'Google, Inc.' s.source = { :git => 'https://github.com/firebase/firebase-ios-sdk.git', - :tag => 'GoogleDataLogger-' + s.version.to_s + :tag => 'GoogleDataTransport-' + s.version.to_s } s.ios.deployment_target = '8.0' @@ -22,9 +22,9 @@ Shared library for iOS SDK data logging needs. s.static_framework = true s.prefix_header_file = false - s.source_files = 'GoogleDataLogger/GoogleDataLogger/**/*' - s.public_header_files = 'GoogleDataLogger/GoogleDataLogger/Classes/Public/*.h' - s.private_header_files = 'GoogleDataLogger/GoogleDataLogger/Classes/Private/*.h' + s.source_files = 'GoogleDataTransport/GoogleDataTransport/**/*' + s.public_header_files = 'GoogleDataTransport/GoogleDataTransport/Classes/Public/*.h' + s.private_header_files = 'GoogleDataTransport/GoogleDataTransport/Classes/Private/*.h' s.dependency 'GoogleUtilities/Logger' @@ -34,19 +34,19 @@ Shared library for iOS SDK data logging needs. 'CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY' => 'YES' } - common_test_sources = ['GoogleDataLogger/Tests/Common/**/*.{h,m}'] + common_test_sources = ['GoogleDataTransport/Tests/Common/**/*.{h,m}'] # Unit test specs s.test_spec 'Tests-Unit' do |test_spec| test_spec.requires_app_host = false - test_spec.source_files = ['GoogleDataLogger/Tests/Unit/**/*.{h,m}'] + common_test_sources + test_spec.source_files = ['GoogleDataTransport/Tests/Unit/**/*.{h,m}'] + common_test_sources end # Integration test specs s.test_spec 'Tests-Integration' do |test_spec| test_spec.requires_app_host = false - test_spec.source_files = ['GoogleDataLogger/Tests/Integration/**/*.{h,m}'] + common_test_sources - test_spec.compiler_flags = '-DGDL_LOG_TRACE_ENABLED=1' + test_spec.source_files = ['GoogleDataTransport/Tests/Integration/**/*.{h,m}'] + common_test_sources + test_spec.compiler_flags = '-DGDT_LOG_TRACE_ENABLED=1' test_spec.dependency 'GCDWebServer' end end diff --git a/GoogleDataLogger/CHANGELOG.md b/GoogleDataTransport/CHANGELOG.md similarity index 100% rename from GoogleDataLogger/CHANGELOG.md rename to GoogleDataTransport/CHANGELOG.md diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLAssert.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTAssert.m similarity index 70% rename from GoogleDataLogger/GoogleDataLogger/Classes/GDLAssert.m rename to GoogleDataTransport/GoogleDataTransport/Classes/GDTAssert.m index 38094d18223..43c88e0487d 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLAssert.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTAssert.m @@ -14,18 +14,18 @@ * limitations under the License. */ -#include "GDLAssert.h" +#include "GDTAssert.h" -GDLAssertionBlock GDLAssertionBlockToRunInsteadOfNSAssert(void) { +GDTAssertionBlock GDTAssertionBlockToRunInsteadOfNSAssert(void) { // This class is only compiled in by unit tests, and this should fail quickly in optimized builds. - Class GDLAssertClass = NSClassFromString(@"GDLAssertHelper"); - if (__builtin_expect(!!GDLAssertClass, 0)) { + Class GDTAssertClass = NSClassFromString(@"GDTAssertHelper"); + if (__builtin_expect(!!GDTAssertClass, 0)) { SEL assertionBlockSEL = NSSelectorFromString(@"assertionBlock"); if (assertionBlockSEL) { - IMP assertionBlockIMP = [GDLAssertClass methodForSelector:assertionBlockSEL]; + IMP assertionBlockIMP = [GDTAssertClass methodForSelector:assertionBlockSEL]; if (assertionBlockIMP) { - GDLAssertionBlock assertionBlock = - ((GDLAssertionBlock(*)(id, SEL))assertionBlockIMP)(GDLAssertClass, assertionBlockSEL); + GDTAssertionBlock assertionBlock = + ((GDTAssertionBlock(*)(id, SEL))assertionBlockIMP)(GDTAssertClass, assertionBlockSEL); if (assertionBlock) { return assertionBlock; } diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLClock.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTClock.m similarity index 82% rename from GoogleDataLogger/GoogleDataLogger/Classes/GDLClock.m rename to GoogleDataTransport/GoogleDataTransport/Classes/GDTClock.m index ec3d3b85040..c51e2712256 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLClock.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTClock.m @@ -14,15 +14,15 @@ * limitations under the License. */ -#import "GDLClock.h" +#import "GDTClock.h" #import // Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all // are subject to drift. That it to say, multiple consecutive calls do not always result in a // time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external -// factors. This class attempts to determine the wall-clock time at the time of log by capturing -// the kernel start and time since boot to determine a wallclock time in UTC. +// factors. This class attempts to determine the wall-clock time at the time of the event by +// capturing the kernel start and time since boot to determine a wallclock time in UTC. // // Timezone offsets at the time of a snapshot are also captured in order to provide local-time // details. Other classes in this library depend on comparing times at some time in the future to @@ -74,7 +74,7 @@ static int64_t UptimeInNanoseconds() { } // TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE. -@implementation GDLClock { +@implementation GDTClock { /** The kernel boot time when this clock was created. */ int64_t _kernelBootTime; @@ -95,17 +95,17 @@ - (instancetype)init { return self; } -+ (GDLClock *)snapshot { - return [[GDLClock alloc] init]; ++ (GDTClock *)snapshot { + return [[GDTClock alloc] init]; } + (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture { - GDLClock *snapshot = [self snapshot]; + GDTClock *snapshot = [self snapshot]; snapshot->_timeMillis += millisInTheFuture; return snapshot; } -- (BOOL)isAfter:(GDLClock *)otherClock { +- (BOOL)isAfter:(GDTClock *)otherClock { // These clocks are trivially comparable when they share a kernel boot time. if (_kernelBootTime == otherClock->_kernelBootTime) { return _uptime > otherClock->_uptime; @@ -129,16 +129,16 @@ - (BOOL)isEqual:(id)object { #pragma mark - NSSecureCoding /** NSKeyedCoder key for timeMillis property. */ -static NSString *const kGDLClockTimeMillisKey = @"GDLClockTimeMillis"; +static NSString *const kGDTClockTimeMillisKey = @"GDTClockTimeMillis"; /** NSKeyedCoder key for timezoneOffsetMillis property. */ -static NSString *const kGDLClockTimezoneOffsetSeconds = @"GDLClockTimezoneOffsetSeconds"; +static NSString *const kGDTClockTimezoneOffsetSeconds = @"GDTClockTimezoneOffsetSeconds"; /** NSKeyedCoder key for _kernelBootTime ivar. */ -static NSString *const kGDLClockKernelBootTime = @"GDLClockKernelBootTime"; +static NSString *const kGDTClockKernelBootTime = @"GDTClockKernelBootTime"; /** NSKeyedCoder key for _uptime ivar. */ -static NSString *const kGDLClockUptime = @"GDLClockUptime"; +static NSString *const kGDTClockUptime = @"GDTClockUptime"; + (BOOL)supportsSecureCoding { return YES; @@ -149,19 +149,19 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self) { // TODO: If the kernelBootTime is more recent, we need to change the kernel boot time and // uptimeMillis ivars - _timeMillis = [aDecoder decodeInt64ForKey:kGDLClockTimeMillisKey]; - _timezoneOffsetSeconds = [aDecoder decodeInt64ForKey:kGDLClockTimezoneOffsetSeconds]; - _kernelBootTime = [aDecoder decodeInt64ForKey:kGDLClockKernelBootTime]; - _uptime = [aDecoder decodeInt64ForKey:kGDLClockUptime]; + _timeMillis = [aDecoder decodeInt64ForKey:kGDTClockTimeMillisKey]; + _timezoneOffsetSeconds = [aDecoder decodeInt64ForKey:kGDTClockTimezoneOffsetSeconds]; + _kernelBootTime = [aDecoder decodeInt64ForKey:kGDTClockKernelBootTime]; + _uptime = [aDecoder decodeInt64ForKey:kGDTClockUptime]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeInt64:_timeMillis forKey:kGDLClockTimeMillisKey]; - [aCoder encodeInt64:_timezoneOffsetSeconds forKey:kGDLClockTimezoneOffsetSeconds]; - [aCoder encodeInt64:_kernelBootTime forKey:kGDLClockKernelBootTime]; - [aCoder encodeInt64:_uptime forKey:kGDLClockUptime]; + [aCoder encodeInt64:_timeMillis forKey:kGDTClockTimeMillisKey]; + [aCoder encodeInt64:_timezoneOffsetSeconds forKey:kGDTClockTimezoneOffsetSeconds]; + [aCoder encodeInt64:_kernelBootTime forKey:kGDTClockKernelBootTime]; + [aCoder encodeInt64:_uptime forKey:kGDTClockUptime]; } @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTEvent.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTEvent.m new file mode 100644 index 00000000000..b77ead823a7 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTEvent.m @@ -0,0 +1,106 @@ +/* + * Copyright 2018 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 + +#import "GDTAssert.h" +#import "GDTEvent_Private.h" + +@implementation GDTEvent + +- (instancetype)initWithMappingID:(NSString *)mappingID target:(NSInteger)target { + GDTAssert(mappingID.length > 0, @"Please give a valid mapping ID"); + GDTAssert(target > 0, @"A target cannot be negative or 0"); + self = [super init]; + if (self) { + _mappingID = mappingID; + _target = target; + _qosTier = GDTEventQosDefault; + } + return self; +} + +- (instancetype)copy { + GDTEvent *copy = [[GDTEvent alloc] initWithMappingID:_mappingID target:_target]; + copy.dataObject = _dataObject; + copy.dataObjectTransportBytes = _dataObjectTransportBytes; + copy.qosTier = _qosTier; + copy.clockSnapshot = _clockSnapshot; + copy.customPrioritizationParams = _customPrioritizationParams; + return copy; +} + +- (NSUInteger)hash { + // This loses some precision, but it's probably fine. + NSUInteger mappingIDHash = [_mappingID hash]; + NSUInteger timeHash = [_clockSnapshot hash]; + NSUInteger dataObjectTransportBytesHash = [_dataObjectTransportBytes hash]; + return mappingIDHash ^ _target ^ dataObjectTransportBytesHash ^ _qosTier ^ timeHash; +} + +- (void)setDataObject:(id)dataObject { + // If you're looking here because of a performance issue in -transportBytes slowing the assignment + // of -dataObject, one way to address this is to add a queue to this class, + // dispatch_(barrier_ if concurrent)async here, and implement the getter with a dispatch_sync. + if (dataObject != _dataObject) { + _dataObject = dataObject; + _dataObjectTransportBytes = [dataObject transportBytes]; + } +} + +#pragma mark - NSSecureCoding and NSCoding Protocols + +/** NSCoding key for mappingID property. */ +static NSString *mappingIDKey = @"_mappingID"; + +/** NSCoding key for target property. */ +static NSString *targetKey = @"_target"; + +/** NSCoding key for dataObjectTransportBytes property. */ +static NSString *dataObjectTransportBytesKey = @"_dataObjectTransportBytesKey"; + +/** NSCoding key for qosTier property. */ +static NSString *qosTierKey = @"_qosTier"; + +/** NSCoding key for clockSnapshot property. */ +static NSString *clockSnapshotKey = @"_clockSnapshot"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + NSString *mappingID = [aDecoder decodeObjectOfClass:[NSObject class] forKey:mappingIDKey]; + NSInteger target = [aDecoder decodeIntegerForKey:targetKey]; + self = [self initWithMappingID:mappingID target:target]; + if (self) { + _dataObjectTransportBytes = [aDecoder decodeObjectOfClass:[NSData class] + forKey:dataObjectTransportBytesKey]; + _qosTier = [aDecoder decodeIntegerForKey:qosTierKey]; + _clockSnapshot = [aDecoder decodeObjectOfClass:[GDTClock class] forKey:clockSnapshotKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_mappingID forKey:mappingIDKey]; + [aCoder encodeInteger:_target forKey:targetKey]; + [aCoder encodeObject:_dataObjectTransportBytes forKey:dataObjectTransportBytesKey]; + [aCoder encodeInteger:_qosTier forKey:qosTierKey]; + [aCoder encodeObject:_clockSnapshot forKey:clockSnapshotKey]; +} + +@end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTRegistrar.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTRegistrar.m new file mode 100644 index 00000000000..91bd0730cbb --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTRegistrar.m @@ -0,0 +1,92 @@ +/* + * Copyright 2018 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 "GDTRegistrar.h" + +#import "GDTRegistrar_Private.h" + +@implementation GDTRegistrar { + /** Backing ivar for targetToUploader property. */ + NSMutableDictionary> *_targetToUploader; + + /** Backing ivar for targetToPrioritizer property. */ + NSMutableDictionary> *_targetToPrioritizer; +} + ++ (instancetype)sharedInstance { + static GDTRegistrar *sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[GDTRegistrar alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _registrarQueue = dispatch_queue_create("com.google.GDTRegistrar", DISPATCH_QUEUE_CONCURRENT); + _targetToPrioritizer = [[NSMutableDictionary alloc] init]; + _targetToUploader = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)registerUploader:(id)backend target:(GDTTarget)target { + __weak GDTRegistrar *weakSelf = self; + dispatch_barrier_async(_registrarQueue, ^{ + GDTRegistrar *strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_targetToUploader[@(target)] = backend; + } + }); +} + +- (void)registerPrioritizer:(id)prioritizer target:(GDTTarget)target { + __weak GDTRegistrar *weakSelf = self; + dispatch_barrier_async(_registrarQueue, ^{ + GDTRegistrar *strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_targetToPrioritizer[@(target)] = prioritizer; + } + }); +} + +- (NSMutableDictionary> *)targetToUploader { + __block NSMutableDictionary> *targetToUploader; + __weak GDTRegistrar *weakSelf = self; + dispatch_sync(_registrarQueue, ^{ + GDTRegistrar *strongSelf = weakSelf; + if (strongSelf) { + targetToUploader = strongSelf->_targetToUploader; + } + }); + return targetToUploader; +} + +- (NSMutableDictionary> *)targetToPrioritizer { + __block NSMutableDictionary> *targetToPrioritizer; + __weak GDTRegistrar *weakSelf = self; + dispatch_sync(_registrarQueue, ^{ + GDTRegistrar *strongSelf = weakSelf; + if (strongSelf) { + targetToPrioritizer = strongSelf->_targetToPrioritizer; + } + }); + return targetToPrioritizer; +} + +@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogStorage.h b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.h similarity index 51% rename from GoogleDataLogger/GoogleDataLogger/Classes/GDLLogStorage.h rename to GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.h index 337adf4ba46..5b82c46c1db 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLLogStorage.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.h @@ -16,12 +16,12 @@ #import -@class GDLLogEvent; +@class GDTEvent; NS_ASSUME_NONNULL_BEGIN -/** Manages the storage of logs. This class is thread-safe. */ -@interface GDLLogStorage : NSObject +/** Manages the storage of events. This class is thread-safe. */ +@interface GDTStorage : NSObject /** Creates and/or returns the storage singleton. * @@ -29,28 +29,28 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedInstance; -/** Stores log.extensionBytes into a shared on-device folder and tracks the log via its hash and - * logTarget properties. +/** Stores event.dataObjectTransportBytes into a shared on-device folder and tracks the event via + * its hash and target properties. * - * @note The log param is expected to be deallocated during this method. + * @note The event param is expected to be deallocated during this method. * - * @param log The log to store. + * @param event The event to store. */ -- (void)storeLog:(GDLLogEvent *)log; +- (void)storeEvent:(GDTEvent *)event; -/** Removes a set of log fields specified by their filenames. +/** Removes a set of event from storage specified by their hash. * - * @param logHashes The set of log files to remove. - * @param logTarget The log target the log files correspond to. + * @param eventHashes The set of event hashes to remove. + * @param target The upload target the event files correspond to. */ -- (void)removeLogs:(NSSet *)logHashes logTarget:(NSNumber *)logTarget; +- (void)removeEvents:(NSSet *)eventHashes target:(NSNumber *)target; -/** Converts a set of log hashes to a set of log files. +/** Converts a set of event hashes to a set of event files. * - * @param logHashes A set of log hashes to get the files of. + * @param eventHashes A set of event hashes to get the files of. * @return A set of equivalent length, containing all the filenames corresponding to the hashes. */ -- (NSSet *)logHashesToFiles:(NSSet *)logHashes; +- (NSSet *)eventHashesToFiles:(NSSet *)eventHashes; @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m new file mode 100644 index 00000000000..566d8664011 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m @@ -0,0 +1,245 @@ +/* + * Copyright 2018 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 "GDTStorage.h" +#import "GDTStorage_Private.h" + +#import + +#import "GDTAssert.h" +#import "GDTConsoleLogger.h" +#import "GDTEvent_Private.h" +#import "GDTRegistrar_Private.h" +#import "GDTUploadCoordinator.h" + +/** Creates and/or returns a singleton NSString that is the shared storage path. + * + * @return The SDK event storage path. + */ +static NSString *GDTStoragePath() { + static NSString *storagePath; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *cachePath = + NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; + storagePath = [NSString stringWithFormat:@"%@/google-sdks-events", cachePath]; + }); + return storagePath; +} + +@implementation GDTStorage + ++ (instancetype)sharedInstance { + static GDTStorage *sharedStorage; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedStorage = [[GDTStorage alloc] init]; + }); + return sharedStorage; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _storageQueue = dispatch_queue_create("com.google.GDTStorage", DISPATCH_QUEUE_SERIAL); + _eventHashToFile = [[NSMutableDictionary alloc] init]; + _targetToEventHashSet = [[NSMutableDictionary alloc] init]; + _uploader = [GDTUploadCoordinator sharedInstance]; + } + return self; +} + +- (void)storeEvent:(GDTEvent *)event { + [self createEventDirectoryIfNotExists]; + + // This is done to ensure that event is deallocated at the end of the ensuing block. + __block GDTEvent *shortLivedEvent = event; + __weak GDTEvent *weakShortLivedEvent = event; + event = nil; + + dispatch_async(_storageQueue, ^{ + // Check that a backend implementation is available for this target. + NSInteger target = shortLivedEvent.target; + + // Check that a prioritizer is available for this target. + id prioritizer = [GDTRegistrar sharedInstance].targetToPrioritizer[@(target)]; + GDTAssert(prioritizer, @"There's no prioritizer registered for the given target."); + + // Write the transport bytes to disk, get a filename. + GDTAssert(shortLivedEvent.dataObjectTransportBytes, + @"The event should have been serialized to bytes"); + NSURL *eventFile = [self saveEventBytesToDisk:shortLivedEvent.dataObjectTransportBytes + eventHash:shortLivedEvent.hash]; + + // Add event to tracking collections. + [self addEventToTrackingCollections:shortLivedEvent eventFile:eventFile]; + + // Check the QoS, if it's high priority, notify the target that it has a high priority event. + if (shortLivedEvent.qosTier == GDTEventQoSFast) { + NSSet *allEventsForTarget = self.targetToEventHashSet[@(target)]; + [self.uploader forceUploadEvents:allEventsForTarget target:target]; + } + + // Have the prioritizer prioritize the event, enforcing that they do not retain it. + @autoreleasepool { + [prioritizer prioritizeEvent:shortLivedEvent]; + shortLivedEvent = nil; + } + if (weakShortLivedEvent) { + GDTLogError(GDTMCEEventWasIllegallyRetained, @"%@", + @"An event should not be retained outside of storage."); + }; + }); +} + +- (void)removeEvents:(NSSet *)eventHashes target:(NSNumber *)target { + dispatch_sync(_storageQueue, ^{ + for (NSNumber *eventHash in eventHashes) { + [self removeEvent:eventHash target:target]; + } + }); +} + +- (NSSet *)eventHashesToFiles:(NSSet *)eventHashes { + NSMutableSet *eventFiles = [[NSMutableSet alloc] init]; + dispatch_sync(_storageQueue, ^{ + for (NSNumber *hashNumber in eventHashes) { + NSURL *eventURL = self.eventHashToFile[hashNumber]; + GDTAssert(eventURL, @"An event file URL couldn't be found for the given hash"); + [eventFiles addObject:eventURL]; + } + }); + return eventFiles; +} + +#pragma mark - Private helper methods + +/** Removes the corresponding event file from disk. + * + * @param eventHash The hash value of the original event. + * @param target The target of the original event. + */ +- (void)removeEvent:(NSNumber *)eventHash target:(NSNumber *)target { + NSURL *eventFile = self.eventHashToFile[eventHash]; + + // Remove from disk, first and foremost. + NSError *error; + [[NSFileManager defaultManager] removeItemAtURL:eventFile error:&error]; + GDTAssert(error == nil, @"There was an error removing an event file: %@", error); + + // Remove from the tracking collections. + [self.eventHashToFile removeObjectForKey:eventHash]; + NSMutableSet *eventHashes = self.targetToEventHashSet[target]; + GDTAssert(eventHashes, @"There wasn't an event set for this target."); + [eventHashes removeObject:eventHash]; + // It's fine to not remove the set if it's empty. + + // Check that a prioritizer is available for this target. + id prioritizer = [GDTRegistrar sharedInstance].targetToPrioritizer[target]; + GDTAssert(prioritizer, @"There's no prioritizer registered for the given target."); + [prioritizer unprioritizeEvent:eventHash]; +} + +/** Creates the storage directory if it does not exist. */ +- (void)createEventDirectoryIfNotExists { + NSError *error; + BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:GDTStoragePath() + withIntermediateDirectories:YES + attributes:0 + error:&error]; + if (!result || error) { + GDTLogError(GDTMCEDirectoryCreationError, @"Error creating the directory: %@", error); + } +} + +/** Saves the event's dataObjectTransportBytes to a file using NSData mechanisms. + * + * @note This method should only be called from a method within a block on _storageQueue to maintain + * thread safety. + * + * @param transportBytes The transport bytes of the event. + * @param eventHash The hash value of the event. + * @return The filename + */ +- (NSURL *)saveEventBytesToDisk:(NSData *)transportBytes eventHash:(NSUInteger)eventHash { + NSString *storagePath = GDTStoragePath(); + NSString *event = [NSString stringWithFormat:@"event-%lu", (unsigned long)eventHash]; + NSURL *eventFilePath = [NSURL fileURLWithPath:[storagePath stringByAppendingPathComponent:event]]; + + BOOL writingSuccess = [transportBytes writeToURL:eventFilePath atomically:YES]; + if (!writingSuccess) { + GDTLogError(GDTMCEFileWriteError, @"An event file could not be written: %@", eventFilePath); + } + + return eventFilePath; +} + +/** Adds the event to internal tracking collections. + * + * @note This method should only be called from a method within a block on _storageQueue to maintain + * thread safety. + * + * @param event The event to track. + * @param eventFile The file the event has been saved to. + */ +- (void)addEventToTrackingCollections:(GDTEvent *)event eventFile:(NSURL *)eventFile { + NSInteger target = event.target; + NSNumber *eventHash = @(event.hash); + NSNumber *targetNumber = @(target); + self.eventHashToFile[eventHash] = eventFile; + NSMutableSet *events = self.targetToEventHashSet[targetNumber]; + if (events) { + [events addObject:eventHash]; + } else { + NSMutableSet *eventSet = [NSMutableSet setWithObject:eventHash]; + self.targetToEventHashSet[targetNumber] = eventSet; + } +} + +#pragma mark - NSSecureCoding + +/** The NSKeyedCoder key for the eventHashToFile property. */ +static NSString *const kGDTEventHashToFileKey = @"eventHashToFileKey"; + +/** The NSKeyedCoder key for the targetToEventHashSet property. */ +static NSString *const kGDTTargetToEventHashSetKey = @"targetToEventHashSetKey"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + // Create the singleton and populate its ivars. + GDTStorage *sharedInstance = [self.class sharedInstance]; + dispatch_sync(sharedInstance.storageQueue, ^{ + Class NSMutableDictionaryClass = [NSMutableDictionary class]; + sharedInstance->_eventHashToFile = [aDecoder decodeObjectOfClass:NSMutableDictionaryClass + forKey:kGDTEventHashToFileKey]; + sharedInstance->_targetToEventHashSet = + [aDecoder decodeObjectOfClass:NSMutableDictionaryClass forKey:kGDTTargetToEventHashSetKey]; + }); + return sharedInstance; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + GDTStorage *sharedInstance = [self.class sharedInstance]; + dispatch_sync(sharedInstance.storageQueue, ^{ + [aCoder encodeObject:sharedInstance->_eventHashToFile forKey:kGDTEventHashToFileKey]; + [aCoder encodeObject:sharedInstance->_targetToEventHashSet forKey:kGDTTargetToEventHashSetKey]; + }); +} + +@end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.h b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.h new file mode 100644 index 00000000000..1dbbe25f3a4 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018 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 + +@class GDTEvent; + +@protocol GDTEventTransformer; + +NS_ASSUME_NONNULL_BEGIN + +/** Manages the transforming of events. It's desirable for this to be its own class + * because running all events through a single instance ensures that transformers are thread-safe. + * Having a per-transport queue to run on isn't sufficient because transformer objects could + * maintain state (or at least, there's nothing to stop them from doing that) and the same instances + * may be used across multiple instances. + */ +@interface GDTTransformer : NSObject + +/** Instantiates or returns the event transformer singleton. + * + * @return The singleton instance of the event transformer. + */ ++ (instancetype)sharedInstance; + +/** Writes the result of applying the given transformers' -transform method on the given event. + * + * @param event The event to apply transformers on. + * @param transformers The list of transformers to apply. + */ +- (void)transformEvent:(GDTEvent *)event + withTransformers:(nullable NSArray> *)transformers; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.m new file mode 100644 index 00000000000..903a95bfe2c --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.m @@ -0,0 +1,68 @@ +/* + * Copyright 2018 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 "GDTTransformer.h" +#import "GDTTransformer_Private.h" + +#import + +#import "GDTAssert.h" +#import "GDTConsoleLogger.h" +#import "GDTEvent_Private.h" +#import "GDTStorage.h" + +@implementation GDTTransformer + ++ (instancetype)sharedInstance { + static GDTTransformer *eventTransformer; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + eventTransformer = [[self alloc] init]; + }); + return eventTransformer; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _eventWritingQueue = dispatch_queue_create("com.google.GDTTransformer", DISPATCH_QUEUE_SERIAL); + _storageInstance = [GDTStorage sharedInstance]; + } + return self; +} + +- (void)transformEvent:(GDTEvent *)event + withTransformers:(NSArray> *)transformers { + GDTAssert(event, @"You can't write a nil event"); + dispatch_async(_eventWritingQueue, ^{ + GDTEvent *transformedEvent = event; + for (id transformer in transformers) { + if ([transformer respondsToSelector:@selector(transform:)]) { + transformedEvent = [transformer transform:transformedEvent]; + if (!transformedEvent) { + return; + } + } else { + GDTLogError(GDTMCETransformerDoesntImplementTransform, + @"Transformer doesn't implement transform: %@", transformer); + return; + } + } + [self.storageInstance storeEvent:transformedEvent]; + }); +} + +@end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransport.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransport.m new file mode 100644 index 00000000000..b66e6277672 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransport.m @@ -0,0 +1,64 @@ +/* + * Copyright 2018 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 "GDTTransport.h" +#import "GDTTransport_Private.h" + +#import "GDTAssert.h" +#import "GDTEvent.h" +#import "GDTEvent_Private.h" +#import "GDTTransformer.h" + +@implementation GDTTransport + +- (instancetype)initWithMappingID:(NSString *)mappingID + transformers:(nullable NSArray> *)transformers + target:(NSInteger)target { + self = [super init]; + if (self) { + GDTAssert(mappingID.length > 0, @"A mapping ID cannot be nil or empty"); + GDTAssert(target > 0, @"A target cannot be negative or 0"); + _mappingID = mappingID; + _transformers = transformers; + _target = target; + _transformerInstance = [GDTTransformer sharedInstance]; + } + return self; +} + +- (void)sendTelemetryEvent:(GDTEvent *)event { + // TODO: Determine if sending an event before registration is allowed. + GDTAssert(event, @"You can't send a nil event"); + GDTEvent *copiedEvent = [event copy]; + copiedEvent.qosTier = GDTEventQoSTelemetry; + copiedEvent.clockSnapshot = [GDTClock snapshot]; + [self.transformerInstance transformEvent:copiedEvent withTransformers:_transformers]; +} + +- (void)sendDataEvent:(GDTEvent *)event { + // TODO: Determine if sending an event before registration is allowed. + GDTAssert(event, @"You can't send a nil event"); + GDTAssert(event.qosTier != GDTEventQoSTelemetry, @"Use -sendTelemetryEvent, please."); + GDTEvent *copiedEvent = [event copy]; + copiedEvent.clockSnapshot = [GDTClock snapshot]; + [self.transformerInstance transformEvent:copiedEvent withTransformers:_transformers]; +} + +- (GDTEvent *)eventForTransport { + return [[GDTEvent alloc] initWithMappingID:_mappingID target:_target]; +} + +@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/GDLUploadCoordinator.h b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.h similarity index 61% rename from GoogleDataLogger/GoogleDataLogger/Classes/GDLUploadCoordinator.h rename to GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.h index 714f8c8a23b..36f9ee9afbf 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/GDLUploadCoordinator.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.h @@ -16,14 +16,14 @@ #import -#import "GDLRegistrar.h" +#import "GDTRegistrar.h" NS_ASSUME_NONNULL_BEGIN -/** This class connects log storage and the uploader implementations, providing logs to an uploader - * and informing the log storage what logs were successfully uploaded or not. +/** This class connects storage and uploader implementations, providing events to an uploader + * and informing the storage what events were successfully uploaded or not. */ -@interface GDLUploadCoordinator : NSObject +@interface GDTUploadCoordinator : NSObject /** Creates and/or returrns the singleton. * @@ -31,13 +31,13 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedInstance; -/** Forces the backend specified by the target to upload the provided set of logs. This should only - * ever happen when the QoS tier of a log requires it. +/** Forces the backend specified by the target to upload the provided set of events. This should + * only ever happen when the QoS tier of an event requires it. * - * @param logHashes The set of log hashes to force upload. - * @param logTarget The log target that should force an upload. + * @param eventHashes The set of event hashes to force upload. + * @param target The target that should force an upload. */ -- (void)forceUploadLogs:(NSSet *)logHashes target:(GDLLogTarget)logTarget; +- (void)forceUploadEvents:(NSSet *)eventHashes target:(GDTTarget)target; @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m new file mode 100644 index 00000000000..0819ac8fa05 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m @@ -0,0 +1,205 @@ +/* + * Copyright 2018 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 "GDTUploadCoordinator.h" +#import "GDTUploadCoordinator_Private.h" + +#import "GDTAssert.h" +#import "GDTClock.h" +#import "GDTConsoleLogger.h" +#import "GDTRegistrar_Private.h" +#import "GDTStorage.h" + +@implementation GDTUploadCoordinator + ++ (instancetype)sharedInstance { + static GDTUploadCoordinator *sharedUploader; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedUploader = [[GDTUploadCoordinator alloc] init]; + [sharedUploader startTimer]; + }); + return sharedUploader; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _coordinationQueue = + dispatch_queue_create("com.google.GDTUploadCoordinator", DISPATCH_QUEUE_SERIAL); + _registrar = [GDTRegistrar sharedInstance]; + _targetToNextUploadTimes = [[NSMutableDictionary alloc] init]; + _targetToInFlightEventSet = [[NSMutableDictionary alloc] init]; + _forcedUploadQueue = [[NSMutableArray alloc] init]; + _timerInterval = 30 * NSEC_PER_SEC; + _timerLeeway = 5 * NSEC_PER_SEC; + } + return self; +} + +- (void)forceUploadEvents:(NSSet *)eventHashes target:(GDTTarget)target { + dispatch_async(_coordinationQueue, ^{ + NSNumber *targetNumber = @(target); + GDTRegistrar *registrar = self->_registrar; + GDTUploadCoordinatorForceUploadBlock forceUploadBlock = ^{ + GDTAssert(eventHashes.count, @"It doesn't make sense to force upload of 0 events"); + id uploader = registrar.targetToUploader[targetNumber]; + NSSet *eventFiles = [self.storage eventHashesToFiles:eventHashes]; + GDTAssert(uploader, @"Target '%@' is missing an implementation", targetNumber); + [uploader uploadEvents:eventFiles onComplete:self.onCompleteBlock]; + self->_targetToInFlightEventSet[targetNumber] = eventHashes; + }; + + // Enqueue the force upload block if there's an in-flight upload for that target already. + if (self->_targetToInFlightEventSet[targetNumber]) { + [self->_forcedUploadQueue insertObject:forceUploadBlock atIndex:0]; + } else { + forceUploadBlock(); + } + }); +} + +#pragma mark - Property overrides + +// GDTStorage and GDTUploadCoordinator +sharedInstance methods call each other, so this breaks +// the loop. +- (GDTStorage *)storage { + if (!_storage) { + _storage = [GDTStorage sharedInstance]; + } + return _storage; +} + +// This should always be called in a thread-safe manner. +- (GDTUploaderCompletionBlock)onCompleteBlock { + __weak GDTUploadCoordinator *weakSelf = self; + static GDTUploaderCompletionBlock onCompleteBlock; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + onCompleteBlock = ^(GDTTarget target, GDTClock *nextUploadAttemptUTC, NSError *error) { + GDTUploadCoordinator *strongSelf = weakSelf; + if (strongSelf) { + dispatch_async(strongSelf.coordinationQueue, ^{ + NSNumber *targetNumber = @(target); + if (error) { + GDTLogWarning(GDTMCWUploadFailed, @"Error during upload: %@", error); + [strongSelf->_targetToInFlightEventSet removeObjectForKey:targetNumber]; + return; + } + strongSelf->_targetToNextUploadTimes[targetNumber] = nextUploadAttemptUTC; + NSSet *eventHashSet = + [strongSelf->_targetToInFlightEventSet objectForKey:targetNumber]; + GDTAssert(eventHashSet, @"There should be an in-flight event set to remove."); + [strongSelf.storage removeEvents:eventHashSet target:targetNumber]; + [strongSelf->_targetToInFlightEventSet removeObjectForKey:targetNumber]; + if (strongSelf->_forcedUploadQueue.count) { + GDTUploadCoordinatorForceUploadBlock queuedBlock = + [strongSelf->_forcedUploadQueue lastObject]; + if (queuedBlock) { + queuedBlock(); + } + [strongSelf->_forcedUploadQueue removeLastObject]; + } + }); + } + }; + }); + return onCompleteBlock; +} + +#pragma mark - Private helper methods + +/** Starts a timer that checks whether or not events can be uploaded at regular intervals. It will + * check the next-upload clocks of all targets to determine if an upload attempt can be made. + */ +- (void)startTimer { + __weak GDTUploadCoordinator *weakSelf = self; + dispatch_sync(_coordinationQueue, ^{ + GDTUploadCoordinator *strongSelf = weakSelf; + GDTAssert(strongSelf, @"self must be real to start a timer."); + strongSelf->_timer = + dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, strongSelf->_coordinationQueue); + dispatch_source_set_timer(strongSelf->_timer, DISPATCH_TIME_NOW, strongSelf->_timerInterval, + strongSelf->_timerLeeway); + dispatch_source_set_event_handler(strongSelf->_timer, ^{ + [self checkPrioritizersAndUploadEvents]; + }); + dispatch_resume(strongSelf->_timer); + }); +} + +/** Checks the next upload time for each target and makes a determination on whether to upload + * events for that target or not. If so, queries the prioritizers + */ +- (void)checkPrioritizersAndUploadEvents { + __weak GDTUploadCoordinator *weakSelf = self; + dispatch_async(_coordinationQueue, ^{ + static int count = 0; + count++; + GDTUploadCoordinator *strongSelf = weakSelf; + if (strongSelf) { + NSArray *targetsReadyForUpload = [self targetsReadyForUpload]; + for (NSNumber *target in targetsReadyForUpload) { + id prioritizer = strongSelf->_registrar.targetToPrioritizer[target]; + id uploader = strongSelf->_registrar.targetToUploader[target]; + GDTAssert(prioritizer && uploader, @"Target '%@' is missing an implementation", target); + GDTUploadConditions conds = [self uploadConditions]; + NSSet *eventHashesToUpload = + [[prioritizer eventsToUploadGivenConditions:conds] copy]; + if (eventHashesToUpload && eventHashesToUpload.count > 0) { + NSAssert(eventHashesToUpload.count > 0, @""); + NSSet *eventFilesToUpload = + [strongSelf.storage eventHashesToFiles:eventHashesToUpload]; + NSAssert(eventFilesToUpload.count == eventHashesToUpload.count, + @"There should be the same number of files to events"); + strongSelf->_targetToInFlightEventSet[target] = eventHashesToUpload; + [uploader uploadEvents:eventFilesToUpload onComplete:self.onCompleteBlock]; + } + } + } + }); +} + +/** */ +- (GDTUploadConditions)uploadConditions { + // TODO: Compute the real upload conditions. + return GDTUploadConditionMobileData; +} + +/** Checks the next upload time for each target and returns an array of targets that are + * able to make an upload attempt. + * + * @return An array of targets wrapped in NSNumbers that are ready for upload attempts. + */ +- (NSArray *)targetsReadyForUpload { + NSMutableArray *targetsReadyForUpload = [[NSMutableArray alloc] init]; + GDTClock *currentTime = [GDTClock snapshot]; + for (NSNumber *target in self.registrar.targetToPrioritizer) { + // Targets in flight are not ready. + if (_targetToInFlightEventSet[target]) { + continue; + } + GDTClock *nextUploadTime = _targetToNextUploadTimes[target]; + + // If no next upload time was specified or if the currentTime > nextUpload time, mark as ready. + if (!nextUploadTime || [currentTime isAfter:nextUploadTime]) { + [targetsReadyForUpload addObject:target]; + } + } + return targetsReadyForUpload; +} + +@end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLAssert.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTAssert.h similarity index 88% rename from GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLAssert.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTAssert.h index c4f1f96cd32..d1797db228f 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLAssert.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTAssert.h @@ -17,7 +17,7 @@ #import /** A block type that could be run instead of NSAssert. No return type, no params. */ -typedef void (^GDLAssertionBlock)(void); +typedef void (^GDTAssertionBlock)(void); /** Returns the result of executing a soft-linked method present in unit tests that allows a block * to be run in lieu of a call to NSAssert. This helps ameliorate issues with catching exceptions @@ -25,7 +25,7 @@ typedef void (^GDLAssertionBlock)(void); * * @return A block that can be run instead of calling NSAssert, or nil. */ -FOUNDATION_EXTERN GDLAssertionBlock _Nullable GDLAssertionBlockToRunInsteadOfNSAssert(void); +FOUNDATION_EXTERN GDTAssertionBlock _Nullable GDTAssertionBlockToRunInsteadOfNSAssert(void); #if !defined(NS_BLOCK_ASSERTIONS) @@ -33,10 +33,10 @@ FOUNDATION_EXTERN GDLAssertionBlock _Nullable GDLAssertionBlockToRunInsteadOfNSA * * @param condition The condition you'd expect to be YES. */ -#define GDLAssert(condition, ...) \ +#define GDTAssert(condition, ...) \ do { \ if (__builtin_expect(!(condition), 0)) { \ - GDLAssertionBlock assertionBlock = GDLAssertionBlockToRunInsteadOfNSAssert(); \ + GDTAssertionBlock assertionBlock = GDTAssertionBlockToRunInsteadOfNSAssert(); \ if (assertionBlock) { \ assertionBlock(); \ } else { \ @@ -47,7 +47,7 @@ FOUNDATION_EXTERN GDLAssertionBlock _Nullable GDLAssertionBlockToRunInsteadOfNSA #else -#define GDLAssert(condition, ...) \ +#define GDTAssert(condition, ...) \ do { \ } while (0); diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogEvent_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTEvent_Private.h similarity index 70% rename from GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogEvent_Private.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTEvent_Private.h index 117f24c556c..67914e7b91a 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogEvent_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTEvent_Private.h @@ -14,19 +14,19 @@ * limitations under the License. */ -#import "GDLLogEvent.h" +#import "GDTEvent.h" -#import "GDLClock.h" +#import "GDTClock.h" NS_ASSUME_NONNULL_BEGIN -@interface GDLLogEvent () +@interface GDTEvent () -/** The serialized bytes of the log object. */ -@property(nonatomic) NSData *extensionBytes; +/** The serialized bytes of the event data object. */ +@property(nonatomic) NSData *dataObjectTransportBytes; -/** The clock snapshot at the time of logging. */ -@property(nonatomic) GDLClock *clockSnapshot; +/** The clock snapshot at the time of the event. */ +@property(nonatomic) GDTClock *clockSnapshot; @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLRegistrar_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTRegistrar_Private.h similarity index 68% rename from GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLRegistrar_Private.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTRegistrar_Private.h index 2eaa1c502e2..90bdcea1758 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLRegistrar_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTRegistrar_Private.h @@ -14,22 +14,21 @@ * limitations under the License. */ -#import +#import -@interface GDLRegistrar () +@interface GDTRegistrar () NS_ASSUME_NONNULL_BEGIN /** The concurrent queue on which all registration occurs. */ @property(nonatomic, readonly) dispatch_queue_t registrarQueue; -/** A map of logTargets to backend implementations. */ -@property(atomic, readonly) - NSMutableDictionary> *logTargetToUploader; +/** A map of targets to backend implementations. */ +@property(atomic, readonly) NSMutableDictionary> *targetToUploader; -/** A map of logTargets to prioritizer implementations. */ +/** A map of targets to prioritizer implementations. */ @property(atomic, readonly) - NSMutableDictionary> *logTargetToPrioritizer; + NSMutableDictionary> *targetToPrioritizer; @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogStorage_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTStorage_Private.h similarity index 68% rename from GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogStorage_Private.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTStorage_Private.h index 0697f466bd6..be3be88b7b0 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogStorage_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTStorage_Private.h @@ -14,26 +14,26 @@ * limitations under the License. */ -#import "GDLLogStorage.h" +#import "GDTStorage.h" -@class GDLUploadCoordinator; +@class GDTUploadCoordinator; NS_ASSUME_NONNULL_BEGIN -@interface GDLLogStorage () +@interface GDTStorage () /** The queue on which all storage work will occur. */ @property(nonatomic) dispatch_queue_t storageQueue; -/** A map of log hash values to log file on-disk URLs. */ -@property(nonatomic) NSMutableDictionary *logHashToLogFile; +/** A map of event hashes to their on-disk file URLs. */ +@property(nonatomic) NSMutableDictionary *eventHashToFile; -/** A map of logTargets to a set of log hash values. */ +/** A map of targets to a set of event hash values. */ @property(nonatomic) - NSMutableDictionary *> *logTargetToLogHashSet; + NSMutableDictionary *> *targetToEventHashSet; -/** The log uploader instance to use. */ -@property(nonatomic) GDLUploadCoordinator *uploader; +/** The upload coordinator instance to use. */ +@property(nonatomic) GDTUploadCoordinator *uploader; @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogWriter_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTTransformer_Private.h similarity index 71% rename from GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogWriter_Private.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTTransformer_Private.h index 11f143633d5..07e91f7578f 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogWriter_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTTransformer_Private.h @@ -14,19 +14,19 @@ * limitations under the License. */ -#import "GDLLogWriter.h" +#import "GDTTransformer.h" -@class GDLLogStorage; +@class GDTStorage; NS_ASSUME_NONNULL_BEGIN -@interface GDLLogWriter () +@interface GDTTransformer () /** The queue on which all work will occur. */ -@property(nonatomic) dispatch_queue_t logWritingQueue; +@property(nonatomic) dispatch_queue_t eventWritingQueue; -/** The log storage instance used to store logs. Should only be used to inject a testing fake. */ -@property(nonatomic) GDLLogStorage *storageInstance; +/** The storage instance used to store events. Should only be used to inject a testing fake. */ +@property(nonatomic) GDTStorage *storageInstance; @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogger_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTTransport_Private.h similarity index 51% rename from GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogger_Private.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTTransport_Private.h index 48c2fb25291..c9e4c907b47 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLLogger_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTTransport_Private.h @@ -14,25 +14,25 @@ * limitations under the License. */ -#import +#import -@class GDLLogWriter; +@class GDTTransformer; NS_ASSUME_NONNULL_BEGIN -@interface GDLLogger () +@interface GDTTransport () -/** The log mapping identifier that a GDLLogBackend will use to map the extension to proto. */ -@property(nonatomic) NSString *logMapID; +/** The mapping identifier that the target backend will use to map the transport bytes to proto. */ +@property(nonatomic) NSString *mappingID; -/** The log transformers that will operate on logs logged by this logger. */ -@property(nonatomic) NSArray> *logTransformers; +/** The transformers that will operate on events sent by this transport. */ +@property(nonatomic) NSArray> *transformers; -/** The target backend of this logger. */ -@property(nonatomic) NSInteger logTarget; +/** The target backend of this transport. */ +@property(nonatomic) NSInteger target; -/** The log writer instance to used to write logs. Allows injecting a fake during testing. */ -@property(nonatomic) GDLLogWriter *logWriterInstance; +/** The transformer instance to used to transform events. Allows injecting a fake during testing. */ +@property(nonatomic) GDTTransformer *transformerInstance; @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLUploadCoordinator_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTUploadCoordinator_Private.h similarity index 59% rename from GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLUploadCoordinator_Private.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTUploadCoordinator_Private.h index 3e4c54bfe1d..2e145a273e2 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Private/GDLUploadCoordinator_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTUploadCoordinator_Private.h @@ -14,37 +14,36 @@ * limitations under the License. */ -#import "GDLUploadCoordinator.h" +#import "GDTUploadCoordinator.h" -@class GDLClock; -@class GDLLogStorage; +@class GDTClock; +@class GDTStorage; /** A convenience typedef to define the a block containing a force upload attempt. */ -typedef void (^GDLUploadCoordinatorForceUploadBlock)(void); +typedef void (^GDTUploadCoordinatorForceUploadBlock)(void); NS_ASSUME_NONNULL_BEGIN -@interface GDLUploadCoordinator () +@interface GDTUploadCoordinator () /** The queue on which all upload coordination will occur. Also used by a dispatch timer. */ @property(nonatomic, readonly) dispatch_queue_t coordinationQueue; /** The completion block to run after an uploader completes. */ -@property(nonatomic, readonly) GDLUploaderCompletionBlock onCompleteBlock; +@property(nonatomic, readonly) GDTUploaderCompletionBlock onCompleteBlock; -/** A map of log targets to their desired next upload time, if they have one. */ -@property(nonatomic, readonly) - NSMutableDictionary *logTargetToNextUploadTimes; +/** A map of targets to their desired next upload time, if they have one. */ +@property(nonatomic, readonly) NSMutableDictionary *targetToNextUploadTimes; -/** A map of log targets to a set of log hashes that has been handed off to the uploader. */ +/** A map of targets to a set of event hashes that has been handed off to the uploader. */ @property(nonatomic, readonly) - NSMutableDictionary *> *logTargetToInFlightLogSet; + NSMutableDictionary *> *targetToInFlightEventSet; -/** A queue of forced uploads. Only populated if the log target already had in-flight logs. */ +/** A queue of forced uploads. Only populated if the target already had in-flight events. */ @property(nonatomic, readonly) - NSMutableArray *forcedUploadQueue; + NSMutableArray *forcedUploadQueue; -/** A timer that will causes regular checks for logs to upload. */ +/** A timer that will causes regular checks for events to upload. */ @property(nonatomic, readonly) dispatch_source_t timer; /** The interval the timer will fire. */ @@ -53,11 +52,11 @@ NS_ASSUME_NONNULL_BEGIN /** Some leeway given to libdispatch for the timer interval event. */ @property(nonatomic, readonly) uint64_t timerLeeway; -/** The log storage object the coordinator will use. Generally used for testing. */ -@property(nonatomic) GDLLogStorage *logStorage; +/** The storage object the coordinator will use. Generally used for testing. */ +@property(nonatomic) GDTStorage *storage; /** The registrar object the coordinator will use. Generally used for testing. */ -@property(nonatomic) GDLRegistrar *registrar; +@property(nonatomic) GDTRegistrar *registrar; /** Starts the upload timer. */ - (void)startTimer; diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLClock.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTClock.h similarity index 83% rename from GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLClock.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTClock.h index 569970c4e38..9190b5a08f9 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLClock.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTClock.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN /** This class manages the device clock and produces snapshots of the current time. */ -@interface GDLClock : NSObject +@interface GDTClock : NSObject /** The wallclock time, UTC, in milliseconds. */ @property(nonatomic, readonly) int64_t timeMillis; @@ -27,13 +27,13 @@ NS_ASSUME_NONNULL_BEGIN /** The offset from UTC in seconds. */ @property(nonatomic, readonly) int64_t timezoneOffsetSeconds; -/** Creates a GDLClock object using the current time and offsets. +/** Creates a GDTClock object using the current time and offsets. * - * @return A new GDLClock object representing the current time state. + * @return A new GDTClock object representing the current time state. */ + (instancetype)snapshot; -/** Creates a GDLClock object representing a time in the future, relative to now. +/** Creates a GDTClock object representing a time in the future, relative to now. * * @param millisInTheFuture The millis in the future from now this clock should represent. * @return An instance representing a future time. @@ -44,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return YES if the calling clock's time is after the given clock's time. */ -- (BOOL)isAfter:(GDLClock *)otherClock; +- (BOOL)isAfter:(GDTClock *)otherClock; @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEvent.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEvent.h new file mode 100644 index 00000000000..13edfe10705 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEvent.h @@ -0,0 +1,78 @@ +/* + * Copyright 2018 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 + +#import "GDTEventDataObject.h" + +NS_ASSUME_NONNULL_BEGIN + +/** The different possible quality of service specifiers. High values indicate high priority. */ +typedef NS_ENUM(NSInteger, GDTEventQoS) { + /** The QoS tier wasn't set, and won't ever be sent. */ + GDTEventQoSUnknown = 0, + + /** This event is internal telemetry data that should not be sent on its own if possible. */ + GDTEventQoSTelemetry = 1, + + /** This event should be sent, but in a batch only roughly once per day. */ + GDTEventQoSDaily = 2, + + /** This event should be sent when requested by the uploader. */ + GDTEventQosDefault = 3, + + /** This event should be sent immediately along with any other data that can be batched. */ + GDTEventQoSFast = 4, + + /** This event should only be uploaded on wifi. */ + GDTEventQoSWifiOnly = 5, +}; + +@interface GDTEvent : NSObject + +/** The mapping identifier, to allow backends to map the transport bytes to a proto. */ +@property(readonly, nonatomic) NSString *mappingID; + +/** The identifier for the backend this event will eventually be sent to. */ +@property(readonly, nonatomic) NSInteger target; + +/** The data object encapsulated in the transport of your choice, as long as it implements + * the GDTEventDataObject protocol. */ +@property(nullable, nonatomic) id dataObject; + +/** The quality of service tier this event belongs to. */ +@property(nonatomic) GDTEventQoS qosTier; + +/** A dictionary provided to aid prioritizers by allowing the passing of arbitrary data. It will be + * retained by a copy in -copy, but not used for -hash. + */ +@property(nullable, nonatomic) NSDictionary *customPrioritizationParams; + +// Please use the designated initializer. +- (instancetype)init NS_UNAVAILABLE; + +/** Initializes an instance using the given mappingID. + * + * @param mappingID The mapping identifier. + * @param target The event's target identifier. + * @return An instance of this class. + */ +- (instancetype)initWithMappingID:(NSString *)mappingID + target:(NSInteger)target NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogProto.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEventDataObject.h similarity index 73% rename from GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogProto.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEventDataObject.h index e10d8737256..2cb0ccc649e 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogProto.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEventDataObject.h @@ -18,14 +18,14 @@ NS_ASSUME_NONNULL_BEGIN -/** This protocol defines the common interface that log protos should implement regardless of the +/** This protocol defines the common interface that event protos should implement regardless of the * underlying transport technology (protobuf, nanopb, etc). */ -@protocol GDLLogProto +@protocol GDTEventDataObject -/** Returns the serialized proto bytes of the implementing log proto. +/** Returns the serialized proto bytes of the implementing event proto. * - * @return the serialized proto bytes of the implementing log proto. + * @return the serialized proto bytes of the implementing event proto. */ - (NSData *)transportBytes; diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogTransformer.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEventTransformer.h similarity index 61% rename from GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogTransformer.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEventTransformer.h index d426e07895d..d7ba9341abd 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogTransformer.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEventTransformer.h @@ -16,22 +16,22 @@ #import -@class GDLLogEvent; +@class GDTEvent; NS_ASSUME_NONNULL_BEGIN -/** Defines the API that log transformers must adopt. */ -@protocol GDLLogTransformer +/** Defines the API that event transformers must adopt. */ +@protocol GDTEventTransformer @required -/** Transforms a log by applying some logic to it. Logs returned can be nil, for example, in - * instances where the log should be sampled. +/** Transforms an event by applying some logic to it. Events returned can be nil, for example, in + * instances where the event should be sampled. * - * @param logEvent The log event to transform. - * @return A transformed log event, or nil if the transformation removed the log event. + * @param event The event to transform. + * @return A transformed event, or nil if the transformation dropped the event. */ -- (GDLLogEvent *)transform:(GDLLogEvent *)logEvent; +- (GDTEvent *)transform:(GDTEvent *)event; @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h new file mode 100644 index 00000000000..0fb001f6fb7 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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 + +@class GDTEvent; + +NS_ASSUME_NONNULL_BEGIN + +/** Options that define a set of upload conditions. This is used to help minimize end user data + * consumption impact. + */ +typedef NS_OPTIONS(NSInteger, GDTUploadConditions) { + + /** An upload would likely use mobile data. */ + GDTUploadConditionMobileData, + + /** An upload would likely use wifi data. */ + GDTUploadConditionWifiData, +}; + +/** This protocol defines the common interface of event prioritization. Prioritizers are + * stateful objects that prioritize events upon insertion into storage and remain prepared to return + * a set of filenames to the storage system. + */ +@protocol GDTPrioritizer + +@required + +/** Accepts an event and uses the event metadata to make choices on how to prioritize the event. + * This method exists as a way to help prioritize which events should be sent, which is dependent on + * the request proto structure of your backend. + * + * @note A couple of things: 1. The event cannot be retained for longer than the execution time of + * this method. 2. You should retain the event hashes, because those are returned in + * -eventsForNextUpload. + * + * @param event The event to prioritize. + */ +- (void)prioritizeEvent:(GDTEvent *)event; + +/** Unprioritizes an event. This method is called when an event has been removed from storage and + * should no longer be given to an uploader. + */ +- (void)unprioritizeEvent:(NSNumber *)eventHash; + +/** Returns a set of events to upload given a set of conditions. + * + * @param conditions A bit mask specifying the current upload conditions. + * @return A set of events to upload with respect to the current conditions. + */ +- (NSSet *)eventsToUploadGivenConditions:(GDTUploadConditions)conditions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLRegistrar.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTRegistrar.h similarity index 55% rename from GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLRegistrar.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTRegistrar.h index 171bbf42151..f1ee1411f43 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLRegistrar.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTRegistrar.h @@ -16,14 +16,14 @@ #import -#import -#import -#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN -/** Manages the registration of log targets with the logging SDK. */ -@interface GDLRegistrar : NSObject +/** Manages the registration of targets with the transport SDK. */ +@interface GDTRegistrar : NSObject /** Creates and/or returns the singleton instance. * @@ -31,19 +31,19 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedInstance; -/** Registers a backend implementation with the GoogleDataLogger infrastructure. +/** Registers a backend implementation with the GoogleDataTransport infrastructure. * * @param backend The backend object to register. - * @param logTarget The logTarget this backend object will be responsible for. + * @param target The target this backend object will be responsible for. */ -- (void)registerUploader:(id)backend logTarget:(GDLLogTarget)logTarget; +- (void)registerUploader:(id)backend target:(GDTTarget)target; -/** Registers a log prioritizer implementation with the GoogleDataLogger infrastructure. +/** Registers a event prioritizer implementation with the GoogleDataTransport infrastructure. * * @param prioritizer The prioritizer object to register. - * @param logTarget The logTarget this prioritizer object will be responsible for. + * @param target The target this prioritizer object will be responsible for. */ -- (void)registerPrioritizer:(id)prioritizer logTarget:(GDLLogTarget)logTarget; +- (void)registerPrioritizer:(id)prioritizer target:(GDTTarget)target; @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogTargets.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTTargets.h similarity index 70% rename from GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogTargets.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTTargets.h index fb235cccfb5..1fae0edf13c 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogTargets.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTTargets.h @@ -16,11 +16,11 @@ #import -/** The list of targets supported by the shared logging infrastructure. If adding a new target, - * please use the previous value +1, and increment GDLLogTargetLast by 1. +/** The list of targets supported by the shared transport infrastructure. If adding a new target, + * please use the previous value +1. */ -typedef NS_ENUM(NSInteger, GDLLogTarget) { +typedef NS_ENUM(NSInteger, GDTTarget) { - /** The CCT log target. */ - kGDLLogTargetCCT = 1000, + /** The CCT target. */ + kGDTTargetCCT = 1000, }; diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTTransport.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTTransport.h new file mode 100644 index 00000000000..68759ceef62 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTTransport.h @@ -0,0 +1,68 @@ +/* + * Copyright 2018 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 + +#import "GDTEventTransformer.h" + +@class GDTEvent; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTTransport : NSObject + +// Please use the designated initializer. +- (instancetype)init NS_UNAVAILABLE; + +/** Initializes a new transport that will send events to the given target backend. + * + * @param mappingID The mapping identifier used by the backend to map the data object transport + * bytes to a proto. + * @param transformers A list of transformers to be applied to events that are sent. + * @param target The target backend of this transport. + * @return A transport that will send events. + */ +- (instancetype)initWithMappingID:(NSString *)mappingID + transformers:(nullable NSArray> *)transformers + target:(NSInteger)target NS_DESIGNATED_INITIALIZER; + +/** Copies and sends an internal telemetry event. Events sent using this API are lower in priority, + * and sometimes won't be sent on their own. + * + * @note This will convert the event's data object to data and release the original event. + * + * @param event The event to send. + */ +- (void)sendTelemetryEvent:(GDTEvent *)event; + +/** Copies and sends an SDK service data event. Events send using this API are higher in priority, + * and will cause a network request at some point in the relative near future. + * + * @note This will convert the event's data object to data and release the original event. + * + * @param event The event to send. + */ +- (void)sendDataEvent:(GDTEvent *)event; + +/** Creates an event for use by this transport. + * + * @return An event that is suited for use by this transport. + */ +- (GDTEvent *)eventForTransport; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogUploader.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTUploader.h similarity index 68% rename from GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogUploader.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTUploader.h index 0d41b1a9e6f..a7f5216d7b8 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GDLLogUploader.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTUploader.h @@ -16,35 +16,35 @@ #import -#import -#import +#import +#import NS_ASSUME_NONNULL_BEGIN /** A convenient typedef to define the block to be called upon completion of an upload to the * backend. * - * target: The log target that was uploading. + * target: The target that was uploading. * nextUploadAttemptUTC: The desired next upload attempt time. * uploadError: Populated with any upload error. If non-nil, a retry will be attempted. */ -typedef void (^GDLUploaderCompletionBlock)(GDLLogTarget target, - GDLClock *nextUploadAttemptUTC, +typedef void (^GDTUploaderCompletionBlock)(GDTTarget target, + GDTClock *nextUploadAttemptUTC, NSError *_Nullable uploadError); -/** This protocol defines the common interface for logging backend implementations. */ -@protocol GDLLogUploader +/** This protocol defines the common interface for uploader implementations. */ +@protocol GDTUploader @required -/** Uploads logs to the backend using this specific backend's chosen format. +/** Uploads events to the backend using this specific backend's chosen format. * - * @param logFiles The set of log files to upload. + * @param eventFiles The set of event files to upload. * @param onComplete A block to invoke upon completing the upload. Has two arguments: * - successfulUploads: The set of filenames uploaded successfully. * - unsuccessfulUploads: The set of filenames not uploaded successfully. */ -- (void)uploadLogs:(NSSet *)logFiles onComplete:(GDLUploaderCompletionBlock)onComplete; +- (void)uploadEvents:(NSSet *)eventFiles onComplete:(GDTUploaderCompletionBlock)onComplete; @end diff --git a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GoogleDataLogger.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GoogleDataTransport.h similarity index 74% rename from GoogleDataLogger/GoogleDataLogger/Classes/Public/GoogleDataLogger.h rename to GoogleDataTransport/GoogleDataTransport/Classes/Public/GoogleDataTransport.h index 7a38d1b4880..f122e2eb47f 100644 --- a/GoogleDataLogger/GoogleDataLogger/Classes/Public/GoogleDataLogger.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GoogleDataTransport.h @@ -14,11 +14,11 @@ * limitations under the License. */ -#import "GDLClock.h" -#import "GDLLogEvent.h" -#import "GDLLogPrioritizer.h" -#import "GDLLogProto.h" -#import "GDLLogTransformer.h" -#import "GDLLogUploader.h" -#import "GDLLogger.h" -#import "GDLRegistrar.h" +#import "GDTClock.h" +#import "GDTEvent.h" +#import "GDTEventDataObject.h" +#import "GDTEventTransformer.h" +#import "GDTPrioritizer.h" +#import "GDTRegistrar.h" +#import "GDTTransport.h" +#import "GDTUploader.h" diff --git a/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h b/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.h similarity index 61% rename from GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h rename to GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.h index 5f5260fafb9..4db5c23eace 100644 --- a/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h +++ b/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.h @@ -16,10 +16,10 @@ #import "GULLogger.h" -#import "GDLAssert.h" +#import "GDTAssert.h" /** The console logger prefix. */ -static GULLoggerService kGDLConsoleLogger = @"[GoogleDataLogger]"; +static GULLoggerService kGDTConsoleLogger = @"[GoogleDataTransport]"; /** A list of message codes to print in the logger that help to correspond printed messages with * code locations. @@ -28,45 +28,45 @@ static GULLoggerService kGDLConsoleLogger = @"[GoogleDataLogger]"; * - MCW => MessageCodeWarning * - MCE => MessageCodeError */ -typedef NS_ENUM(NSInteger, GDLMessageCode) { +typedef NS_ENUM(NSInteger, GDTMessageCode) { - /** For warning messages concerning transportBytes: not being implemented by a log extension. */ - GDLMCWExtensionMissingBytesImpl = 1, + /** For warning messages concerning transportBytes: not being implemented by a data object. */ + GDTMCWDataObjectMissingBytesImpl = 1, - /** For warning message concerning a failed log upload. */ - GDLMCWUploadFailed = 2, + /** For warning message concerning a failed event upload. */ + GDTMCWUploadFailed = 2, - /** For error messages concerning transform: not being implemented by a log transformer. */ - GDLMCETransformerDoesntImplementTransform = 1000, + /** For error messages concerning transform: not being implemented by an event transformer. */ + GDTMCETransformerDoesntImplementTransform = 1000, - /** For error messages concerning a GDLLogEvent living past the storeLog: invocation. */ - GDLMCELogEventWasIllegallyRetained = 1001, + /** For error messages concerning a GDTEvent living past the storeLog: invocation. */ + GDTMCEEventWasIllegallyRetained = 1001, /** For error messages concerning the creation of a directory failing. */ - GDLMCEDirectoryCreationError = 1002, + GDTMCEDirectoryCreationError = 1002, - /** For error messages concerning the writing of a log file. */ - GDLMCEFileWriteError = 1003 + /** For error messages concerning the writing of a event file. */ + GDTMCEFileWriteError = 1003 }; /** */ -FOUNDATION_EXTERN NSString *_Nonnull GDLMessageCodeEnumToString(GDLMessageCode code); +FOUNDATION_EXTERN NSString *_Nonnull GDTMessageCodeEnumToString(GDTMessageCode code); /** Logs the warningMessage string to the console at the warning level. * * @param warningMessageFormat The format string to log to the console. */ -FOUNDATION_EXTERN void GDLLogWarning(GDLMessageCode messageCode, +FOUNDATION_EXTERN void GDTLogWarning(GDTMessageCode messageCode, NSString *_Nonnull warningMessageFormat, ...) NS_FORMAT_FUNCTION(2, 3); // A define to wrap GULLogWarning with slightly more convenient usage. -#define GDLLogWarning(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ - GULLogWarning(kGDLConsoleLogger, YES, GDLMessageCodeEnumToString(MESSAGE_CODE), MESSAGE_FORMAT, \ +#define GDTLogWarning(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ + GULLogWarning(kGDTConsoleLogger, YES, GDTMessageCodeEnumToString(MESSAGE_CODE), MESSAGE_FORMAT, \ __VA_ARGS__); // A define to wrap GULLogError with slightly more convenient usage and a failing assert. -#define GDLLogError(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ - GULLogError(kGDLConsoleLogger, YES, GDLMessageCodeEnumToString(MESSAGE_CODE), MESSAGE_FORMAT, \ +#define GDTLogError(MESSAGE_CODE, MESSAGE_FORMAT, ...) \ + GULLogError(kGDTConsoleLogger, YES, GDTMessageCodeEnumToString(MESSAGE_CODE), MESSAGE_FORMAT, \ __VA_ARGS__); \ - GDLAssert(NO, MESSAGE_FORMAT, __VA_ARGS__); + GDTAssert(NO, MESSAGE_FORMAT, __VA_ARGS__); diff --git a/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.m b/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.m similarity index 79% rename from GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.m rename to GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.m index a12676e5a3b..4fc91484ead 100644 --- a/GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.m +++ b/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.m @@ -14,8 +14,8 @@ * limitations under the License. */ -#import "GDLConsoleLogger.h" +#import "GDTConsoleLogger.h" -NSString* GDLMessageCodeEnumToString(GDLMessageCode code) { - return [[NSString alloc] initWithFormat:@"I-GDL%06ld", (long)code]; +NSString* GDTMessageCodeEnumToString(GDTMessageCode code) { + return [[NSString alloc] initWithFormat:@"I-GDT%06ld", (long)code]; } diff --git a/GoogleDataLogger/README.md b/GoogleDataTransport/README.md similarity index 90% rename from GoogleDataLogger/README.md rename to GoogleDataTransport/README.md index c5aaeeeb960..8916f3d8d46 100644 --- a/GoogleDataLogger/README.md +++ b/GoogleDataTransport/README.md @@ -1,4 +1,4 @@ -# Google Data Logger Lib +# Google Data Transport Lib This library is for internal Google use only. It allows the logging of data and telemetry from Google SDKs. diff --git a/GoogleDataLogger/Tests/Common/Categories/GDLRegistrar+Testing.h b/GoogleDataTransport/Tests/Common/Categories/GDTRegistrar+Testing.h similarity index 84% rename from GoogleDataLogger/Tests/Common/Categories/GDLRegistrar+Testing.h rename to GoogleDataTransport/Tests/Common/Categories/GDTRegistrar+Testing.h index e242d144339..0a05b22ebc1 100644 --- a/GoogleDataLogger/Tests/Common/Categories/GDLRegistrar+Testing.h +++ b/GoogleDataTransport/Tests/Common/Categories/GDTRegistrar+Testing.h @@ -14,13 +14,13 @@ * limitations under the License. */ -#import "GDLRegistrar.h" -#import "GDLRegistrar_Private.h" +#import "GDTRegistrar.h" +#import "GDTRegistrar_Private.h" NS_ASSUME_NONNULL_BEGIN -/** Testing-only methods for GDLRegistrar. */ -@interface GDLRegistrar (Testing) +/** Testing-only methods for GDTRegistrar. */ +@interface GDTRegistrar (Testing) /** Resets the properties of the singleon, but does not reallocate a new singleton. */ - (void)reset; diff --git a/GoogleDataLogger/Tests/Common/Categories/GDLRegistrar+Testing.m b/GoogleDataTransport/Tests/Common/Categories/GDTRegistrar+Testing.m similarity index 76% rename from GoogleDataLogger/Tests/Common/Categories/GDLRegistrar+Testing.m rename to GoogleDataTransport/Tests/Common/Categories/GDTRegistrar+Testing.m index ce64c79c4fe..c84e0e74298 100644 --- a/GoogleDataLogger/Tests/Common/Categories/GDLRegistrar+Testing.m +++ b/GoogleDataTransport/Tests/Common/Categories/GDTRegistrar+Testing.m @@ -14,16 +14,16 @@ * limitations under the License. */ -#import "GDLRegistrar+Testing.h" +#import "GDTRegistrar+Testing.h" -#import "GDLRegistrar_Private.h" +#import "GDTRegistrar_Private.h" -@implementation GDLRegistrar (Testing) +@implementation GDTRegistrar (Testing) - (void)reset { dispatch_sync(self.registrarQueue, ^{ - [self.logTargetToPrioritizer removeAllObjects]; - [self.logTargetToUploader removeAllObjects]; + [self.targetToPrioritizer removeAllObjects]; + [self.targetToUploader removeAllObjects]; }); } diff --git a/GoogleDataLogger/Tests/Common/Categories/GDLLogStorage+Testing.h b/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.h similarity index 85% rename from GoogleDataLogger/Tests/Common/Categories/GDLLogStorage+Testing.h rename to GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.h index e0f3486b079..5fa7423e371 100644 --- a/GoogleDataLogger/Tests/Common/Categories/GDLLogStorage+Testing.h +++ b/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.h @@ -16,13 +16,13 @@ #import -#import "GDLLogStorage.h" -#import "GDLLogStorage_Private.h" +#import "GDTStorage.h" +#import "GDTStorage_Private.h" NS_ASSUME_NONNULL_BEGIN -/** Testing-only methods for GDLLogStorage. */ -@interface GDLLogStorage (Testing) +/** Testing-only methods for GDTStorage. */ +@interface GDTStorage (Testing) /** Resets the properties of the singleon, but does not reallocate a new singleton. This also * doesn't remove stored files from disk. diff --git a/GoogleDataLogger/Tests/Common/Categories/GDLLogStorage+Testing.m b/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.m similarity index 79% rename from GoogleDataLogger/Tests/Common/Categories/GDLLogStorage+Testing.m rename to GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.m index 22bec982c07..95f884cfda2 100644 --- a/GoogleDataLogger/Tests/Common/Categories/GDLLogStorage+Testing.m +++ b/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.m @@ -14,14 +14,14 @@ * limitations under the License. */ -#import "GDLLogStorage+Testing.h" +#import "GDTStorage+Testing.h" -@implementation GDLLogStorage (Testing) +@implementation GDTStorage (Testing) - (void)reset { dispatch_sync(self.storageQueue, ^{ - [self.logTargetToLogHashSet removeAllObjects]; - [self.logHashToLogFile removeAllObjects]; + [self.targetToEventHashSet removeAllObjects]; + [self.eventHashToFile removeAllObjects]; }); } diff --git a/GoogleDataLogger/Tests/Common/Categories/GDLUploadCoordinator+Testing.h b/GoogleDataTransport/Tests/Common/Categories/GDTUploadCoordinator+Testing.h similarity index 93% rename from GoogleDataLogger/Tests/Common/Categories/GDLUploadCoordinator+Testing.h rename to GoogleDataTransport/Tests/Common/Categories/GDTUploadCoordinator+Testing.h index 9a7e0fdde4a..7bd314bfcb0 100644 --- a/GoogleDataLogger/Tests/Common/Categories/GDLUploadCoordinator+Testing.h +++ b/GoogleDataTransport/Tests/Common/Categories/GDTUploadCoordinator+Testing.h @@ -14,11 +14,11 @@ * limitations under the License. */ -#import "GDLUploadCoordinator.h" +#import "GDTUploadCoordinator.h" NS_ASSUME_NONNULL_BEGIN -@interface GDLUploadCoordinator (Testing) +@interface GDTUploadCoordinator (Testing) /** Resets the properties of the singleton, but does not reallocate a new singleton. */ - (void)reset; diff --git a/GoogleDataLogger/Tests/Common/Categories/GDLUploadCoordinator+Testing.m b/GoogleDataTransport/Tests/Common/Categories/GDTUploadCoordinator+Testing.m similarity index 74% rename from GoogleDataLogger/Tests/Common/Categories/GDLUploadCoordinator+Testing.m rename to GoogleDataTransport/Tests/Common/Categories/GDTUploadCoordinator+Testing.m index 71d06d618be..b1c4c3238be 100644 --- a/GoogleDataLogger/Tests/Common/Categories/GDLUploadCoordinator+Testing.m +++ b/GoogleDataTransport/Tests/Common/Categories/GDTUploadCoordinator+Testing.m @@ -14,22 +14,22 @@ * limitations under the License. */ -#import "GDLUploadCoordinator+Testing.h" +#import "GDTUploadCoordinator+Testing.h" #import -#import "GDLLogStorage.h" -#import "GDLRegistrar.h" -#import "GDLUploadCoordinator_Private.h" +#import "GDTRegistrar.h" +#import "GDTStorage.h" +#import "GDTUploadCoordinator_Private.h" -@implementation GDLUploadCoordinator (Testing) +@implementation GDTUploadCoordinator (Testing) - (void)reset { - [self.logTargetToNextUploadTimes removeAllObjects]; - [self.logTargetToInFlightLogSet removeAllObjects]; + [self.targetToNextUploadTimes removeAllObjects]; + [self.targetToInFlightEventSet removeAllObjects]; [self.forcedUploadQueue removeAllObjects]; - self.logStorage = [GDLLogStorage sharedInstance]; - self.registrar = [GDLRegistrar sharedInstance]; + self.storage = [GDTStorage sharedInstance]; + self.registrar = [GDTRegistrar sharedInstance]; } - (void)stopTimer { diff --git a/GoogleDataLogger/Tests/Common/Fakes/GDLLogStorageFake.h b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.h similarity index 79% rename from GoogleDataLogger/Tests/Common/Fakes/GDLLogStorageFake.h rename to GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.h index 03136e6bcea..0e1b5fe384b 100644 --- a/GoogleDataLogger/Tests/Common/Fakes/GDLLogStorageFake.h +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.h @@ -14,15 +14,15 @@ * limitations under the License. */ -#import "GDLLogStorage.h" +#import "GDTStorage.h" NS_ASSUME_NONNULL_BEGIN /** A functionless fake that can be injected into classes that need it. */ -@interface GDLLogStorageFake : GDLLogStorage +@interface GDTStorageFake : GDTStorage -/** The logs to return from -logHashesToFiles. */ -@property(nonatomic) NSSet *logsToReturnFromLogHashesToFiles; +/** The events to return from -eventHashesToFiles. */ +@property(nonatomic) NSSet *eventsToReturnFromEventHashesToFiles; @end diff --git a/GoogleDataLogger/Tests/Common/Fakes/GDLLogStorageFake.m b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.m similarity index 60% rename from GoogleDataLogger/Tests/Common/Fakes/GDLLogStorageFake.m rename to GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.m index dfc1baca5fd..2dd454b0409 100644 --- a/GoogleDataLogger/Tests/Common/Fakes/GDLLogStorageFake.m +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.m @@ -14,25 +14,22 @@ * limitations under the License. */ -#import "GDLLogStorageFake.h" +#import "GDTStorageFake.h" -@implementation GDLLogStorageFake +@implementation GDTStorageFake -- (void)storeLog:(GDLLogEvent *)log { +- (void)storeEvent:(GDTEvent *)event { } -- (NSSet *)logHashesToFiles:(NSSet *)logHashes { - if (_logsToReturnFromLogHashesToFiles) { - return _logsToReturnFromLogHashesToFiles; +- (NSSet *)eventHashesToFiles:(NSSet *)eventHashes { + if (_eventsToReturnFromEventHashesToFiles) { + return _eventsToReturnFromEventHashesToFiles; } else { return [[NSSet alloc] init]; } } -- (void)removeLog:(NSNumber *)logHash logTarget:(NSNumber *)logTarget { -} - -- (void)removeLogs:(NSSet *)logHashes logTarget:(NSNumber *)logTarget { +- (void)removeEvents:(NSSet *)eventHashes target:(NSNumber *)target { } @end diff --git a/GoogleDataLogger/Tests/Common/Fakes/GDLLogWriterFake.h b/GoogleDataTransport/Tests/Common/Fakes/GDTTransformerFake.h similarity index 90% rename from GoogleDataLogger/Tests/Common/Fakes/GDLLogWriterFake.h rename to GoogleDataTransport/Tests/Common/Fakes/GDTTransformerFake.h index e7ce6ec0bf5..13d376fe2a7 100644 --- a/GoogleDataLogger/Tests/Common/Fakes/GDLLogWriterFake.h +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTTransformerFake.h @@ -14,12 +14,12 @@ * limitations under the License. */ -#import "GDLLogWriter.h" +#import "GDTTransformer.h" NS_ASSUME_NONNULL_BEGIN /** A functionless fake that can be injected into classes that need it. */ -@interface GDLLogWriterFake : GDLLogWriter +@interface GDTTransformerFake : GDTTransformer @end diff --git a/GoogleDataLogger/Tests/Common/Fakes/GDLLogWriterFake.m b/GoogleDataTransport/Tests/Common/Fakes/GDTTransformerFake.m similarity index 76% rename from GoogleDataLogger/Tests/Common/Fakes/GDLLogWriterFake.m rename to GoogleDataTransport/Tests/Common/Fakes/GDTTransformerFake.m index d30236c44d4..de9b3f700f2 100644 --- a/GoogleDataLogger/Tests/Common/Fakes/GDLLogWriterFake.m +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTTransformerFake.m @@ -14,12 +14,12 @@ * limitations under the License. */ -#import "GDLLogWriterFake.h" +#import "GDTTransformerFake.h" -@implementation GDLLogWriterFake +@implementation GDTTransformerFake -- (void)writeLog:(GDLLogEvent *)log - afterApplyingTransformers:(NSArray> *)logTransformers { +- (void)transformEvent:(GDTEvent *)event + withTransformers:(NSArray> *)transformers { } @end diff --git a/GoogleDataLogger/Tests/Common/Fakes/GDLUploadCoordinatorFake.h b/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.h similarity index 88% rename from GoogleDataLogger/Tests/Common/Fakes/GDLUploadCoordinatorFake.h rename to GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.h index 666517b097a..2d18c69b5e4 100644 --- a/GoogleDataLogger/Tests/Common/Fakes/GDLUploadCoordinatorFake.h +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.h @@ -14,11 +14,11 @@ * limitations under the License. */ -#import "GDLUploadCoordinator.h" +#import "GDTUploadCoordinator.h" NS_ASSUME_NONNULL_BEGIN -@interface GDLUploadCoordinatorFake : GDLUploadCoordinator +@interface GDTUploadCoordinatorFake : GDTUploadCoordinator @property(nonatomic) BOOL forceUploadCalled; diff --git a/GoogleDataLogger/Tests/Common/Fakes/GDLUploadCoordinatorFake.m b/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.m similarity index 79% rename from GoogleDataLogger/Tests/Common/Fakes/GDLUploadCoordinatorFake.m rename to GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.m index 1b70f26aed9..2e10ccaa3ce 100644 --- a/GoogleDataLogger/Tests/Common/Fakes/GDLUploadCoordinatorFake.m +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.m @@ -14,11 +14,11 @@ * limitations under the License. */ -#import "GDLUploadCoordinatorFake.h" +#import "GDTUploadCoordinatorFake.h" -@implementation GDLUploadCoordinatorFake +@implementation GDTUploadCoordinatorFake -- (void)forceUploadLogs:(NSSet *)logFiles target:(GDLLogTarget)logTarget { +- (void)forceUploadEvents:(NSSet *)eventFiles target:(GDTTarget)target { self.forceUploadCalled = YES; } diff --git a/GoogleDataTransport/Tests/Integration/GDTIntegrationTest.m b/GoogleDataTransport/Tests/Integration/GDTIntegrationTest.m new file mode 100644 index 00000000000..e8451401d89 --- /dev/null +++ b/GoogleDataTransport/Tests/Integration/GDTIntegrationTest.m @@ -0,0 +1,173 @@ +/* + * Copyright 2019 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 + +#import + +#import "GDTIntegrationTestPrioritizer.h" +#import "GDTIntegrationTestUploader.h" +#import "GDTTestServer.h" + +#import "GDTStorage_Private.h" +#import "GDTUploadCoordinator+Testing.h" + +/** A test-only event data object used in this integration test. */ +@interface GDTIntegrationTestEvent : NSObject + +@end + +@implementation GDTIntegrationTestEvent + +- (NSData *)transportBytes { + // In real usage, protobuf's -data method or a custom implementation using nanopb are used. + return [[NSString stringWithFormat:@"%@", [NSDate date]] dataUsingEncoding:NSUTF8StringEncoding]; +} + +@end + +/** A test-only event transformer. */ +@interface GDTIntegrationTestTransformer : NSObject + +@end + +@implementation GDTIntegrationTestTransformer + +- (GDTEvent *)transform:(GDTEvent *)event { + // drop half the events during transforming. + if (arc4random_uniform(2) == 1) { + event = nil; + } + return event; +} + +@end + +@interface GDTIntegrationTest : XCTestCase + +/** A test prioritizer. */ +@property(nonatomic) GDTIntegrationTestPrioritizer *prioritizer; + +/** A test uploader. */ +@property(nonatomic) GDTIntegrationTestUploader *uploader; + +/** The first test transport. */ +@property(nonatomic) GDTTransport *transport1; + +/** The second test transport. */ +@property(nonatomic) GDTTransport *transport2; + +@end + +@implementation GDTIntegrationTest + +- (void)tearDown { + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 0); + }); +} + +- (void)testEndToEndEvent { + XCTestExpectation *expectation = [self expectationWithDescription:@"server got the request"]; + expectation.assertForOverFulfill = NO; + + // Create the server. + GDTTestServer *testServer = [[GDTTestServer alloc] init]; + [testServer setResponseCompletedBlock:^(GCDWebServerRequest *_Nonnull request, + GCDWebServerResponse *_Nonnull response) { + [expectation fulfill]; + }]; + [testServer registerTestPaths]; + [testServer start]; + + // Create eventgers. + self.transport1 = [[GDTTransport alloc] initWithMappingID:@"eventMap1" + transformers:nil + target:kGDTIntegrationTestTarget]; + + self.transport2 = [[GDTTransport alloc] initWithMappingID:@"eventMap2" + transformers:nil + target:kGDTIntegrationTestTarget]; + + // Create a prioritizer and uploader. + self.prioritizer = [[GDTIntegrationTestPrioritizer alloc] init]; + self.uploader = [[GDTIntegrationTestUploader alloc] initWithServerURL:testServer.serverURL]; + + // Set the interval to be much shorter than the standard timer. + [GDTUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC * 0.1; + [GDTUploadCoordinator sharedInstance].timerLeeway = NSEC_PER_SEC * 0.01; + + // Confirm no events are in disk. + XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 0); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet.count, 0); + + // Generate some events data. + [self generateEvents]; + + // Confirm events are on disk. + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertGreaterThan([GDTStorage sharedInstance].eventHashToFile.count, 0); + XCTAssertGreaterThan([GDTStorage sharedInstance].targetToEventHashSet.count, 0); + }); + + // Confirm events were sent and received. + [self waitForExpectations:@[ expectation ] timeout:10.0]; + + // Generate events for a bit. + NSUInteger lengthOfTestToRunInSeconds = 30; + [GDTUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC * 5; + [GDTUploadCoordinator sharedInstance].timerLeeway = NSEC_PER_SEC * 1; + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC); + dispatch_source_set_event_handler(timer, ^{ + static int numberOfTimesCalled = 0; + numberOfTimesCalled++; + if (numberOfTimesCalled < lengthOfTestToRunInSeconds) { + [self generateEvents]; + } else { + dispatch_source_cancel(timer); + } + }); + dispatch_resume(timer); + + // Run for a bit, a couple seconds longer than the previous bit. + [[NSRunLoop currentRunLoop] + runUntilDate:[NSDate dateWithTimeIntervalSinceNow:lengthOfTestToRunInSeconds + 2]]; + + [testServer stop]; +} + +/** Generates and events a bunch of random events. */ +- (void)generateEvents { + for (int i = 0; i < 50; i++) { + // Choose a random eventger, and randomly choose if it's a telemetry event. + GDTTransport *transport = arc4random_uniform(2) ? self.transport1 : self.transport2; + BOOL isTelemetryEvent = arc4random_uniform(2); + + // Create an event. + GDTEvent *event = [transport eventForTransport]; + event.dataObject = [[GDTIntegrationTestEvent alloc] init]; + + if (isTelemetryEvent) { + [transport sendTelemetryEvent:event]; + } else { + [transport sendDataEvent:event]; + } + } +} + +@end diff --git a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestPrioritizer.h b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.h similarity index 72% rename from GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestPrioritizer.h rename to GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.h index 30489f12b9b..ab763c3c64f 100644 --- a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestPrioritizer.h +++ b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.h @@ -16,12 +16,12 @@ #import -#import +#import -/** The integration test log target. Normally, you should use a value in GDLLogTargets.h. */ -static GDLLogTarget kGDLIntegrationTestTarget = 100; +/** The integration test target. Normally, you should use a value in GDTTargets.h. */ +static GDTTarget kGDTIntegrationTestTarget = 100; /** An integration test prioritization class. */ -@interface GDLIntegrationTestPrioritizer : NSObject +@interface GDTIntegrationTestPrioritizer : NSObject @end diff --git a/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.m b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.m new file mode 100644 index 00000000000..07a937cccb1 --- /dev/null +++ b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.m @@ -0,0 +1,75 @@ +/* + * Copyright 2019 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 "GDTIntegrationTestPrioritizer.h" + +@interface GDTIntegrationTestPrioritizer () + +/** Events that are only supposed to be uploaded whilst on wifi. */ +@property(nonatomic) NSMutableSet *wifiOnlyEvents; + +/** Events that can be uploaded on any type of connection. */ +@property(nonatomic) NSMutableSet *nonWifiEvents; + +/** The queue on which this prioritizer operates. */ +@property(nonatomic) dispatch_queue_t queue; + +@end + +@implementation GDTIntegrationTestPrioritizer + +- (instancetype)init { + self = [super init]; + if (self) { + _queue = + dispatch_queue_create("com.google.GDTIntegrationTestPrioritizer", DISPATCH_QUEUE_SERIAL); + _wifiOnlyEvents = [[NSMutableSet alloc] init]; + _nonWifiEvents = [[NSMutableSet alloc] init]; + [[GDTRegistrar sharedInstance] registerPrioritizer:self target:kGDTIntegrationTestTarget]; + } + return self; +} + +- (void)prioritizeEvent:(GDTEvent *)event { + dispatch_sync(_queue, ^{ + if (event.qosTier == GDTEventQoSWifiOnly) { + [self.wifiOnlyEvents addObject:@(event.hash)]; + } else { + [self.nonWifiEvents addObject:@(event.hash)]; + } + }); +} + +- (void)unprioritizeEvent:(NSNumber *)eventHash { + dispatch_sync(_queue, ^{ + [self.wifiOnlyEvents removeObject:eventHash]; + [self.nonWifiEvents removeObject:eventHash]; + }); +} + +- (nonnull NSSet *)eventsToUploadGivenConditions:(GDTUploadConditions)conditions { + __block NSSet *events; + dispatch_sync(_queue, ^{ + if ((conditions & GDTUploadConditionWifiData) == GDTUploadConditionWifiData) { + events = self.wifiOnlyEvents; + } else { + events = self.nonWifiEvents; + } + }); + return events; +} + +@end diff --git a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestUploader.h b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.h similarity index 85% rename from GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestUploader.h rename to GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.h index 6b42b22cd5b..70d72307f9e 100644 --- a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestUploader.h +++ b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.h @@ -16,12 +16,12 @@ #import -#import +#import -#import "GDLIntegrationTestPrioritizer.h" +#import "GDTIntegrationTestPrioritizer.h" /** An integration test uploader. */ -@interface GDLIntegrationTestUploader : NSObject +@interface GDTIntegrationTestUploader : NSObject /** Instantiates an instance of this uploader with the given server URL. * diff --git a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestUploader.m b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.m similarity index 75% rename from GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestUploader.m rename to GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.m index 8216c920c74..1a47e89fd78 100644 --- a/GoogleDataLogger/Tests/Integration/Helpers/GDLIntegrationTestUploader.m +++ b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.m @@ -14,13 +14,13 @@ * limitations under the License. */ -#import "GDLIntegrationTestUploader.h" +#import "GDTIntegrationTestUploader.h" -#import "GDLIntegrationTestPrioritizer.h" +#import "GDTIntegrationTestPrioritizer.h" -#import "GDLTestServer.h" +#import "GDTTestServer.h" -@implementation GDLIntegrationTestUploader { +@implementation GDTIntegrationTestUploader { /** The current upload task. */ NSURLSessionUploadTask *_currentUploadTask; @@ -32,12 +32,13 @@ - (instancetype)initWithServerURL:(NSURL *)serverURL { self = [super init]; if (self) { _serverURL = serverURL; - [[GDLRegistrar sharedInstance] registerUploader:self logTarget:kGDLIntegrationTestTarget]; + [[GDTRegistrar sharedInstance] registerUploader:self target:kGDTIntegrationTestTarget]; } return self; } -- (void)uploadLogs:(NSSet *)logFiles onComplete:(GDLUploaderCompletionBlock)onComplete { +- (void)uploadEvents:(NSSet *)eventFiles + onComplete:(GDTUploaderCompletionBlock)onComplete { NSAssert(!_currentUploadTask, @"An upload shouldn't be initiated with another in progress."); NSURL *serverURL = arc4random_uniform(2) ? [_serverURL URLByAppendingPathComponent:@"log"] : [_serverURL URLByAppendingPathComponent:@"logBatch"]; @@ -46,12 +47,12 @@ - (void)uploadLogs:(NSSet *)logFiles onComplete:(GDLUploaderCompletionB request.HTTPMethod = @"POST"; NSMutableData *uploadData = [[NSMutableData alloc] init]; - NSLog(@"Uploading batch of %lu logs: ", (unsigned long)logFiles.count); + NSLog(@"Uploading batch of %lu events: ", (unsigned long)eventFiles.count); // In real usage, you'd create an instance of whatever request proto your server needs. - for (NSURL *logFile in logFiles) { - NSData *fileData = [NSData dataWithContentsOfURL:logFile]; - NSAssert(fileData, @"A log file shouldn't be empty"); + for (NSURL *eventFile in eventFiles) { + NSData *fileData = [NSData dataWithContentsOfURL:eventFile]; + NSAssert(fileData, @"A event file shouldn't be empty"); [uploadData appendData:fileData]; } NSURLSessionUploadTask *uploadTask = @@ -61,11 +62,11 @@ - (void)uploadLogs:(NSSet *)logFiles onComplete:(GDLUploaderCompletionB NSError *_Nullable error) { NSLog(@"Batch upload complete."); // Remove from the prioritizer if there were no errors. - NSAssert(!error, @"There should be no errors uploading logs: %@", error); + NSAssert(!error, @"There should be no errors uploading events: %@", error); if (onComplete) { // In real usage, the server would/should return a desired next upload time. - GDLClock *nextUploadTime = [GDLClock clockSnapshotInTheFuture:1000]; - onComplete(kGDLIntegrationTestTarget, nextUploadTime, error); + GDTClock *nextUploadTime = [GDTClock clockSnapshotInTheFuture:1000]; + onComplete(kGDTIntegrationTestTarget, nextUploadTime, error); } }]; [uploadTask resume]; diff --git a/GoogleDataLogger/Tests/Integration/TestServer/GDLTestServer.h b/GoogleDataTransport/Tests/Integration/TestServer/GDTTestServer.h similarity index 97% rename from GoogleDataLogger/Tests/Integration/TestServer/GDLTestServer.h rename to GoogleDataTransport/Tests/Integration/TestServer/GDTTestServer.h index cc47b5fc793..0ed0e004e71 100644 --- a/GoogleDataLogger/Tests/Integration/TestServer/GDLTestServer.h +++ b/GoogleDataTransport/Tests/Integration/TestServer/GDTTestServer.h @@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN @class GCDWebServerResponse; /** This class provides a hermetic test service that runs on the test device/simulator. */ -@interface GDLTestServer : NSObject +@interface GDTTestServer : NSObject /** The URL of the server. */ @property(nonatomic, readonly) NSURL *serverURL; diff --git a/GoogleDataLogger/Tests/Integration/TestServer/GDLTestServer.m b/GoogleDataTransport/Tests/Integration/TestServer/GDTTestServer.m similarity index 97% rename from GoogleDataLogger/Tests/Integration/TestServer/GDLTestServer.m rename to GoogleDataTransport/Tests/Integration/TestServer/GDTTestServer.m index 707f0fe79db..0d815e02758 100644 --- a/GoogleDataLogger/Tests/Integration/TestServer/GDLTestServer.m +++ b/GoogleDataTransport/Tests/Integration/TestServer/GDTTestServer.m @@ -14,9 +14,9 @@ * limitations under the License. */ -#import "GDLTestServer.h" +#import "GDTTestServer.h" -@interface GDLTestServer () +@interface GDTTestServer () /** The server object. */ @property(nonatomic) GCDWebServer *server; @@ -26,7 +26,7 @@ @interface GDLTestServer () @end -@implementation GDLTestServer +@implementation GDTTestServer - (instancetype)init { self = [super init]; diff --git a/GoogleDataLogger/Tests/Unit/GDLClockTest.m b/GoogleDataTransport/Tests/Unit/GDTClockTest.m similarity index 71% rename from GoogleDataLogger/Tests/Unit/GDLClockTest.m rename to GoogleDataTransport/Tests/Unit/GDTClockTest.m index 583843dac90..a8ddfd25427 100644 --- a/GoogleDataLogger/Tests/Unit/GDLClockTest.m +++ b/GoogleDataTransport/Tests/Unit/GDTClockTest.m @@ -14,53 +14,53 @@ * limitations under the License. */ -#import "GDLTestCase.h" +#import "GDTTestCase.h" -#import "GDLClock.h" +#import "GDTClock.h" -@interface GDLClockTest : GDLTestCase +@interface GDTClockTest : GDTTestCase @end -@implementation GDLClockTest +@implementation GDTClockTest /** Tests the default initializer. */ - (void)testInit { - XCTAssertNotNil([[GDLClockTest alloc] init]); + XCTAssertNotNil([[GDTClockTest alloc] init]); } /** Tests taking a snapshot. */ - (void)testSnapshot { - GDLClock *snapshot; - XCTAssertNoThrow(snapshot = [GDLClock snapshot]); + GDTClock *snapshot; + XCTAssertNoThrow(snapshot = [GDTClock snapshot]); XCTAssertGreaterThan(snapshot.timeMillis, 0); } /** Tests that the hash of two snapshots right after each other isn't equal. */ - (void)testHash { - GDLClock *snapshot1 = [GDLClock snapshot]; - GDLClock *snapshot2 = [GDLClock snapshot]; + GDTClock *snapshot1 = [GDTClock snapshot]; + GDTClock *snapshot2 = [GDTClock snapshot]; XCTAssertNotEqual([snapshot1 hash], [snapshot2 hash]); } /** Tests that the class supports NSSecureEncoding. */ - (void)testSupportsSecureEncoding { - XCTAssertTrue([GDLClock supportsSecureCoding]); + XCTAssertTrue([GDTClock supportsSecureCoding]); } /** Tests encoding and decoding a clock using a keyed archiver. */ - (void)testEncoding { - GDLClock *clock = [GDLClock snapshot]; + GDTClock *clock = [GDTClock snapshot]; NSData *clockData = [NSKeyedArchiver archivedDataWithRootObject:clock]; - GDLClock *unarchivedClock = [NSKeyedUnarchiver unarchiveObjectWithData:clockData]; + GDTClock *unarchivedClock = [NSKeyedUnarchiver unarchiveObjectWithData:clockData]; XCTAssertEqual([clock hash], [unarchivedClock hash]); XCTAssertEqualObjects(clock, unarchivedClock); } /** Tests creating a clock that represents a future time. */ - (void)testClockSnapshotInTheFuture { - GDLClock *clock1 = [GDLClock snapshot]; - GDLClock *clock2 = [GDLClock clockSnapshotInTheFuture:1]; + GDTClock *clock1 = [GDTClock snapshot]; + GDTClock *clock2 = [GDTClock clockSnapshotInTheFuture:1]; XCTAssertTrue([clock2 isAfter:clock1]); } diff --git a/GoogleDataTransport/Tests/Unit/GDTEventTest.m b/GoogleDataTransport/Tests/Unit/GDTEventTest.m new file mode 100644 index 00000000000..62d2ef657db --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/GDTEventTest.m @@ -0,0 +1,61 @@ +/* + * Copyright 2018 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 "GDTTestCase.h" + +#import + +#import "GDTEvent_Private.h" + +@interface GDTEventTest : GDTTestCase + +@end + +@implementation GDTEventTest + +/** Tests the designated initializer. */ +- (void)testInit { + XCTAssertNotNil([[GDTEvent alloc] initWithMappingID:@"1" target:1]); + XCTAssertThrows([[GDTEvent alloc] initWithMappingID:@"" target:1]); +} + +/** Tests NSKeyedArchiver encoding and decoding. */ +- (void)testArchiving { + XCTAssertTrue([GDTEvent supportsSecureCoding]); + GDTClock *clockSnapshot = [GDTClock snapshot]; + int64_t timeMillis = clockSnapshot.timeMillis; + int64_t timezoneOffsetSeconds = clockSnapshot.timezoneOffsetSeconds; + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"testID" target:42]; + event.dataObjectTransportBytes = [@"someData" dataUsingEncoding:NSUTF8StringEncoding]; + event.qosTier = GDTEventQoSTelemetry; + event.clockSnapshot = clockSnapshot; + + NSData *archiveData = [NSKeyedArchiver archivedDataWithRootObject:event]; + + // To ensure that all the objects being retained by the original event are dealloc'd. + event = nil; + + GDTEvent *decodedEvent = [NSKeyedUnarchiver unarchiveObjectWithData:archiveData]; + XCTAssertEqualObjects(decodedEvent.mappingID, @"testID"); + XCTAssertEqual(decodedEvent.target, 42); + XCTAssertEqualObjects(decodedEvent.dataObjectTransportBytes, + [@"someData" dataUsingEncoding:NSUTF8StringEncoding]); + XCTAssertEqual(decodedEvent.qosTier, GDTEventQoSTelemetry); + XCTAssertEqual(decodedEvent.clockSnapshot.timeMillis, timeMillis); + XCTAssertEqual(decodedEvent.clockSnapshot.timezoneOffsetSeconds, timezoneOffsetSeconds); +} + +@end diff --git a/GoogleDataTransport/Tests/Unit/GDTRegistrarTest.m b/GoogleDataTransport/Tests/Unit/GDTRegistrarTest.m new file mode 100644 index 00000000000..e2e6004c142 --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/GDTRegistrarTest.m @@ -0,0 +1,59 @@ +/* + * Copyright 2018 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 "GDTTestCase.h" + +#import + +#import "GDTRegistrar_Private.h" +#import "GDTTestPrioritizer.h" +#import "GDTTestUploader.h" + +@interface GDTRegistrarTest : GDTTestCase + +@property(nonatomic) GDTTarget target; + +@end + +@implementation GDTRegistrarTest + +- (void)setUp { + [super setUp]; + _target = 23; +} + +/** Tests the default initializer. */ +- (void)testInit { + XCTAssertNotNil([[GDTRegistrarTest alloc] init]); +} + +/** Test registering an uploader. */ +- (void)testRegisterUpload { + GDTRegistrar *registrar = [GDTRegistrar sharedInstance]; + GDTTestUploader *uploader = [[GDTTestUploader alloc] init]; + XCTAssertNoThrow([registrar registerUploader:uploader target:self.target]); + XCTAssertEqual(uploader, registrar.targetToUploader[@(_target)]); +} + +/** Test registering a prioritizer. */ +- (void)testRegisterPrioritizer { + GDTRegistrar *registrar = [GDTRegistrar sharedInstance]; + GDTTestPrioritizer *prioritizer = [[GDTTestPrioritizer alloc] init]; + XCTAssertNoThrow([registrar registerPrioritizer:prioritizer target:self.target]); + XCTAssertEqual(prioritizer, registrar.targetToPrioritizer[@(_target)]); +} + +@end diff --git a/GoogleDataTransport/Tests/Unit/GDTStorageTest.m b/GoogleDataTransport/Tests/Unit/GDTStorageTest.m new file mode 100644 index 00000000000..021c280ba29 --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/GDTStorageTest.m @@ -0,0 +1,321 @@ +/* + * Copyright 2018 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 "GDTTestCase.h" + +#import + +#import "GDTEvent_Private.h" +#import "GDTRegistrar.h" +#import "GDTRegistrar_Private.h" +#import "GDTStorage.h" +#import "GDTStorage_Private.h" + +#import "GDTTestPrioritizer.h" +#import "GDTTestUploader.h" + +#import "GDTAssertHelper.h" +#import "GDTRegistrar+Testing.h" +#import "GDTStorage+Testing.h" +#import "GDTUploadCoordinatorFake.h" + +static NSInteger target = 1337; + +@interface GDTStorageTest : GDTTestCase + +/** The test backend implementation. */ +@property(nullable, nonatomic) GDTTestUploader *testBackend; + +/** The test prioritizer implementation. */ +@property(nullable, nonatomic) GDTTestPrioritizer *testPrioritizer; + +/** The uploader fake. */ +@property(nonatomic) GDTUploadCoordinatorFake *uploaderFake; + +@end + +@implementation GDTStorageTest + +- (void)setUp { + [super setUp]; + self.testBackend = [[GDTTestUploader alloc] init]; + self.testPrioritizer = [[GDTTestPrioritizer alloc] init]; + [[GDTRegistrar sharedInstance] registerUploader:_testBackend target:target]; + [[GDTRegistrar sharedInstance] registerPrioritizer:_testPrioritizer target:target]; + self.uploaderFake = [[GDTUploadCoordinatorFake alloc] init]; + [GDTStorage sharedInstance].uploader = self.uploaderFake; +} + +- (void)tearDown { + [super tearDown]; + // Destroy these objects before the next test begins. + self.testBackend = nil; + self.testPrioritizer = nil; + [[GDTRegistrar sharedInstance] reset]; + [[GDTStorage sharedInstance] reset]; + [GDTStorage sharedInstance].uploader = [GDTUploadCoordinator sharedInstance]; + self.uploaderFake = nil; +} + +/** Tests the singleton pattern. */ +- (void)testInit { + XCTAssertEqual([GDTStorage sharedInstance], [GDTStorage sharedInstance]); +} + +/** Tests storing an event. */ +- (void)testStoreEvent { + NSUInteger eventHash; + // event is autoreleased, and the pool needs to drain. + @autoreleasepool { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; + eventHash = event.hash; + XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + } + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 1); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 1); + NSURL *eventFile = [GDTStorage sharedInstance].eventHashToFile[@(eventHash)]; + XCTAssertNotNil(eventFile); + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + NSError *error; + XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:eventFile error:&error]); + XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error); + }); +} + +/** Tests removing an event. */ +- (void)testRemoveEvent { + NSUInteger eventHash; + // event is autoreleased, and the pool needs to drain. + @autoreleasepool { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; + eventHash = event.hash; + XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + } + __block NSURL *eventFile; + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + eventFile = [GDTStorage sharedInstance].eventHashToFile[@(eventHash)]; + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + }); + [[GDTStorage sharedInstance] removeEvents:[NSSet setWithObject:@(eventHash)] target:@(target)]; + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 0); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 0); + }); +} + +/** Tests removing a set of events. */ +- (void)testRemoveEvents { + GDTStorage *storage = [GDTStorage sharedInstance]; + NSUInteger event1Hash, event2Hash, event3Hash; + + // events are autoreleased, and the pool needs to drain. + @autoreleasepool { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding]; + event1Hash = event.hash; + XCTAssertNoThrow([storage storeEvent:event]); + + event = [[GDTEvent alloc] initWithMappingID:@"100" target:target]; + event.dataObjectTransportBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; + event2Hash = event.hash; + XCTAssertNoThrow([storage storeEvent:event]); + + event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding]; + event3Hash = event.hash; + XCTAssertNoThrow([storage storeEvent:event]); + } + NSSet *eventHashSet = + [NSSet setWithObjects:@(event1Hash), @(event2Hash), @(event3Hash), nil]; + NSSet *eventFiles = [storage eventHashesToFiles:eventHashSet]; + [storage removeEvents:eventHashSet target:@(target)]; + dispatch_sync(storage.storageQueue, ^{ + XCTAssertNil(storage.eventHashToFile[@(event1Hash)]); + XCTAssertNil(storage.eventHashToFile[@(event2Hash)]); + XCTAssertNil(storage.eventHashToFile[@(event3Hash)]); + XCTAssertEqual(storage.targetToEventHashSet[@(target)].count, 0); + for (NSURL *eventFile in eventFiles) { + XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + } + }); +} + +/** Tests storing a few different events. */ +- (void)testStoreMultipleEvents { + NSUInteger event1Hash, event2Hash, event3Hash; + + // events are autoreleased, and the pool needs to drain. + @autoreleasepool { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding]; + event1Hash = event.hash; + XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + + event = [[GDTEvent alloc] initWithMappingID:@"100" target:target]; + event.dataObjectTransportBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; + event2Hash = event.hash; + XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + + event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding]; + event3Hash = event.hash; + XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + } + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 3); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 3); + + NSURL *event1File = [GDTStorage sharedInstance].eventHashToFile[@(event1Hash)]; + XCTAssertNotNil(event1File); + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:event1File.path]); + NSError *error; + XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:event1File error:&error]); + XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error); + + NSURL *event2File = [GDTStorage sharedInstance].eventHashToFile[@(event2Hash)]; + XCTAssertNotNil(event2File); + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:event2File.path]); + error = nil; + XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:event2File error:&error]); + XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error); + + NSURL *event3File = [GDTStorage sharedInstance].eventHashToFile[@(event3Hash)]; + XCTAssertNotNil(event3File); + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:event3File.path]); + error = nil; + XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:event3File error:&error]); + XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error); + }); +} + +/** Tests enforcing that a prioritizer does not retain an event in memory. */ +- (void)testEventDeallocationIsEnforced { + XCTestExpectation *errorExpectation = [self expectationWithDescription:@"event retain error"]; + [GDTAssertHelper setAssertionBlock:^{ + [errorExpectation fulfill]; + }]; + + // event is referenced past -storeEvent, ensuring it's retained, which should assert. + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; + + // Store the event and wait for the expectation. + [[GDTStorage sharedInstance] storeEvent:event]; + [self waitForExpectations:@[ errorExpectation ] timeout:5.0]; + + NSURL *eventFile; + eventFile = [GDTStorage sharedInstance].eventHashToFile[@(event.hash)]; + + // This isn't strictly necessary because of the -waitForExpectations above. + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + }); + + // Ensure event was removed. + NSNumber *eventHash = @(event.hash); + [[GDTStorage sharedInstance] removeEvents:[NSSet setWithObject:eventHash] target:@(target)]; + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 0); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 0); + }); +} + +/** Tests encoding and decoding the storage singleton correctly. */ +- (void)testNSSecureCoding { + XCTAssertTrue([GDTStorage supportsSecureCoding]); + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; + NSUInteger eventHash = event.hash; + XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + event = nil; + NSData *storageData = [NSKeyedArchiver archivedDataWithRootObject:[GDTStorage sharedInstance]]; + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertNotNil([GDTStorage sharedInstance].eventHashToFile[@(eventHash)]); + }); + [[GDTStorage sharedInstance] removeEvents:[NSSet setWithObject:@(eventHash)] target:@(target)]; + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertNil([GDTStorage sharedInstance].eventHashToFile[@(eventHash)]); + }); + + // TODO(mikehaney24): Ensure that the object created by alloc is discarded? + [NSKeyedUnarchiver unarchiveObjectWithData:storageData]; + XCTAssertNotNil([GDTStorage sharedInstance].eventHashToFile[@(eventHash)]); +} + +/** Tests sending a fast priority event causes an upload attempt. */ +- (void)testQoSTierFast { + NSUInteger eventHash; + // event is autoreleased, and the pool needs to drain. + @autoreleasepool { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; + event.qosTier = GDTEventQoSFast; + eventHash = event.hash; + XCTAssertFalse(self.uploaderFake.forceUploadCalled); + XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + } + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertTrue(self.uploaderFake.forceUploadCalled); + XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 1); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 1); + NSURL *eventFile = [GDTStorage sharedInstance].eventHashToFile[@(eventHash)]; + XCTAssertNotNil(eventFile); + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + NSError *error; + XCTAssertTrue([[NSFileManager defaultManager] removeItemAtURL:eventFile error:&error]); + XCTAssertNil(error, @"There was an error deleting the eventFile: %@", error); + }); +} + +/** Tests convert a set of event hashes to a set of event file URLS. */ +- (void)testEventHashesToFiles { + GDTStorage *storage = [GDTStorage sharedInstance]; + NSUInteger event1Hash, event2Hash, event3Hash; + + // events are autoreleased, and the pool needs to drain. + @autoreleasepool { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString1" dataUsingEncoding:NSUTF8StringEncoding]; + event1Hash = event.hash; + XCTAssertNoThrow([storage storeEvent:event]); + + event = [[GDTEvent alloc] initWithMappingID:@"100" target:target]; + event.dataObjectTransportBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; + event2Hash = event.hash; + XCTAssertNoThrow([storage storeEvent:event]); + + event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + event.dataObjectTransportBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding]; + event3Hash = event.hash; + XCTAssertNoThrow([storage storeEvent:event]); + } + NSSet *eventHashSet = + [NSSet setWithObjects:@(event1Hash), @(event2Hash), @(event3Hash), nil]; + NSSet *eventFiles = [storage eventHashesToFiles:eventHashSet]; + dispatch_sync(storage.storageQueue, ^{ + XCTAssertEqual(eventFiles.count, 3); + XCTAssertTrue([eventFiles containsObject:storage.eventHashToFile[@(event1Hash)]]); + XCTAssertTrue([eventFiles containsObject:storage.eventHashToFile[@(event2Hash)]]); + XCTAssertTrue([eventFiles containsObject:storage.eventHashToFile[@(event3Hash)]]); + }); +} + +@end diff --git a/GoogleDataLogger/Tests/Unit/GDLTestCase.h b/GoogleDataTransport/Tests/Unit/GDTTestCase.h similarity index 87% rename from GoogleDataLogger/Tests/Unit/GDLTestCase.h rename to GoogleDataTransport/Tests/Unit/GDTTestCase.h index 62bc35a4908..5db1fc6c5f1 100644 --- a/GoogleDataLogger/Tests/Unit/GDLTestCase.h +++ b/GoogleDataTransport/Tests/Unit/GDTTestCase.h @@ -16,12 +16,13 @@ #import -#import "GDLAssertHelper.h" +#import "GDTAssertHelper.h" NS_ASSUME_NONNULL_BEGIN -/** This class defines shared testing infrastructure across all unit tests in GoogleDataLogger. */ -@interface GDLTestCase : XCTestCase +/** This class defines shared testing infrastructure across all unit tests in GoogleDataTransport. + */ +@interface GDTTestCase : XCTestCase @end diff --git a/GoogleDataLogger/Tests/Unit/GDLTestCase.m b/GoogleDataTransport/Tests/Unit/GDTTestCase.m similarity index 77% rename from GoogleDataLogger/Tests/Unit/GDLTestCase.m rename to GoogleDataTransport/Tests/Unit/GDTTestCase.m index 288e1521bed..5c788a80614 100644 --- a/GoogleDataLogger/Tests/Unit/GDLTestCase.m +++ b/GoogleDataTransport/Tests/Unit/GDTTestCase.m @@ -14,18 +14,18 @@ * limitations under the License. */ -#import "GDLTestCase.h" +#import "GDTTestCase.h" -#import "GDLUploadCoordinator+Testing.h" +#import "GDTUploadCoordinator+Testing.h" -@implementation GDLTestCase +@implementation GDTTestCase - (void)setUp { - [[GDLUploadCoordinator sharedInstance] stopTimer]; + [[GDTUploadCoordinator sharedInstance] stopTimer]; } - (void)tearDown { - [GDLAssertHelper setAssertionBlock:nil]; + [GDTAssertHelper setAssertionBlock:nil]; } @end diff --git a/GoogleDataTransport/Tests/Unit/GDTTransformerTest.m b/GoogleDataTransport/Tests/Unit/GDTTransformerTest.m new file mode 100644 index 00000000000..ffc476f6725 --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/GDTTransformerTest.m @@ -0,0 +1,127 @@ +/* + * Copyright 2018 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 "GDTTestCase.h" + +#import + +#import "GDTDataObjectTesterClasses.h" +#import "GDTEvent.h" +#import "GDTStorage.h" +#import "GDTTransformer.h" +#import "GDTTransformer_Private.h" + +#import "GDTAssertHelper.h" +#import "GDTStorageFake.h" + +@interface GDTTransformerTestNilingTransformer : NSObject + +@end + +@implementation GDTTransformerTestNilingTransformer + +- (GDTEvent *)transform:(GDTEvent *)eventEvent { + return nil; +} + +@end + +@interface GDTTransformerTestNewEventTransformer : NSObject + +@end + +@implementation GDTTransformerTestNewEventTransformer + +- (GDTEvent *)transform:(GDTEvent *)eventEvent { + return [[GDTEvent alloc] initWithMappingID:@"new" target:1]; +} + +@end + +@interface GDTTransformerTest : GDTTestCase + +@end + +@implementation GDTTransformerTest + +- (void)setUp { + [super setUp]; + dispatch_sync([GDTTransformer sharedInstance].eventWritingQueue, ^{ + [GDTTransformer sharedInstance].storageInstance = [[GDTStorageFake alloc] init]; + }); +} + +- (void)tearDown { + [super tearDown]; + dispatch_sync([GDTTransformer sharedInstance].eventWritingQueue, ^{ + [GDTTransformer sharedInstance].storageInstance = [GDTStorage sharedInstance]; + }); +} + +/** Tests the default initializer. */ +- (void)testInit { + XCTAssertNotNil([[GDTTransformer alloc] init]); +} + +/** Tests the pointer equality of result of the -sharedInstance method. */ +- (void)testSharedInstance { + XCTAssertEqual([GDTTransformer sharedInstance], [GDTTransformer sharedInstance]); +} + +/** Tests writing a event without a transformer. */ +- (void)testWriteEventWithoutTransformers { + GDTTransformer *transformer = [GDTTransformer sharedInstance]; + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"1" target:1]; + event.dataObject = [[GDTDataObjectTesterSimple alloc] init]; + XCTAssertNoThrow([transformer transformEvent:event withTransformers:nil]); +} + +/** Tests writing a event with a transformer that nils out the event. */ +- (void)testWriteEventWithTransformersThatNilTheEvent { + GDTTransformer *transformer = [GDTTransformer sharedInstance]; + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"2" target:1]; + event.dataObject = [[GDTDataObjectTesterSimple alloc] init]; + NSArray> *transformers = + @[ [[GDTTransformerTestNilingTransformer alloc] init] ]; + XCTAssertNoThrow([transformer transformEvent:event withTransformers:transformers]); +} + +/** Tests writing a event with a transformer that creates a new event. */ +- (void)testWriteEventWithTransformersThatCreateANewEvent { + GDTTransformer *transformer = [GDTTransformer sharedInstance]; + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"2" target:1]; + event.dataObject = [[GDTDataObjectTesterSimple alloc] init]; + NSArray> *transformers = + @[ [[GDTTransformerTestNewEventTransformer alloc] init] ]; + XCTAssertNoThrow([transformer transformEvent:event withTransformers:transformers]); +} + +/** Tests that using a transformer without transform: implemented throws. */ +- (void)testWriteEventWithBadTransformer { + GDTTransformer *transformer = [GDTTransformer sharedInstance]; + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"2" target:1]; + event.dataObject = [[GDTDataObjectTesterSimple alloc] init]; + NSArray *transformers = @[ [[NSObject alloc] init] ]; + + XCTestExpectation *errorExpectation = [self expectationWithDescription:@"transform: is missing"]; + [GDTAssertHelper setAssertionBlock:^{ + [errorExpectation fulfill]; + }]; + [transformer transformEvent:event withTransformers:transformers]; + [self waitForExpectations:@[ errorExpectation ] timeout:5.0]; +} + +@end diff --git a/GoogleDataTransport/Tests/Unit/GDTTransportTest.m b/GoogleDataTransport/Tests/Unit/GDTTransportTest.m new file mode 100644 index 00000000000..8e5f1a3216d --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/GDTTransportTest.m @@ -0,0 +1,57 @@ +/* + * Copyright 2018 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 "GDTTestCase.h" + +#import +#import + +#import "GDTTransport_Private.h" + +#import "GDTDataObjectTesterClasses.h" +#import "GDTTransformerFake.h" + +@interface GDTTransportTest : GDTTestCase + +@end + +@implementation GDTTransportTest + +/** Tests the default initializer. */ +- (void)testInit { + XCTAssertNotNil([[GDTTransport alloc] initWithMappingID:@"1" transformers:nil target:1]); + XCTAssertThrows([[GDTTransport alloc] initWithMappingID:@"" transformers:nil target:1]); +} + +/** Tests sending a telemetry event. */ +- (void)testSendTelemetryEvent { + GDTTransport *transport = [[GDTTransport alloc] initWithMappingID:@"1" transformers:nil target:1]; + transport.transformerInstance = [[GDTTransformerFake alloc] init]; + GDTEvent *event = [transport eventForTransport]; + event.dataObject = [[GDTDataObjectTesterSimple alloc] init]; + XCTAssertNoThrow([transport sendTelemetryEvent:event]); +} + +/** Tests sending a data event. */ +- (void)testSendDataEvent { + GDTTransport *transport = [[GDTTransport alloc] initWithMappingID:@"1" transformers:nil target:1]; + transport.transformerInstance = [[GDTTransformerFake alloc] init]; + GDTEvent *event = [transport eventForTransport]; + event.dataObject = [[GDTDataObjectTesterSimple alloc] init]; + XCTAssertNoThrow([transport sendDataEvent:event]); +} + +@end diff --git a/GoogleDataTransport/Tests/Unit/GDTUploadCoordinatorTest.m b/GoogleDataTransport/Tests/Unit/GDTUploadCoordinatorTest.m new file mode 100644 index 00000000000..24df204a17b --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/GDTUploadCoordinatorTest.m @@ -0,0 +1,197 @@ +/* + * Copyright 2018 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 "GDTTestCase.h" + +#import "GDTUploadCoordinator.h" +#import "GDTUploadCoordinator_Private.h" + +#import "GDTRegistrar+Testing.h" +#import "GDTStorageFake.h" +#import "GDTTestPrioritizer.h" +#import "GDTTestUploader.h" +#import "GDTUploadCoordinator+Testing.h" + +@interface GDTUploadCoordinatorTest : GDTTestCase + +/** A storage fake to inject into GDTUploadCoordinator. */ +@property(nonatomic) GDTStorageFake *storageFake; + +/** A test prioritizer. */ +@property(nonatomic) GDTTestPrioritizer *prioritizer; + +/** A test uploader. */ +@property(nonatomic) GDTTestUploader *uploader; + +/** A target for the prioritizer and uploader to use. */ +@property(nonatomic) GDTTarget target; + +@end + +@implementation GDTUploadCoordinatorTest + +- (void)setUp { + [super setUp]; + self.storageFake = [[GDTStorageFake alloc] init]; + self.target = 42; + self.prioritizer = [[GDTTestPrioritizer alloc] init]; + self.uploader = [[GDTTestUploader alloc] init]; + + [[GDTRegistrar sharedInstance] registerPrioritizer:_prioritizer target:_target]; + [[GDTRegistrar sharedInstance] registerUploader:_uploader target:_target]; + + GDTUploadCoordinator *uploadCoordinator = [GDTUploadCoordinator sharedInstance]; + uploadCoordinator.storage = self.storageFake; + uploadCoordinator.timerInterval = NSEC_PER_SEC; + uploadCoordinator.timerLeeway = 0; +} + +- (void)tearDown { + [super tearDown]; + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + [[GDTUploadCoordinator sharedInstance] reset]; + }); + [[GDTRegistrar sharedInstance] reset]; + self.storageFake = nil; + self.prioritizer = nil; + self.uploader = nil; +} + +/** Tests the default initializer. */ +- (void)testSharedInstance { + XCTAssertEqual([GDTUploadCoordinator sharedInstance], [GDTUploadCoordinator sharedInstance]); +} + +/** Tests that forcing a event upload works. */ +- (void)testForceUploadEvents { + XCTestExpectation *expectation = [self expectationWithDescription:@"uploader will upload"]; + self.uploader.uploadEventsBlock = + ^(NSSet *_Nonnull eventFiles, GDTUploaderCompletionBlock _Nonnull completionBlock) { + [expectation fulfill]; + }; + NSSet *fakeEventSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; + self.storageFake.eventsToReturnFromEventHashesToFiles = fakeEventSet; + NSSet *eventSet = [NSSet setWithObjects:@(1234), nil]; + XCTAssertNoThrow([[GDTUploadCoordinator sharedInstance] forceUploadEvents:eventSet + target:_target]); + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + [self waitForExpectations:@[ expectation ] timeout:0.1]; + }); +} + +/** Tests forcing an upload while that target currently has a request in flight queues. */ +- (void)testForceUploadEventsEnqueuesIftargetAlreadyHasEventsInFlight { + [GDTUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 100; + [GDTUploadCoordinator sharedInstance].timerLeeway = NSEC_PER_SEC / 1000; + XCTestExpectation *expectation = [self expectationWithDescription:@"uploader will upload"]; + self.uploader.uploadEventsBlock = + ^(NSSet *_Nonnull eventFiles, GDTUploaderCompletionBlock _Nonnull completionBlock) { + [expectation fulfill]; + }; + NSSet *fakeEventSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; + self.storageFake.eventsToReturnFromEventHashesToFiles = fakeEventSet; + NSSet *eventSet = [NSSet setWithObjects:@(1234), nil]; + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + [GDTUploadCoordinator sharedInstance].targetToInFlightEventSet[@(self->_target)] = + [[NSSet alloc] init]; + }); + XCTAssertNoThrow([[GDTUploadCoordinator sharedInstance] forceUploadEvents:eventSet + target:_target]); + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + XCTAssertEqual([GDTUploadCoordinator sharedInstance].forcedUploadQueue.count, 1); + [GDTUploadCoordinator sharedInstance].onCompleteBlock( + self.target, [GDTClock clockSnapshotInTheFuture:1000], nil); + }); + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + [self waitForExpectations:@[ expectation ] timeout:0.1]; + }); +} + +/** Tests the timer is running at the desired frequency. */ +- (void)testTimerIsRunningAtDesiredFrequency { + __block int numberOfTimesCalled = 0; + self.prioritizer.eventsForNextUploadBlock = ^{ + numberOfTimesCalled++; + }; + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + // Timer should fire 10 times a second. + [GDTUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 10; + [GDTUploadCoordinator sharedInstance].timerLeeway = 0; + }); + [[GDTUploadCoordinator sharedInstance] startTimer]; + + // Run for 1 second. + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; + + // It's expected that the timer called the prioritizer 10 times +/- 3 during that 1 second + the + // coordinator running before that. + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + XCTAssertEqualWithAccuracy(numberOfTimesCalled, 10, 3); + }); +} + +/** Tests uploading events via the coordinator timer. */ +- (void)testUploadingEventsViaTimer { + NSSet *fakeEventSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; + self.storageFake.eventsToReturnFromEventHashesToFiles = fakeEventSet; + __block int uploadAttempts = 0; + __weak GDTUploadCoordinatorTest *weakSelf = self; + self.prioritizer.eventsForNextUploadFake = [NSSet setWithObjects:@(1234), nil]; + self.uploader.uploadEventsBlock = + ^(NSSet *_Nonnull eventFiles, GDTUploaderCompletionBlock _Nonnull completionBlock) { + GDTUploadCoordinatorTest *strongSelf = weakSelf; + completionBlock(strongSelf->_target, [GDTClock clockSnapshotInTheFuture:100], nil); + uploadAttempts++; + }; + [GDTUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 10; + [GDTUploadCoordinator sharedInstance].timerLeeway = 0; + + [[GDTUploadCoordinator sharedInstance] startTimer]; + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + // More than two attempts should have been made. + XCTAssertGreaterThan(uploadAttempts, 2); + }); +} + +/** Tests the situation in which the uploader failed to upload the events for some reason. */ +- (void)testThatAFailedUploadResultsInAnEventualRetry { + NSSet *fakeEventSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; + self.storageFake.eventsToReturnFromEventHashesToFiles = fakeEventSet; + __block int uploadAttempts = 0; + __weak GDTUploadCoordinatorTest *weakSelf = self; + self.prioritizer.eventsForNextUploadFake = [NSSet setWithObjects:@(1234), nil]; + self.uploader.uploadEventsBlock = + ^(NSSet *_Nonnull eventFiles, GDTUploaderCompletionBlock _Nonnull completionBlock) { + GDTUploadCoordinatorTest *strongSelf = weakSelf; + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:1337 userInfo:nil]; + completionBlock(strongSelf->_target, [GDTClock clockSnapshotInTheFuture:100], error); + uploadAttempts++; + }; + [GDTUploadCoordinator sharedInstance].timerInterval = NSEC_PER_SEC / 10; + [GDTUploadCoordinator sharedInstance].timerLeeway = 0; + + [[GDTUploadCoordinator sharedInstance] startTimer]; + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ + // More than two attempts should have been made. + XCTAssertGreaterThan(uploadAttempts, 2); + }); +} + +@end diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLAssertHelper.h b/GoogleDataTransport/Tests/Unit/Helpers/GDTAssertHelper.h similarity index 80% rename from GoogleDataLogger/Tests/Unit/Helpers/GDLAssertHelper.h rename to GoogleDataTransport/Tests/Unit/Helpers/GDTAssertHelper.h index 3a1526afbb7..c88833f8ade 100644 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLAssertHelper.h +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTAssertHelper.h @@ -16,15 +16,15 @@ #import -#import "GDLAssert.h" +#import "GDTAssert.h" NS_ASSUME_NONNULL_BEGIN -/** Allows the setting a block to be used in the GDLAssert macro instead of a call to NSAssert. */ -@interface GDLAssertHelper : NSObject +/** Allows the setting a block to be used in the GDTAssert macro instead of a call to NSAssert. */ +@interface GDTAssertHelper : NSObject /** A class property that can be run instead of NSAssert. */ -@property(class, nullable, nonatomic) GDLAssertionBlock assertionBlock; +@property(class, nullable, nonatomic) GDTAssertionBlock assertionBlock; @end diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLAssertHelper.m b/GoogleDataTransport/Tests/Unit/Helpers/GDTAssertHelper.m similarity index 78% rename from GoogleDataLogger/Tests/Unit/Helpers/GDLAssertHelper.m rename to GoogleDataTransport/Tests/Unit/Helpers/GDTAssertHelper.m index d46b5f4977e..6b46b80262f 100644 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLAssertHelper.m +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTAssertHelper.m @@ -14,18 +14,18 @@ * limitations under the License. */ -#import "GDLAssertHelper.h" +#import "GDTAssertHelper.h" -@implementation GDLAssertHelper +@implementation GDTAssertHelper // The backing store for the class variable assertionBlock. -static GDLAssertionBlock gSharedAssertionBlock; +static GDTAssertionBlock gSharedAssertionBlock; -+ (GDLAssertionBlock)assertionBlock { ++ (GDTAssertionBlock)assertionBlock { return gSharedAssertionBlock; } -+ (void)setAssertionBlock:(GDLAssertionBlock)assertionBlock { ++ (void)setAssertionBlock:(GDTAssertionBlock)assertionBlock { gSharedAssertionBlock = assertionBlock; } diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLLogExtensionTesterClasses.h b/GoogleDataTransport/Tests/Unit/Helpers/GDTDataObjectTesterClasses.h similarity index 81% rename from GoogleDataLogger/Tests/Unit/Helpers/GDLLogExtensionTesterClasses.h rename to GoogleDataTransport/Tests/Unit/Helpers/GDTDataObjectTesterClasses.h index 9c476446107..c5c46d097cf 100644 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLLogExtensionTesterClasses.h +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTDataObjectTesterClasses.h @@ -16,12 +16,12 @@ #import -#import +#import NS_ASSUME_NONNULL_BEGIN -/** A class to represent a simple log proto. */ -@interface GDLLogExtensionTesterSimple : NSObject +/** A class to represent a simple data object proto. */ +@interface GDTDataObjectTesterSimple : NSObject /** A string that will be turned into bytes. */ @property(nonatomic) NSString *aString; diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLLogExtensionTesterClasses.m b/GoogleDataTransport/Tests/Unit/Helpers/GDTDataObjectTesterClasses.m similarity index 90% rename from GoogleDataLogger/Tests/Unit/Helpers/GDLLogExtensionTesterClasses.m rename to GoogleDataTransport/Tests/Unit/Helpers/GDTDataObjectTesterClasses.m index e0aaf25cce5..73e2bb80543 100644 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLLogExtensionTesterClasses.m +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTDataObjectTesterClasses.m @@ -14,9 +14,9 @@ * limitations under the License. */ -#import "GDLLogExtensionTesterClasses.h" +#import "GDTDataObjectTesterClasses.h" -@implementation GDLLogExtensionTesterSimple +@implementation GDTDataObjectTesterSimple - (instancetype)init { self = [super init]; diff --git a/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.h b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.h new file mode 100644 index 00000000000..5fa5716efe5 --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.h @@ -0,0 +1,39 @@ +/* + * Copyright 2018 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 + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This class implements the event prioritizer protocol for testing purposes, providing APIs to + * allow tests to alter the prioritizer behavior without creating a bunch of specialized classes. + */ +@interface GDTTestPrioritizer : NSObject + +/** The return value of -eventsForNextUpload. */ +@property(nullable, nonatomic) NSSet *eventsForNextUploadFake; + +/** Allows the running of a block of code during -prioritizeEvent. */ +@property(nullable, nonatomic) void (^prioritizeEventBlock)(GDTEvent *event); + +/** A block that can run before -eventsForNextUpload completes. */ +@property(nullable, nonatomic) void (^eventsForNextUploadBlock)(void); + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestPrioritizer.m b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.m similarity index 59% rename from GoogleDataLogger/Tests/Unit/Helpers/GDLTestPrioritizer.m rename to GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.m index c6df2d8b5dd..0c48ed875a7 100644 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestPrioritizer.m +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.m @@ -14,32 +14,32 @@ * limitations under the License. */ -#import "GDLTestPrioritizer.h" +#import "GDTTestPrioritizer.h" -@implementation GDLTestPrioritizer +@implementation GDTTestPrioritizer - (instancetype)init { self = [super init]; if (self) { - _logsForNextUploadFake = [[NSSet alloc] init]; + _eventsForNextUploadFake = [[NSSet alloc] init]; } return self; } -- (NSSet *)logsToUploadGivenConditions:(GDLUploadConditions)conditions { - if (_logsForNextUploadBlock) { - _logsForNextUploadBlock(); +- (NSSet *)eventsToUploadGivenConditions:(GDTUploadConditions)conditions { + if (_eventsForNextUploadBlock) { + _eventsForNextUploadBlock(); } - return _logsForNextUploadFake; + return _eventsForNextUploadFake; } -- (void)prioritizeLog:(GDLLogEvent *)logEvent { - if (_prioritizeLogBlock) { - _prioritizeLogBlock(logEvent); +- (void)prioritizeEvent:(GDTEvent *)event { + if (_prioritizeEventBlock) { + _prioritizeEventBlock(event); } } -- (void)unprioritizeLog:(nonnull NSNumber *)logHash { +- (void)unprioritizeEvent:(nonnull NSNumber *)eventHash { } @end diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestUploader.h b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestUploader.h similarity index 67% rename from GoogleDataLogger/Tests/Unit/Helpers/GDLTestUploader.h rename to GoogleDataTransport/Tests/Unit/Helpers/GDTTestUploader.h index 40945237b0f..8995f789c19 100644 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestUploader.h +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestUploader.h @@ -16,18 +16,18 @@ #import -#import "GDLLogUploader.h" +#import "GDTUploader.h" NS_ASSUME_NONNULL_BEGIN -/** This class implements the log backend protocol for testing purposes, providing APIs to allow +/** This class implements a backend uploader protocol for testing purposes, providing APIs to allow * tests to alter the uploader behavior without creating a bunch of specialized classes. */ -@interface GDLTestUploader : NSObject +@interface GDTTestUploader : NSObject -/** A block that can be ran in -uploadLogs:onComplete:. */ -@property(nullable, nonatomic) void (^uploadLogsBlock) - (NSSet *logFiles, GDLUploaderCompletionBlock completionBlock); +/** A block that can be ran in -uploadEvents:onComplete:. */ +@property(nullable, nonatomic) void (^uploadEventsBlock) + (NSSet *eventFiles, GDTUploaderCompletionBlock completionBlock); @end diff --git a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestUploader.m b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestUploader.m similarity index 67% rename from GoogleDataLogger/Tests/Unit/Helpers/GDLTestUploader.m rename to GoogleDataTransport/Tests/Unit/Helpers/GDTTestUploader.m index 0b013f443da..74a84b78efa 100644 --- a/GoogleDataLogger/Tests/Unit/Helpers/GDLTestUploader.m +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestUploader.m @@ -14,15 +14,16 @@ * limitations under the License. */ -#import "GDLTestUploader.h" +#import "GDTTestUploader.h" -@implementation GDLTestUploader +@implementation GDTTestUploader -- (void)uploadLogs:(NSSet *)logFiles onComplete:(GDLUploaderCompletionBlock)onComplete { - if (_uploadLogsBlock) { - _uploadLogsBlock(logFiles, onComplete); +- (void)uploadEvents:(NSSet *)eventFiles + onComplete:(GDTUploaderCompletionBlock)onComplete { + if (_uploadEventsBlock) { + _uploadEventsBlock(eventFiles, onComplete); } else if (onComplete) { - onComplete(kGDLLogTargetCCT, [GDLClock snapshot], nil); + onComplete(kGDTTargetCCT, [GDTClock snapshot], nil); } } diff --git a/GoogleDataLogger/generate_project.sh b/GoogleDataTransport/generate_project.sh similarity index 89% rename from GoogleDataLogger/generate_project.sh rename to GoogleDataTransport/generate_project.sh index 8672ba70f62..c9d869d101f 100755 --- a/GoogleDataLogger/generate_project.sh +++ b/GoogleDataTransport/generate_project.sh @@ -18,4 +18,4 @@ # From https://stackoverflow.com/questions/59895/getting-the-source-directory-of-a-bash-script-from-within DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -pod gen "$DIR/../GoogleDataLogger.podspec" --auto-open --gen-directory="$DIR/gen" --clean +pod gen "$DIR/../GoogleDataTransport.podspec" --auto-open --gen-directory="$DIR/gen" --clean diff --git a/scripts/build.sh b/scripts/build.sh index 20c0b754923..eed71c66d88 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -324,17 +324,17 @@ case "$product-$method-$platform" in build ;; - GoogleDataLogger-xcodebuild-iOS) + GoogleDataTransport-xcodebuild-iOS) RunXcodebuild \ - -workspace 'GoogleDataLogger/gen/GoogleDataLogger/GoogleDataLogger.xcworkspace' \ - -scheme "GoogleDataLogger-Unit-Tests-Unit" \ + -workspace 'GoogleDataTransport/gen/GoogleDataTransport/GoogleDataTransport.xcworkspace' \ + -scheme "GoogleDataTransport-Unit-Tests-Unit" \ "${xcb_flags[@]}" \ build \ test RunXcodebuild \ - -workspace 'GoogleDataLogger/gen/GoogleDataLogger/GoogleDataLogger.xcworkspace' \ - -scheme "GoogleDataLogger-Unit-Tests-Integration" \ + -workspace 'GoogleDataTransport/gen/GoogleDataTransport/GoogleDataTransport.xcworkspace' \ + -scheme "GoogleDataTransport-Unit-Tests-Integration" \ "${xcb_flags[@]}" \ build \ test diff --git a/scripts/if_changed.sh b/scripts/if_changed.sh index 09b7f73b35d..7c821158bba 100755 --- a/scripts/if_changed.sh +++ b/scripts/if_changed.sh @@ -66,8 +66,8 @@ else check_changes '^(Firestore/(core|third_party)|cmake)' ;; - GoogleDataLogger-*) - check_changes '^(GoogleDataLogger|GoogleDataLogger.podspec)' + GoogleDataTransport-*) + check_changes '^(GoogleDataTransport|GoogleDataTransport.podspec)' ;; *) diff --git a/scripts/install_prereqs.sh b/scripts/install_prereqs.sh index dcdba446788..ff7105c2c2a 100755 --- a/scripts/install_prereqs.sh +++ b/scripts/install_prereqs.sh @@ -73,9 +73,9 @@ case "$PROJECT-$PLATFORM-$METHOD" in bundle exec pod install --project-directory=SymbolCollisionTest --repo-update ;; - GoogleDataLogger-iOS-xcodebuild) + GoogleDataTransport-iOS-xcodebuild) gem install xcpretty - bundle exec pod gen GoogleDataLogger.podspec --gen-directory=GoogleDataLogger/gen + bundle exec pod gen GoogleDataTransport.podspec --gen-directory=GoogleDataTransport/gen ;; *) echo "Unknown project-platform-method combo" 1>&2