diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTClock.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTClock.m index c51e2712256..208aaede20b 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTClock.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTClock.m @@ -118,8 +118,7 @@ - (BOOL)isAfter:(GDTClock *)otherClock { } - (NSUInteger)hash { - // These casts lose some precision, but it's probably fine. - return (NSUInteger)_kernelBootTime ^ (NSUInteger)_uptime ^ (NSUInteger)_timeMillis; + return [@(_kernelBootTime) hash] ^ [@(_uptime) hash] ^ [@(_timeMillis) hash]; } - (BOOL)isEqual:(id)object { diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTEvent.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTEvent.m index b77ead823a7..a4a3c9171e3 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTEvent.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTEvent.m @@ -16,6 +16,8 @@ #import +#import + #import "GDTAssert.h" #import "GDTEvent_Private.h" @@ -61,6 +63,10 @@ - (void)setDataObject:(id)dataObject { } } +- (GDTStoredEvent *)storedEventWithFileURL:(NSURL *)fileURL { + return [[GDTStoredEvent alloc] initWithFileURL:fileURL event:self]; +} + #pragma mark - NSSecureCoding and NSCoding Protocols /** NSCoding key for mappingID property. */ diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.h b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.h index cd7e27b0d9c..ad47c837845 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.h @@ -17,6 +17,7 @@ #import @class GDTEvent; +@class GDTStoredEvent; NS_ASSUME_NONNULL_BEGIN @@ -30,27 +31,17 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)sharedInstance; /** Stores event.dataObjectTransportBytes into a shared on-device folder and tracks the event via - * its hash and target properties. - * - * @note The event param is expected to be deallocated during this method. + * a GDTStoredEvent instance. * * @param event The event to store. */ - (void)storeEvent:(GDTEvent *)event; -/** Removes a set of event from storage specified by their hash. - * - * @param eventHashes The set of event hashes to remove. - * @param target The upload target the event files correspond to. - */ -- (void)removeEvents:(NSSet *)eventHashes target:(NSNumber *)target; - -/** Converts a set of event hashes to a set of event files. +/** Removes a set of events from storage specified by their hash. * - * @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. + * @param events The set of stored events to remove. */ -- (NSDictionary *)eventHashesToFiles:(NSSet *)eventHashes; +- (void)removeEvents:(NSSet *)events; @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m index 1ab827dbf7e..c11d142dd4d 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStorage.m @@ -18,6 +18,7 @@ #import "GDTStorage_Private.h" #import +#import #import "GDTAssert.h" #import "GDTConsoleLogger.h" @@ -55,8 +56,8 @@ - (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]; + _targetToEventSet = [[NSMutableDictionary alloc] init]; + _storedEvents = [[NSMutableOrderedSet alloc] init]; _uploader = [GDTUploadCoordinator sharedInstance]; } return self; @@ -65,94 +66,64 @@ - (instancetype)init { - (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; + NSInteger target = event.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]; + GDTAssert(event.dataObjectTransportBytes, @"The event should have been serialized to bytes"); + NSURL *eventFile = [self saveEventBytesToDisk:event.dataObjectTransportBytes + eventHash:event.hash]; + GDTStoredEvent *storedEvent = [event storedEventWithFileURL:eventFile]; // Add event to tracking collections. - [self addEventToTrackingCollections:shortLivedEvent eventFile:eventFile]; + [self addEventToTrackingCollections:storedEvent]; // 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)]; + if (event.qosTier == GDTEventQoSFast) { + NSSet *allEventsForTarget = self.targetToEventSet[storedEvent.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."); - }; + // Have the prioritizer prioritize the event. + [prioritizer prioritizeEvent:storedEvent]; }); } -- (void)removeEvents:(NSSet *)eventHashes target:(NSNumber *)target { - dispatch_sync(_storageQueue, ^{ - for (NSNumber *eventHash in eventHashes) { - [self removeEvent:eventHash target:target]; - } - }); -} - -- (NSDictionary *)eventHashesToFiles:(NSSet *)eventHashes { - NSMutableDictionary *eventHashesToFiles = [[NSMutableDictionary 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"); - eventHashesToFiles[hashNumber] = eventURL; +- (void)removeEvents:(NSSet *)events { + NSSet *eventsToRemove = [events copy]; + dispatch_async(_storageQueue, ^{ + // Check that a prioritizer is available for this target. + id prioritizer; + + for (GDTStoredEvent *event in eventsToRemove) { + // Remove from disk, first and foremost. + NSError *error; + [[NSFileManager defaultManager] removeItemAtURL:event.eventFileURL error:&error]; + GDTAssert(error == nil, @"There was an error removing an event file: %@", error); + + if (!prioritizer) { + prioritizer = [GDTRegistrar sharedInstance].targetToPrioritizer[event.target]; + } else { + GDTAssert(prioritizer == [GDTRegistrar sharedInstance].targetToPrioritizer[event.target], + @"All logs within an upload set should have the same prioritizer."); + } + + // Remove from the tracking collections. + [self.storedEvents removeObject:event]; + [self.targetToEventSet[event.target] removeObject:event]; } + GDTAssert(prioritizer, @"There's no prioritizer registered for the given target."); + [prioritizer unprioritizeEvents:events]; }); - return eventHashesToFiles; } #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; @@ -179,6 +150,9 @@ - (NSURL *)saveEventBytesToDisk:(NSData *)transportBytes eventHash:(NSUInteger)e NSString *event = [NSString stringWithFormat:@"event-%lu", (unsigned long)eventHash]; NSURL *eventFilePath = [NSURL fileURLWithPath:[storagePath stringByAppendingPathComponent:event]]; + GDTAssert(![[NSFileManager defaultManager] fileExistsAtPath:eventFilePath.path], + @"An event shouldn't already exist at this path: %@", eventFilePath.path); + BOOL writingSuccess = [transportBytes writeToURL:eventFilePath atomically:YES]; if (!writingSuccess) { GDTLogError(GDTMCEFileWriteError, @"An event file could not be written: %@", eventFilePath); @@ -193,29 +167,22 @@ - (NSURL *)saveEventBytesToDisk:(NSData *)transportBytes eventHash:(NSUInteger)e * 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; - } +- (void)addEventToTrackingCollections:(GDTStoredEvent *)event { + [_storedEvents addObject:event]; + NSMutableSet *events = self.targetToEventSet[event.target]; + events = events ? events : [[NSMutableSet alloc] init]; + [events addObject:event]; + _targetToEventSet[event.target] = events; } #pragma mark - NSSecureCoding -/** The NSKeyedCoder key for the eventHashToFile property. */ -static NSString *const kGDTEventHashToFileKey = @"eventHashToFileKey"; +/** The NSKeyedCoder key for the storedEvents property. */ +static NSString *const kGDTStorageStoredEventsKey = @"GDTStorageStoredEventsKey"; -/** The NSKeyedCoder key for the targetToEventHashSet property. */ -static NSString *const kGDTTargetToEventHashSetKey = @"targetToEventHashSetKey"; +/** The NSKeyedCoder key for the targetToEventSet property. */ +static NSString *const kGDTStorageTargetToEventSetKey = @"GDTStorageTargetToEventSetKey"; + (BOOL)supportsSecureCoding { return YES; @@ -225,11 +192,11 @@ - (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]; + sharedInstance->_storedEvents = [aDecoder decodeObjectOfClass:[NSMutableOrderedSet class] + forKey:kGDTStorageStoredEventsKey]; + sharedInstance->_targetToEventSet = + [aDecoder decodeObjectOfClass:[NSMutableDictionary class] + forKey:kGDTStorageTargetToEventSetKey]; }); return sharedInstance; } @@ -237,8 +204,8 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { - (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]; + [aCoder encodeObject:sharedInstance->_storedEvents forKey:kGDTStorageStoredEventsKey]; + [aCoder encodeObject:sharedInstance->_targetToEventSet forKey:kGDTStorageTargetToEventSetKey]; }); } diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTStoredEvent.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStoredEvent.m new file mode 100644 index 00000000000..9c662b0f8e7 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTStoredEvent.m @@ -0,0 +1,84 @@ +/* + * 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 "GDTStoredEvent.h" + +#import + +@implementation GDTStoredEvent + +- (instancetype)initWithFileURL:(NSURL *)URL event:(GDTEvent *)event { + self = [super init]; + if (self) { + _eventFileURL = URL; + _mappingID = event.mappingID; + _target = @(event.target); + _qosTier = event.qosTier; + _clockSnapshot = event.clockSnapshot; + _customPrioritizationParams = event.customPrioritizationParams; + } + return self; +} + +#pragma mark - NSSecureCoding + +/** Coding key for eventFileURL ivar. */ +static NSString *kEventFileURLKey = @"GDTStoredEventEventFileURLKey"; + +/** Coding key for mappingID ivar. */ +static NSString *kMappingIDKey = @"GDTStoredEventMappingIDKey"; + +/** Coding key for target ivar. */ +static NSString *kTargetKey = @"GDTStoredEventTargetKey"; + +/** Coding key for qosTier ivar. */ +static NSString *kQosTierKey = @"GDTStoredEventQosTierKey"; + +/** Coding key for clockSnapshot ivar. */ +static NSString *kClockSnapshotKey = @"GDTStoredEventClockSnapshotKey"; + +/** Coding key for customPrioritizationParams ivar. */ +static NSString *kCustomPrioritizationParamsKey = @"GDTStoredEventcustomPrioritizationParamsKey"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)aCoder { + [aCoder encodeObject:_eventFileURL forKey:kEventFileURLKey]; + [aCoder encodeObject:_mappingID forKey:kMappingIDKey]; + [aCoder encodeObject:_target forKey:kTargetKey]; + [aCoder encodeObject:@(_qosTier) forKey:kQosTierKey]; + [aCoder encodeObject:_clockSnapshot forKey:kClockSnapshotKey]; + [aCoder encodeObject:_customPrioritizationParams forKey:kCustomPrioritizationParamsKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { + self = [self init]; + if (self) { + _eventFileURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kEventFileURLKey]; + _mappingID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kMappingIDKey]; + _target = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:kTargetKey]; + NSNumber *qosTier = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:kQosTierKey]; + _qosTier = [qosTier intValue]; + _clockSnapshot = [aDecoder decodeObjectOfClass:[GDTClock class] forKey:kClockSnapshotKey]; + _customPrioritizationParams = [aDecoder decodeObjectOfClass:[NSDictionary class] + forKey:kCustomPrioritizationParamsKey]; + } + return self; +} + +@end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.m index 903a95bfe2c..0afa6db3536 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransformer.m @@ -21,7 +21,6 @@ #import "GDTAssert.h" #import "GDTConsoleLogger.h" -#import "GDTEvent_Private.h" #import "GDTStorage.h" @implementation GDTTransformer diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransport.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransport.m index b66e6277672..9ae81b6011c 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransport.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTTransport.m @@ -18,8 +18,8 @@ #import "GDTTransport_Private.h" #import "GDTAssert.h" +#import "GDTClock.h" #import "GDTEvent.h" -#import "GDTEvent_Private.h" #import "GDTTransformer.h" @implementation GDTTransport diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.h b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.h index 36f9ee9afbf..aafebd10587 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.h @@ -34,10 +34,10 @@ NS_ASSUME_NONNULL_BEGIN /** 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 eventHashes The set of event hashes to force upload. + * @param events The set of event hashes to force upload. * @param target The target that should force an upload. */ -- (void)forceUploadEvents:(NSSet *)eventHashes target:(GDTTarget)target; +- (void)forceUploadEvents:(NSSet *)events target:(GDTTarget)target; @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m index eb582381d98..137ddc336b6 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m @@ -51,18 +51,19 @@ - (instancetype)init { return self; } -- (void)forceUploadEvents:(NSSet *)eventHashes target:(GDTTarget)target { +- (void)forceUploadEvents:(NSSet *)events target:(GDTTarget)target { dispatch_async(_coordinationQueue, ^{ + GDTLogWarning(GDTMCWForcedUpload, @"%@", @"A high priority event has caused an upload."); NSNumber *targetNumber = @(target); GDTRegistrar *registrar = self->_registrar; GDTUploadCoordinatorForceUploadBlock forceUploadBlock = ^{ - GDTAssert(eventHashes.count, @"It doesn't make sense to force upload of 0 events"); + GDTAssert(events.count, @"It doesn't make sense to force upload of 0 events"); id uploader = registrar.targetToUploader[targetNumber]; GDTUploadPackage *package = [[GDTUploadPackage alloc] init]; - package.eventHashes = [eventHashes copy]; + package.events = [events copy]; GDTAssert(uploader, @"Target '%@' is missing an implementation", targetNumber); [uploader uploadPackage:package onComplete:self.onCompleteBlock]; - self->_targetToInFlightEventSet[targetNumber] = eventHashes; + self->_targetToInFlightEventSet[targetNumber] = events; }; // Enqueue the force upload block if there's an in-flight upload for that target already. @@ -102,10 +103,10 @@ - (GDTUploaderCompletionBlock)onCompleteBlock { return; } strongSelf->_targetToNextUploadTimes[targetNumber] = nextUploadAttemptUTC; - NSSet *eventHashSet = + NSSet *events = [strongSelf->_targetToInFlightEventSet objectForKey:targetNumber]; - GDTAssert(eventHashSet, @"There should be an in-flight event set to remove."); - [strongSelf.storage removeEvents:eventHashSet target:targetNumber]; + GDTAssert(events, @"There should be an in-flight event set to remove."); + [strongSelf.storage removeEvents:events]; [strongSelf->_targetToInFlightEventSet removeObjectForKey:targetNumber]; if (strongSelf->_forcedUploadQueue.count) { GDTUploadCoordinatorForceUploadBlock queuedBlock = @@ -161,10 +162,8 @@ - (void)checkPrioritizersAndUploadEvents { GDTUploadConditions conds = [self uploadConditions]; GDTUploadPackage *package = [[prioritizer uploadPackageWithConditions:conds] copy]; package.storage = strongSelf.storage; - if (package.eventHashes && package.eventHashes.count > 0) { - NSAssert(package.eventHashesToFiles.count == package.eventHashes.count, - @"There should be the same number of files to events"); - strongSelf->_targetToInFlightEventSet[target] = package.eventHashes; + if (package.events && package.events.count > 0) { + strongSelf->_targetToInFlightEventSet[target] = package.events; [uploader uploadPackage:package onComplete:self.onCompleteBlock]; } } diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadPackage.m b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadPackage.m index 6994adb3e1a..3373619487b 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadPackage.m +++ b/GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadPackage.m @@ -31,28 +31,18 @@ - (instancetype)init { - (instancetype)copy { GDTUploadPackage *newPackage = [[GDTUploadPackage alloc] init]; - newPackage->_eventHashes = _eventHashes; + newPackage->_events = [_events copy]; return newPackage; } - (NSUInteger)hash { - return [_eventHashes hash]; + return [_events hash]; } - (BOOL)isEqual:(id)object { return [self hash] == [object hash]; } -- (void)setEventHashes:(NSSet *)eventHashes { - if (eventHashes != _eventHashes) { - _eventHashes = [eventHashes copy]; - } -} - -- (NSDictionary *)eventHashesToFiles { - return [_storage eventHashesToFiles:_eventHashes]; -} - - (void)setStorage:(GDTStorage *)storage { if (storage != _storage) { _storage = storage; diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTStorage_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTStorage_Private.h index be3be88b7b0..6973da149c0 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTStorage_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTStorage_Private.h @@ -25,12 +25,12 @@ NS_ASSUME_NONNULL_BEGIN /** The queue on which all storage work will occur. */ @property(nonatomic) dispatch_queue_t storageQueue; -/** A map of event hashes to their on-disk file URLs. */ -@property(nonatomic) NSMutableDictionary *eventHashToFile; - -/** A map of targets to a set of event hash values. */ +/** A map of targets to a set of stored events. */ @property(nonatomic) - NSMutableDictionary *> *targetToEventHashSet; + NSMutableDictionary *> *targetToEventSet; + +/** All the events that have been stored. */ +@property(readonly, nonatomic) NSMutableOrderedSet *storedEvents; /** The upload coordinator instance to use. */ @property(nonatomic) GDTUploadCoordinator *uploader; diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTUploadCoordinator_Private.h b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTUploadCoordinator_Private.h index 2e145a273e2..65aecbbc0a1 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTUploadCoordinator_Private.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Private/GDTUploadCoordinator_Private.h @@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN /** A map of targets to a set of event hashes that has been handed off to the uploader. */ @property(nonatomic, readonly) - NSMutableDictionary *> *targetToInFlightEventSet; + NSMutableDictionary *> *targetToInFlightEventSet; /** A queue of forced uploads. Only populated if the target already had in-flight events. */ @property(nonatomic, readonly) diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEvent.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEvent.h index 6f0a8b77e21..b7535499927 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEvent.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTEvent.h @@ -19,6 +19,7 @@ #import "GDTEventDataObject.h" @class GDTClock; +@class GDTStoredEvent; NS_ASSUME_NONNULL_BEGIN @@ -63,6 +64,8 @@ typedef NS_ENUM(NSInteger, GDTEventQoS) { /** 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. + * + * @note Ensure that classes contained therein implement NSSecureCoding to prevent loss of data. */ @property(nullable, nonatomic) NSDictionary *customPrioritizationParams; @@ -78,6 +81,13 @@ typedef NS_ENUM(NSInteger, GDTEventQoS) { - (instancetype)initWithMappingID:(NSString *)mappingID target:(NSInteger)target NS_DESIGNATED_INITIALIZER; +/** Returns the GDTStoredEvent equivalent of self. + * + * @param fileURL The file URL of the result of the dataObject's -transportBytes. + * @return An equivalent GDTStoredEvent. + */ +- (GDTStoredEvent *)storedEventWithFileURL:(NSURL *)fileURL; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h index 526b6f750dc..a59da0e8a94 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h @@ -18,7 +18,7 @@ #import -@class GDTEvent; +@class GDTStoredEvent; NS_ASSUME_NONNULL_BEGIN @@ -46,18 +46,19 @@ typedef NS_OPTIONS(NSInteger, GDTUploadConditions) { * 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 - * -uploadPackageWithConditions. - * * @param event The event to prioritize. */ -- (void)prioritizeEvent:(GDTEvent *)event; +- (void)prioritizeEvent:(GDTStoredEvent *)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. +/** Unprioritizes a set of events. This method is called after all the events in the set have been + * removed from storage and from disk. It's passed as a set so that instead of having N blocks + * dispatched to a queue, it can be a single block--this prevents possible race conditions in which + * the storage system has removed the events, but the prioritizers haven't unprioritized the events + * because it was being done one at a time. + * + * @param events The set of events to unprioritize. */ -- (void)unprioritizeEvent:(NSNumber *)eventHash; +- (void)unprioritizeEvents:(NSSet *)events; /** Returns a set of events to upload given a set of conditions. * diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTStoredEvent.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTStoredEvent.h new file mode 100644 index 00000000000..dfd62dc7e18 --- /dev/null +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTStoredEvent.h @@ -0,0 +1,57 @@ +/* + * 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 + +@class GDTEvent; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTStoredEvent : NSObject + +/** The file URL containing the transport bytes of this event. */ +@property(readonly, nonatomic) NSURL *eventFileURL; + +/** 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) NSNumber *target; + +/** The quality of service tier this event belongs to. */ +@property(readonly, nonatomic) GDTEventQoS qosTier; + +/** The clock snapshot at the time of the event. */ +@property(readonly, nonatomic) GDTClock *clockSnapshot; + +/** A dictionary provided to aid prioritizers by allowing the passing of arbitrary data. + * + * @note Ensure that custom classes in this dict implement NSSecureCoding to prevent loss of data. + */ +@property(readonly, nullable, nonatomic) NSDictionary *customPrioritizationParams; + +/** Initializes a stored event with the given URL and event. + * + * @param URL The file URL of the transport bytes associated with the event. + * @param event The event this stored event represents. + * @return An instance of this class. + */ +- (instancetype)initWithFileURL:(NSURL *)URL event:(GDTEvent *)event; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTUploadPackage.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTUploadPackage.h index 08dc4d01321..4a57b924c5b 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTUploadPackage.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTUploadPackage.h @@ -16,13 +16,12 @@ #import +@class GDTStoredEvent; + /** This class is a container that's handed off to uploaders. */ @interface GDTUploadPackage : NSObject -/** The set of event hashes in this upload package. */ -@property(nonatomic) NSSet *eventHashes; - -/** A lazily-determined map of event hashes to their files. */ -@property(nonatomic, readonly) NSDictionary *eventHashesToFiles; +/** The set of stored events in this upload package. */ +@property(nonatomic) NSSet *events; @end diff --git a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GoogleDataTransport.h b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GoogleDataTransport.h index f122e2eb47f..823a0b5e4e3 100644 --- a/GoogleDataTransport/GoogleDataTransport/Classes/Public/GoogleDataTransport.h +++ b/GoogleDataTransport/GoogleDataTransport/Classes/Public/GoogleDataTransport.h @@ -20,5 +20,6 @@ #import "GDTEventTransformer.h" #import "GDTPrioritizer.h" #import "GDTRegistrar.h" +#import "GDTStoredEvent.h" #import "GDTTransport.h" #import "GDTUploader.h" diff --git a/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.h b/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.h index 4db5c23eace..5cc08887e96 100644 --- a/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.h +++ b/GoogleDataTransport/GoogleDataTransport/DependencyWrappers/GDTConsoleLogger.h @@ -33,20 +33,20 @@ typedef NS_ENUM(NSInteger, GDTMessageCode) { /** For warning messages concerning transportBytes: not being implemented by a data object. */ GDTMCWDataObjectMissingBytesImpl = 1, - /** For warning message concerning a failed event upload. */ + /** For warning messages concerning a failed event upload. */ GDTMCWUploadFailed = 2, + /** For warning messages concerning a forced event upload. */ + GDTMCWForcedUpload = 3, + /** For error messages concerning transform: not being implemented by an event transformer. */ GDTMCETransformerDoesntImplementTransform = 1000, - /** For error messages concerning a GDTEvent living past the storeLog: invocation. */ - GDTMCEEventWasIllegallyRetained = 1001, - /** For error messages concerning the creation of a directory failing. */ - GDTMCEDirectoryCreationError = 1002, + GDTMCEDirectoryCreationError = 1001, /** For error messages concerning the writing of a event file. */ - GDTMCEFileWriteError = 1003 + GDTMCEFileWriteError = 1002 }; /** */ diff --git a/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.m b/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.m index 95f884cfda2..6c57fcc162c 100644 --- a/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.m +++ b/GoogleDataTransport/Tests/Common/Categories/GDTStorage+Testing.m @@ -20,8 +20,8 @@ @implementation GDTStorage (Testing) - (void)reset { dispatch_sync(self.storageQueue, ^{ - [self.targetToEventHashSet removeAllObjects]; - [self.eventHashToFile removeAllObjects]; + [self.targetToEventSet removeAllObjects]; + [self.storedEvents removeAllObjects]; }); } diff --git a/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.h b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.h index 0e1b5fe384b..90312dcdd1f 100644 --- a/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.h +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.h @@ -21,9 +21,6 @@ NS_ASSUME_NONNULL_BEGIN /** A functionless fake that can be injected into classes that need it. */ @interface GDTStorageFake : GDTStorage -/** The events to return from -eventHashesToFiles. */ -@property(nonatomic) NSSet *eventsToReturnFromEventHashesToFiles; - @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.m b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.m index 2dd454b0409..eaf5d010e1f 100644 --- a/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.m +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTStorageFake.m @@ -21,15 +21,7 @@ @implementation GDTStorageFake - (void)storeEvent:(GDTEvent *)event { } -- (NSSet *)eventHashesToFiles:(NSSet *)eventHashes { - if (_eventsToReturnFromEventHashesToFiles) { - return _eventsToReturnFromEventHashesToFiles; - } else { - return [[NSSet alloc] init]; - } -} - -- (void)removeEvents:(NSSet *)eventHashes target:(NSNumber *)target { +- (void)removeEvents:(NSSet *)events { } @end diff --git a/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.m b/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.m index 2e10ccaa3ce..184e69d5f0c 100644 --- a/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.m +++ b/GoogleDataTransport/Tests/Common/Fakes/GDTUploadCoordinatorFake.m @@ -18,7 +18,7 @@ @implementation GDTUploadCoordinatorFake -- (void)forceUploadEvents:(NSSet *)eventFiles target:(GDTTarget)target { +- (void)forceUploadEvents:(NSSet *)events target:(GDTTarget)target { self.forceUploadCalled = YES; } diff --git a/GoogleDataTransport/Tests/Integration/GDTIntegrationTest.m b/GoogleDataTransport/Tests/Integration/GDTIntegrationTest.m index e8451401d89..3d796b728c7 100644 --- a/GoogleDataTransport/Tests/Integration/GDTIntegrationTest.m +++ b/GoogleDataTransport/Tests/Integration/GDTIntegrationTest.m @@ -48,7 +48,7 @@ @implementation GDTIntegrationTestTransformer - (GDTEvent *)transform:(GDTEvent *)event { // drop half the events during transforming. - if (arc4random_uniform(2) == 1) { + if (arc4random_uniform(2) == 0) { event = nil; } return event; @@ -76,7 +76,7 @@ @implementation GDTIntegrationTest - (void)tearDown { dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ - XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 0); + XCTAssertEqual([GDTStorage sharedInstance].storedEvents.count, 0); }); } @@ -98,9 +98,10 @@ - (void)testEndToEndEvent { transformers:nil target:kGDTIntegrationTestTarget]; - self.transport2 = [[GDTTransport alloc] initWithMappingID:@"eventMap2" - transformers:nil - target:kGDTIntegrationTestTarget]; + self.transport2 = + [[GDTTransport alloc] initWithMappingID:@"eventMap2" + transformers:@[ [[GDTIntegrationTestTransformer alloc] init] ] + target:kGDTIntegrationTestTarget]; // Create a prioritizer and uploader. self.prioritizer = [[GDTIntegrationTestPrioritizer alloc] init]; @@ -111,16 +112,16 @@ - (void)testEndToEndEvent { [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); + XCTAssertEqual([GDTStorage sharedInstance].storedEvents.count, 0); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventSet.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); + XCTAssertGreaterThan([GDTStorage sharedInstance].storedEvents.count, 0); + XCTAssertGreaterThan([GDTStorage sharedInstance].targetToEventSet.count, 0); }); // Confirm events were sent and received. @@ -144,9 +145,9 @@ - (void)testEndToEndEvent { }); dispatch_resume(timer); - // Run for a bit, a couple seconds longer than the previous bit. + // Run for a bit, several seconds longer than the previous bit. [[NSRunLoop currentRunLoop] - runUntilDate:[NSDate dateWithTimeIntervalSinceNow:lengthOfTestToRunInSeconds + 2]]; + runUntilDate:[NSDate dateWithTimeIntervalSinceNow:lengthOfTestToRunInSeconds + 5]]; [testServer stop]; } @@ -154,7 +155,7 @@ - (void)testEndToEndEvent { /** 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. + // Choose a random transport, and randomly choose if it's a telemetry event. GDTTransport *transport = arc4random_uniform(2) ? self.transport1 : self.transport2; BOOL isTelemetryEvent = arc4random_uniform(2); diff --git a/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.m b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.m index 0e55b2905d8..24e83b12c04 100644 --- a/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.m +++ b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestPrioritizer.m @@ -21,10 +21,10 @@ @interface GDTIntegrationTestPrioritizer () /** Events that are only supposed to be uploaded whilst on wifi. */ -@property(nonatomic) NSMutableSet *wifiOnlyEvents; +@property(nonatomic) NSMutableSet *wifiOnlyEvents; /** Events that can be uploaded on any type of connection. */ -@property(nonatomic) NSMutableSet *nonWifiEvents; +@property(nonatomic) NSMutableSet *nonWifiEvents; /** The queue on which this prioritizer operates. */ @property(nonatomic) dispatch_queue_t queue; @@ -45,22 +45,22 @@ - (instancetype)init { return self; } -- (void)prioritizeEvent:(GDTEvent *)event { - NSUInteger eventHash = event.hash; - NSInteger qosTier = event.qosTier; +- (void)prioritizeEvent:(GDTStoredEvent *)event { dispatch_async(_queue, ^{ - if (qosTier == GDTEventQoSWifiOnly) { - [self.wifiOnlyEvents addObject:@(eventHash)]; + if (event.qosTier == GDTEventQoSWifiOnly) { + [self.wifiOnlyEvents addObject:event]; } else { - [self.nonWifiEvents addObject:@(eventHash)]; + [self.nonWifiEvents addObject:event]; } }); } -- (void)unprioritizeEvent:(NSNumber *)eventHash { +- (void)unprioritizeEvents:(NSSet *)events { dispatch_async(_queue, ^{ - [self.wifiOnlyEvents removeObject:eventHash]; - [self.nonWifiEvents removeObject:eventHash]; + for (GDTStoredEvent *event in events) { + [self.wifiOnlyEvents removeObject:event]; + [self.nonWifiEvents removeObject:event]; + } }); } @@ -69,9 +69,9 @@ - (GDTUploadPackage *)uploadPackageWithConditions:(GDTUploadConditions)condition [[GDTIntegrationTestUploadPackage alloc] init]; dispatch_sync(_queue, ^{ if ((conditions & GDTUploadConditionWifiData) == GDTUploadConditionWifiData) { - uploadPackage.eventHashes = self.wifiOnlyEvents; + uploadPackage.events = self.wifiOnlyEvents; } else { - uploadPackage.eventHashes = self.nonWifiEvents; + uploadPackage.events = self.nonWifiEvents; } }); return uploadPackage; diff --git a/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.m b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.m index 77f306d1a82..96da7e0235d 100644 --- a/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.m +++ b/GoogleDataTransport/Tests/Integration/Helpers/GDTIntegrationTestUploader.m @@ -47,12 +47,12 @@ - (void)uploadPackage:(GDTUploadPackage *)package request.HTTPMethod = @"POST"; NSMutableData *uploadData = [[NSMutableData alloc] init]; - NSLog(@"Uploading batch of %lu events: ", (unsigned long)[package eventHashes].count); + NSLog(@"Uploading batch of %lu events: ", (unsigned long)[package events].count); // In real usage, you'd create an instance of whatever request proto your server needs. - for (NSURL *eventFile in [package.eventHashesToFiles allValues]) { - NSData *fileData = [NSData dataWithContentsOfURL:eventFile]; - NSAssert(fileData, @"A event file shouldn't be empty"); + for (GDTStoredEvent *event in package.events) { + NSData *fileData = [NSData dataWithContentsOfURL:event.eventFileURL]; + NSAssert(fileData, @"An event file shouldn't be empty"); [uploadData appendData:fileData]; } NSURLSessionUploadTask *uploadTask = diff --git a/GoogleDataTransport/Tests/Unit/GDTStorageTest.m b/GoogleDataTransport/Tests/Unit/GDTStorageTest.m index e16185f983a..85ac674b009 100644 --- a/GoogleDataTransport/Tests/Unit/GDTStorageTest.m +++ b/GoogleDataTransport/Tests/Unit/GDTStorageTest.m @@ -17,6 +17,7 @@ #import "GDTTestCase.h" #import +#import #import "GDTEvent_Private.h" #import "GDTRegistrar.h" @@ -77,18 +78,16 @@ - (void)testInit { /** 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)]; + XCTAssertEqual([GDTStorage sharedInstance].storedEvents.count, 1); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventSet[@(target)].count, 1); + NSURL *eventFile = [[GDTStorage sharedInstance].storedEvents lastObject].eventFileURL; XCTAssertNotNil(eventFile); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); NSError *error; @@ -99,104 +98,113 @@ - (void)testStoreEvent { /** 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)]; + eventFile = [[GDTStorage sharedInstance].storedEvents lastObject].eventFileURL; XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); }); - [[GDTStorage sharedInstance] removeEvents:[NSSet setWithObject:@(eventHash)] target:@(target)]; + [[GDTStorage sharedInstance] removeEvents:[GDTStorage sharedInstance].storedEvents.set]; dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); - XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 0); - XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 0); + XCTAssertEqual([GDTStorage sharedInstance].storedEvents.count, 0); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventSet[@(target)].count, 0); }); } /** Tests removing a set of events. */ - (void)testRemoveEvents { GDTStorage *storage = [GDTStorage sharedInstance]; - NSUInteger event1Hash, event2Hash, event3Hash; + __block GDTStoredEvent *storedEvent1, *storedEvent2, *storedEvent3; // 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]); + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + storedEvent1 = [storage.storedEvents lastObject]; + }); event = [[GDTEvent alloc] initWithMappingID:@"100" target:target]; event.dataObjectTransportBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; - event2Hash = event.hash; XCTAssertNoThrow([storage storeEvent:event]); + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + storedEvent2 = [storage.storedEvents lastObject]; + }); event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; event.dataObjectTransportBytes = [@"testString3" dataUsingEncoding:NSUTF8StringEncoding]; - event3Hash = event.hash; XCTAssertNoThrow([storage storeEvent:event]); + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + storedEvent3 = [storage.storedEvents lastObject]; + }); } - NSSet *eventHashSet = - [NSSet setWithObjects:@(event1Hash), @(event2Hash), @(event3Hash), nil]; - NSDictionary *eventFiles = [storage eventHashesToFiles:eventHashSet]; - [storage removeEvents:eventHashSet target:@(target)]; + NSSet *eventSet = + [NSSet setWithObjects:storedEvent1, storedEvent2, storedEvent3, nil]; + [storage removeEvents:eventSet]; 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 allValues]) { - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); + XCTAssertFalse([storage.storedEvents containsObject:storedEvent1]); + XCTAssertFalse([storage.storedEvents containsObject:storedEvent2]); + XCTAssertFalse([storage.storedEvents containsObject:storedEvent3]); + XCTAssertEqual(storage.targetToEventSet[@(target)].count, 0); + for (GDTStoredEvent *event in eventSet) { + XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:event.eventFileURL.path]); } }); } /** Tests storing a few different events. */ - (void)testStoreMultipleEvents { - NSUInteger event1Hash, event2Hash, event3Hash; + __block GDTStoredEvent *storedEvent1, *storedEvent2, *storedEvent3; // 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]); + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + storedEvent1 = [[GDTStorage sharedInstance].storedEvents lastObject]; + }); event = [[GDTEvent alloc] initWithMappingID:@"100" target:target]; event.dataObjectTransportBytes = [@"testString2" dataUsingEncoding:NSUTF8StringEncoding]; - event2Hash = event.hash; XCTAssertNoThrow([[GDTStorage sharedInstance] storeEvent:event]); + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + storedEvent2 = [[GDTStorage sharedInstance].storedEvents lastObject]; + }); 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, ^{ + storedEvent3 = [[GDTStorage sharedInstance].storedEvents lastObject]; + }); } dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ - XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 3); - XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 3); + XCTAssertEqual([GDTStorage sharedInstance].storedEvents.count, 3); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventSet[@(target)].count, 3); - NSURL *event1File = [GDTStorage sharedInstance].eventHashToFile[@(event1Hash)]; + NSURL *event1File = storedEvent1.eventFileURL; 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)]; + NSURL *event2File = storedEvent2.eventFileURL; 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)]; + NSURL *event3File = storedEvent3.eventFileURL; XCTAssertNotNil(event3File); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:event3File.path]); error = nil; @@ -207,21 +215,24 @@ - (void)testStoreMultipleEvents { /** 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]; + __weak GDTEvent *weakEvent; + GDTStoredEvent *storedEvent; + @autoreleasepool { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"404" target:target]; + weakEvent = event; + event.dataObjectTransportBytes = [@"testString" dataUsingEncoding:NSUTF8StringEncoding]; - // Store the event and wait for the expectation. - [[GDTStorage sharedInstance] storeEvent:event]; - [self waitForExpectations:@[ errorExpectation ] timeout:5.0]; + // Store the event and wait for the expectation. + [[GDTStorage sharedInstance] storeEvent:event]; + storedEvent = [event storedEventWithFileURL:[NSURL fileURLWithPath:@"/test"]]; + } + dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ + XCTAssertNil(weakEvent); + XCTAssertNotNil(storedEvent); + }); NSURL *eventFile; - eventFile = [GDTStorage sharedInstance].eventHashToFile[@(event.hash)]; + eventFile = [[GDTStorage sharedInstance].storedEvents lastObject].eventFileURL; // This isn't strictly necessary because of the -waitForExpectations above. dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ @@ -229,12 +240,11 @@ - (void)testEventDeallocationIsEnforced { }); // Ensure event was removed. - NSNumber *eventHash = @(event.hash); - [[GDTStorage sharedInstance] removeEvents:[NSSet setWithObject:eventHash] target:@(target)]; + [[GDTStorage sharedInstance] removeEvents:[GDTStorage sharedInstance].storedEvents.set]; dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); - XCTAssertEqual([GDTStorage sharedInstance].eventHashToFile.count, 0); - XCTAssertEqual([GDTStorage sharedInstance].targetToEventHashSet[@(target)].count, 0); + XCTAssertEqual([GDTStorage sharedInstance].storedEvents.count, 0); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventSet[@(target)].count, 0); }); } @@ -243,40 +253,37 @@ - (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)]); + XCTAssertNotNil([[GDTStorage sharedInstance].storedEvents lastObject]); }); - [[GDTStorage sharedInstance] removeEvents:[NSSet setWithObject:@(eventHash)] target:@(target)]; + [[GDTStorage sharedInstance] removeEvents:[GDTStorage sharedInstance].storedEvents.set]; dispatch_sync([GDTStorage sharedInstance].storageQueue, ^{ - XCTAssertNil([GDTStorage sharedInstance].eventHashToFile[@(eventHash)]); + XCTAssertNil([[GDTStorage sharedInstance].storedEvents lastObject]); }); // TODO(mikehaney24): Ensure that the object created by alloc is discarded? - [NSKeyedUnarchiver unarchiveObjectWithData:storageData]; - XCTAssertNotNil([GDTStorage sharedInstance].eventHashToFile[@(eventHash)]); + GDTStorage *unarchivedStorage = [NSKeyedUnarchiver unarchiveObjectWithData:storageData]; + XCTAssertNotNil([unarchivedStorage.storedEvents lastObject]); } /** 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)]; + XCTAssertEqual([GDTStorage sharedInstance].storedEvents.count, 1); + XCTAssertEqual([GDTStorage sharedInstance].targetToEventSet[@(target)].count, 1); + NSURL *eventFile = [[GDTStorage sharedInstance].storedEvents lastObject].eventFileURL; XCTAssertNotNil(eventFile); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:eventFile.path]); NSError *error; @@ -285,37 +292,4 @@ - (void)testQoSTierFast { }); } -/** 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]; - NSDictionary *eventFiles = [storage eventHashesToFiles:eventHashSet]; - dispatch_sync(storage.storageQueue, ^{ - XCTAssertEqual(eventFiles.count, 3); - XCTAssertEqualObjects(eventFiles[@(event1Hash)], storage.eventHashToFile[@(event1Hash)]); - XCTAssertEqualObjects(eventFiles[@(event2Hash)], storage.eventHashToFile[@(event2Hash)]); - XCTAssertEqualObjects(eventFiles[@(event3Hash)], storage.eventHashToFile[@(event3Hash)]); - }); -} - @end diff --git a/GoogleDataTransport/Tests/Unit/GDTStoredEventTest.m b/GoogleDataTransport/Tests/Unit/GDTStoredEventTest.m new file mode 100644 index 00000000000..026c5234820 --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/GDTStoredEventTest.m @@ -0,0 +1,53 @@ +/* + * 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 "GDTTestCase.h" + +#import +#import + +@interface GDTStoredEventTest : GDTTestCase + +@end + +@implementation GDTStoredEventTest + +/** Tests the default initializer. */ +- (void)testInit { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"testing" target:1]; + event.clockSnapshot = [GDTClock snapshot]; + GDTStoredEvent *storedEvent = [[GDTStoredEvent alloc] initWithFileURL:[NSURL URLWithString:@"1"] + event:event]; + XCTAssertNotNil(storedEvent); +} + +/** Tests encoding and decoding. */ +- (void)testNSSecureCoding { + XCTAssertTrue([GDTStoredEvent supportsSecureCoding]); + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"testing" target:1]; + event.clockSnapshot = [GDTClock snapshot]; + event.qosTier = GDTEventQoSTelemetry; + GDTStoredEvent *storedEvent = [[GDTStoredEvent alloc] initWithFileURL:[NSURL URLWithString:@"1"] + event:event]; + XCTAssertNotNil(storedEvent); + XCTAssertNotNil(storedEvent.mappingID); + XCTAssertNotNil(storedEvent.target); + XCTAssertEqual(storedEvent.qosTier, GDTEventQoSTelemetry); + XCTAssertNotNil(storedEvent.clockSnapshot); + XCTAssertNil(storedEvent.customPrioritizationParams); + XCTAssertNotNil(storedEvent.eventFileURL); +} +@end diff --git a/GoogleDataTransport/Tests/Unit/GDTUploadCoordinatorTest.m b/GoogleDataTransport/Tests/Unit/GDTUploadCoordinatorTest.m index e5955f1e90b..874ebac9ac7 100644 --- a/GoogleDataTransport/Tests/Unit/GDTUploadCoordinatorTest.m +++ b/GoogleDataTransport/Tests/Unit/GDTUploadCoordinatorTest.m @@ -19,6 +19,7 @@ #import "GDTUploadCoordinator.h" #import "GDTUploadCoordinator_Private.h" +#import "GDTEventGenerator.h" #import "GDTRegistrar+Testing.h" #import "GDTStorageFake.h" #import "GDTTestPrioritizer.h" @@ -83,9 +84,7 @@ - (void)testForceUploadEvents { ^(GDTUploadPackage *_Nonnull package, GDTUploaderCompletionBlock _Nonnull completionBlock) { [expectation fulfill]; }; - NSSet *fakeEventSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; - self.storageFake.eventsToReturnFromEventHashesToFiles = fakeEventSet; - NSSet *eventSet = [NSSet setWithObjects:@(1234), nil]; + NSSet *eventSet = [GDTEventGenerator generate3StoredEvents]; XCTAssertNoThrow([[GDTUploadCoordinator sharedInstance] forceUploadEvents:eventSet target:_target]); dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ @@ -102,9 +101,7 @@ - (void)testForceUploadEventsEnqueuesIftargetAlreadyHasEventsInFlight { ^(GDTUploadPackage *_Nonnull package, GDTUploaderCompletionBlock _Nonnull completionBlock) { [expectation fulfill]; }; - NSSet *fakeEventSet = [NSSet setWithObjects:[NSURL URLWithString:@"file:///fake"], nil]; - self.storageFake.eventsToReturnFromEventHashesToFiles = fakeEventSet; - NSSet *eventSet = [NSSet setWithObjects:@(1234), nil]; + NSSet *eventSet = [GDTEventGenerator generate3StoredEvents]; dispatch_sync([GDTUploadCoordinator sharedInstance].coordinationQueue, ^{ [GDTUploadCoordinator sharedInstance].targetToInFlightEventSet[@(self->_target)] = [[NSSet alloc] init]; @@ -146,12 +143,10 @@ - (void)testTimerIsRunningAtDesiredFrequency { /** 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; GDTTestUploadPackage *uploadPackage = [[GDTTestUploadPackage alloc] init]; - uploadPackage.eventHashes = [NSSet setWithObjects:@(1234), nil]; + uploadPackage.events = [GDTEventGenerator generate3StoredEvents]; self.prioritizer.uploadPackage = uploadPackage; self.uploader.uploadEventsBlock = ^(GDTUploadPackage *_Nonnull package, GDTUploaderCompletionBlock _Nonnull completionBlock) { @@ -173,12 +168,10 @@ - (void)testUploadingEventsViaTimer { /** 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; GDTTestUploadPackage *uploadPackage = [[GDTTestUploadPackage alloc] init]; - uploadPackage.eventHashes = [NSSet setWithObjects:@(1234), nil]; + uploadPackage.events = [GDTEventGenerator generate3StoredEvents]; self.prioritizer.uploadPackage = uploadPackage; self.uploader.uploadEventsBlock = ^(GDTUploadPackage *_Nonnull package, GDTUploaderCompletionBlock _Nonnull completionBlock) { diff --git a/GoogleDataTransport/Tests/Unit/GDTUploadPackageTest.m b/GoogleDataTransport/Tests/Unit/GDTUploadPackageTest.m index 22cf65cbaa1..347cb4b4187 100644 --- a/GoogleDataTransport/Tests/Unit/GDTUploadPackageTest.m +++ b/GoogleDataTransport/Tests/Unit/GDTUploadPackageTest.m @@ -18,6 +18,7 @@ #import +#import "GDTEventGenerator.h" #import "GDTUploadPackage_Private.h" @interface GDTUploadPackageTest : GDTTestCase @@ -36,23 +37,26 @@ - (void)testRegisterUpload { GDTUploadPackage *uploadPackage = [[GDTUploadPackage alloc] init]; GDTUploadPackage *uploadPackageCopy = [uploadPackage copy]; XCTAssertNotEqual(uploadPackage, uploadPackageCopy); - XCTAssertEqualObjects(uploadPackage.eventHashes, uploadPackageCopy.eventHashes); + XCTAssertEqualObjects(uploadPackage.events, uploadPackageCopy.events); XCTAssertEqualObjects(uploadPackage, uploadPackageCopy); - uploadPackage.eventHashes = [NSSet set]; + uploadPackage.events = [NSSet set]; uploadPackageCopy = [uploadPackage copy]; XCTAssertNotEqual(uploadPackage, uploadPackageCopy); - XCTAssertEqualObjects(uploadPackage.eventHashes, uploadPackageCopy.eventHashes); + XCTAssertEqualObjects(uploadPackage.events, uploadPackageCopy.events); XCTAssertEqualObjects(uploadPackage, uploadPackageCopy); - NSMutableSet *set = [[NSMutableSet alloc] initWithObjects:@1, @2, @3, nil]; - [set addObject:@4]; - uploadPackage.eventHashes = set; + NSMutableSet *set = [GDTEventGenerator generate3StoredEvents]; + uploadPackage.events = set; uploadPackageCopy = [uploadPackage copy]; XCTAssertNotEqual(uploadPackage, uploadPackageCopy); - [set addObject:@5]; - XCTAssertFalse([uploadPackageCopy.eventHashes containsObject:@5]); - XCTAssertEqualObjects(uploadPackage.eventHashes, uploadPackageCopy.eventHashes); + GDTStoredEvent *newEvent = [[GDTEventGenerator generate3StoredEvents] anyObject]; + [set addObject:newEvent]; + XCTAssertFalse([uploadPackageCopy.events containsObject:newEvent]); + XCTAssertNotEqualObjects(uploadPackage.events, uploadPackageCopy.events); + XCTAssertNotEqualObjects(uploadPackage, uploadPackageCopy); + [set removeObject:newEvent]; + XCTAssertEqualObjects(uploadPackage.events, uploadPackageCopy.events); XCTAssertEqualObjects(uploadPackage, uploadPackageCopy); } diff --git a/GoogleDataTransport/Tests/Unit/Helpers/GDTEventGenerator.h b/GoogleDataTransport/Tests/Unit/Helpers/GDTEventGenerator.h new file mode 100644 index 00000000000..07bbb80a5bb --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTEventGenerator.h @@ -0,0 +1,32 @@ +/* + * 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 + +@class GDTStoredEvent; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTEventGenerator : NSObject + +/** Generates 3 stored events with consecutive clock snapshots. + * + * @return A set of 3 generated stored events. + */ ++ (NSMutableSet *)generate3StoredEvents; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/Tests/Unit/Helpers/GDTEventGenerator.m b/GoogleDataTransport/Tests/Unit/Helpers/GDTEventGenerator.m new file mode 100644 index 00000000000..8f0aaa8475b --- /dev/null +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTEventGenerator.m @@ -0,0 +1,49 @@ +/* + * 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 "GDTEventGenerator.h" + +#import +#import +#import + +#import "GDTEvent_Private.h" + +@implementation GDTEventGenerator + ++ (NSMutableSet *)generate3StoredEvents { + static NSUInteger counter = 0; + NSString *cachePath = NSTemporaryDirectory(); + NSString *filePath = + [cachePath stringByAppendingPathComponent:[NSString stringWithFormat:@"test-%ld.txt", + (unsigned long)counter]]; + int howManyToGenerate = 3; + NSMutableSet *set = [[NSMutableSet alloc] initWithCapacity:howManyToGenerate]; + for (int i = 0; i < howManyToGenerate; i++) { + GDTEvent *event = [[GDTEvent alloc] initWithMappingID:@"1337" target:50]; + event.clockSnapshot = [GDTClock snapshot]; + event.qosTier = GDTEventQosDefault; + event.dataObjectTransportBytes = [@"testing!" dataUsingEncoding:NSUTF8StringEncoding]; + [[NSFileManager defaultManager] createFileAtPath:filePath + contents:[NSData data] + attributes:nil]; + [set addObject:[event storedEventWithFileURL:[NSURL fileURLWithPath:filePath]]]; + counter++; + } + return set; +} + +@end diff --git a/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.h b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.h index 7b061148c21..bdb3bff0c4e 100644 --- a/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.h +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable, nonatomic) GDTUploadPackage *uploadPackage; /** Allows the running of a block of code during -prioritizeEvent. */ -@property(nullable, nonatomic) void (^prioritizeEventBlock)(GDTEvent *event); +@property(nullable, nonatomic) void (^prioritizeEventBlock)(GDTStoredEvent *event); /** A block that can run before -uploadPackageWithConditions completes. */ @property(nullable, nonatomic) void (^uploadPackageWithConditionsBlock)(void); diff --git a/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.m b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.m index 99da6d99947..28ea28f48a8 100644 --- a/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.m +++ b/GoogleDataTransport/Tests/Unit/Helpers/GDTTestPrioritizer.m @@ -35,13 +35,13 @@ - (GDTUploadPackage *)uploadPackageWithConditions:(GDTUploadConditions)condition return _uploadPackage; } -- (void)prioritizeEvent:(GDTEvent *)event { +- (void)prioritizeEvent:(GDTStoredEvent *)event { if (_prioritizeEventBlock) { _prioritizeEventBlock(event); } } -- (void)unprioritizeEvent:(nonnull NSNumber *)eventHash { +- (void)unprioritizeEvents:(NSSet *)events { } @end