./codecov.yml LICENSE SPTPersistentCache.podspec SPTPersistentCache.xcodeproj/project.pbxproj SPTPersistentCache.xcodeproj/xcshareddata/xcschemes/SPTPersistentCache.xcscheme SPTPersistentCache.xcworkspace/contents.xcworkspacedata SPTPersistentCache.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist SPTPersistentCacheDemo/SPTPersistentCacheDemo.xcodeproj/project.pbxproj SPTPersistentCacheDemo/SPTPersistentCacheDemo.xcodeproj/xcshareddata/xcschemes/SPTPersistentCacheDemo.xcscheme SPTPersistentCacheDemo/SPTPersistentCacheDemo/AppDelegate.h SPTPersistentCacheDemo/SPTPersistentCacheDemo/AppDelegate.m SPTPersistentCacheDemo/SPTPersistentCacheDemo/Assets.xcassets/AppIcon.appiconset/Contents.json SPTPersistentCacheDemo/SPTPersistentCacheDemo/Base.lproj/LaunchScreen.storyboard SPTPersistentCacheDemo/SPTPersistentCacheDemo/Base.lproj/Main.storyboard SPTPersistentCacheDemo/SPTPersistentCacheDemo/DetailViewController.h SPTPersistentCacheDemo/SPTPersistentCacheDemo/DetailViewController.m SPTPersistentCacheDemo/SPTPersistentCacheDemo/Info.plist SPTPersistentCacheDemo/SPTPersistentCacheDemo/MasterViewController.h SPTPersistentCacheDemo/SPTPersistentCacheDemo/MasterViewController.m SPTPersistentCacheDemo/SPTPersistentCacheDemo/main.m SPTPersistentCacheFramework/Info.plist SPTPersistentCacheFramework/SPTPersistentCacheFramework.xcodeproj/project.pbxproj SPTPersistentCacheFramework/SPTPersistentCacheFramework.xcodeproj/xcshareddata/xcschemes/SPTPersistentCache-OSX.xcscheme SPTPersistentCacheFramework/SPTPersistentCacheFramework.xcodeproj/xcshareddata/xcschemes/SPTPersistentCache-iOS.xcscheme Sources/NSError+SPTPersistentCacheDomainErrors.h Sources/NSError+SPTPersistentCacheDomainErrors.m Sources/SPTPersistentCache+Private.h Sources/SPTPersistentCache.m Sources/SPTPersistentCacheDebugUtilities.h Sources/SPTPersistentCacheDebugUtilities.m Sources/SPTPersistentCacheFileManager+Private.h Sources/SPTPersistentCacheFileManager.h Sources/SPTPersistentCacheFileManager.m Sources/SPTPersistentCacheGarbageCollector.h Sources/SPTPersistentCacheGarbageCollector.m Sources/SPTPersistentCacheHeader.m Sources/SPTPersistentCacheObjectDescription.h Sources/SPTPersistentCacheObjectDescription.m Sources/SPTPersistentCacheOptions.m Sources/SPTPersistentCachePosixWrapper.h Sources/SPTPersistentCachePosixWrapper.m Sources/SPTPersistentCacheRecord+Private.h Sources/SPTPersistentCacheRecord.m Sources/SPTPersistentCacheResponse+Private.h Sources/SPTPersistentCacheResponse.m Sources/SPTPersistentCacheTypeUtilities.h Sources/SPTPersistentCacheTypeUtilities.m Sources/crc32iso3309.c Sources/crc32iso3309.h Tests/Info.plist Tests/NSError+SPTPersistentCacheDomainErrorsTests.m Tests/NSFileManagerMock.h Tests/NSFileManagerMock.m Tests/Resources/aad0e75ab0a6828d0a9b37a68198cc9d70d84850 Tests/Resources/ab3d97d4d7b3df5417490aa726c5a49b9ee98038 Tests/Resources/b02c1be08c00bac5f4f1a62c6f353a24487bb024 Tests/Resources/b3e04bf446b486412a13659af71e3a333c6152f4 Tests/Resources/b53aed36cdc67dd43b496db74843ac32fe1f64bb Tests/Resources/b91998ae68b9639cee6243df0886d69bdeb75854 Tests/Resources/c3b19963fc076930dd36ce3968757704bbc97357 Tests/Resources/c5aec3eef2478bfe47aef16787a6b4df31eb45f2 Tests/Resources/e5a1921f8f75d42412e08aff4da33e1132f7ee8a Tests/Resources/e5b8abdc091921d49e86687e28b74abb3139df70 Tests/Resources/ee678d23b8dba2997c52741e88fa7a1fdeaf2863 Tests/Resources/ee6b44ab07fa3937a6d37f449355b64c09677295 Tests/Resources/eee9747e967c4440eb90bb812d148aa3d0056700 Tests/Resources/f1eeb834607dcc2b01909bd740d4356f2abb4cd1 Tests/Resources/f50512901688b79a7852999d384d097a71fad788 Tests/Resources/f7501f27f70162a9a7da196c5d2ece3151a2d80a Tests/Resources/fc22d4f65c1ba875f6bb5ba7d35a7fd12851ed5c Tests/SPTPersistentCacheDebugUtilitiesTests.m Tests/SPTPersistentCacheFileManagerTests.m Tests/SPTPersistentCacheGarbageCollectorTests.m Tests/SPTPersistentCacheHeaderTests.m Tests/SPTPersistentCacheObjectDescriptionStyleValidator.h Tests/SPTPersistentCacheObjectDescriptionStyleValidator.m Tests/SPTPersistentCacheObjectDescriptionTests.m Tests/SPTPersistentCacheOptionsTests.m Tests/SPTPersistentCachePerformanceTests.m Tests/SPTPersistentCachePosixWrapperMock.h Tests/SPTPersistentCachePosixWrapperMock.m Tests/SPTPersistentCacheRecordTests.m Tests/SPTPersistentCacheResponseTests.m Tests/SPTPersistentCacheTests.m Viewer/AppDelegate.h Viewer/AppDelegate.m Viewer/Base.lproj/MainMenu.xib Viewer/Images.xcassets/AppIcon.appiconset/Contents.json Viewer/Info.plist Viewer/MainWindowController.h Viewer/MainWindowController.m Viewer/MainWindowController.xib Viewer/SPTPersistentCacheViewer.xcodeproj/project.pbxproj Viewer/SPTPersistentCacheViewer.xcodeproj/xcshareddata/xcschemes/SPTPersistentCacheViewer.xcscheme Viewer/main.m ci/run.sh ci/spotify_os.xcconfig ci/validate_license_conformance.sh include/SPTPersistentCache/SPTPersistentCache.h include/SPTPersistentCache/SPTPersistentCacheHeader.h include/SPTPersistentCache/SPTPersistentCacheImplementation.h include/SPTPersistentCache/SPTPersistentCacheOptions.h include/SPTPersistentCache/SPTPersistentCacheRecord.h include/SPTPersistentCache/SPTPersistentCacheResponse.h include/SPTPersistentCache/module.modulemap project.xcconfig <<<<<< network # path=./SPTPersistentCacheTests.xctest.coverage.txt /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/NSError+SPTPersistentCacheDomainErrors.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "NSError+SPTPersistentCacheDomainErrors.h" 22| | 23| |@implementation NSError (SPTPersistentCacheDomainErrors) 24| | 25| |+ (instancetype)spt_persistentDataCacheErrorWithCode:(SPTPersistentCacheLoadingError)persistentDataCacheLoadingError 26| 86|{ 27| 86| return [NSError errorWithDomain:SPTPersistentCacheErrorDomain 28| 86| code:persistentDataCacheLoadingError 29| 86| userInfo:nil]; 30| 86|} 31| | 32| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCache.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "SPTPersistentCache+Private.h" 22| | 23| |#import 24| |#import 25| |#import 26| |#import 27| | 28| |#import "SPTPersistentCacheRecord+Private.h" 29| |#import "SPTPersistentCacheResponse+Private.h" 30| |#import "SPTPersistentCacheGarbageCollector.h" 31| |#import "NSError+SPTPersistentCacheDomainErrors.h" 32| |#import "SPTPersistentCacheFileManager.h" 33| |#import "SPTPersistentCacheTypeUtilities.h" 34| |#import "SPTPersistentCacheDebugUtilities.h" 35| |#import "SPTPersistentCachePosixWrapper.h" 36| | 37| |#include 38| |#import 39| | 40| |#include "crc32iso3309.h" 41| | 42| |// Enable for more precise logging 43| |//#define DEBUG_OUTPUT_ENABLED 44| | 45| |typedef SPTPersistentCacheResponse* (^SPTPersistentCacheFileProcessingBlockType)(int filedes); 46| |typedef void (^SPTPersistentCacheRecordHeaderGetCallbackType)(SPTPersistentCacheRecordHeader *header); 47| | 48| |NSString *const SPTPersistentCacheErrorDomain = @"persistent.cache.error"; 49| |static NSString * const SPTDataCacheFileNameKey = @"SPTDataCacheFileNameKey"; 50| |static NSString * const SPTDataCacheFileAttributesKey = @"SPTDataCacheFileAttributesKey"; 51| | 52| |static const uint64_t SPTPersistentCacheTTLUpperBoundInSec = 86400 * 31 * 2; 53| | 54| |void SPTPersistentCacheSafeDispatch(_Nullable dispatch_queue_t queue, _Nonnull dispatch_block_t block) 55| 1.16k|{ 56| 1.16k| const dispatch_queue_t dispatchQueue = queue ?: dispatch_get_main_queue(); 57| 1.16k| if (dispatchQueue == dispatch_get_main_queue() && [NSThread isMainThread]) { 58| 3| block(); 59| 1.16k| } else { 60| 1.16k| dispatch_async(dispatchQueue, block); 61| 1.16k| } 62| 1.16k|} 63| | 64| |// Class extension exists in SPTPersistentCache+Private.h 65| | 66| |#pragma mark - SPTPersistentCache 67| | 68| |@implementation SPTPersistentCache 69| | 70| |- (instancetype)init 71| 7|{ 72| 7| return [self initWithOptions:[SPTPersistentCacheOptions new]]; 73| 7|} 74| | 75| |- (instancetype)initWithOptions:(SPTPersistentCacheOptions *)options 76| 85|{ 77| 85| self = [super init]; 78| 85| if (self) { 79| 85| _workQueue = [[NSOperationQueue alloc] init]; 80| 85| _workQueue.name = options.identifierForQueue; 81| 85| _workQueue.maxConcurrentOperationCount = options.maxConcurrentOperations; 82| 85| NSAssert(_workQueue, @"The work queue couldn’t be created using the given options: %@", options); 83| 85| 84| 85| _options = [options copy]; 85| 85| _fileManager = [NSFileManager defaultManager]; 86| 85| _debugOutput = [self.options.debugOutput copy]; 87| 85| _dataCacheFileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:_options]; 88| 85| _posixWrapper = [SPTPersistentCachePosixWrapper new]; 89| 85| _garbageCollector = [[SPTPersistentCacheGarbageCollector alloc] initWithCache:self 90| 85| options:_options 91| 85| queue:_workQueue]; 92| 85| 93| 85| 94| 85| if (![_dataCacheFileManager createCacheDirectory]) { 95| 1| return nil; 96| 1| } 97| 84| } 98| 84| return self; 99| 84|} 100| | 101| |- (BOOL)loadDataForKey:(NSString *)key 102| | withCallback:(SPTPersistentCacheResponseCallback _Nullable)callback 103| | onQueue:(dispatch_queue_t _Nullable)queue 104| 156|{ 105| 156| if (callback == nil || queue == nil) { 106| 1| return NO; 107| 1| } 108| 155| 109| 155| callback = [callback copy]; 110| 155| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeRead type:SPTPersistentCacheDebugTimingTypeQueued]; 111| 155| [self doWork:^{ 112| 155| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeRead type:SPTPersistentCacheDebugTimingTypeStarting]; 113| 155| [self loadDataForKeySync:key withCallback:callback onQueue:queue]; 114| 155| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeRead type:SPTPersistentCacheDebugTimingTypeFinished]; 115| 155| } priority:self.options.readPriority qos:self.options.readQualityOfService]; 116| 155| return YES; 117| 155|} 118| | 119| |- (BOOL)loadDataForKeysWithPrefix:(NSString *)prefix 120| | chooseKeyCallback:(SPTPersistentCacheChooseKeyCallback _Nullable)chooseKeyCallback 121| | withCallback:(SPTPersistentCacheResponseCallback _Nullable)callback 122| | onQueue:(dispatch_queue_t _Nullable)queue 123| 6|{ 124| 6| if (callback == nil || queue == nil || chooseKeyCallback == nil) { 125| 1| return NO; 126| 1| } 127| 5| [self logTimingForKey:prefix method:SPTPersistentCacheDebugMethodTypeRead type:SPTPersistentCacheDebugTimingTypeQueued]; 128| 5| [self doWork:^{ 129| 5| [self logTimingForKey:prefix method:SPTPersistentCacheDebugMethodTypeRead type:SPTPersistentCacheDebugTimingTypeStarting]; 130| 5| NSString *path = [self.dataCacheFileManager subDirectoryPathForKey:prefix]; 131| 5| NSMutableArray * __block keys = [NSMutableArray array]; 132| 5| 133| 5| // WARNING: Do not use enumeratorAtURL never ever. Its unsafe bcuz gets locked forever 134| 5| NSError *error = nil; 135| 5| NSArray *content = [self.fileManager contentsOfDirectoryAtPath:path error:&error]; 136| 5| 137| 5| if (content == nil) { 138| 2| // If no directory is exist its fine, say not found to user 139| 2| if (error.code == NSFileReadNoSuchFileError || error.code == NSFileNoSuchFileError) { 140| 1| [self dispatchEmptyResponseWithResult:SPTPersistentCacheResponseCodeNotFound 141| 1| callback:callback 142| 1| onQueue:queue]; 143| 1| } else { 144| 1| [self debugOutput:@"PersistentDataCache: Unable to get dir contents: %@, error: %@", path, [error localizedDescription]]; 145| 1| [self dispatchError:error 146| 1| result:SPTPersistentCacheResponseCodeOperationError 147| 1| callback:callback 148| 1| onQueue:queue]; 149| 1| } 150| 2| return; 151| 2| } 152| 3| 153| 17| [content enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) { 154| 17| NSString *file = key; 155| 17| if ([file hasPrefix:prefix]) { 156| 3| [keys addObject:file]; 157| 3| } 158| 17| }]; 159| 3| 160| 3| NSMutableArray * __block keysToConsider = [NSMutableArray array]; 161| 3| 162| 3| // Validate keys for expiration before giving it back to caller. Its important since giving expired keys 163| 3| // is wrong since caller can miss data that are no expired by picking expired key. 164| 3| for (NSString *key in keys) { 165| 3| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 166| 3| 167| 3| // WARNING: We may skip return result here bcuz in that case we will skip the key as invalid 168| 3| [self alterHeaderForFileAtPath:filePath withBlock:^(SPTPersistentCacheRecordHeader *header) { 169| 3| // Satisfy Req.#1.2 170| 3| if ([self isDataCanBeReturnedWithHeader:header]) { 171| 3| [keysToConsider addObject:key]; 172| 3| } 173| 3| } writeBack:NO complain:YES]; 174| 3| } 175| 3| 176| 3| // If not keys left after validation we are done with not found callback 177| 3| if (keysToConsider.count == 0) { 178| 1| [self dispatchEmptyResponseWithResult:SPTPersistentCacheResponseCodeNotFound 179| 1| callback:callback 180| 1| onQueue:queue]; 181| 1| return; 182| 1| } 183| 2| 184| 2| NSString *keyToOpen = chooseKeyCallback(keysToConsider); 185| 2| 186| 2| // If user told us 'nil' he didnt found abything interesting in keys so we are done wiht not found 187| 2| if (keyToOpen == nil) { 188| 1| [self dispatchEmptyResponseWithResult:SPTPersistentCacheResponseCodeNotFound 189| 1| callback:callback 190| 1| onQueue:queue]; 191| 1| return; 192| 1| } 193| 1| 194| 1| [self loadDataForKeySync:keyToOpen withCallback:callback onQueue:queue]; 195| 1| [self logTimingForKey:prefix method:SPTPersistentCacheDebugMethodTypeRead type:SPTPersistentCacheDebugTimingTypeFinished]; 196| 1| } priority:self.options.readPriority qos:self.options.readQualityOfService]; 197| 5| 198| 5| return YES; 199| 5|} 200| | 201| |- (BOOL)storeData:(NSData *)data 202| | forKey:(NSString *)key 203| | locked:(BOOL)locked 204| | withCallback:(SPTPersistentCacheResponseCallback _Nullable)callback 205| | onQueue:(dispatch_queue_t _Nullable)queue 206| | 207| 693|{ 208| 693| return [self storeData:data forKey:key ttl:0 locked:locked withCallback:callback onQueue:queue]; 209| 693|} 210| | 211| |- (BOOL)storeData:(NSData *)data 212| | forKey:(NSString *)key 213| | ttl:(NSUInteger)ttl 214| | locked:(BOOL)locked 215| | withCallback:(SPTPersistentCacheResponseCallback _Nullable)callback 216| | onQueue:(dispatch_queue_t _Nullable)queue 217| | 218| 909|{ 219| 909| if (data == nil || key == nil || (callback != nil && queue == nil)) { 220| 1| return NO; 221| 1| } 222| 908| 223| 908| callback = [callback copy]; 224| 908| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeStore type:SPTPersistentCacheDebugTimingTypeQueued]; 225| 908| [self doWork:^{ 226| 908| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeStore type:SPTPersistentCacheDebugTimingTypeStarting]; 227| 908| [self storeDataSync:data forKey:key ttl:ttl locked:locked withCallback:callback onQueue:queue]; 228| 908| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeStore type:SPTPersistentCacheDebugTimingTypeFinished]; 229| 908| } priority:self.options.writePriority qos:self.options.writeQualityOfService]; 230| 908| return YES; 231| 908|} 232| | 233| | 234| |// TODO: return NOT_PERMITTED on try to touch TLL>0 235| |- (void)touchDataForKey:(NSString *)key 236| | callback:(SPTPersistentCacheResponseCallback _Nullable)callback 237| | onQueue:(dispatch_queue_t _Nullable)queue 238| 24|{ 239| 24| if (callback != nil) { 240| 23| NSAssert(queue, @"You must specify the queue"); 241| 23| } 242| 24| 243| 24| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeStore type:SPTPersistentCacheDebugTimingTypeQueued]; 244| 24| [self doWork:^{ 245| 24| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeStore type:SPTPersistentCacheDebugTimingTypeStarting]; 246| 24| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 247| 24| 248| 24| BOOL __block expired = NO; 249| 24| 250| 24| SPTPersistentCacheResponse *response = [self alterHeaderForFileAtPath:filePath 251| 24| withBlock:^(SPTPersistentCacheRecordHeader *header) { 252| 18| // Satisfy Req.#1.2 and Req.#1.3 253| 18| if (![self isDataCanBeReturnedWithHeader:header]) { 254| 0| expired = YES; 255| 0| return; 256| 0| } 257| 18| // Touch files that have default expiration policy 258| 18| if (header->ttl == 0) { 259| 13| header->updateTimeSec = spt_uint64rint(self.currentDateTimeInterval); 260| 13| } 261| 18| } 262| 24| writeBack:YES 263| 24| complain:NO]; 264| 24| 265| 24| // Satisfy Req.#1.2 266| 24| if (expired) { 267| 0| response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeNotFound 268| 0| error:nil 269| 0| record:nil]; 270| 0| } 271| 24| 272| 24| if (callback) { 273| 23| SPTPersistentCacheSafeDispatch(queue, ^{ 274| 23| callback(response); 275| 23| }); 276| 23| } 277| 24| [self logTimingForKey:key method:SPTPersistentCacheDebugMethodTypeStore type:SPTPersistentCacheDebugTimingTypeFinished]; 278| 24| } priority:self.options.writePriority qos:self.options.writeQualityOfService]; 279| 24|} 280| | 281| |- (void)removeDataForKeysSync:(NSArray *)keys 282| 2|{ 283| 18| for (NSString *key in keys) { 284| 18| [self.dataCacheFileManager removeDataForKey:key]; 285| 18| } 286| 2|} 287| | 288| |- (void)removeDataForKeys:(NSArray *)keys 289| | callback:(SPTPersistentCacheResponseCallback _Nullable)callback 290| | onQueue:(dispatch_queue_t _Nullable)queue 291| 1|{ 292| 1| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeQueued]; 293| 1| [self doWork:^{ 294| 1| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeStarting]; 295| 1| 296| 1| [self removeDataForKeysSync:keys]; 297| 1| if (callback) { 298| 1| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 299| 1| error:nil 300| 1| record:nil]; 301| 1| SPTPersistentCacheSafeDispatch(queue, ^{ 302| 1| callback(response); 303| 1| }); 304| 1| } 305| 1| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeFinished]; 306| 1| } priority:self.options.deletePriority qos:self.options.deleteQualityOfService]; 307| 1| 308| 1|} 309| | 310| |- (BOOL)lockDataForKeys:(NSArray *)keys 311| | callback:(SPTPersistentCacheResponseCallback _Nullable)callback 312| | onQueue:(dispatch_queue_t _Nullable)queue 313| 3|{ 314| 3| if ((callback != nil && queue == nil) || keys.count == 0) { 315| 1| return NO; 316| 1| } 317| 2| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeLock type:SPTPersistentCacheDebugTimingTypeQueued]; 318| 2| [self doWork:^{ 319| 2| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeLock type:SPTPersistentCacheDebugTimingTypeStarting]; 320| 14| for (NSString *key in keys) { 321| 14| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 322| 14| BOOL __block expired = NO; 323| 14| SPTPersistentCacheResponse *response = [self alterHeaderForFileAtPath:filePath 324| 14| withBlock:^(SPTPersistentCacheRecordHeader *header) { 325| 10| // Satisfy Req.#1.2 326| 10| if ([self isDataExpiredWithHeader:header]) { 327| 1| expired = YES; 328| 1| return; 329| 1| } 330| 9| ++header->refCount; 331| 9| // Do not update access time since file is locked 332| 9| } 333| 14| writeBack:YES 334| 14| complain:YES]; 335| 14| // Satisfy Req.#1.2 336| 14| if (expired) { 337| 1| response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeNotFound 338| 1| error:nil 339| 1| record:nil]; 340| 1| } 341| 14| if (callback) { 342| 14| SPTPersistentCacheSafeDispatch(queue, ^{ 343| 14| callback(response); 344| 14| }); 345| 14| } 346| 14| 347| 14| } // for 348| 2| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeLock type:SPTPersistentCacheDebugTimingTypeFinished]; 349| 2| } priority:self.options.writePriority qos:self.options.writeQualityOfService]; 350| 2| return YES; 351| 2|} 352| | 353| |- (BOOL)unlockDataForKeys:(NSArray *)keys 354| | callback:(SPTPersistentCacheResponseCallback _Nullable)callback 355| | onQueue:(dispatch_queue_t _Nullable)queue 356| 53|{ 357| 53| if ((callback != nil && queue == nil) || keys.count == 0) { 358| 1| return NO; 359| 1| } 360| 52| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeUnlock type:SPTPersistentCacheDebugTimingTypeQueued]; 361| 52| [self doWork:^{ 362| 52| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeUnlock type:SPTPersistentCacheDebugTimingTypeStarting]; 363| 55| for (NSString *key in keys) { 364| 55| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 365| 55| SPTPersistentCacheResponse *response = [self alterHeaderForFileAtPath:filePath 366| 55| withBlock:^(SPTPersistentCacheRecordHeader *header){ 367| 55| if (header->refCount > 0) { 368| 6| --header->refCount; 369| 49| } else { 370| 49| [self debugOutput:@"PersistentDataCache: Error trying to decrement refCount below 0 for file at path:%@", filePath]; 371| 49| } 372| 55| } 373| 55| writeBack:YES 374| 55| complain:YES]; 375| 55| if (callback) { 376| 54| SPTPersistentCacheSafeDispatch(queue, ^{ 377| 54| callback(response); 378| 54| }); 379| 54| } 380| 55| } // for 381| 52| [self logTimingForKey:[keys description] method:SPTPersistentCacheDebugMethodTypeUnlock type:SPTPersistentCacheDebugTimingTypeFinished]; 382| 52| } priority:self.options.deletePriority qos:self.options.deleteQualityOfService]; 383| 52| return YES; 384| 52|} 385| | 386| |- (void)scheduleGarbageCollector 387| 2|{ 388| 2| [self.garbageCollector schedule]; 389| 2|} 390| | 391| |- (void)unscheduleGarbageCollector 392| 1|{ 393| 1| [self.garbageCollector unschedule]; 394| 1|} 395| | 396| |- (void)pruneWithCallback:(SPTPersistentCacheResponseCallback _Nullable)callback 397| | onQueue:(dispatch_queue_t _Nullable)queue 398| 1|{ 399| 1| [self logTimingForKey:@"prune" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeQueued]; 400| 1| [self doWork:^{ 401| 1| [self logTimingForKey:@"prune" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeStarting]; 402| 1| [self.dataCacheFileManager removeAllData]; 403| 1| if (callback) { 404| 1| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 405| 1| error:nil 406| 1| record:nil]; 407| 1| SPTPersistentCacheSafeDispatch(queue, ^{ 408| 1| callback(response); 409| 1| }); 410| 1| } 411| 1| [self logTimingForKey:@"prune" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeFinished]; 412| 1| } priority:self.options.deletePriority qos:self.options.deleteQualityOfService]; 413| 1|} 414| | 415| |- (void)wipeLockedFilesWithCallback:(SPTPersistentCacheResponseCallback _Nullable)callback 416| | onQueue:(dispatch_queue_t _Nullable)queue 417| 1|{ 418| 1| [self logTimingForKey:@"wipeLocked" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeQueued]; 419| 1| [self doWork:^{ 420| 1| [self logTimingForKey:@"wipeLocked" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeStarting]; 421| 1| [self collectGarbageForceExpire:NO forceLocked:YES]; 422| 1| if (callback) { 423| 1| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 424| 1| error:nil 425| 1| record:nil]; 426| 1| SPTPersistentCacheSafeDispatch(queue, ^{ 427| 1| callback(response); 428| 1| }); 429| 1| } 430| 1| [self logTimingForKey:@"wipeLocked" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeFinished]; 431| 1| } priority:self.options.deletePriority qos:self.options.deleteQualityOfService]; 432| 1| 433| 1|} 434| | 435| |- (void)wipeNonLockedFilesWithCallback:(SPTPersistentCacheResponseCallback _Nullable)callback 436| | onQueue:(dispatch_queue_t _Nullable)queue 437| 1|{ 438| 1| [self logTimingForKey:@"wipeNonLocked" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeQueued]; 439| 1| [self doWork:^{ 440| 1| [self logTimingForKey:@"wipeNonLocked" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeStarting]; 441| 1| [self collectGarbageForceExpire:YES forceLocked:NO]; 442| 1| if (callback) { 443| 1| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 444| 1| error:nil 445| 1| record:nil]; 446| 1| SPTPersistentCacheSafeDispatch(queue, ^{ 447| 1| callback(response); 448| 1| }); 449| 1| } 450| 1| [self logTimingForKey:@"wipeNonLocked" method:SPTPersistentCacheDebugMethodTypeRemove type:SPTPersistentCacheDebugTimingTypeFinished]; 451| 1| } priority:self.options.deletePriority qos:self.options.deleteQualityOfService]; 452| 1|} 453| | 454| |- (NSUInteger)totalUsedSizeInBytes 455| 3|{ 456| 3| return self.dataCacheFileManager.totalUsedSizeInBytes; 457| 3|} 458| | 459| |- (NSUInteger)lockedItemsSizeInBytes 460| 7|{ 461| 7| NSUInteger size = 0; 462| 7| NSURL *urlPath = [NSURL fileURLWithPath:self.options.cachePath]; 463| 7| NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtURL:urlPath 464| 7| includingPropertiesForKeys:@[NSURLIsDirectoryKey] 465| 7| options:NSDirectoryEnumerationSkipsHiddenFiles 466| 7| errorHandler:nil]; 467| 7| 468| 7| // Enumerate the dirEnumerator results, each value is stored in allURLs 469| 7| NSURL *theURL = nil; 470| 215| while ((theURL = [dirEnumerator nextObject])) { 471| 208| 472| 208| // Retrieve the file name. From cached during the enumeration. 473| 208| NSNumber *isDirectory; 474| 208| NSError *error = nil; 475| 208| if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) { 476| 146| if ([isDirectory boolValue] == NO) { 477| 74| 478| 74| NSString *key = theURL.lastPathComponent; 479| 74| // That satisfies Req.#1.3 480| 74| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 481| 74| BOOL __block locked = NO; 482| 74| // WARNING: We may skip return result here bcuz in that case we will not count file as locked 483| 74| [self alterHeaderForFileAtPath:filePath withBlock:^(SPTPersistentCacheRecordHeader *header) { 484| 58| locked = header->refCount > 0; 485| 58| } writeBack:NO complain:YES]; 486| 74| if (locked) { 487| 22| size += [self.dataCacheFileManager getFileSizeAtPath:filePath]; 488| 22| } 489| 74| } 490| 146| } else { 491| 62| [self debugOutput:@"Unable to fetch isDir#3 attribute:%@ error: %@", theURL, error]; 492| 62| } 493| 208| } 494| 7| 495| 7| return size; 496| 7|} 497| | 498| |- (void)dealloc 499| 78|{ 500| 78| [_garbageCollector unschedule]; 501| 78|} 502| | 503| |/** 504| | Load method used internally to load data. Called on work queue. 505| | */ 506| |- (void)loadDataForKeySync:(NSString *)key 507| | withCallback:(SPTPersistentCacheResponseCallback)callback 508| | onQueue:(dispatch_queue_t)queue 509| 156|{ 510| 156| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 511| 156| 512| 156| // File not exist -> inform user 513| 156| if (![self.fileManager fileExistsAtPath:filePath]) { 514| 47| [self dispatchEmptyResponseWithResult:SPTPersistentCacheResponseCodeNotFound callback:callback onQueue:queue]; 515| 47| return; 516| 109| } else { 517| 109| // File exist 518| 109| NSError *error = nil; 519| 109| NSMutableData *rawData = [NSMutableData dataWithContentsOfFile:filePath 520| 109| options:NSDataReadingMappedIfSafe 521| 109| error:&error]; 522| 109| if (rawData == nil) { 523| 1| // File read with error -> inform user 524| 1| [self dispatchError:error 525| 1| result:SPTPersistentCacheResponseCodeOperationError 526| 1| callback:callback 527| 1| onQueue:queue]; 528| 108| } else { 529| 108| SPTPersistentCacheRecordHeader *header = SPTPersistentCacheGetHeaderFromData(rawData.mutableBytes, rawData.length); 530| 108| 531| 108| // If not enough data to cast to header, its not the file we can process 532| 108| if (header == NULL) { 533| 7| NSError *headerError = [NSError spt_persistentDataCacheErrorWithCode:SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader]; 534| 7| [self dispatchError:headerError 535| 7| result:SPTPersistentCacheResponseCodeOperationError 536| 7| callback:callback 537| 7| onQueue:queue]; 538| 7| return; 539| 7| } 540| 101| 541| 101| SPTPersistentCacheRecordHeader localHeader; 542| 101| memcpy(&localHeader, header, sizeof(localHeader)); 543| 101| 544| 101| // Check header is valid 545| 101| NSError *headerError = SPTPersistentCacheCheckValidHeader(&localHeader); 546| 101| if (headerError != nil) { 547| 21| [self dispatchError:headerError 548| 21| result:SPTPersistentCacheResponseCodeOperationError 549| 21| callback:callback 550| 21| onQueue:queue]; 551| 21| return; 552| 21| } 553| 80| 554| 80| const NSUInteger refCount = localHeader.refCount; 555| 80| 556| 80| // We return locked files even if they expired, GC doesnt collect them too so they valuable to user 557| 80| // Satisfy Req.#1.2 558| 80| if (![self isDataCanBeReturnedWithHeader:&localHeader]) { 559| |#ifdef DEBUG_OUTPUT_ENABLED 560| | [self debugOutput:@"PersistentDataCache: Record with key: %@ expired, t:%llu, TTL:%llu", key, localHeader.updateTimeSec, localHeader.ttl]; 561| |#endif 562| | [self dispatchEmptyResponseWithResult:SPTPersistentCacheResponseCodeNotFound 563| 18| callback:callback 564| 18| onQueue:queue]; 565| 18| return; 566| 18| } 567| 62| 568| 62| // Check that payload is correct size 569| 62| if (localHeader.payloadSizeBytes != [rawData length] - SPTPersistentCacheRecordHeaderSize) { 570| 4| [self debugOutput:@"PersistentDataCache: Error: Wrong payload size for key:%@ , will return error", key]; 571| 4| [self dispatchError:[NSError spt_persistentDataCacheErrorWithCode:SPTPersistentCacheLoadingErrorWrongPayloadSize] 572| 4| result:SPTPersistentCacheResponseCodeOperationError 573| 4| callback:callback onQueue:queue]; 574| 4| return; 575| 4| } 576| 58| 577| 58| NSRange payloadRange = NSMakeRange(SPTPersistentCacheRecordHeaderSize, (NSUInteger)localHeader.payloadSizeBytes); 578| 58| NSData *payload = [rawData subdataWithRange:payloadRange]; 579| 58| const NSUInteger ttl = (NSUInteger)localHeader.ttl; 580| 58| 581| 58| 582| 58| SPTPersistentCacheRecord *record = [[SPTPersistentCacheRecord alloc] initWithData:payload 583| 58| key:key 584| 58| refCount:refCount 585| 58| ttl:ttl]; 586| 58| 587| 58| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 588| 58| error:nil 589| 58| record:record]; 590| 58| // If data ttl == 0 we update access time 591| 58| if (ttl == 0) { 592| 38| localHeader.updateTimeSec = spt_uint64rint(self.currentDateTimeInterval); 593| 38| localHeader.crc = SPTPersistentCacheCalculateHeaderCRC(&localHeader); 594| 38| memcpy(header, &localHeader, sizeof(localHeader)); 595| 38| 596| 38| // Write back with updated access attributes 597| 38| NSError *werror = nil; 598| 38| if (![rawData writeToFile:filePath options:NSDataWritingAtomic error:&werror]) { 599| 1| [self debugOutput:@"PersistentDataCache: Error writing back record:%@, error:%@", filePath.lastPathComponent, werror]; 600| 37| } else { 601| |#ifdef DEBUG_OUTPUT_ENABLED 602| | [self debugOutput:@"PersistentDataCache: Writing back record:%@ OK", filePath.lastPathComponent]; 603| |#endif 604| | } 605| 38| } 606| 58| 607| 58| // Callback only after we finished everyhing to avoid situation when user gets notified and we are still writting 608| 58| SPTPersistentCacheSafeDispatch(queue, ^{ 609| 58| callback(response); 610| 58| }); 611| 58| 612| 58| } // if rawData 613| 109| } // file exist 614| 156|} 615| | 616| |/** 617| | Store method used internaly. Called on work queue. 618| | */ 619| |- (NSError *)storeDataSync:(NSData *)data 620| | forKey:(NSString *)key 621| | ttl:(NSUInteger)ttl 622| | locked:(BOOL)isLocked 623| | withCallback:(SPTPersistentCacheResponseCallback)callback 624| | onQueue:(dispatch_queue_t)queue 625| 908|{ 626| 908| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 627| 908| 628| 908| NSString *subDir = [self.dataCacheFileManager subDirectoryPathForKey:key]; 629| 908| [self.fileManager createDirectoryAtPath:subDir withIntermediateDirectories:YES attributes:nil error:nil]; 630| 908| 631| 908| const NSUInteger payloadLength = [data length]; 632| 908| const NSUInteger rawDataLength = SPTPersistentCacheRecordHeaderSize + payloadLength; 633| 908| 634| 908| NSMutableData *rawData = [NSMutableData dataWithCapacity:rawDataLength]; 635| 908| 636| 908| SPTPersistentCacheRecordHeader header = SPTPersistentCacheRecordHeaderMake(ttl, 637| 908| payloadLength, 638| 908| spt_uint64rint(self.currentDateTimeInterval), 639| 908| isLocked); 640| 908| 641| 908| [rawData appendBytes:&header length:SPTPersistentCacheRecordHeaderSize]; 642| 908| [rawData appendData:data]; 643| 908| 644| 908| NSError *error = nil; 645| 908| 646| 908| if (![rawData writeToFile:filePath options:NSDataWritingAtomic error:&error]) { 647| 1| [self debugOutput:@"PersistentDataCache: Error writting to file:%@ , for key:%@. Removing it...", filePath, key]; 648| 1| [self removeDataForKeysSync:@[key]]; 649| 1| [self dispatchError:error result:SPTPersistentCacheResponseCodeOperationError callback:callback onQueue:queue]; 650| 907| } else { 651| 907| 652| 907| if (callback != nil) { 653| 905| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 654| 905| error:nil 655| 905| record:nil]; 656| 905| 657| 905| SPTPersistentCacheSafeDispatch(queue, ^{ 658| 905| callback(response); 659| 905| }); 660| 905| } 661| 907| } 662| 908| 663| 908| return error; 664| 908|} 665| | 666| |/** 667| | Method to work safely with opened file referenced by file descriptor. 668| | Method handles file closing properly in case of errors. 669| | Descriptor is passed to a jobBlock for further usage. 670| | */ 671| |- (SPTPersistentCacheResponse *)guardOpenFileWithPath:(NSString *)filePath 672| | jobBlock:(SPTPersistentCacheFileProcessingBlockType)jobBlock 673| | complain:(BOOL)needComplains 674| | writeBack:(BOOL)writeBack 675| 295|{ 676| 295| if (![self.fileManager fileExistsAtPath:filePath]) { 677| 0| if (needComplains) { 678| 0| [self debugOutput:@"PersistentDataCache: Record not exist at path:%@", filePath]; 679| 0| } 680| 0| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeNotFound error:nil record:nil]; 681| 0| 682| 295| } else { 683| 295| const int SPTPersistentCacheInvalidResult = -1; 684| 295| const int flags = (writeBack ? O_RDWR : O_RDONLY); 685| 295| 686| 295| int fd = open([filePath UTF8String], flags); 687| 295| if (fd == SPTPersistentCacheInvalidResult) { 688| 1| const int errorNumber = errno; 689| 1| NSString *errorDescription = @(strerror(errorNumber)); 690| 1| [self debugOutput:@"PersistentDataCache: Error opening file:%@ , error:%@", filePath, errorDescription]; 691| 1| NSError *error = [NSError errorWithDomain:NSPOSIXErrorDomain 692| 1| code:errorNumber 693| 1| userInfo:@{ NSLocalizedDescriptionKey: errorDescription }]; 694| 1| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationError 695| 1| error:error 696| 1| record:nil]; 697| 1| } 698| 294| 699| 294| SPTPersistentCacheResponse *response = jobBlock(fd); 700| 294| 701| 294| fd = [self.posixWrapper close:fd]; 702| 294| if (fd == SPTPersistentCacheInvalidResult) { 703| 1| const int errorNumber = errno; 704| 1| NSString *errorDescription = @(strerror(errorNumber)); 705| 1| [self debugOutput:@"PersistentDataCache: Error closing file:%@ , error:%@", filePath, errorDescription]; 706| 1| NSError *error = [NSError errorWithDomain:NSPOSIXErrorDomain 707| 1| code:errorNumber 708| 1| userInfo:@{ NSLocalizedDescriptionKey: errorDescription }]; 709| 1| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationError 710| 1| error:error 711| 1| record:nil]; 712| 1| } 713| 293| 714| 293| return response; 715| 293| } 716| 295|} 717| | 718| |/** 719| | Method used to read/write file header. 720| | */ 721| |- (SPTPersistentCacheResponse *)alterHeaderForFileAtPath:(NSString *)filePath 722| | withBlock:(SPTPersistentCacheRecordHeaderGetCallbackType)modifyBlock 723| | writeBack:(BOOL)needWriteBack 724| | complain:(BOOL)needComplains 725| 295|{ 726| 295| return [self guardOpenFileWithPath:filePath jobBlock:^SPTPersistentCacheResponse*(int filedes) { 727| 294| 728| 294| SPTPersistentCacheRecordHeader header; 729| 294| ssize_t readBytes = [self.posixWrapper read:filedes 730| 294| buffer:&header 731| 294| bufferSize:SPTPersistentCacheRecordHeaderSize]; 732| 294| if (readBytes != (ssize_t)SPTPersistentCacheRecordHeaderSize) { 733| 14| NSError *error = [NSError spt_persistentDataCacheErrorWithCode:SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader]; 734| 14| if (readBytes == -1) { 735| 1| const int errorNumber = errno; 736| 1| NSString *errorDescription = @(strerror(errorNumber)); 737| 1| error = [NSError errorWithDomain:NSPOSIXErrorDomain 738| 1| code:errorNumber 739| 1| userInfo:@{ NSLocalizedDescriptionKey: errorDescription }]; 740| 1| } 741| 14| 742| 14| [self debugOutput:@"PersistentDataCache: Error not enough data to read the header of file path:%@ , error:%@", 743| 14| filePath, [error localizedDescription]]; 744| 14| 745| 14| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationError 746| 14| error:error 747| 14| record:nil]; 748| 14| } 749| 280| 750| 280| NSError *nsError = SPTPersistentCacheCheckValidHeader(&header); 751| 280| if (nsError != nil) { 752| 39| [self debugOutput:@"PersistentDataCache: Error checking header at file path:%@ , error:%@", filePath, nsError]; 753| 39| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationError 754| 39| error:nsError 755| 39| record:nil]; 756| 39| } 757| 241| 758| 241| modifyBlock(&header); 759| 241| 760| 241| if (needWriteBack) { 761| 83| 762| 83| uint32_t oldCRC = header.crc; 763| 83| header.crc = SPTPersistentCacheCalculateHeaderCRC(&header); 764| 83| 765| 83| // If nothing has changed we do nothing then 766| 83| if (oldCRC == header.crc) { 767| 56| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 768| 56| error:nil 769| 56| record:nil]; 770| 56| } 771| 27| 772| 27| // Set file pointer to the beginning of the file 773| 27| off_t seekOffset = [self.posixWrapper lseek:filedes seekType:SEEK_SET seekAmount:0]; 774| 27| if (seekOffset != 0) { 775| 1| const int errorNumber = errno; 776| 1| NSString *errorDescription = @(strerror(errorNumber)); 777| 1| [self debugOutput:@"PersistentDataCache: Error seeking to begin of file path:%@ , error:%@", filePath, errorDescription]; 778| 1| NSError *error = [NSError errorWithDomain:NSPOSIXErrorDomain 779| 1| code:errorNumber 780| 1| userInfo:@{ NSLocalizedDescriptionKey: errorDescription }]; 781| 1| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationError 782| 1| error:error 783| 1| record:nil]; 784| 1| 785| 26| } else { 786| 26| ssize_t writtenBytes = [self.posixWrapper write:filedes 787| 26| buffer:&header 788| 26| bufferSize:SPTPersistentCacheRecordHeaderSize]; 789| 26| if (writtenBytes != (ssize_t)SPTPersistentCacheRecordHeaderSize) { 790| 1| const int errorNumber = errno; 791| 1| NSString *errorDescription = @(strerror(errorNumber)); 792| 1| [self debugOutput:@"PersistentDataCache: Error writting header at file path:%@ , error:%@", filePath, errorDescription]; 793| 1| NSError *error = [NSError errorWithDomain:NSPOSIXErrorDomain 794| 1| code:errorNumber 795| 1| userInfo:@{ NSLocalizedDescriptionKey: errorDescription }]; 796| 1| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationError 797| 1| error:error 798| 1| record:nil]; 799| 1| 800| 25| } else { 801| 25| int result = [self.posixWrapper fsync:filedes]; 802| 25| if (result == -1) { 803| 1| const int errorNumber = errno; 804| 1| NSString *errorDescription = @(strerror(errorNumber)); 805| 1| [self debugOutput:@"PersistentDataCache: Error flushing file:%@ , error:%@", filePath, errorDescription]; 806| 1| NSError *error = [NSError errorWithDomain:NSPOSIXErrorDomain 807| 1| code:errorNumber 808| 1| userInfo:@{ NSLocalizedDescriptionKey: errorDescription }]; 809| 1| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationError 810| 1| error:error 811| 1| record:nil]; 812| 1| } 813| 182| } 814| 26| } 815| 27| } 816| 182| 817| 182| return [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseCodeOperationSucceeded 818| 182| error:nil 819| 182| record:nil]; 820| 182| } complain:needComplains writeBack:needWriteBack]; 821| 295|} 822| | 823| |/** 824| | Only this method check data expiration. Past check is also supported. 825| | */ 826| |- (BOOL)isDataExpiredWithHeader:(SPTPersistentCacheRecordHeader *)header 827| 124|{ 828| 124| assert(header != nil); 829| 124| uint64_t ttl = header->ttl; 830| 124| uint64_t current = spt_uint64rint(self.currentDateTimeInterval); 831| 124| int64_t threshold = (int64_t)((ttl > 0) ? ttl : self.options.defaultExpirationPeriod); 832| 124| 833| 124| if (ttl > SPTPersistentCacheTTLUpperBoundInSec) { 834| 1| [self debugOutput:@"PersistentDataCache: WARNING: TTL seems too big: %llu > %llu sec", ttl, SPTPersistentCacheTTLUpperBoundInSec]; 835| 1| } 836| 124| 837| 124| return (int64_t)(current - header->updateTimeSec) > threshold; 838| 124|} 839| | 840| |/** 841| | Methos checks whether data can be given to caller with accordance to API. 842| | */ 843| |- (BOOL)isDataCanBeReturnedWithHeader:(SPTPersistentCacheRecordHeader *)header 844| 114|{ 845| 114| return !([self isDataExpiredWithHeader:header] && header->refCount == 0); 846| 114|} 847| | 848| |- (void)runRegularGC 849| 1|{ 850| 1| [self collectGarbageForceExpire:NO forceLocked:NO]; 851| 1|} 852| | 853| |- (void)collectGarbageForceExpire:(BOOL)forceExpire forceLocked:(BOOL)forceLocked 854| 5|{ 855| 5| [self debugOutput:@"PersistentDataCache: Run GC with forceExpire:%d forceLock:%d", forceExpire, forceLocked]; 856| 5| 857| 5| NSURL *urlPath = [NSURL fileURLWithPath:self.options.cachePath]; 858| 5| NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtURL:urlPath 859| 5| includingPropertiesForKeys:@[NSURLIsDirectoryKey] 860| 5| options:NSDirectoryEnumerationSkipsHiddenFiles 861| 5| errorHandler:nil]; 862| 5| 863| 5| // Enumerate the dirEnumerator results, each value is stored in allURLs 864| 5| NSURL *theURL = nil; 865| 160| while ((theURL = [dirEnumerator nextObject])) { 866| 155| 867| 155| // Retrieve the file name. From cached during the enumeration. 868| 155| NSNumber *isDirectory; 869| 155| if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]) { 870| 124| if ([isDirectory boolValue] == NO) { 871| 68| 872| 68| NSString *key = theURL.lastPathComponent; 873| 68| // That satisfies Req.#1.3 874| 68| NSString *filePath = [self.dataCacheFileManager pathForKey:key]; 875| 68| BOOL __block needRemove = NO; 876| 68| int __block reason = 0; 877| 68| // WARNING: We may skip return result here bcuz in that case we won't remove file we do not know what is it 878| 68| [self alterHeaderForFileAtPath:filePath withBlock:^(SPTPersistentCacheRecordHeader *header) { 879| 52| if (forceExpire && forceLocked) { 880| 13| // delete all 881| 13| needRemove = YES; 882| 13| reason = 1; 883| 39| } else if (forceExpire && !forceLocked) { 884| 13| // delete those: header->refCount == 0 885| 13| needRemove = header->refCount == 0; 886| 13| reason = 2; 887| 26| } else if (!forceExpire && forceLocked) { 888| 13| // delete those: header->refCount > 0 889| 13| needRemove = header->refCount > 0; 890| 13| reason = 3; 891| 13| } else { 892| 13| // delete those: [self isDataExpiredWithHeader:header] && header->refCount == 0 893| 13| needRemove = ![self isDataCanBeReturnedWithHeader:header]; 894| 13| reason = 4; 895| 13| } 896| 52| } writeBack:NO complain:YES]; 897| 68| if (needRemove) { 898| 34| [self debugOutput:@"PersistentDataCache: gc removing record: %@, reason:%d", filePath.lastPathComponent, reason]; 899| 34| [self.dataCacheFileManager removeDataForKey:key]; 900| 34| } 901| 68| } // is dir 902| 124| } else { 903| 31| [self debugOutput:@"Unable to fetch isDir#4 attribute:%@", theURL]; 904| 31| } 905| 155| } // for 906| 5|} 907| | 908| |- (void)dispatchEmptyResponseWithResult:(SPTPersistentCacheResponseCode)result 909| | callback:(SPTPersistentCacheResponseCallback _Nullable)callback 910| | onQueue:(dispatch_queue_t _Nullable)queue 911| 70|{ 912| 70| if (callback == nil) { 913| 1| return; 914| 1| } 915| 69| 916| 69| SPTPersistentCacheSafeDispatch(queue, ^{ 917| 69| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:result 918| 69| error:nil 919| 69| record:nil]; 920| 69| callback(response); 921| 69| }); 922| 69|} 923| | 924| |- (void)dispatchError:(NSError *)error 925| | result:(SPTPersistentCacheResponseCode)result 926| | callback:(SPTPersistentCacheResponseCallback _Nullable)callback 927| | onQueue:(dispatch_queue_t _Nullable)queue 928| 37|{ 929| 37| if (callback == nil) { 930| 1| return; 931| 1| } 932| 36| 933| 36| SPTPersistentCacheSafeDispatch(queue, ^{ 934| 36| SPTPersistentCacheResponse *response = [[SPTPersistentCacheResponse alloc] initWithResult:result 935| 36| error:error 936| 36| record:nil]; 937| 36| callback(response); 938| 36| }); 939| 36|} 940| | 941| |- (void)debugOutput:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) 942| 317|{ 943| 317| SPTPersistentCacheDebugCallback const debugOutput = self.debugOutput; 944| 317| 945| 317| if (debugOutput && format.length > 0) { 946| 317| va_list list; 947| 317| va_start(list, format); 948| 317| NSString * const message = [[NSString alloc] initWithFormat:format arguments:list]; 949| 317| va_end(list); 950| 317| 951| 317| debugOutput(message); 952| 317| } 953| 317|} 954| | 955| |- (BOOL)pruneBySize 956| 6|{ 957| 6| if (self.options.sizeConstraintBytes == 0) { 958| 1| return NO; 959| 1| } 960| 5| 961| 5| // Find all the image names and attributes and sort oldest last 962| 5| NSMutableArray *images = [self storedImageNamesAndAttributes]; 963| 5| 964| 5| // Find the free space on the disk 965| 5| SPTPersistentCacheDiskSize currentCacheSize = (SPTPersistentCacheDiskSize)[self lockedItemsSizeInBytes]; 966| 26| for (NSDictionary *image in images) { 967| 26| currentCacheSize += [image[SPTDataCacheFileAttributesKey][NSFileSize] integerValue]; 968| 26| } 969| 5| 970| 5| SPTPersistentCacheDiskSize optimalCacheSize = [self.dataCacheFileManager optimizedDiskSizeForCacheSize:currentCacheSize]; 971| 5| 972| 5| // Remove oldest data until we reach acceptable cache size 973| 31| while (currentCacheSize > optimalCacheSize && images.count) { 974| 26| NSDictionary *image = images.lastObject; 975| 26| [images removeLastObject]; 976| 26| 977| 26| NSString *fileName = image[SPTDataCacheFileNameKey]; 978| 26| NSError *localError = nil; 979| 26| if (fileName.length > 0 && ![self.fileManager removeItemAtPath:fileName error:&localError]) { 980| 13| [self debugOutput:@"PersistentDataCache: %@ ERROR %@", @(__PRETTY_FUNCTION__), [localError localizedDescription]]; 981| 13| continue; 982| 13| } else { 983| 13| [self debugOutput:@"PersistentDataCache: evicting by size key:%@", fileName.lastPathComponent]; 984| 13| } 985| 26| 986| 26| currentCacheSize -= [image[SPTDataCacheFileAttributesKey][NSFileSize] integerValue]; 987| 13| } 988| 5| return YES; 989| 5|} 990| | 991| |- (NSMutableArray *)storedImageNamesAndAttributes 992| 5|{ 993| 5| NSURL *urlPath = [NSURL fileURLWithPath:self.options.cachePath]; 994| 5| 995| 5| // Enumerate the directory (specified elsewhere in your code) 996| 5| // Ignore hidden files 997| 5| // The errorHandler: parameter is set to nil. Typically you'd want to present a panel 998| 5| NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtURL:urlPath 999| 5| includingPropertiesForKeys:@[NSURLIsDirectoryKey] 1000| 5| options:NSDirectoryEnumerationSkipsHiddenFiles 1001| 5| errorHandler:nil]; 1002| 5| 1003| 5| // An array to store the all the enumerated file names in 1004| 5| NSMutableArray *images = [NSMutableArray array]; 1005| 5| 1006| 5| // Enumerate the dirEnumerator results, each value is stored in allURLs 1007| 5| NSURL *theURL = nil; 1008| 151| while ((theURL = [dirEnumerator nextObject])) { 1009| 146| 1010| 146| // Retrieve the file name. From cached during the enumeration. 1011| 146| NSNumber *isDirectory; 1012| 146| if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]) { 1013| 115| 1014| 115| if ([isDirectory boolValue] == NO) { 1015| 57| const char *filePath = theURL.fileSystemRepresentation; 1016| 57| NSString *filePathString = [NSString stringWithUTF8String:filePath]; 1017| 57| 1018| 57| // We skip locked files always 1019| 57| BOOL __block locked = NO; 1020| 57| 1021| 57| // WARNING: We may skip return result here bcuz in that case we will remove unknown file as unlocked trash 1022| 57| [self alterHeaderForFileAtPath:filePathString 1023| 57| withBlock:^(SPTPersistentCacheRecordHeader *header) { 1024| 45| locked = (header->refCount > 0); 1025| 45| } writeBack:NO 1026| 57| complain:YES]; 1027| 57| 1028| 57| if (locked) { 1029| 18| continue; 1030| 18| } 1031| 39| 1032| 39| /* We use this since this is most reliable method to get file info and URL stuff fails sometimes 1033| 39| which is described in apple doc and its our case here */ 1034| 39| 1035| 39| struct stat fileStat; 1036| 39| int ret = [self.posixWrapper stat:filePath statStruct:&fileStat]; 1037| 39| if (ret == -1) { 1038| 13| [self debugOutput:@"Cannot find the stats of file: %@", theURL.absoluteString]; 1039| 13| continue; 1040| 13| } 1041| 26| 1042| 26| /* 1043| 26| Use modification time even for files with TTL 1044| 26| Files with TTL have updateTime set once on creation. 1045| 26| */ 1046| 26| NSDate *mdate = [NSDate dateWithTimeIntervalSince1970:(fileStat.st_mtimespec.tv_sec + fileStat.st_mtimespec.tv_nsec*1e9)]; 1047| 26| NSNumber *fsize = [NSNumber numberWithLongLong:fileStat.st_size]; 1048| 26| NSDictionary *values = @{NSFileModificationDate : mdate, NSFileSize: fsize}; 1049| 26| 1050| 26| [images addObject:@{ SPTDataCacheFileNameKey : filePathString, 1051| 26| SPTDataCacheFileAttributesKey : values }]; 1052| 26| } 1053| 115| } else { 1054| 31| [self debugOutput:@"Unable to fetch isDir#5 attribute:%@", theURL]; 1055| 31| } 1056| 146| } 1057| 5| 1058| 5| // Oldest goes last 1059| 67| NSComparisonResult(^SPTSortFilesByModificationDate)(id, id) = ^NSComparisonResult(NSDictionary *file1, NSDictionary *file2) { 1060| 67| NSDate *date1 = file1[SPTDataCacheFileAttributesKey][NSFileModificationDate]; 1061| 67| NSDate *date2 = file2[SPTDataCacheFileAttributesKey][NSFileModificationDate]; 1062| 67| return [date2 compare:date1]; 1063| 67| }; 1064| 5| 1065| 5| NSArray *sortedImages = [images sortedArrayUsingComparator:SPTSortFilesByModificationDate]; 1066| 5| 1067| 5| return [sortedImages mutableCopy]; 1068| 5|} 1069| | 1070| |- (NSTimeInterval)currentDateTimeInterval 1071| 2|{ 1072| 2| return [[NSDate date] timeIntervalSince1970]; 1073| 2|} 1074| | 1075| |- (void)doWork:(void (^)(void))block priority:(NSOperationQueuePriority)priority qos:(NSQualityOfService)qos 1076| 1.15k|{ 1077| 1.15k| NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:block]; 1078| 1.15k| operation.qualityOfService = qos; 1079| 1.15k| operation.queuePriority = priority; 1080| 1.15k| [self.workQueue addOperation:operation]; 1081| 1.15k|} 1082| | 1083| |- (void)logTimingForKey:(NSString *)key method:(SPTPersistentCacheDebugMethodType)method type:(SPTPersistentCacheDebugTimingType)type 1084| 3.44k|{ 1085| 3.44k| if (self.options.timingCallback) { 1086| 0| dispatch_async(dispatch_get_main_queue(), ^{ 1087| 0| self.options.timingCallback(key, method, type, mach_absolute_time()); 1088| 0| }); 1089| 0| } 1090| 3.44k|} 1091| | 1092| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheDebugUtilities.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| | 22| |#import "SPTPersistentCacheDebugUtilities.h" 23| | 24| |void SPTPersistentCacheSafeDebugCallback(NSString *debugMessage, 25| | SPTPersistentCacheDebugCallback debugCallback) 26| 105|{ 27| 105| if (debugCallback) { 28| 86| debugCallback(debugMessage); 29| 86| } 30| 105|} /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheFileManager.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "SPTPersistentCacheFileManager+Private.h" 22| |#import "SPTPersistentCacheDebugUtilities.h" 23| |#import 24| | 25| |static const double SPTPersistentCacheFileManagerMinFreeDiskSpace = 0.1; 26| | 27| |const NSUInteger SPTPersistentCacheFileManagerSubDirNameLength = 2; 28| | 29| |@implementation SPTPersistentCacheFileManager 30| | 31| |#pragma mark - Initializer 32| | 33| |- (instancetype)initWithOptions:(SPTPersistentCacheOptions *)options 34| 155|{ 35| 155| self = [super init]; 36| 155| if (self) { 37| 155| _options = [options copy]; 38| 155| _fileManager = [NSFileManager defaultManager]; 39| 155| _debugOutput = options.debugOutput; 40| 155| } 41| 155| return self; 42| 155|} 43| | 44| |#pragma mark - 45| | 46| |- (BOOL)createCacheDirectory 47| 87|{ 48| 87| BOOL isDirectory = NO; 49| 87| 50| 87| BOOL exists = [self.fileManager fileExistsAtPath:self.options.cachePath isDirectory:&isDirectory]; 51| 87| if (exists && !isDirectory) { 52| 0| SPTPersistentCacheSafeDebugCallback( 53| 0| [NSString stringWithFormat:@"PersistentDataCache: Unable to create dir: %@ - file exists at path", self.options.cachePath], 54| 0| self.debugOutput); 55| 0| return NO; 56| 0| } 57| 87| 58| 87| if (exists == NO) { 59| 56| NSError *error = nil; 60| 56| BOOL didCreateDirectory = [self.fileManager createDirectoryAtPath:self.options.cachePath 61| 56| withIntermediateDirectories:YES 62| 56| attributes:nil 63| 56| error:&error]; 64| 56| if (didCreateDirectory == NO) { 65| 1| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: Unable to create dir: %@ with error:%@", self.options.cachePath, error], self.debugOutput); 66| 1| 67| 1| return NO; 68| 1| } 69| 86| } 70| 86| 71| 86| return YES; 72| 86|} 73| | 74| |/** 75| | 2 letter separation is handled only by this method. All other code is agnostic to this fact. 76| | */ 77| |- (NSString *)subDirectoryPathForKey:(NSString *)key 78| 2.67k|{ 79| 2.67k| // make folder tree: xx/ zx/ xy/ yz/ etc. 80| 2.67k| NSString *subDir = self.options.cachePath; 81| 2.67k| 82| 2.67k| if (self.options.useDirectorySeparation && key.length >= SPTPersistentCacheFileManagerSubDirNameLength) { 83| 2.67k| NSString *subDirectoryName = [key substringToIndex:SPTPersistentCacheFileManagerSubDirNameLength]; 84| 2.67k| subDir = [self.options.cachePath stringByAppendingPathComponent:subDirectoryName]; 85| 2.67k| } 86| 2.67k| 87| 2.67k| return subDir; 88| 2.67k|} 89| | 90| |- (NSString *)pathForKey:(NSString *)key 91| 1.76k|{ 92| 1.76k| NSString *subDirectoryPathForKey = [self subDirectoryPathForKey:key]; 93| 1.76k| 94| 1.76k| return [subDirectoryPathForKey stringByAppendingPathComponent:key]; 95| 1.76k|} 96| | 97| |- (void)removeAllData 98| 2|{ 99| 2| NSURL *urlPath = [NSURL fileURLWithPath:self.options.cachePath]; 100| 2| 101| 2| NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtURL:urlPath 102| 2| includingPropertiesForKeys:@[NSURLIsDirectoryKey] 103| 2| options:NSDirectoryEnumerationSkipsHiddenFiles 104| 2| errorHandler:nil]; 105| 2| 106| 2| // Enumerate the dirEnumerator results, each value is stored in allURLs 107| 2| NSURL *theURL = nil; 108| 37| while ((theURL = [dirEnumerator nextObject])) { 109| 35| // Retrieve the file name. From cached during the enumeration. 110| 35| NSNumber *isDirectory; 111| 35| if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]) { 112| 35| if ([isDirectory boolValue] == NO) { 113| 19| NSString *key = theURL.lastPathComponent; 114| 19| 115| 19| // That satisfies Req.#1.3 116| 19| [self removeDataForKey:key]; 117| 19| } 118| 35| } 119| 35| } 120| 2|} 121| | 122| |- (void)removeDataForKey:(NSString *)key 123| 72|{ 124| 72| NSError *error = nil; 125| 72| 126| 72| NSString *filePath = [self pathForKey:key]; 127| 72| 128| 72| if (![self.fileManager removeItemAtPath:filePath error:&error]) { 129| 1| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: Error removing data for Key:%@ , error:%@", key, error], self.debugOutput); 130| 1| } 131| 72|} 132| | 133| |- (NSUInteger)getFileSizeAtPath:(NSString *)filePath 134| 48|{ 135| 48| NSError *error = nil; 136| 48| NSDictionary *attrs = [self.fileManager attributesOfItemAtPath:filePath error:&error]; 137| 48| if (attrs == nil) { 138| 1| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: Error getting attributes for file: %@, error: %@", filePath, error], self.debugOutput); 139| 1| } 140| 48| return (NSUInteger)[attrs fileSize]; 141| 48|} 142| | 143| |- (NSUInteger)totalUsedSizeInBytes 144| 3|{ 145| 3| NSUInteger size = 0; 146| 3| NSURL *urlPath = [NSURL fileURLWithPath:self.options.cachePath]; 147| 3| NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtURL:urlPath 148| 3| includingPropertiesForKeys:@[NSURLIsDirectoryKey] 149| 3| options:NSDirectoryEnumerationSkipsHiddenFiles 150| 3| errorHandler:nil]; 151| 3| 152| 3| // Enumerate the dirEnumerator results, each value is stored in allURLs 153| 3| NSURL *theURL = nil; 154| 70| while ((theURL = [dirEnumerator nextObject])) { 155| 67| 156| 67| // Retrieve the file name. From cached during the enumeration. 157| 67| NSNumber *isDirectory; 158| 67| if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]) { 159| 67| if ([isDirectory boolValue] == NO) { 160| 25| NSString *key = theURL.lastPathComponent; 161| 25| 162| 25| NSString *filePath = [self pathForKey:key]; 163| 25| size += [self getFileSizeAtPath:filePath]; 164| 25| } 165| 67| } else { 166| 0| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"Unable to fetch isDir#2 attribute:%@", theURL], self.debugOutput); 167| 0| } 168| 67| } 169| 3| 170| 3| return size; 171| 3|} 172| | 173| |- (SPTPersistentCacheDiskSize)optimizedDiskSizeForCacheSize:(SPTPersistentCacheDiskSize)currentCacheSize 174| 8|{ 175| 8| SPTPersistentCacheDiskSize tempCacheSize = (SPTPersistentCacheDiskSize)self.options.sizeConstraintBytes; 176| 8| 177| 8| NSError *error = nil; 178| 8| 179| 8| NSDictionary *fileSystemAttributes = [self.fileManager attributesOfFileSystemForPath:self.options.cachePath 180| 8| error:&error]; 181| 8| if (fileSystemAttributes) { 182| 5| // Never use the last SPTImageLoaderMinimumFreeDiskSpace of the disk for caching 183| 5| NSNumber *fileSystemSize = fileSystemAttributes[NSFileSystemSize]; 184| 5| NSNumber *fileSystemFreeSpace = fileSystemAttributes[NSFileSystemFreeSize]; 185| 5| 186| 5| SPTPersistentCacheDiskSize totalSpace = fileSystemSize.longLongValue; 187| 5| SPTPersistentCacheDiskSize freeSpace = fileSystemFreeSpace.longLongValue + currentCacheSize; 188| 5| SPTPersistentCacheDiskSize proposedCacheSize = freeSpace - llrint(totalSpace * 189| 5| SPTPersistentCacheFileManagerMinFreeDiskSpace); 190| 5| 191| 5| tempCacheSize = MAX(0, proposedCacheSize); 192| 5| 193| 5| } else { 194| 3| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: %@ ERROR %@", @(__PRETTY_FUNCTION__), [error localizedDescription]], self.debugOutput); 195| 3| } 196| 8| 197| 8| return MIN(tempCacheSize, (SPTPersistentCacheDiskSize)self.options.sizeConstraintBytes); 198| 8|} 199| | 200| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheGarbageCollector.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "SPTPersistentCacheGarbageCollector.h" 22| |#import "SPTPersistentCacheDebugUtilities.h" 23| |#import "SPTPersistentCache+Private.h" 24| | 25| |static BOOL SPTPersistentCacheGarbageCollectorSchedulerIsInMainQueue(void); 26| | 27| |static const NSTimeInterval SPTPersistentCacheGarbageCollectorSchedulerTimerTolerance = 300; 28| | 29| |@interface SPTPersistentCacheGarbageCollector () 30| |@property (nonatomic, strong) NSTimer *timer; 31| |@property (nonatomic, copy) SPTPersistentCacheOptions *options; 32| |@end 33| | 34| | 35| |@implementation SPTPersistentCacheGarbageCollector 36| | 37| |#pragma mark - Initializer 38| | 39| |- (instancetype)initWithCache:(SPTPersistentCache *)cache 40| | options:(SPTPersistentCacheOptions *)options 41| | queue:(NSOperationQueue *)queue 42| 92|{ 43| 92| self = [super init]; 44| 92| if (self) { 45| 92| _options = [options copy]; 46| 92| _cache = cache; 47| 92| _queue = queue; 48| 92| } 49| 92| return self; 50| 92|} 51| | 52| |- (void)dealloc 53| 78|{ 54| 78| /** 55| 78| Intentionally Left Blank 56| 78| 57| 78| Our timer should be invalidated by unscheduling this garbage collector 58| 78| on the -dealloc method of the object owning the reference. 59| 78| */ 60| 78|} 61| | 62| |#pragma mark - 63| | 64| |- (void)enqueueGarbageCollection:(NSTimer *)timer 65| 1|{ 66| 1| __weak __typeof(self) const weakSelf = self; 67| 1| NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 68| 1| // We want to shadow `self` in this case. 69| 1| _Pragma("clang diagnostic push"); 70| 1| _Pragma("clang diagnostic ignored \"-Wshadow\""); 71| 1| __typeof(weakSelf) const self = weakSelf; 72| 1| _Pragma("clang diagnostic pop"); 73| 1| 74| 1| SPTPersistentCache * const cache = self.cache; 75| 1| 76| 1| [cache runRegularGC]; 77| 1| [cache pruneBySize]; 78| 1| }]; 79| 1| operation.queuePriority = self.options.garbageCollectionPriority; 80| 1| operation.qualityOfService = self.options.garbageCollectionQualityOfService; 81| 1| [self.queue addOperation:operation]; 82| 1|} 83| | 84| |- (void)schedule 85| 9|{ 86| 9| if (!SPTPersistentCacheGarbageCollectorSchedulerIsInMainQueue()) { 87| 1| dispatch_async(dispatch_get_main_queue(), ^{ 88| 1| [self schedule]; 89| 1| }); 90| 1| 91| 1| return; 92| 1| } 93| 8| 94| 8| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"runGarbageCollector:%@", self.timer], 95| 8| self.options.debugOutput); 96| 8| 97| 8| if (self.isGarbageCollectionScheduled) { 98| 1| return; 99| 1| } 100| 7| 101| 7| self.timer = [NSTimer timerWithTimeInterval:self.options.garbageCollectionInterval 102| 7| target:self 103| 7| selector:@selector(enqueueGarbageCollection:) 104| 7| userInfo:nil 105| 7| repeats:YES]; 106| 7| 107| 7| self.timer.tolerance = SPTPersistentCacheGarbageCollectorSchedulerTimerTolerance; 108| 7| 109| 7| [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode]; 110| 7|} 111| | 112| |- (void)unschedule 113| 81|{ 114| 81| if (!SPTPersistentCacheGarbageCollectorSchedulerIsInMainQueue()) { 115| 0| dispatch_async(dispatch_get_main_queue(), ^{ 116| 0| [self unschedule]; 117| 0| }); 118| 0| 119| 0| return; 120| 0| } 121| 81| 122| 81| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"stopGarbageCollector:%@", self.timer], 123| 81| self.options.debugOutput); 124| 81| 125| 81| [self.timer invalidate]; 126| 81| 127| 81| self.timer = nil; 128| 81|} 129| | 130| |- (BOOL)isGarbageCollectionScheduled 131| 17|{ 132| 17| return (self.timer != nil); 133| 17|} 134| |@end 135| | 136| |static BOOL SPTPersistentCacheGarbageCollectorSchedulerIsInMainQueue(void) 137| 90|{ 138| 90| NSString *currentQueueLabelString = [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)]; 139| 90| NSString *mainQueueLabelString = [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())]; 140| 90| return [currentQueueLabelString isEqualToString:mainQueueLabelString]; 141| 90|} 142| | /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheHeader.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |#import "NSError+SPTPersistentCacheDomainErrors.h" 24| | 25| |#include "crc32iso3309.h" 26| | 27| |const SPTPersistentCacheMagicType SPTPersistentCacheMagicValue = 0x46545053; // SPTF 28| |const size_t SPTPersistentCacheRecordHeaderSize = sizeof(SPTPersistentCacheRecordHeader); 29| | 30| |_Static_assert(sizeof(SPTPersistentCacheRecordHeader) == 64, 31| | "Struct SPTPersistentCacheRecordHeader has to be packed without padding"); 32| |_Static_assert(sizeof(SPTPersistentCacheRecordHeader) % 4 == 0, 33| | "Struct size has to be multiple of 4"); 34| | 35| |NS_INLINE BOOL SPTPersistentCachePointerMagicAlignCheck(const void *ptr) 36| 433|{ 37| 433| const unsigned align = _Alignof(SPTPersistentCacheMagicType); 38| 433| uint64_t v = (uint64_t)(ptr); 39| 433| return (v % align == 0); 40| 433|} 41| | 42| |SPTPersistentCacheRecordHeader SPTPersistentCacheRecordHeaderMake(uint64_t ttl, 43| | uint64_t payloadSize, 44| | uint64_t updateTime, 45| | BOOL isLocked) 46| | 47| 909|{ 48| 909| SPTPersistentCacheRecordHeader dummy; 49| 909| memset(&dummy, 0, SPTPersistentCacheRecordHeaderSize); 50| 909| SPTPersistentCacheRecordHeader *header = &dummy; 51| 909| 52| 909| header->magic = SPTPersistentCacheMagicValue; 53| 909| header->headerSize = (uint32_t)SPTPersistentCacheRecordHeaderSize; 54| 909| header->refCount = (isLocked ? 1 : 0); 55| 909| header->ttl = ttl; 56| 909| header->payloadSizeBytes = payloadSize; 57| 909| header->updateTimeSec = updateTime; 58| 909| header->crc = SPTPersistentCacheCalculateHeaderCRC(header); 59| 909| 60| 909| return dummy; 61| 909|} 62| | 63| |SPTPersistentCacheRecordHeader *SPTPersistentCacheGetHeaderFromData(void *data, size_t size) 64| 110|{ 65| 110| if (size < SPTPersistentCacheRecordHeaderSize) { 66| 8| return NULL; 67| 8| } 68| 102| 69| 102| return (SPTPersistentCacheRecordHeader *)data; 70| 102|} 71| | 72| |int /*SPTPersistentCacheLoadingError*/ SPTPersistentCacheValidateHeader(const SPTPersistentCacheRecordHeader *header) 73| 434|{ 74| 434| if (header == NULL) { 75| 1| return SPTPersistentCacheLoadingErrorInternalInconsistency; 76| 1| } 77| 433| 78| 433| // Check that header could be read according to alignment 79| 433| if (!SPTPersistentCachePointerMagicAlignCheck(header)) { 80| 1| return SPTPersistentCacheLoadingErrorHeaderAlignmentMismatch; 81| 1| } 82| 432| 83| 432| // 1. Check magic 84| 432| if (header->magic != SPTPersistentCacheMagicValue) { 85| 21| return SPTPersistentCacheLoadingErrorMagicMismatch; 86| 21| } 87| 411| 88| 411| // 2. Check CRC 89| 411| uint32_t crc = SPTPersistentCacheCalculateHeaderCRC(header); 90| 411| if (crc != header->crc) { 91| 21| return SPTPersistentCacheLoadingErrorInvalidHeaderCRC; 92| 21| } 93| 390| 94| 390| // 3. Check header size 95| 390| if (header->headerSize != SPTPersistentCacheRecordHeaderSize) { 96| 21| return SPTPersistentCacheLoadingErrorWrongHeaderSize; 97| 21| } 98| 369| 99| 369| return -1; 100| 369|} 101| | 102| |NSError * SPTPersistentCacheCheckValidHeader(SPTPersistentCacheRecordHeader *header) 103| 381|{ 104| 381| int code = SPTPersistentCacheValidateHeader(header); 105| 381| if (code == -1) { // No error 106| 321| return nil; 107| 321| } 108| 60| 109| 60| return [NSError spt_persistentDataCacheErrorWithCode:code]; 110| 60|} 111| | 112| |uint32_t SPTPersistentCacheCalculateHeaderCRC(const SPTPersistentCacheRecordHeader *header) 113| 1.59k|{ 114| 1.59k| if (header == NULL) { 115| 1| return 0; 116| 1| } 117| 1.59k| 118| 1.59k| return spt_crc32((const uint8_t *)header, SPTPersistentCacheRecordHeaderSize - sizeof(header->crc)); 119| 1.59k|} 120| | 121| | /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheObjectDescription.h: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |/// The termination sentinel that must be used toghether with `SPTPersistentCacheObjectDescription()`. 24| |extern id const SPTPersistentCacheObjectDescriptionTerminationSentinel; 25| | 26| |/** 27| | Creates a standardized description string for the given _object_ and a variable list of _value_ to _key_ pairs. 28| | Each value and key must be an object conforming to the `NSObject` protocol. 29| | 30| | The function takes a variable list of value and key pairs. Just like the variadic `NSDictionary` initializer. You 31| | must terminate the list using `SPTPersistentCacheObjectDescriptionTerminationSentinel`. 32| | 33| | @note It’s recommended that you use the convenience macro `SPTPersistentCacheObjectDescription` over this function 34| | directly. As it adds the termination sentinel for you. 35| | 36| | @warning The list of variadic arguments **MUST** end with the custom termination sentinel: 37| | `SPTPersistentCacheObjectDescriptionTerminationSentinel`. We need a custom sentinel as the function supports 38| | arguments being `nil`. 39| | 40| | @return A standardized description string on the format ``. 41| | */ 42| |extern NSString *_SPTPersistentCacheObjectDescription(id object, id firstValue, ...); 43| | 44| |/** 45| | Creates a standardized description string for the given _object_ and a variable list of _value_ to _key_ pairs. 46| | 47| | The function takes a variable list of value and key pairs. Just like the variadic `NSDictionary` initializer. It 48| | will automatically insert the termination sentinel for you. 49| | 50| | @return A standardized description string on the format ``. 51| | */ 52| 15|#define SPTPersistentCacheObjectDescription(object, firstValue, ...) _SPTPersistentCacheObjectDescription((object), (firstValue), __VA_ARGS__, SPTPersistentCacheObjectDescriptionTerminationSentinel) /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheObjectDescription.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "SPTPersistentCacheObjectDescription.h" 22| | 23| |id const SPTPersistentCacheObjectDescriptionTerminationSentinel = @"0xDEADC0DE"; 24| | 25| |NS_INLINE BOOL SPTIsObjectDescriptionTerminationSentinel(id const object) 26| 185|{ 27| 185| return object == SPTPersistentCacheObjectDescriptionTerminationSentinel; 28| 185|} 29| | 30| |static void SPTPersistentCacheObjectDescriptionAppendToString(NSMutableString *description, id object, id firstValue, va_list valueKeyPairs) 31| 25|{ 32| 25| NSCParameterAssert(description); 33| 25| NSCParameterAssert(object); 34| 25| 35| 25| id value = firstValue; 36| 25| id key = va_arg(valueKeyPairs, id); 37| 25| 38| 54| while (!SPTIsObjectDescriptionTerminationSentinel(value) && !SPTIsObjectDescriptionTerminationSentinel(key)) { 39| 51| if (value != object && key != object) { 40| 50| [description appendFormat:@"; %@ = \"%@\"", key, value]; 41| 50| } 42| 51| 43| 51| value = va_arg(valueKeyPairs, id); 44| 51| if (SPTIsObjectDescriptionTerminationSentinel(value)) { 45| 22| break; 46| 22| } 47| 29| 48| 29| key = va_arg(valueKeyPairs, id); 49| 29| } 50| 25| 51| 25|} 52| | 53| |NSString *_SPTPersistentCacheObjectDescription(id object, id firstValue, ...) 54| 27|{ 55| 27| if (object == nil) { 56| 1| return nil; 57| 1| } 58| 26| 59| 26| NSString * const objectClassName = NSStringFromClass(object.class); 60| 26| NSMutableString * const description = [NSMutableString stringWithFormat:@"<%@: %p", objectClassName, (void *)object]; 61| 26| 62| 26| NSString * (^ const closeAndReturnDescriptionBlock)(void) = ^{ 63| 26| [description appendString:@">"]; 64| 26| return [description copy]; 65| 26| }; 66| 26| 67| 26| if (SPTIsObjectDescriptionTerminationSentinel(firstValue)) { 68| 1| return closeAndReturnDescriptionBlock(); 69| 1| } 70| 25| 71| 25| va_list valueKeyPairs; 72| 25| va_start(valueKeyPairs, firstValue); 73| 25| SPTPersistentCacheObjectDescriptionAppendToString(description, object, firstValue, valueKeyPairs); 74| 25| va_end(valueKeyPairs); 75| 25| 76| 25| return closeAndReturnDescriptionBlock(); 77| 25|} /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheOptions.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| |#import "SPTPersistentCacheObjectDescription.h" 23| |#import "SPTPersistentCacheDebugUtilities.h" 24| | 25| |#pragma mark - Constants 26| | 27| |const NSUInteger SPTPersistentCacheDefaultExpirationTimeSec = 10 * 60; 28| |const NSUInteger SPTPersistentCacheDefaultGCIntervalSec = 6 * 60 + 3; 29| |static const NSUInteger SPTPersistentCacheDefaultCacheSizeInBytes = 0; // unbounded 30| | 31| |const NSUInteger SPTPersistentCacheMinimumGCIntervalLimit = 60; 32| |const NSUInteger SPTPersistentCacheMinimumExpirationLimit = 60; 33| | 34| | 35| |#pragma mark Helper Functions 36| | 37| |static NSUInteger SPTGuardedPropertyValue(NSUInteger proposedValue, NSUInteger minimumValue, SEL propertySelector, SPTPersistentCacheDebugCallback debugCallback); 38| | 39| | 40| |#pragma mark - SPTPersistentCacheOptions Implementation 41| | 42| |@implementation SPTPersistentCacheOptions 43| | 44| |#pragma mark Object Life Cycle 45| | 46| |- (instancetype)init 47| 457|{ 48| 457| self = [super init]; 49| 457| 50| 457| if (self) { 51| 457| _cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"/com.spotify.temppersistent.image.cache"]; 52| 457| _cacheIdentifier = @"persistent.cache"; 53| 457| _useDirectorySeparation = YES; 54| 457| 55| 457| _garbageCollectionInterval = SPTPersistentCacheDefaultGCIntervalSec; 56| 457| _defaultExpirationPeriod = SPTPersistentCacheDefaultExpirationTimeSec; 57| 457| _sizeConstraintBytes = SPTPersistentCacheDefaultCacheSizeInBytes; 58| 457| _maxConcurrentOperations = NSOperationQueueDefaultMaxConcurrentOperationCount; 59| 457| _writePriority = NSOperationQueuePriorityNormal; 60| 457| _writeQualityOfService = NSQualityOfServiceDefault; 61| 457| _readPriority = NSOperationQueuePriorityNormal; 62| 457| _readQualityOfService = NSQualityOfServiceDefault; 63| 457| _deletePriority = NSOperationQueuePriorityNormal; 64| 457| _deleteQualityOfService = NSQualityOfServiceDefault; 65| 457| _garbageCollectionPriority = NSOperationQueuePriorityLow; 66| 457| _garbageCollectionQualityOfService = NSQualityOfServiceBackground; 67| 457| } 68| 457| 69| 457| return self; 70| 457|} 71| | 72| |#pragma mark Queue Management Options 73| | 74| |- (NSString *)identifierForQueue 75| 91|{ 76| 91| return [NSString stringWithFormat:@"%@.queue.%lu.%lu.%p", 77| 91| self.cacheIdentifier, 78| 91| (unsigned long)self.garbageCollectionInterval, 79| 91| (unsigned long)self.defaultExpirationPeriod, 80| 91| (void *)self]; 81| 91|} 82| | 83| |#pragma mark Garbage Collection Options 84| | 85| |- (void)setGarbageCollectionInterval:(NSUInteger)garbageCollectionInterval 86| 336|{ 87| 336| if (_garbageCollectionInterval == garbageCollectionInterval) { 88| 332| return; 89| 332| } 90| 4| _garbageCollectionInterval = SPTGuardedPropertyValue(garbageCollectionInterval, 91| 4| SPTPersistentCacheMinimumGCIntervalLimit, 92| 4| @selector(garbageCollectionInterval), 93| 4| self.debugOutput); 94| 4|} 95| | 96| |- (void)setDefaultExpirationPeriod:(NSUInteger)defaultExpirationPeriod 97| 412|{ 98| 412| if (_defaultExpirationPeriod == defaultExpirationPeriod) { 99| 386| return; 100| 386| } 101| 26| _defaultExpirationPeriod = SPTGuardedPropertyValue(defaultExpirationPeriod, 102| 26| SPTPersistentCacheMinimumExpirationLimit, 103| 26| @selector(defaultExpirationPeriod), 104| 26| self.debugOutput); 105| 26|} 106| | 107| |#pragma mark NSCopying 108| | 109| |- (id)copyWithZone:(NSZone *)zone 110| 333|{ 111| 333| SPTPersistentCacheOptions * const copy = [[self.class allocWithZone:zone] init]; 112| 333| 113| 333| copy.cacheIdentifier = self.cacheIdentifier; 114| 333| copy.cachePath = self.cachePath; 115| 333| copy.useDirectorySeparation = self.useDirectorySeparation; 116| 333| 117| 333| copy.garbageCollectionInterval = self.garbageCollectionInterval; 118| 333| copy.defaultExpirationPeriod = self.defaultExpirationPeriod; 119| 333| copy.sizeConstraintBytes = self.sizeConstraintBytes; 120| 333| 121| 333| copy.debugOutput = self.debugOutput; 122| 333| copy.timingCallback = self.timingCallback; 123| 333| 124| 333| copy.maxConcurrentOperations = self.maxConcurrentOperations; 125| 333| copy.writePriority = self.writePriority; 126| 333| copy.writeQualityOfService = self.writeQualityOfService; 127| 333| copy.readPriority = self.readPriority; 128| 333| copy.readQualityOfService = self.readQualityOfService; 129| 333| copy.deletePriority = self.deletePriority; 130| 333| copy.deleteQualityOfService = self.deleteQualityOfService; 131| 333| copy.garbageCollectionPriority = self.garbageCollectionPriority; 132| 333| copy.garbageCollectionQualityOfService = self.garbageCollectionQualityOfService; 133| 333| 134| 333| return copy; 135| 333|} 136| | 137| |#pragma mark Describing an Object 138| | 139| |- (NSString *)description 140| 2|{ 141| 2| return SPTPersistentCacheObjectDescription(self, self.cacheIdentifier, @"cache-identifier"); 142| 2|} 143| | 144| |- (NSString *)debugDescription 145| 2|{ 146| 2| return SPTPersistentCacheObjectDescription(self, 147| 2| self.cacheIdentifier, @"cache-identifier", 148| 2| self.cachePath, @"cache-path", 149| 2| self.identifierForQueue, @"identifier-for-queue", 150| 2| @(self.useDirectorySeparation), @"use-directory-separation", 151| 2| @(self.garbageCollectionInterval), @"garbage-collection-interval", 152| 2| @(self.defaultExpirationPeriod), @"default-expiration-period", 153| 2| @(self.sizeConstraintBytes), @"size-constraint-bytes"); 154| 2|} 155| | 156| |@end 157| | 158| | 159| |@implementation SPTPersistentCacheOptions (Deprecated) 160| | 161| |- (instancetype)initWithCachePath:(NSString *)cachePath 162| | identifier:(NSString *)cacheIdentifier 163| | defaultExpirationInterval:(NSUInteger)defaultExpirationInterval 164| | garbageCollectorInterval:(NSUInteger)garbageCollectorInterval 165| | debug:(nullable SPTPersistentCacheDebugCallback)debugCallback 166| 2|{ 167| 2| self = [self init]; 168| 2| 169| 2| if (self) { 170| 2| _cachePath = [cachePath copy]; 171| 2| _cacheIdentifier = [cacheIdentifier copy]; 172| 2| 173| 2| _defaultExpirationPeriod = SPTGuardedPropertyValue(defaultExpirationInterval, SPTPersistentCacheMinimumExpirationLimit, @selector(defaultExpirationPeriod), debugCallback); 174| 2| _garbageCollectionInterval = SPTGuardedPropertyValue(garbageCollectorInterval, SPTPersistentCacheMinimumGCIntervalLimit, @selector(garbageCollectionInterval), debugCallback); 175| 2| 176| 2| _debugOutput = [debugCallback copy]; 177| 2| } 178| 2| 179| 2| return self; 180| 2|} 181| | 182| |- (BOOL)folderSeparationEnabled 183| 2|{ 184| 2| return self.useDirectorySeparation; 185| 2|} 186| | 187| |- (void)setFolderSeparationEnabled:(BOOL)folderSeparationEnabled 188| 1|{ 189| 1| self.useDirectorySeparation = folderSeparationEnabled; 190| 1|} 191| | 192| |- (NSUInteger)gcIntervalSec 193| 1|{ 194| 1| return self.garbageCollectionInterval; 195| 1|} 196| | 197| |- (NSUInteger)defaultExpirationPeriodSec 198| 1|{ 199| 1| return self.defaultExpirationPeriod; 200| 1|} 201| | 202| |@end 203| | 204| | 205| |static NSUInteger SPTGuardedPropertyValue(NSUInteger proposedValue, NSUInteger minimumValue, SEL propertySelector, SPTPersistentCacheDebugCallback debugCallback) 206| 34|{ 207| 34| if (proposedValue >= minimumValue) { 208| 26| return proposedValue; 209| 26| } 210| 8| 211| 8| SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: Forcing \"%@\" to %lu seconds", NSStringFromSelector(propertySelector), (unsigned long)SPTPersistentCacheMinimumExpirationLimit], debugCallback); 212| 8| return minimumValue; 213| 8|} /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCachePosixWrapper.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "SPTPersistentCachePosixWrapper.h" 22| | 23| |@implementation SPTPersistentCachePosixWrapper 24| | 25| |- (int)close:(int)descriptor 26| 253|{ 27| 253| return close(descriptor); 28| 253|} 29| | 30| |- (ssize_t)read:(int)descriptor buffer:(void *)buffer bufferSize:(size_t)bufferSize 31| 293|{ 32| 293| return read(descriptor, buffer, bufferSize); 33| 293|} 34| | 35| |- (off_t)lseek:(int)descriptor seekType:(off_t)seekType seekAmount:(int)seekAmount 36| 24|{ 37| 24| return lseek(descriptor, seekType, seekAmount); 38| 24|} 39| | 40| |- (ssize_t)write:(int)descriptor buffer:(const void *)buffer bufferSize:(size_t)bufferSize 41| 24|{ 42| 24| return write(descriptor, buffer, bufferSize); 43| 24|} 44| | 45| |- (int)fsync:(int)descriptor 46| 24|{ 47| 24| return fsync(descriptor); 48| 24|} 49| | 50| |- (int)stat:(const char *)path statStruct:(struct stat *)statStruct 51| 26|{ 52| 26| return stat(path, statStruct); 53| 26|} 54| | 55| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheRecord.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| |#import "SPTPersistentCacheObjectDescription.h" 23| | 24| |@implementation SPTPersistentCacheRecord 25| | 26| |#pragma mark SPTPersistentCacheRecord 27| | 28| |- (instancetype)initWithData:(NSData *)data 29| | key:(NSString *)key 30| | refCount:(NSUInteger)refCount 31| | ttl:(NSUInteger)ttl 32| 63|{ 33| 63| self = [super init]; 34| 63| if (self) { 35| 63| _refCount = refCount; 36| 63| _ttl = ttl; 37| 63| _key = [key copy]; 38| 63| _data = data; 39| 63| } 40| 63| return self; 41| 63|} 42| | 43| |#pragma mark Describing Object 44| | 45| |- (NSString *)description 46| 2|{ 47| 2| return SPTPersistentCacheObjectDescription(self, self.key, @"key"); 48| 2|} 49| | 50| |- (NSString *)debugDescription 51| 3|{ 52| 3| return SPTPersistentCacheObjectDescription(self, self.key, @"key", @(self.ttl), @"ttl", @(self.refCount), @"ref-count"); 53| 3|} 54| | 55| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheResponse.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| |#import 23| |#import "SPTPersistentCacheObjectDescription.h" 24| |#import "SPTPersistentCacheResponse+Private.h" 25| | 26| |@interface SPTPersistentCacheResponse () 27| | 28| |@property (nonatomic, assign, readwrite) SPTPersistentCacheResponseCode result; 29| |@property (nonatomic, strong, readwrite) NSError *error; 30| |@property (nonatomic, strong, readwrite) SPTPersistentCacheRecord *record; 31| | 32| |@end 33| | 34| |@implementation SPTPersistentCacheResponse 35| | 36| |- (instancetype)initWithResult:(SPTPersistentCacheResponseCode)result 37| | error:(NSError *)error 38| | record:(SPTPersistentCacheRecord *)record 39| 1.37k|{ 40| 1.37k| self = [super init]; 41| 1.37k| if (self) { 42| 1.37k| _result = result; 43| 1.37k| _error = error; 44| 1.37k| _record = record; 45| 1.37k| } 46| 1.37k| return self; 47| 1.37k|} 48| | 49| |#pragma mark Describing Object 50| | 51| |NSString *NSStringFromSPTPersistentCacheResponseCode(SPTPersistentCacheResponseCode code) 52| 6|{ 53| 6| switch (code) { 54| 4| case SPTPersistentCacheResponseCodeNotFound: return @"not-found"; 55| 1| case SPTPersistentCacheResponseCodeOperationError: return @"operation-error"; 56| 1| case SPTPersistentCacheResponseCodeOperationSucceeded: return @"operation-success"; 57| 6| } 58| 6|} 59| | 60| |- (NSString *)description 61| 2|{ 62| 2| return SPTPersistentCacheObjectDescription(self, NSStringFromSPTPersistentCacheResponseCode(self.result), @"result"); 63| 2|} 64| | 65| |- (NSString *)debugDescription 66| 1|{ 67| 1| return SPTPersistentCacheObjectDescription(self, 68| 1| NSStringFromSPTPersistentCacheResponseCode(self.result), @"result", 69| 1| self.record.debugDescription, @"record", 70| 1| self.error.debugDescription, @"error"); 71| 1|} 72| | 73| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/SPTPersistentCacheTypeUtilities.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| | 22| |#import "SPTPersistentCacheTypeUtilities.h" 23| | 24| | 25| |uint64_t spt_uint64rint(double value) 26| 1.08k|{ 27| 1.08k| return (uint64_t)llrint(value); 28| 1.08k|} /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Sources/crc32iso3309.c: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#include "crc32iso3309.h" 22| | 23| |/** 24| | Algorithms is taken from RFC-1952. Appendix: Sample CRC Code 25| | */ 26| | 27| |/* Table of CRCs of all 8-bit messages. */ 28| |static const uint32_t crc_table[] = { 29| | 0x00000000U, 0x77073096U, 0xee0e612cU, 0x990951baU, 30| | 0x076dc419U, 0x706af48fU, 0xe963a535U, 0x9e6495a3U, 31| | 0x0edb8832U, 0x79dcb8a4U, 0xe0d5e91eU, 0x97d2d988U, 32| | 0x09b64c2bU, 0x7eb17cbdU, 0xe7b82d07U, 0x90bf1d91U, 33| | 0x1db71064U, 0x6ab020f2U, 0xf3b97148U, 0x84be41deU, 34| | 0x1adad47dU, 0x6ddde4ebU, 0xf4d4b551U, 0x83d385c7U, 35| | 0x136c9856U, 0x646ba8c0U, 0xfd62f97aU, 0x8a65c9ecU, 36| | 0x14015c4fU, 0x63066cd9U, 0xfa0f3d63U, 0x8d080df5U, 37| | 0x3b6e20c8U, 0x4c69105eU, 0xd56041e4U, 0xa2677172U, 38| | 0x3c03e4d1U, 0x4b04d447U, 0xd20d85fdU, 0xa50ab56bU, 39| | 0x35b5a8faU, 0x42b2986cU, 0xdbbbc9d6U, 0xacbcf940U, 40| | 0x32d86ce3U, 0x45df5c75U, 0xdcd60dcfU, 0xabd13d59U, 41| | 0x26d930acU, 0x51de003aU, 0xc8d75180U, 0xbfd06116U, 42| | 0x21b4f4b5U, 0x56b3c423U, 0xcfba9599U, 0xb8bda50fU, 43| | 0x2802b89eU, 0x5f058808U, 0xc60cd9b2U, 0xb10be924U, 44| | 0x2f6f7c87U, 0x58684c11U, 0xc1611dabU, 0xb6662d3dU, 45| | 0x76dc4190U, 0x01db7106U, 0x98d220bcU, 0xefd5102aU, 46| | 0x71b18589U, 0x06b6b51fU, 0x9fbfe4a5U, 0xe8b8d433U, 47| | 0x7807c9a2U, 0x0f00f934U, 0x9609a88eU, 0xe10e9818U, 48| | 0x7f6a0dbbU, 0x086d3d2dU, 0x91646c97U, 0xe6635c01U, 49| | 0x6b6b51f4U, 0x1c6c6162U, 0x856530d8U, 0xf262004eU, 50| | 0x6c0695edU, 0x1b01a57bU, 0x8208f4c1U, 0xf50fc457U, 51| | 0x65b0d9c6U, 0x12b7e950U, 0x8bbeb8eaU, 0xfcb9887cU, 52| | 0x62dd1ddfU, 0x15da2d49U, 0x8cd37cf3U, 0xfbd44c65U, 53| | 0x4db26158U, 0x3ab551ceU, 0xa3bc0074U, 0xd4bb30e2U, 54| | 0x4adfa541U, 0x3dd895d7U, 0xa4d1c46dU, 0xd3d6f4fbU, 55| | 0x4369e96aU, 0x346ed9fcU, 0xad678846U, 0xda60b8d0U, 56| | 0x44042d73U, 0x33031de5U, 0xaa0a4c5fU, 0xdd0d7cc9U, 57| | 0x5005713cU, 0x270241aaU, 0xbe0b1010U, 0xc90c2086U, 58| | 0x5768b525U, 0x206f85b3U, 0xb966d409U, 0xce61e49fU, 59| | 0x5edef90eU, 0x29d9c998U, 0xb0d09822U, 0xc7d7a8b4U, 60| | 0x59b33d17U, 0x2eb40d81U, 0xb7bd5c3bU, 0xc0ba6cadU, 61| | 0xedb88320U, 0x9abfb3b6U, 0x03b6e20cU, 0x74b1d29aU, 62| | 0xead54739U, 0x9dd277afU, 0x04db2615U, 0x73dc1683U, 63| | 0xe3630b12U, 0x94643b84U, 0x0d6d6a3eU, 0x7a6a5aa8U, 64| | 0xe40ecf0bU, 0x9309ff9dU, 0x0a00ae27U, 0x7d079eb1U, 65| | 0xf00f9344U, 0x8708a3d2U, 0x1e01f268U, 0x6906c2feU, 66| | 0xf762575dU, 0x806567cbU, 0x196c3671U, 0x6e6b06e7U, 67| | 0xfed41b76U, 0x89d32be0U, 0x10da7a5aU, 0x67dd4accU, 68| | 0xf9b9df6fU, 0x8ebeeff9U, 0x17b7be43U, 0x60b08ed5U, 69| | 0xd6d6a3e8U, 0xa1d1937eU, 0x38d8c2c4U, 0x4fdff252U, 70| | 0xd1bb67f1U, 0xa6bc5767U, 0x3fb506ddU, 0x48b2364bU, 71| | 0xd80d2bdaU, 0xaf0a1b4cU, 0x36034af6U, 0x41047a60U, 72| | 0xdf60efc3U, 0xa867df55U, 0x316e8eefU, 0x4669be79U, 73| | 0xcb61b38cU, 0xbc66831aU, 0x256fd2a0U, 0x5268e236U, 74| | 0xcc0c7795U, 0xbb0b4703U, 0x220216b9U, 0x5505262fU, 75| | 0xc5ba3bbeU, 0xb2bd0b28U, 0x2bb45a92U, 0x5cb36a04U, 76| | 0xc2d7ffa7U, 0xb5d0cf31U, 0x2cd99e8bU, 0x5bdeae1dU, 77| | 0x9b64c2b0U, 0xec63f226U, 0x756aa39cU, 0x026d930aU, 78| | 0x9c0906a9U, 0xeb0e363fU, 0x72076785U, 0x05005713U, 79| | 0x95bf4a82U, 0xe2b87a14U, 0x7bb12baeU, 0x0cb61b38U, 80| | 0x92d28e9bU, 0xe5d5be0dU, 0x7cdcefb7U, 0x0bdbdf21U, 81| | 0x86d3d2d4U, 0xf1d4e242U, 0x68ddb3f8U, 0x1fda836eU, 82| | 0x81be16cdU, 0xf6b9265bU, 0x6fb077e1U, 0x18b74777U, 83| | 0x88085ae6U, 0xff0f6a70U, 0x66063bcaU, 0x11010b5cU, 84| | 0x8f659effU, 0xf862ae69U, 0x616bffd3U, 0x166ccf45U, 85| | 0xa00ae278U, 0xd70dd2eeU, 0x4e048354U, 0x3903b3c2U, 86| | 0xa7672661U, 0xd06016f7U, 0x4969474dU, 0x3e6e77dbU, 87| | 0xaed16a4aU, 0xd9d65adcU, 0x40df0b66U, 0x37d83bf0U, 88| | 0xa9bcae53U, 0xdebb9ec5U, 0x47b2cf7fU, 0x30b5ffe9U, 89| | 0xbdbdf21cU, 0xcabac28aU, 0x53b39330U, 0x24b4a3a6U, 90| | 0xbad03605U, 0xcdd70693U, 0x54de5729U, 0x23d967bfU, 91| | 0xb3667a2eU, 0xc4614ab8U, 0x5d681b02U, 0x2a6f2b94U, 92| | 0xb40bbe37U, 0xc30c8ea1U, 0x5a05df1bU, 0x2d02ef8dU 93| |}; 94| | 95| |/* 96| | Update a running crc with the bytes buf[0..len-1] and return 97| | the updated crc. The crc should be initialized to zero. Pre- and 98| | post-conditioning (one's complement) is performed within this 99| | function so it shouldn't be done by the caller. Usage example: 100| | 101| | uint32_t crc = 0L; 102| | 103| | while (read_buffer(buffer, length) != EOF) { 104| | crc = update_crc(crc, buffer, length); 105| | } 106| | if (crc != original_crc) error(); 107| | */ 108| |static uint32_t update_crc(uint32_t crc, const uint8_t *buf, size_t len) 109| 1.59k|{ 110| 1.59k| uint32_t c = crc ^ 0xffFFffFFU; 111| 1.59k| 112| 97.3k| for (size_t n = 0; n < len; ++n) { 113| 95.7k| c = crc_table[(c ^ buf[n]) & 0xFF] ^ (c >> 8); 114| 95.7k| } 115| 1.59k| return c ^ 0xffFFffFFU; 116| 1.59k|} 117| | 118| |/* Return the CRC of the bytes buf[0..len-1]. */ 119| |uint32_t spt_crc32(const uint8_t *buf, size_t len) 120| 1.59k|{ 121| 1.59k| return update_crc(0L, buf, len); 122| 1.59k|} /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/NSError+SPTPersistentCacheDomainErrorsTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |#import "NSError+SPTPersistentCacheDomainErrors.h" 24| | 25| |@interface NSError_SPTPersistentCacheDomainErrorsTests : XCTestCase 26| | 27| |@end 28| | 29| |@implementation NSError_SPTPersistentCacheDomainErrorsTests 30| | 31| | 32| |- (void)testPersistentDataCacheErrorFactoryMethod 33| 1|{ 34| 1| SPTPersistentCacheLoadingError errorCode = SPTPersistentCacheLoadingErrorHeaderAlignmentMismatch; 35| 1| 36| 1| NSError *error = [NSError spt_persistentDataCacheErrorWithCode:SPTPersistentCacheLoadingErrorHeaderAlignmentMismatch]; 37| 1| 38| 1| XCTAssertEqual(error.domain, SPTPersistentCacheErrorDomain); 39| 1| XCTAssertEqual(error.code, errorCode); 40| 1|} 41| | 42| | 43| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/NSFileManagerMock.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "NSFileManagerMock.h" 22| | 23| |@implementation NSFileManagerMock 24| | 25| |- (BOOL)fileExistsAtPath:(NSString *)path 26| 37|{ 27| 37| self.lastPathCalledOnExists = path; 28| 37| BOOL exists = [super fileExistsAtPath:path]; 29| 37| if (self.blockCalledOnFileExistsAtPath) { 30| 1| self.blockCalledOnFileExistsAtPath(); 31| 1| } 32| 37| return exists; 33| 37|} 34| | 35| |- (BOOL)removeItemAtPath:(NSString *)path error:(NSError * _Nullable __autoreleasing *)error 36| 13|{ 37| 13| if (self.disableRemoveFile) { 38| 13| return NO; 39| 13| } 40| 0| return [super removeItemAtPath:path error:error]; 41| 0|} 42| | 43| |- (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError * _Nullable __autoreleasing *)error 44| 1|{ 45| 1| if (self.mock_attributesOfItemsAtPaths) { 46| 1| return self.mock_attributesOfItemsAtPaths[path]; 47| 1| } 48| 0| return [super attributesOfItemAtPath:path error:error]; 49| 0|} 50| | 51| |- (NSDictionary *)attributesOfFileSystemForPath:(NSString *)path error:(NSError * _Nullable __autoreleasing *)error 52| 1|{ 53| 1| if (self.mock_attributesOfFileSystemForPaths) { 54| 1| return self.mock_attributesOfFileSystemForPaths[path]; 55| 1| } 56| 0| return [super attributesOfFileSystemForPath:path error:error]; 57| 0|} 58| | 59| |- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError * _Nullable __autoreleasing *)error 60| 1|{ 61| 1| if (self.mock_contentsOfDirectoryAtPaths) { 62| 1| return self.mock_contentsOfDirectoryAtPaths[path]; 63| 1| } 64| 0| return [super contentsOfDirectoryAtPath:path error:error]; 65| 0|} 66| | 67| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheDebugUtilitiesTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| | 22| |#import 23| |#import "SPTPersistentCacheDebugUtilities.h" 24| | 25| | 26| |@interface SPTPersistentCacheDebugUtilitiesTests : XCTestCase 27| |@end 28| | 29| |@implementation SPTPersistentCacheDebugUtilitiesTests 30| | 31| |- (void)testNilDebugCallback 32| 1|{ 33| 1| XCTAssertNoThrow(SPTPersistentCacheSafeDebugCallback(@"", 34| 1| nil), 35| 1| @"A nil callback shouldn't cause an exception."); 36| 1|} 37| | 38| |- (void)testDebugCallback 39| 1|{ 40| 1| __block NSString *stringSetInsideBlock = nil; 41| 1| 42| 1| NSString *testString = @"Test"; 43| 1| SPTPersistentCacheSafeDebugCallback(testString, ^(NSString *message){ 44| 1| stringSetInsideBlock = message; 45| 1| }); 46| 1| 47| 1| XCTAssertEqualObjects(stringSetInsideBlock, 48| 1| testString, 49| 1| @"The debug callback was not executed. :{"); 50| 1|} 51| | 52| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheFileManagerTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |#import "SPTPersistentCacheFileManager+Private.h" 24| |#import "NSFileManagerMock.h" 25| | 26| |#import 27| | 28| |#pragma mark - 29| | 30| |@interface SPTPersistentCacheFileManagerForTests : SPTPersistentCacheFileManager 31| | 32| |@property (nonatomic, strong, readwrite) NSFileManagerMock *test_fileManager; 33| |@property (nonatomic, copy, readwrite) SPTPersistentCacheDebugCallback test_debugOutput; 34| | 35| |@end 36| | 37| |@implementation SPTPersistentCacheFileManagerForTests 38| | 39| |- (NSFileManager *)fileManager 40| 11|{ 41| 11| return self.test_fileManager ?: super.fileManager; 42| 11|} 43| | 44| |- (SPTPersistentCacheDebugCallback)debugOutput 45| 4|{ 46| 4| return self.test_debugOutput ?: super.debugOutput; 47| 4|} 48| | 49| |@end 50| | 51| | 52| |#pragma mark - 53| | 54| |@interface SPTPersistentCacheFileManagerTests : XCTestCase 55| |@property (nonatomic, copy) NSString *cachePath; 56| |@property (nonatomic, strong) SPTPersistentCacheOptions *options; 57| |@property (nonatomic, strong) SPTPersistentCacheFileManagerForTests *cacheFileManager; 58| |@end 59| | 60| |@implementation SPTPersistentCacheFileManagerTests 61| | 62| |- (void)setUp 63| 11|{ 64| 11| [super setUp]; 65| 11| 66| 11| NSString *testName = NSStringFromSelector(self.invocation.selector ?: _cmd); 67| 11| NSString *uniqCachePart = [NSString stringWithFormat:@"/pcache-%@", testName]; 68| 11| self.cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:uniqCachePart]; 69| 11| 70| 11| SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new]; 71| 11| options.cachePath = self.cachePath; 72| 11| options.cacheIdentifier = @"test"; 73| 11| self.options = options; 74| 11| 75| 11| self.cacheFileManager = [[SPTPersistentCacheFileManagerForTests alloc] initWithOptions:self.options]; 76| 11|} 77| | 78| |- (void)tearDown 79| 11|{ 80| 11| [super tearDown]; 81| 11| 82| 11| [[NSFileManager defaultManager] removeItemAtPath:self.cachePath error:nil]; 83| 11|} 84| | 85| |- (void)testCreateCacheDirectory 86| 1|{ 87| 1| BOOL didCreateCacheDirectory = [self.cacheFileManager createCacheDirectory]; 88| 1| BOOL isDirectory = NO; 89| 1| 90| 1| BOOL wasFileCreated = [[NSFileManager defaultManager] fileExistsAtPath:self.options.cachePath 91| 1| isDirectory:&isDirectory]; 92| 1| 93| 1| XCTAssertTrue(didCreateCacheDirectory); 94| 1| XCTAssertTrue(wasFileCreated); 95| 1| XCTAssertTrue(isDirectory); 96| 1|} 97| | 98| |- (void)testExistingCacheDirectory 99| 1|{ 100| 1| BOOL didCreateDirectoryUsingNSFileManager = [[NSFileManager defaultManager] createDirectoryAtPath:self.options.cachePath 101| 1| withIntermediateDirectories:YES 102| 1| attributes:nil 103| 1| error:nil]; 104| 1| BOOL directoryDidExist = [self.cacheFileManager createCacheDirectory]; 105| 1| 106| 1| XCTAssertTrue(didCreateDirectoryUsingNSFileManager); 107| 1| XCTAssertTrue(directoryDidExist); 108| 1|} 109| | 110| |- (void)testSubdirectoryPathForKeyWithShortKey 111| 1|{ 112| 1| NSString *shortKey = @"AA"; 113| 1| 114| 1| NSString *subDirectoryPath = [self.cacheFileManager subDirectoryPathForKey:shortKey]; 115| 1| 116| 1| NSString *expectedSubDirectoryPath = [self.options.cachePath stringByAppendingPathComponent:shortKey]; 117| 1| 118| 1| XCTAssertEqualObjects(subDirectoryPath, 119| 1| expectedSubDirectoryPath); 120| 1|} 121| | 122| |- (void)testSubdirectoryPathForKeyWithLongKey 123| 1|{ 124| 1| NSString *key = @"AABBCC"; 125| 1| 126| 1| NSString *subDirectoryPath = [self.cacheFileManager subDirectoryPathForKey:key]; 127| 1| 128| 1| NSString *expectedSubDirectoryPath = [self.options.cachePath stringByAppendingPathComponent:[key substringToIndex:SPTPersistentCacheFileManagerSubDirNameLength]]; 129| 1| 130| 1| XCTAssertEqualObjects(subDirectoryPath, 131| 1| expectedSubDirectoryPath); 132| 1|} 133| | 134| |- (void)testPathForKey 135| 1|{ 136| 1| NSString *key = @"AABBCC"; 137| 1| 138| 1| NSString *pathForKey = [self.cacheFileManager pathForKey:key]; 139| 1| 140| 1| NSString *expectedSubDirectoryPath = [self.options.cachePath stringByAppendingPathComponent:[key substringToIndex:SPTPersistentCacheFileManagerSubDirNameLength]]; 141| 1| 142| 1| expectedSubDirectoryPath = [expectedSubDirectoryPath stringByAppendingPathComponent:key]; 143| 1| 144| 1| XCTAssertEqualObjects(pathForKey, 145| 1| expectedSubDirectoryPath); 146| 1|} 147| | 148| |- (void)testRemoveFileForKey 149| 1|{ 150| 1| NSString *shortKey = @"AA"; 151| 1| 152| 1| NSString *pathForKey = [self.cacheFileManager pathForKey:shortKey]; 153| 1| 154| 1| [self createFileForKey:shortKey]; 155| 1| 156| 1| [self.cacheFileManager removeDataForKey:shortKey]; 157| 1| 158| 1| XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:pathForKey]); 159| 1|} 160| | 161| |- (void)testOptimizedDiskSizeForCacheSizeInsanelyBig 162| 1|{ 163| 1| SPTPersistentCacheDiskSize insanelyBigCacheSize = LONG_LONG_MAX; 164| 1| 165| 1| SPTPersistentCacheDiskSize optimizedSize = [self.cacheFileManager optimizedDiskSizeForCacheSize:insanelyBigCacheSize]; 166| 1| 167| 1| XCTAssertEqual(optimizedSize, (SPTPersistentCacheDiskSize)self.options.sizeConstraintBytes); 168| 1|} 169| | 170| |- (void)testOptimizedDiskSizeForCacheSizeSmall 171| 1|{ 172| 1| SPTPersistentCacheDiskSize smallCacheSize = 1024 * 1024 * 1; 173| 1| 174| 1| SPTPersistentCacheDiskSize optimizedSize = [self.cacheFileManager optimizedDiskSizeForCacheSize:smallCacheSize]; 175| 1| 176| 1| XCTAssertEqual(optimizedSize, (SPTPersistentCacheDiskSize)0); 177| 1|} 178| | 179| |- (void)testRemoveAllDataButKeysWithoutKeys 180| 1|{ 181| 1| NSString *keyOne = @"AA"; 182| 1| NSString *pathForDirectoryOne = [self createFileForKey:keyOne]; 183| 1| 184| 1| NSString *keyTwo = @"AB"; 185| 1| NSString *pathForDirectoryTwo = [self createFileForKey:keyTwo]; 186| 1| 187| 1| [self.cacheFileManager removeAllData]; 188| 1| 189| 1| BOOL isFileOneAtPath = [[NSFileManager defaultManager] fileExistsAtPath:pathForDirectoryOne 190| 1| isDirectory:nil]; 191| 1| 192| 1| BOOL isFileTwoAtPath = [[NSFileManager defaultManager] fileExistsAtPath:pathForDirectoryTwo 193| 1| isDirectory:nil]; 194| 1| 195| 1| XCTAssertTrue(!isFileOneAtPath && !isFileTwoAtPath, 196| 1| @"Removing all keys with nil or empty argument should have removed all data"); 197| 1|} 198| | 199| |- (void)testFileManagerFailsToGetAttributesOfFile 200| 1|{ 201| 1| __block BOOL called = NO; 202| 1| self.cacheFileManager.test_debugOutput = ^(NSString *string) { 203| 1| called = YES; 204| 1| }; 205| 1| 206| 1| NSFileManagerMock *fileManager = [NSFileManagerMock new]; 207| 1| fileManager.mock_attributesOfItemsAtPaths = @{}; 208| 1| self.cacheFileManager.test_fileManager = fileManager; 209| 1| 210| 1| [self.cacheFileManager getFileSizeAtPath:@"TEST"]; 211| 1| XCTAssertTrue(called); 212| 1|} 213| | 214| |- (void)DISABLED_testTotalUsedSizeInBytesFailWithNSURLGetResourceValue 215| 0|{ 216| 0| __block BOOL called = NO; 217| 0| self.cacheFileManager.test_debugOutput = ^(NSString *string) { 218| 0| called = YES; 219| 0| }; 220| 0| Method originalMethod = class_getInstanceMethod(NSURL.class, @selector(getResourceValue:forKey:error:)); 221| 0| IMP originalMethodImplementation = method_getImplementation(originalMethod); 222| 0| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 223| 0| return nil; 224| 0| }); 225| 0| method_setImplementation(originalMethod, fakeMethodImplementation); 226| 0| [self.cacheFileManager totalUsedSizeInBytes]; 227| 0| method_setImplementation(originalMethod, originalMethodImplementation); 228| 0| XCTAssertTrue(called); 229| 0|} 230| | 231| |- (void)testOptimizedDiskSizeForCacheSizeFileManagerFail 232| 1|{ 233| 1| __block BOOL called = NO; 234| 1| self.cacheFileManager.test_debugOutput = ^(NSString *string) { 235| 1| called = YES; 236| 1| }; 237| 1| 238| 1| NSFileManagerMock *fileManager = [NSFileManagerMock new]; 239| 1| fileManager.mock_attributesOfFileSystemForPaths = @{}; 240| 1| self.cacheFileManager.test_fileManager = fileManager; 241| 1| 242| 1| [self.cacheFileManager optimizedDiskSizeForCacheSize:100]; 243| 1| XCTAssertTrue(called); 244| 1|} 245| | 246| |#pragma mark - Helper Functions 247| | 248| |- (NSString *)createFileForKey:(NSString *)key 249| 3|{ 250| 3| NSError *error; 251| 3| 252| 3| NSString *pathForKey = [self.cacheFileManager pathForKey:key]; 253| 3| 254| 3| [self createDirectoryForKey:key]; 255| 3| 256| 3| BOOL didCreateFile = [@"TestString" writeToFile:pathForKey 257| 3| atomically:YES 258| 3| encoding:NSUTF8StringEncoding 259| 3| error:&error]; 260| 3| 261| 3| XCTAssertTrue(didCreateFile, 262| 3| @"Error while creating test file for key. %@", error); 263| 3| 264| 3| return didCreateFile ? pathForKey: nil; 265| 3|} 266| | 267| |- (NSString *)createDirectoryForKey:(NSString *)key 268| 3|{ 269| 3| NSString *pathForKey = [self.cacheFileManager pathForKey:key]; 270| 3| 271| 3| NSError *error; 272| 3| 273| 3| NSString *pathForDirectory = [pathForKey stringByDeletingLastPathComponent]; 274| 3| 275| 3| BOOL didCreateDirectory = [[NSFileManager defaultManager] createDirectoryAtPath:pathForDirectory 276| 3| withIntermediateDirectories:YES 277| 3| attributes:nil 278| 3| error:&error]; 279| 3| 280| 3| XCTAssertTrue(didCreateDirectory, 281| 3| @"Error while creating test directory for key. %@", error); 282| 3| 283| 3| return didCreateDirectory ? pathForDirectory : nil; 284| 3|} 285| | 286| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheGarbageCollectorTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| | 22| |#import 23| |#import "SPTPersistentCacheGarbageCollector.h" 24| |#import 25| | 26| |@interface SPTPersistentCacheGarbageCollector () 27| |@property (nonatomic, strong) NSTimer *timer; 28| |- (void)enqueueGarbageCollection:(NSTimer *)timer; 29| |@end 30| | 31| | 32| | 33| |@interface SPTPersistentCacheForTimerProxyUnitTests : SPTPersistentCache 34| |@property (nonatomic, strong) NSOperationQueue *queue; 35| |@property (nonatomic, weak) XCTestExpectation *testExpectation; 36| |@property (nonatomic, assign) BOOL wasCalledFromIncorrectQueue; 37| |@property (nonatomic, assign) BOOL wasRunRegularGCCalled; 38| |@property (nonatomic, assign) BOOL wasPruneBySizeCalled; 39| |@end 40| | 41| |@implementation SPTPersistentCacheForTimerProxyUnitTests 42| | 43| |- (void)runRegularGC 44| 1|{ 45| 1| self.wasCalledFromIncorrectQueue = ![[NSOperationQueue currentQueue].name isEqual:self.queue.name]; 46| 1| self.wasRunRegularGCCalled = (YES && !self.wasPruneBySizeCalled); 47| 1|} 48| | 49| |- (void)pruneBySize 50| 1|{ 51| 1| self.wasCalledFromIncorrectQueue = ![[NSOperationQueue currentQueue].name isEqual:self.queue.name]; 52| 1| self.wasPruneBySizeCalled = (YES && self.wasRunRegularGCCalled); 53| 1| [self.testExpectation fulfill]; 54| 1|} 55| | 56| |@end 57| | 58| |@interface SPTPersistentCacheGarbageCollectorTests : XCTestCase 59| |@property (nonatomic, strong) SPTPersistentCacheOptions *options; 60| |@property (nonatomic, strong) SPTPersistentCacheGarbageCollector *garbageCollector; 61| |@property (nonatomic, strong) SPTPersistentCache *cache; 62| |@property (nonatomic, strong) NSOperationQueue *operationQueue; 63| |@end 64| | 65| |@implementation SPTPersistentCacheGarbageCollectorTests 66| | 67| |- (void)setUp 68| 7|{ 69| 7| [super setUp]; 70| 7| 71| 7| self.cache = [[SPTPersistentCacheForTimerProxyUnitTests alloc] init]; 72| 7| 73| 7| self.options = [SPTPersistentCacheOptions new]; 74| 7| 75| 7| self.operationQueue = [[NSOperationQueue alloc] init]; 76| 7| self.operationQueue.name = @"cacheQueue"; 77| 7| 78| 7| self.garbageCollector = [[SPTPersistentCacheGarbageCollector alloc] initWithCache:self.cache 79| 7| options:self.options 80| 7| queue:self.operationQueue]; 81| 7|} 82| | 83| |- (void)testDesignatedInitializer 84| 1|{ 85| 1| __strong SPTPersistentCache *strongCache = self.garbageCollector.cache; 86| 1| 87| 1| XCTAssertEqual(self.garbageCollector.queue, self.operationQueue); 88| 1| XCTAssertEqualObjects(strongCache, self.cache); 89| 1| XCTAssertNil(self.garbageCollector.timer); 90| 1|} 91| | 92| |- (void)testGarbageCollectorEnqueue 93| 1|{ 94| 1| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"testGarbageCollectorEnqueue"]; 95| 1| 96| 1| SPTPersistentCacheForTimerProxyUnitTests *dataCacheForUnitTests = (SPTPersistentCacheForTimerProxyUnitTests *)self.garbageCollector.cache; 97| 1| dataCacheForUnitTests.queue = self.garbageCollector.queue; 98| 1| 99| 1| dataCacheForUnitTests.testExpectation = expectation; 100| 1| [self.garbageCollector enqueueGarbageCollection:nil]; 101| 1| 102| 1| [self waitForExpectationsWithTimeout:1.0 handler:^(NSError * _Nullable error) { 103| 1| XCTAssertTrue(dataCacheForUnitTests.wasRunRegularGCCalled); 104| 1| XCTAssertTrue(dataCacheForUnitTests.wasPruneBySizeCalled); 105| 1| XCTAssertFalse(dataCacheForUnitTests.wasCalledFromIncorrectQueue); 106| 1| }]; 107| 1|} 108| | 109| |- (void)testIsGarbageCollectionScheduled 110| 1|{ 111| 1| XCTAssertFalse(self.garbageCollector.isGarbageCollectionScheduled); 112| 1| [self.garbageCollector schedule]; 113| 1| XCTAssertTrue(self.garbageCollector.isGarbageCollectionScheduled); 114| 1| [self.garbageCollector unschedule]; 115| 1| XCTAssertFalse(self.garbageCollector.isGarbageCollectionScheduled); 116| 1|} 117| | 118| |- (void)testScheduleGarbageCollection 119| 1|{ 120| 1| [self.garbageCollector schedule]; 121| 1| XCTAssertNotNil(self.garbageCollector.timer); 122| 1| XCTAssertTrue(self.garbageCollector.timer.isValid); 123| 1| XCTAssertEqualWithAccuracy(self.garbageCollector.timer.timeInterval, self.options.garbageCollectionInterval, 0.0); 124| 1|} 125| | 126| |- (void)testRepeatedScheduleGarbageCollection 127| 1|{ 128| 1| [self.garbageCollector schedule]; 129| 1| NSTimer *timerFirstCall = self.garbageCollector.timer; 130| 1| 131| 1| [self.garbageCollector schedule]; 132| 1| NSTimer *timerSecondCall = self.garbageCollector.timer; 133| 1| 134| 1| XCTAssertEqualObjects(timerFirstCall, timerSecondCall); 135| 1|} 136| | 137| |- (void)testUnscheduleGarbageCollection 138| 1|{ 139| 1| [self.garbageCollector schedule]; 140| 1| [self.garbageCollector unschedule]; 141| 1| XCTAssertNil(self.garbageCollector.timer); 142| 1|} 143| | 144| |- (void)testSchedulingGarbageCollectionOnAnotherThread 145| 1|{ 146| 1| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Scheduled Expectation"]; 147| 1| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ { 148| 1| [self.garbageCollector schedule]; 149| 1| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 150| 1| [expectation fulfill]; 151| 1| }); 152| 1| }); 153| 1| [self waitForExpectationsWithTimeout:5.0 handler:nil]; 154| 1| XCTAssertTrue(self.garbageCollector.isGarbageCollectionScheduled); 155| 1|} 156| | 157| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheHeaderTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |#import 24| |#import 25| |#import "SPTPersistentCacheTypeUtilities.h" 26| | 27| | 28| |@interface SPTPersistentCacheHeaderTests : XCTestCase 29| | 30| |@end 31| | 32| |@implementation SPTPersistentCacheHeaderTests 33| | 34| |- (void)testGetHeaderFromDataWithSmallSizeData 35| 1|{ 36| 1| size_t smallerSize = SPTPersistentCacheRecordHeaderSize - 1; 37| 1| void *smallData = malloc(smallerSize); 38| 1| 39| 1| XCTAssertEqual(SPTPersistentCacheGetHeaderFromData(smallData, smallerSize), NULL); 40| 1| 41| 1| free(smallData); 42| 1|} 43| | 44| |- (void)testGetHeaderFromDataWithRightSize 45| 1|{ 46| 1| size_t rightHeaderSize = SPTPersistentCacheRecordHeaderSize; 47| 1| void *data = malloc(rightHeaderSize); 48| 1| 49| 1| XCTAssertEqual(SPTPersistentCacheGetHeaderFromData(data, rightHeaderSize), data); 50| 1| 51| 1| free(data); 52| 1|} 53| | 54| |- (void)testValidateMisalignedHeader 55| 1|{ 56| 1| #pragma clang diagnostic push 57| 1| #pragma clang diagnostic ignored "-Wcast-align" 58| 1| int headerValidationResult = SPTPersistentCacheValidateHeader((SPTPersistentCacheRecordHeader *)3); 59| 1| #pragma mark diagnostic pop 60| 1| XCTAssertEqual(headerValidationResult, SPTPersistentCacheLoadingErrorHeaderAlignmentMismatch); 61| 1|} 62| | 63| |- (void)testValidateNULLHeader 64| 1|{ 65| 1| int headerValidationResult = SPTPersistentCacheValidateHeader(NULL); 66| 1| 67| 1| XCTAssertEqual(headerValidationResult, SPTPersistentCacheLoadingErrorInternalInconsistency); 68| 1|} 69| | 70| |- (void)testCalculateNULLHeaderCRC 71| 1|{ 72| 1| uint32_t headerCRC = SPTPersistentCacheCalculateHeaderCRC(NULL); 73| 1| 74| 1| XCTAssertEqual(headerCRC, (uint32_t)0); 75| 1|} 76| | 77| |- (void)testSPTPersistentCacheRecordHeaderMake 78| 1|{ 79| 1| uint64_t ttl = 64; 80| 1| uint64_t payloadSize = 400; 81| 1| uint64_t updateTime = spt_uint64rint([[NSDate date] timeIntervalSince1970]); 82| 1| BOOL isLocked = YES; 83| 1| 84| 1| 85| 1| SPTPersistentCacheRecordHeader header = SPTPersistentCacheRecordHeaderMake(ttl, 86| 1| payloadSize, 87| 1| updateTime, 88| 1| isLocked); 89| 1| 90| 1| XCTAssertEqual(header.reserved1, (uint64_t)0); 91| 1| XCTAssertEqual(header.reserved2, (uint64_t)0); 92| 1| XCTAssertEqual(header.reserved3, (uint64_t)0); 93| 1| XCTAssertEqual(header.reserved4, (uint64_t)0); 94| 1| XCTAssertEqual(header.flags, (uint32_t)0); 95| 1| XCTAssertEqual(header.magic, SPTPersistentCacheMagicValue); 96| 1| XCTAssertEqual(header.headerSize, (uint32_t)SPTPersistentCacheRecordHeaderSize); 97| 1| XCTAssertEqual(!!header.refCount, isLocked); 98| 1| XCTAssertEqual(header.ttl, ttl); 99| 1| XCTAssertEqual(header.payloadSizeBytes, payloadSize); 100| 1| XCTAssertEqual(header.updateTimeSec, updateTime); 101| 1| XCTAssertEqual(header.crc, SPTPersistentCacheCalculateHeaderCRC(&header)); 102| 1|} 103| | 104| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheObjectDescriptionStyleValidator.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "SPTPersistentCacheObjectDescriptionStyleValidator.h" 22| | 23| |@interface SPTPersistentCacheObjectDescriptionStyleValidator () 24| | 25| |@property (nonatomic, strong) NSRegularExpression *regex; 26| | 27| |@end 28| | 29| |@implementation SPTPersistentCacheObjectDescriptionStyleValidator 30| | 31| |- (instancetype)init 32| 18|{ 33| 18| self = [super init]; 34| 18| if (self) { 35| 18| // Regex to match a description: ^<[\w]+: 0x[\da-fA-F]+((; [^=]+= "[^"]+")+)?>$ 36| 18| 37| 18| // The NSRegularExpression class is currently only available in the Foundation framework of iOS 4 38| 18| _regex = [NSRegularExpression regularExpressionWithPattern:@"^<[\\w]+: 0x[\\da-fA-F]+((; [^=]+= \"[^\"]+\")+)?>$" 39| 18| options:NSRegularExpressionAnchorsMatchLines 40| 18| error:nil]; 41| 18| } 42| 18| 43| 18| return self; 44| 18|} 45| | 46| |- (BOOL)isValidStyleDescription:(NSString *)description 47| 13|{ 48| 13| // Shorter than: 49| 13| if (description.length < 8) { 50| 0| return NO; 51| 0| } 52| 13| 53| 13| const NSRange descriptionRange = NSMakeRange(0, description.length); 54| 13| const NSMatchingOptions options = (NSMatchingOptions)0; 55| 13| const NSUInteger matches = [self.regex numberOfMatchesInString:description options:options range:descriptionRange]; 56| 13| 57| 13| return matches == 1; 58| 13|} 59| | 60| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheObjectDescriptionTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |#import "SPTPersistentCacheObjectDescription.h" 24| |#import "SPTPersistentCacheObjectDescriptionStyleValidator.h" 25| | 26| |@interface SPTPersistentCacheObjectDescriptionTests : XCTestCase 27| | 28| |@property (nonatomic, strong) SPTPersistentCacheObjectDescriptionStyleValidator *styleValidator; 29| | 30| |@end 31| | 32| |@implementation SPTPersistentCacheObjectDescriptionTests 33| | 34| |#pragma mark Test Life Time 35| | 36| |- (void)setUp 37| 13|{ 38| 13| [super setUp]; 39| 13| self.styleValidator = [SPTPersistentCacheObjectDescriptionStyleValidator new]; 40| 13|} 41| | 42| |#pragma mark Basic Properties 43| | 44| |- (void)testNilObject 45| 1|{ 46| 1| XCTAssertNil(_SPTPersistentCacheObjectDescription(nil, @"value1", @"ke1", SPTPersistentCacheObjectDescriptionTerminationSentinel)); 47| 1|} 48| | 49| |- (void)testWithoutValues 50| 1|{ 51| 1| NSString * const object = @"object1"; 52| 1| NSString * const expected = [NSString stringWithFormat:@"<%@: %p>", object.class, (void *)object]; 53| 1| 54| 1| NSString * const description = _SPTPersistentCacheObjectDescription(object, SPTPersistentCacheObjectDescriptionTerminationSentinel); 55| 1| 56| 1| XCTAssertEqualObjects(description, expected); 57| 1|} 58| | 59| |- (void)testSingleValueKeyPair 60| 1|{ 61| 1| NSString * const object1 = @"object1"; 62| 1| 63| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 64| 1| 65| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, @"value1", @"key1", SPTPersistentCacheObjectDescriptionTerminationSentinel); 66| 1| 67| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 68| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 69| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 70| 1| 71| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:@"key1 = \"value1\""]; 72| 1| XCTAssertNotEqual(keyValuePair1Range.location, NSNotFound, @"The description should contain the key-value pair. Description: \"%@\"", description1); 73| 1|} 74| | 75| |- (void)testMultipleValueKey 76| 1|{ 77| 1| NSString * const object1 = @"object1"; 78| 1| 79| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 80| 1| 81| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, @"value1", @"key1", @"value2", @"key2", SPTPersistentCacheObjectDescriptionTerminationSentinel); 82| 1| 83| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 84| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 85| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 86| 1| 87| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:@"key1 = \"value1\""]; 88| 1| XCTAssertNotEqual(keyValuePair1Range.location, NSNotFound, @"The description should contain the first key-value pair. Description: \"%@\"", description1); 89| 1| 90| 1| const NSRange keyValuePair2Range = [description1 rangeOfString:@"key2 = \"value2\""]; 91| 1| XCTAssertNotEqual(keyValuePair2Range.location, NSNotFound, @"The description should contain the second key-value pair. Description: \"%@\"", description1); 92| 1| 93| 1| XCTAssertGreaterThan(keyValuePair2Range.location, keyValuePair1Range.location, @"The second key-value pair should come after the first. Description: \"%@\"", description1); 94| 1|} 95| | 96| |- (void)testNilValue 97| 1|{ 98| 1| NSString * const object1 = @"object1"; 99| 1| 100| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 101| 1| NSString * const expectedKeyValueString = [NSString stringWithFormat:@"key1 = \"%@\"", nil]; 102| 1| 103| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, nil, @"key1", SPTPersistentCacheObjectDescriptionTerminationSentinel); 104| 1| 105| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 106| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 107| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 108| 1| 109| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:expectedKeyValueString]; 110| 1| XCTAssertNotEqual(keyValuePair1Range.location, NSNotFound, @"The description should contain the key-value pair. Description: \"%@\"", description1); 111| 1|} 112| | 113| |- (void)testNilKey 114| 1|{ 115| 1| NSString * const object1 = @"object1"; 116| 1| 117| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 118| 1| NSString * const expectedKeyValueString = [NSString stringWithFormat:@"%@ = \"value1\"", nil]; 119| 1| 120| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, @"value1", nil, SPTPersistentCacheObjectDescriptionTerminationSentinel); 121| 1| 122| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 123| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 124| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 125| 1| 126| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:expectedKeyValueString]; 127| 1| XCTAssertNotEqual(keyValuePair1Range.location, NSNotFound, @"The description should contain the key-value pair. Description: \"%@\"", description1); 128| 1|} 129| | 130| |- (void)testMultipleTerminationSentinels 131| 1|{ 132| 1| NSString * const object1 = @"object1"; 133| 1| 134| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 135| 1| 136| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, @"value1", @"key1", SPTPersistentCacheObjectDescriptionTerminationSentinel, @"value2", @"key2", SPTPersistentCacheObjectDescriptionTerminationSentinel); 137| 1| 138| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 139| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 140| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 141| 1| 142| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:@"key1 = \"value1\""]; 143| 1| XCTAssertNotEqual(keyValuePair1Range.location, NSNotFound, @"The description should contain the first key-value pair. Description: \"%@\"", description1); 144| 1| 145| 1| const NSRange keyValuePair2Range = [description1 rangeOfString:@"key2 = \"value2\""]; 146| 1| XCTAssertEqual(keyValuePair2Range.location, NSNotFound, @"The description should NOT contain the second key-value pair as it comes after a termination sentinel. Description: \"%@\"", description1); 147| 1|} 148| | 149| |- (void)testMisalignedFirstValueKeyPairs 150| 1|{ 151| 1| NSString * const object1 = @"object1"; 152| 1| 153| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 154| 1| 155| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, @"value1", SPTPersistentCacheObjectDescriptionTerminationSentinel, @"key1", @"value2", @"key2"); 156| 1| 157| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 158| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 159| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 160| 1| 161| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:@"key1 = \"value1\""]; 162| 1| XCTAssertEqual(keyValuePair1Range.location, NSNotFound, @"The description should NOT contain the first key-value pair since it’s misaligned. Description: \"%@\"", description1); 163| 1| 164| 1| const NSRange keyValuePair2Range = [description1 rangeOfString:@"key2 = \"value2\""]; 165| 1| XCTAssertEqual(keyValuePair2Range.location, NSNotFound, @"The description should NOT contain the second key-value pair as it comes after a termination sentinel. Description: \"%@\"", description1); 166| 1|} 167| | 168| |- (void)testMisalignedSecondValueKeyPairs 169| 1|{ 170| 1| NSString * const object1 = @"object1"; 171| 1| 172| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 173| 1| 174| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, @"value1", @"key1", @"value2", SPTPersistentCacheObjectDescriptionTerminationSentinel, @"key2"); 175| 1| 176| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 177| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 178| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 179| 1| 180| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:@"key1 = \"value1\""]; 181| 1| XCTAssertNotEqual(keyValuePair1Range.location, NSNotFound, @"The description should contain the first key-value pair. Description: \"%@\"", description1); 182| 1| 183| 1| const NSRange keyValuePair2Range = [description1 rangeOfString:@"key2 = \"value2\""]; 184| 1| XCTAssertEqual(keyValuePair2Range.location, NSNotFound, @"The description should NOT contain the second key-value pair as it’s misaligned. Description: \"%@\"", description1); 185| 1|} 186| | 187| |- (void)testMissingKey 188| 1|{ 189| 1| NSString * const object1 = @"object1"; 190| 1| 191| 1| NSString * const expectedPrefix = [NSString stringWithFormat:@"<%@: 0x", object1.class]; 192| 1| 193| 1| NSString * const description1 = SPTPersistentCacheObjectDescription(object1, @"value1", @"key1", @"value2", @"key2", @"value3"); 194| 1| 195| 1| XCTAssertTrue([self.styleValidator isValidStyleDescription:description1], @"A description must be of the valid style ``. Description: \"%@\"", description1); 196| 1| XCTAssertTrue([description1 hasPrefix:expectedPrefix], @"The description should have the expected prefix containing the class name. Description: \"%@\"", description1); 197| 1| XCTAssertTrue([description1 hasSuffix:@">"], @"The description should always end with `>`. Description: \"%@\"", description1); 198| 1| 199| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:@"key1 = \"value1\""]; 200| 1| XCTAssertNotEqual(keyValuePair1Range.location, NSNotFound, @"The description should contain the first key-value pair. Description: \"%@\"", description1); 201| 1| 202| 1| const NSRange keyValuePair2Range = [description1 rangeOfString:@"key2 = \"value2\""]; 203| 1| XCTAssertNotEqual(keyValuePair2Range.location, NSNotFound, @"The description should contain the second key-value pair. Description: \"%@\"", description1); 204| 1| 205| 1| const NSRange value3Range = [description1 rangeOfString:@"value3"]; 206| 1| XCTAssertEqual(value3Range.location, NSNotFound, @"The description should NOT contain the third value as the key is missing. Description: \"%@\"", description1); 207| 1|} 208| | 209| |- (void)testIsStable 210| 1|{ 211| 1| NSString * const object1 = @"object1"; 212| 1| 213| 1| NSString * const description1 = _SPTPersistentCacheObjectDescription(object1, @"value1", @"key1", @32, @42, SPTPersistentCacheObjectDescriptionTerminationSentinel); 214| 1| NSString * const description2 = _SPTPersistentCacheObjectDescription(object1, @"value1", @"key1", @32, @42, SPTPersistentCacheObjectDescriptionTerminationSentinel); 215| 1| 216| 1| XCTAssertEqualObjects(description1, description2); 217| 1|} 218| | 219| |- (void)testDetecsRecursiveDescription 220| 1|{ 221| 1| NSString * const object1 = @"object1"; 222| 1| 223| 1| NSString * const description1 = SPTPersistentCacheObjectDescription(object1, object1, @"key1", @32, @42); 224| 1| 225| 1| const NSRange keyValuePair1Range = [description1 rangeOfString:@"key1 = \""]; 226| 1| XCTAssertEqual(keyValuePair1Range.location, NSNotFound, @"The description should NOT contain the first key-value pair as it would be recursive. Description: \"%@\"", description1); 227| 1| 228| 1| const NSRange keyValuePair2Range = [description1 rangeOfString:@"42 = \"32\""]; 229| 1| XCTAssertNotEqual(keyValuePair2Range.location, NSNotFound, @"The description should contain the second key-value pair. Description: \"%@\"", description1); 230| 1|} 231| | 232| |#pragma mark Convenience Macro 233| | 234| |- (void)testConvenienceMacroProcudeSameResultAsDirectFunctionAccess 235| 1|{ 236| 1| NSString * const object1 = @"object1"; 237| 1| 238| 1| NSString * const directDescription = _SPTPersistentCacheObjectDescription(object1, @"foo", @"bar", @"hi", @"hello", SPTPersistentCacheObjectDescriptionTerminationSentinel); 239| 1| NSString * const macroDescription = SPTPersistentCacheObjectDescription(object1, @"foo", @"bar", @"hi", @"hello"); 240| 1| 241| 1| XCTAssertEqualObjects(directDescription, macroDescription); 242| 1|} 243| | 244| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheOptionsTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |#import 24| |#import "SPTPersistentCacheObjectDescriptionStyleValidator.h" 25| | 26| |static NSString * const SPTPersistentCacheOptionsPathComponent = @"com.spotify.tmp.cache"; 27| | 28| |@interface SPTPersistentCacheOptionsTests : XCTestCase 29| |@property (nonatomic, strong) SPTPersistentCacheOptions *dataCacheOptions; 30| |@end 31| | 32| |@implementation SPTPersistentCacheOptionsTests 33| | 34| |- (void)setUp 35| 13|{ 36| 13| [super setUp]; 37| 13| 38| 13| self.dataCacheOptions = [SPTPersistentCacheOptions new]; 39| 13|} 40| | 41| |- (void)testDefaultInitializer 42| 1|{ 43| 1| XCTAssertTrue(self.dataCacheOptions.useDirectorySeparation, @"Directory separation should be enabled"); 44| 1| XCTAssertEqual(self.dataCacheOptions.garbageCollectionInterval, SPTPersistentCacheDefaultGCIntervalSec); 45| 1| XCTAssertEqual(self.dataCacheOptions.defaultExpirationPeriod, SPTPersistentCacheDefaultExpirationTimeSec); 46| 1| XCTAssertNotNil(self.dataCacheOptions.cachePath, @"The cache path cannot be nil"); 47| 1| XCTAssertNotNil(self.dataCacheOptions.cacheIdentifier, @"The cache identifier cannot be nil"); 48| 1| XCTAssertNotNil(self.dataCacheOptions.identifierForQueue, @"The identifier for queue shouldn't be nil"); 49| 1|} 50| | 51| |- (void)testMinimumGarbageCollectorIntervalForDeprecatedInit 52| 1|{ 53| 1| _Pragma("clang diagnostic push"); 54| 1| _Pragma("clang diagnostic ignored \"-Wdeprecated\""); 55| 1| SPTPersistentCacheOptions *dataCacheOptions = [[SPTPersistentCacheOptions alloc] initWithCachePath:[NSTemporaryDirectory() stringByAppendingPathComponent:SPTPersistentCacheOptionsPathComponent] 56| 1| identifier:@"test" 57| 1| defaultExpirationInterval:1 58| 1| garbageCollectorInterval:1 59| 1| debug:nil]; 60| 1| _Pragma("clang diagnostic pop"); 61| 1| 62| 1| XCTAssertEqual(dataCacheOptions.garbageCollectionInterval, 63| 1| SPTPersistentCacheMinimumGCIntervalLimit); 64| 1|} 65| | 66| |- (void)testMinimumGarbageCollectorInterval 67| 1|{ 68| 1| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"debugOuput is executed"]; 69| 1| 70| 1| SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new]; 71| 1| options.cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTPersistentCacheOptionsPathComponent]; 72| 1| options.cacheIdentifier = @"test"; 73| 1| options.debugOutput = ^(NSString *message) { 74| 1| XCTAssertNotEqual([message rangeOfString:@"garbageCollectionInterval"].location, NSNotFound, @"The \"garbageCollectionInterval\" property name should be in the message (\"%@\")", message); 75| 1| [expectation fulfill]; 76| 1| }; 77| 1| 78| 1| options.garbageCollectionInterval = 1; 79| 1| [self waitForExpectationsWithTimeout:1.0 handler:nil]; 80| 1| 81| 1| XCTAssertEqual(options.garbageCollectionInterval, 82| 1| SPTPersistentCacheMinimumGCIntervalLimit); 83| 1|} 84| | 85| |- (void)testMinimumDefaultExpirationIntervalForDeprecatedInit 86| 1|{ 87| 1| _Pragma("clang diagnostic push"); 88| 1| _Pragma("clang diagnostic ignored \"-Wdeprecated\""); 89| 1| SPTPersistentCacheOptions *dataCacheOptions = [[SPTPersistentCacheOptions alloc] initWithCachePath:[NSTemporaryDirectory() stringByAppendingPathComponent:SPTPersistentCacheOptionsPathComponent] 90| 1| identifier:@"test" 91| 1| defaultExpirationInterval:1 92| 1| garbageCollectorInterval:1 93| 1| debug:nil]; 94| 1| _Pragma("clang diagnostic pop"); 95| 1| 96| 1| XCTAssertEqual(dataCacheOptions.defaultExpirationPeriod, 97| 1| SPTPersistentCacheMinimumExpirationLimit); 98| 1|} 99| | 100| |- (void)testMinimumDefaultExpiration 101| 1|{ 102| 1| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"debugOuput is executed"]; 103| 1| 104| 1| SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new]; 105| 1| options.cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTPersistentCacheOptionsPathComponent]; 106| 1| options.cacheIdentifier = @"test"; 107| 1| options.debugOutput = ^(NSString *message) { 108| 1| XCTAssertNotEqual([message rangeOfString:@"defaultExpirationPeriod"].location, NSNotFound, @"The \"defaultExpirationPeriod\" property name should be in the message (\"%@\")", message); 109| 1| [expectation fulfill]; 110| 1| }; 111| 1| 112| 1| options.defaultExpirationPeriod = 1; 113| 1| [self waitForExpectationsWithTimeout:1.0 handler:nil]; 114| 1| 115| 1| XCTAssertEqual(options.defaultExpirationPeriod, 116| 1| SPTPersistentCacheMinimumExpirationLimit); 117| 1|} 118| | 119| |#pragma mark Copying 120| | 121| |- (void)testCopying 122| 1|{ 123| 1| SPTPersistentCacheOptions * const original = [SPTPersistentCacheOptions new]; 124| 1| original.cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTPersistentCacheOptionsPathComponent]; 125| 1| original.cacheIdentifier = @"test"; 126| 1| original.useDirectorySeparation = NO; 127| 1| original.garbageCollectionInterval = SPTPersistentCacheDefaultGCIntervalSec + 10; 128| 1| original.defaultExpirationPeriod = SPTPersistentCacheDefaultExpirationTimeSec + 10; 129| 1| original.sizeConstraintBytes = 1024 * 1024; 130| 1| original.debugOutput = ^(NSString *message) { 131| 0| NSLog(@"Foo: %@", message); 132| 0| }; 133| 1| 134| 1| const NSRange lastDotRange = [original.identifierForQueue rangeOfString:@"." options:NSBackwardsSearch]; 135| 1| NSString * const queueIdentifierPrefix = [original.identifierForQueue substringToIndex:lastDotRange.location]; 136| 1| 137| 1| SPTPersistentCacheOptions * const copy = [original copy]; 138| 1| 139| 1| XCTAssertNotEqual(original, copy, @"The original and copy shouldn’t be the same object"); 140| 1| 141| 1| XCTAssertNotNil(copy.debugOutput, @"The debug output callback block should exist after copy"); 142| 1| 143| 1| XCTAssertTrue([copy.identifierForQueue hasPrefix:queueIdentifierPrefix] , @"The values of the property \"identifierForQueue\" should have the same prefix"); 144| 1| XCTAssertEqualObjects(original.cachePath, copy.cachePath, @"The values of the property \"cachePath\" should be equal"); 145| 1| XCTAssertEqualObjects(original.cacheIdentifier, copy.cacheIdentifier, @"The values of the property \"cacheIdentifier\" should be equal"); 146| 1| 147| 1| XCTAssertEqual(original.useDirectorySeparation, copy.useDirectorySeparation, @"The values of the property \"useDirectorySeparation\" should be equal"); 148| 1| XCTAssertEqual(original.garbageCollectionInterval, copy.garbageCollectionInterval, @"The values of the property \"garbageCollectionInterval\" should be equal"); 149| 1| XCTAssertEqual(original.defaultExpirationPeriod, copy.defaultExpirationPeriod, @"The values of the property \"defaultExpirationPeriod\" should be equal"); 150| 1| XCTAssertEqual(original.sizeConstraintBytes, copy.sizeConstraintBytes, @"The values of the property \"sizeConstraintBytes\" should be equal"); 151| 1|} 152| | 153| |#pragma mark Compatibility Properties for Deprecated Properties 154| | 155| |- (void)testFolderSeparationEnabled 156| 1|{ 157| 1| _Pragma("clang diagnostic push"); 158| 1| _Pragma("clang diagnostic ignored \"-Wdeprecated\""); 159| 1| 160| 1| SPTPersistentCacheOptions * const options = [SPTPersistentCacheOptions new]; 161| 1| 162| 1| XCTAssertEqual(options.folderSeparationEnabled, options.useDirectorySeparation); 163| 1| 164| 1| options.folderSeparationEnabled = NO; 165| 1| XCTAssertFalse(options.useDirectorySeparation, @"Setting the compatibility property should update the real property"); 166| 1| XCTAssertEqual(options.folderSeparationEnabled, options.useDirectorySeparation); 167| 1| 168| 1| _Pragma("clang diagnostic pop"); 169| 1|} 170| | 171| |- (void)testGcIntervalSec 172| 1|{ 173| 1| _Pragma("clang diagnostic push"); 174| 1| _Pragma("clang diagnostic ignored \"-Wdeprecated\""); 175| 1| 176| 1| SPTPersistentCacheOptions * const options = [SPTPersistentCacheOptions new]; 177| 1| options.garbageCollectionInterval = SPTPersistentCacheDefaultGCIntervalSec + 37; 178| 1| 179| 1| XCTAssertEqual(options.gcIntervalSec, options.garbageCollectionInterval); 180| 1| 181| 1| _Pragma("clang diagnostic pop"); 182| 1|} 183| | 184| |- (void)testDefaultExpirationPeriodSec 185| 1|{ 186| 1| _Pragma("clang diagnostic push"); 187| 1| _Pragma("clang diagnostic ignored \"-Wdeprecated\""); 188| 1| 189| 1| SPTPersistentCacheOptions * const options = [SPTPersistentCacheOptions new]; 190| 1| options.defaultExpirationPeriod = SPTPersistentCacheDefaultExpirationTimeSec + 37; 191| 1| 192| 1| XCTAssertEqual(options.defaultExpirationPeriodSec, options.defaultExpirationPeriod); 193| 1| 194| 1| _Pragma("clang diagnostic pop"); 195| 1|} 196| | 197| |#pragma mark Describing objects 198| | 199| |- (void)testDescriptionAdheresToStyle 200| 1|{ 201| 1| SPTPersistentCacheObjectDescriptionStyleValidator *styleValidator = [SPTPersistentCacheObjectDescriptionStyleValidator new]; 202| 1| 203| 1| NSString * const description = self.dataCacheOptions.description; 204| 1| 205| 1| XCTAssertTrue([styleValidator isValidStyleDescription:description], @"The description string should follow our style."); 206| 1|} 207| | 208| |- (void)testDescriptionContainsClassName 209| 1|{ 210| 1| NSString * const description = self.dataCacheOptions.description; 211| 1| 212| 1| const NSRange classNameRange = [description rangeOfString:@"SPTPersistentCacheOptions"]; 213| 1| XCTAssertNotEqual(classNameRange.location, NSNotFound, @"The class name should exist in the description"); 214| 1|} 215| | 216| |- (void)testDebugDescriptionAdheresToStyle 217| 1|{ 218| 1| SPTPersistentCacheObjectDescriptionStyleValidator *styleValidator = [SPTPersistentCacheObjectDescriptionStyleValidator new]; 219| 1| 220| 1| NSString * const debugDescription = self.dataCacheOptions.debugDescription; 221| 1| 222| 1| XCTAssertTrue([styleValidator isValidStyleDescription:debugDescription], @"The debugDescription string should follow our style."); 223| 1|} 224| | 225| |- (void)testDebugDescriptionContainsClassName 226| 1|{ 227| 1| NSString * const debugDescription = self.dataCacheOptions.debugDescription; 228| 1| 229| 1| const NSRange classNameRange = [debugDescription rangeOfString:@"SPTPersistentCacheOptions"]; 230| 1| XCTAssertNotEqual(classNameRange.location, NSNotFound, @"The class name should exist in the debugDescription"); 231| 1|} 232| | 233| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCachePerformanceTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| | 22| |#import 23| |#import 24| | 25| |#import 26| |double convertMachToMilliSeconds(uint64_t mach_time); 27| | 28| |static const int SPTPersistentCachePerformanceIterationCount = 200; 29| | 30| |@interface SPTPersistentCacheTiming : NSObject 31| |@property (nonatomic) uint64_t queueTime; 32| |@property (nonatomic) uint64_t startTime; 33| |@property (nonatomic) uint64_t endTime; 34| | 35| |- (double) calculateTimeInQueue; 36| |- (double) calculateTimeExecuting; 37| |- (double) calculateTotalTime; 38| |@end 39| | 40| |@implementation SPTPersistentCacheTiming 41| | 42| |- (double) calculateTimeInQueue 43| 0|{ 44| 0| return convertMachToMilliSeconds(self.startTime) - convertMachToMilliSeconds(self.queueTime); 45| 0|} 46| |- (double) calculateTimeExecuting 47| 0|{ 48| 0| return convertMachToMilliSeconds(self.endTime) - convertMachToMilliSeconds(self.startTime); 49| 0|} 50| |- (double) calculateTotalTime 51| 0|{ 52| 0| return convertMachToMilliSeconds(self.endTime) - convertMachToMilliSeconds(self.queueTime); 53| 0|} 54| | 55| |@end 56| | 57| |@interface SPTPersistentCachePerformanceTests : XCTestCase 58| | 59| | 60| |@property (nonatomic, strong) SPTPersistentCache *dataCache; 61| |@property (nonatomic, strong) NSMutableArray *fileContents; 62| |@property (nonatomic, strong) NSMutableArray *unLockTimings; 63| |@property (nonatomic, strong) NSMutableArray *readTimings; 64| |@property (nonatomic, strong) NSString *cachePath; 65| | 66| |@end 67| | 68| |@implementation SPTPersistentCachePerformanceTests 69| | 70| |- (void)setUp 71| 0|{ 72| 0| [super setUp]; 73| 0| 74| 0| NSArray *fileNames = @[ 75| 0| @"aad0e75ab0a6828d0a9b37a68198cc9d70d84850", 76| 0| @"ab3d97d4d7b3df5417490aa726c5a49b9ee98038", 77| 0| @"b02c1be08c00bac5f4f1a62c6f353a24487bb024", 78| 0| @"b3e04bf446b486412a13659af71e3a333c6152f4", 79| 0| @"b53aed36cdc67dd43b496db74843ac32fe1f64bb", 80| 0| @"b91998ae68b9639cee6243df0886d69bdeb75854", 81| 0| @"c3b19963fc076930dd36ce3968757704bbc97357", 82| 0| @"c5aec3eef2478bfe47aef16787a6b4df31eb45f2", 83| 0| @"e5a1921f8f75d42412e08aff4da33e1132f7ee8a", 84| 0| @"e5b8abdc091921d49e86687e28b74abb3139df70", 85| 0| @"ee6b44ab07fa3937a6d37f449355b64c09677295", 86| 0| @"ee678d23b8dba2997c52741e88fa7a1fdeaf2863", 87| 0| @"eee9747e967c4440eb90bb812d148aa3d0056700", 88| 0| @"f1eeb834607dcc2b01909bd740d4356f2abb4cd1", 89| 0| @"f7501f27f70162a9a7da196c5d2ece3151a2d80a", 90| 0| @"f50512901688b79a7852999d384d097a71fad788", 91| 0| @"fc22d4f65c1ba875f6bb5ba7d35a7fd12851ed5c" 92| 0| ]; 93| 0| 94| 0| self.fileContents = [NSMutableArray array]; 95| 0| 96| 0| self.cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent: 97| 0| [NSString stringWithFormat:@"pdc-%@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]]]; 98| 0| 99| 0| NSLog(@"%@", self.cachePath); 100| 0| 101| 0| NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 102| 0| for (NSString *fileName in fileNames) { 103| 0| NSString *filePath = [bundle pathForResource:fileName ofType:nil]; 104| 0| NSData *fileData = [NSData dataWithContentsOfFile:filePath]; 105| 0| if (!fileData) { 106| 0| fileData = [NSData data]; 107| 0| } 108| 0| [self.fileContents addObject:fileData]; 109| 0| } 110| 0| 111| 0| 112| 0| self.unLockTimings = [NSMutableArray arrayWithCapacity:SPTPersistentCachePerformanceIterationCount]; 113| 0| self.readTimings = [NSMutableArray arrayWithCapacity:SPTPersistentCachePerformanceIterationCount]; 114| 0| for (NSUInteger i = 0; i < SPTPersistentCachePerformanceIterationCount; i++) { 115| 0| self.unLockTimings[i] = (SPTPersistentCacheTiming *)[NSNull null]; 116| 0| self.readTimings[i] = (SPTPersistentCacheTiming *)[NSNull null]; 117| 0| } 118| 0| 119| 0| //Setup a cache 120| 0| SPTPersistentCacheOptions *options = [[SPTPersistentCacheOptions alloc] init]; 121| 0| options.cachePath = self.cachePath; 122| 0| options.readPriority = NSOperationQueuePriorityVeryHigh; 123| 0| options.readQualityOfService = NSOperationQualityOfServiceUserInteractive; 124| 0| options.writePriority = NSOperationQueuePriorityNormal; 125| 0| options.writeQualityOfService = NSOperationQualityOfServiceUserInitiated; 126| 0| options.deletePriority = NSOperationQueuePriorityLow; 127| 0| options.deleteQualityOfService = NSOperationQualityOfServiceUtility; 128| 0| options.timingCallback = ^(NSString *key, SPTPersistentCacheDebugMethodType method, SPTPersistentCacheDebugTimingType type, uint64_t machTime){ 129| 0| NSMutableArray *timings = nil; 130| 0| switch (method) { 131| 0| case SPTPersistentCacheDebugMethodTypeUnlock: 132| 0| timings = self.unLockTimings; 133| 0| break; 134| 0| case SPTPersistentCacheDebugMethodTypeRead: 135| 0| timings = self.readTimings; 136| 0| break; 137| 0| case SPTPersistentCacheDebugMethodTypeRemove: 138| 0| case SPTPersistentCacheDebugMethodTypeStore: 139| 0| case SPTPersistentCacheDebugMethodTypeLock: 140| 0| break; 141| 0| } 142| 0| if (timings == nil) { 143| 0| return; 144| 0| } 145| 0| NSUInteger index = (NSUInteger)[[key stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\n(),"]] integerValue]; 146| 0| SPTPersistentCacheTiming *timing = timings[index]; 147| 0| if ([[NSNull null] isEqual:timing]) { 148| 0| timing = [[SPTPersistentCacheTiming alloc] init]; 149| 0| timings[index] = timing; 150| 0| } 151| 0| switch (type) { 152| 0| case SPTPersistentCacheDebugTimingTypeStarting: 153| 0| timing.startTime = machTime; 154| 0| break; 155| 0| 156| 0| case SPTPersistentCacheDebugTimingTypeQueued: 157| 0| timing.queueTime = machTime; 158| 0| break; 159| 0| case SPTPersistentCacheDebugTimingTypeFinished: 160| 0| timing.endTime = machTime; 161| 0| break; 162| 0| } 163| 0| }; 164| 0| self.dataCache = [[SPTPersistentCache alloc] initWithOptions:options]; 165| 0|} 166| | 167| |- (void)tearDown 168| 0|{ 169| 0| NSError *error = nil; 170| 0| if (![[NSFileManager defaultManager] removeItemAtPath:self.cachePath error:&error]) { 171| 0| NSLog(@"Error removing cache: %@", error); 172| 0| } 173| 0| 174| 0| [super tearDown]; 175| 0|} 176| | 177| |- (void)testUnlockPlusReads 178| 0|{ 179| 0| //Load with data & lock 180| 0| for (NSUInteger i = 0; i < SPTPersistentCachePerformanceIterationCount; i++) { 181| 0| NSUInteger fileIndex = i % self.fileContents.count; 182| 0| NSData *data = self.fileContents[fileIndex]; 183| 0| NSString *key = [NSString stringWithFormat:@"%lu", (unsigned long)i]; 184| 0| __weak XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"%@ store", key]]; 185| 0| [self.dataCache storeData:data forKey:key locked:YES withCallback:^(SPTPersistentCacheResponse * _Nonnull response) { 186| 0| [expectation fulfill]; 187| 0| } onQueue:dispatch_get_main_queue()]; 188| 0| } 189| 0| //Wait till all data is loaded 190| 0| [self waitForExpectationsWithTimeout:60 handler:nil]; 191| 0| //Sleep to make sure everything is settled down 192| 0| [NSThread sleepForTimeInterval:5]; 193| 0| 194| 0| //Queue unlock data 195| 0| for (int i = 0; i < SPTPersistentCachePerformanceIterationCount; i++) { 196| 0| NSString *key = [NSString stringWithFormat:@"%d", i]; 197| 0| __weak XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"%@ unlock", key]]; 198| 0| [self.dataCache unlockDataForKeys:@[key] callback:^(SPTPersistentCacheResponse * _Nonnull response) { 199| 0| [expectation fulfill]; 200| 0| } onQueue:dispatch_get_main_queue()]; 201| 0| } 202| 0| 203| 0| //Queue read data 204| 0| for (int i = 0; i < SPTPersistentCachePerformanceIterationCount; i++) { 205| 0| NSString *key = [NSString stringWithFormat:@"%d", i]; 206| 0| __weak XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"%@ read", key]]; 207| 0| [self.dataCache loadDataForKey:key withCallback:^(SPTPersistentCacheResponse * _Nonnull response) { 208| 0| [expectation fulfill]; 209| 0| } onQueue:dispatch_get_main_queue()]; 210| 0| } 211| 0| 212| 0| [self waitForExpectationsWithTimeout:60 handler:nil]; 213| 0| 214| 0| //Report times 215| 0| for (SPTPersistentCacheTiming *timing in self.unLockTimings) { 216| 0| NSLog(@"****Unlock Queue time %f, execution time: %f total time:%f", [timing calculateTimeInQueue], 217| 0| [timing calculateTimeExecuting], 218| 0| [timing calculateTotalTime]); 219| 0| } 220| 0| for (SPTPersistentCacheTiming *timing in self.readTimings) { 221| 0| NSLog(@"****Read Queue time %f, execution time: %f total time:%f", [timing calculateTimeInQueue], 222| 0| [timing calculateTimeExecuting], 223| 0| [timing calculateTotalTime]); 224| 0| } 225| 0|} 226| | 227| |double convertMachToMilliSeconds(uint64_t mach_time) 228| 0|{ 229| 0| mach_timebase_info_data_t info; 230| 0| mach_timebase_info(&info); 231| 0| 232| 0| const double timeNS = (double)mach_time * (double)info.numer / (double)info.denom; 233| 0| 234| 0| return timeNS/1000000; 235| 0|} 236| | 237| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCachePosixWrapperMock.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import "SPTPersistentCachePosixWrapperMock.h" 22| | 23| |@implementation SPTPersistentCachePosixWrapperMock 24| | 25| |- (int)close:(int)descriptor 26| 41|{ 27| 41| return self.closeValue; 28| 41|} 29| | 30| |- (ssize_t)read:(int)descriptor buffer:(void *)buffer bufferSize:(size_t)bufferSize 31| 41|{ 32| 41| if (self.readOverridden) { 33| 1| return self.readValue; 34| 1| } 35| 40| return [super read:descriptor buffer:buffer bufferSize:bufferSize]; 36| 40|} 37| | 38| |- (off_t)lseek:(int)descriptor seekType:(off_t)seekType seekAmount:(int)seekAmount 39| 3|{ 40| 3| return self.lseekValue; 41| 3|} 42| | 43| |- (ssize_t)write:(int)descriptor buffer:(const void *)buffer bufferSize:(size_t)bufferSize 44| 2|{ 45| 2| return self.writeValue; 46| 2|} 47| | 48| |- (int)fsync:(int)descriptor 49| 1|{ 50| 1| return self.fsyncValue; 51| 1|} 52| | 53| |- (int)stat:(const char *)path statStruct:(struct stat *)statStruct 54| 13|{ 55| 13| return self.statValue; 56| 13|} 57| | 58| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheRecordTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| | 22| |#import 23| |#import 24| |#import "SPTPersistentCacheRecord+Private.h" 25| |#import "SPTPersistentCacheObjectDescriptionStyleValidator.h" 26| | 27| | 28| |static const NSUInteger SPTPersistentCacheRecordTestRefCount = 43244555; 29| |static const NSUInteger SPTPersistentCacheRecordTestTTL = 43244555; 30| |static NSString * const SPTPersistentCacheRecordTestKey = @"key1"; 31| |static NSString * const SPTPersistentCacheRecordTestDataString = @"https://spotify.com"; 32| | 33| |@interface SPTPersistentCacheRecordTests : XCTestCase 34| |@property (nonatomic, strong) SPTPersistentCacheRecord *dataCacheRecord; 35| |@end 36| | 37| |@implementation SPTPersistentCacheRecordTests 38| | 39| |- (void)setUp 40| 5|{ 41| 5| [super setUp]; 42| 5| 43| 5| NSData * const testData =[SPTPersistentCacheRecordTestDataString dataUsingEncoding:NSUTF8StringEncoding]; 44| 5| 45| 5| self.dataCacheRecord = [[SPTPersistentCacheRecord alloc] initWithData:testData 46| 5| key:SPTPersistentCacheRecordTestKey 47| 5| refCount:SPTPersistentCacheRecordTestRefCount 48| 5| ttl:SPTPersistentCacheRecordTestTTL]; 49| 5|} 50| | 51| |- (void)testDesignatedInitializer 52| 1|{ 53| 1| XCTAssertEqualObjects(self.dataCacheRecord.data, [SPTPersistentCacheRecordTestDataString dataUsingEncoding:NSUTF8StringEncoding]); 54| 1| XCTAssertEqualObjects(self.dataCacheRecord.key, SPTPersistentCacheRecordTestKey); 55| 1| XCTAssertEqual(self.dataCacheRecord.refCount, SPTPersistentCacheRecordTestRefCount); 56| 1| XCTAssertEqual(self.dataCacheRecord.ttl, SPTPersistentCacheRecordTestTTL); 57| 1|} 58| | 59| |#pragma mark Test describing objects 60| | 61| |- (void)testDescriptionAdheresToStyle 62| 1|{ 63| 1| SPTPersistentCacheObjectDescriptionStyleValidator *styleValidator = [SPTPersistentCacheObjectDescriptionStyleValidator new]; 64| 1| 65| 1| NSString * const description = self.dataCacheRecord.description; 66| 1| 67| 1| XCTAssertTrue([styleValidator isValidStyleDescription:description], @"The description string should follow our style."); 68| 1|} 69| | 70| |- (void)testDescriptionContainsClassName 71| 1|{ 72| 1| NSString * const description = self.dataCacheRecord.description; 73| 1| 74| 1| const NSRange classNameRange = [description rangeOfString:@"SPTPersistentCacheRecord"]; 75| 1| XCTAssertNotEqual(classNameRange.location, NSNotFound, @"The class name should exist in the description"); 76| 1|} 77| | 78| |- (void)testDebugDescriptionAdheresToStyle 79| 1|{ 80| 1| SPTPersistentCacheObjectDescriptionStyleValidator *styleValidator = [SPTPersistentCacheObjectDescriptionStyleValidator new]; 81| 1| 82| 1| NSString * const debugDescription = self.dataCacheRecord.debugDescription; 83| 1| 84| 1| XCTAssertTrue([styleValidator isValidStyleDescription:debugDescription], @"The debugDescription string should follow our style."); 85| 1|} 86| | 87| |- (void)testDebugDescriptionContainsClassName 88| 1|{ 89| 1| NSString * const debugDescription = self.dataCacheRecord.debugDescription; 90| 1| 91| 1| const NSRange classNameRange = [debugDescription rangeOfString:@"SPTPersistentCacheRecord"]; 92| 1| XCTAssertNotEqual(classNameRange.location, NSNotFound, @"The class name should exist in the debugDescription"); 93| 1|} 94| | 95| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheResponseTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| | 22| |#import 23| |#import "SPTPersistentCacheRecord+Private.h" 24| |#import 25| |#import "SPTPersistentCacheResponse+Private.h" 26| |#import 27| |#import "SPTPersistentCacheObjectDescriptionStyleValidator.h" 28| | 29| | 30| |static const SPTPersistentCacheResponseCode SPTPersistentCacheResponseTestsTestCode = SPTPersistentCacheResponseCodeNotFound; 31| | 32| |@interface SPTPersistentCacheResponseTests : XCTestCase 33| |@property (nonatomic, strong) SPTPersistentCacheResponse *persistentCacheResponse; 34| |@property (nonatomic, strong) NSError *testError; 35| |@property (nonatomic, strong) SPTPersistentCacheRecord *testCacheRecord; 36| |@end 37| | 38| |@implementation SPTPersistentCacheResponseTests 39| | 40| |- (void)setUp 41| 5|{ 42| 5| [super setUp]; 43| 5| 44| 5| self.testError = [NSError errorWithDomain:@"" 45| 5| code:404 46| 5| userInfo:nil]; 47| 5| 48| 5| self.testCacheRecord = [[SPTPersistentCacheRecord alloc] init]; 49| 5| 50| 5| self.persistentCacheResponse = [[SPTPersistentCacheResponse alloc] initWithResult:SPTPersistentCacheResponseTestsTestCode 51| 5| error:self.testError 52| 5| record:self.testCacheRecord]; 53| 5|} 54| | 55| |- (void)testDesignatedInitializer 56| 1|{ 57| 1| XCTAssertEqual(self.persistentCacheResponse.result, SPTPersistentCacheResponseTestsTestCode); 58| 1| XCTAssertEqualObjects(self.persistentCacheResponse.error, self.testError); 59| 1| XCTAssertEqualObjects(self.persistentCacheResponse.record, self.testCacheRecord); 60| 1|} 61| | 62| |#pragma mark Test describing objects 63| | 64| |- (void)testDescriptionAdheresToStyle 65| 1|{ 66| 1| SPTPersistentCacheObjectDescriptionStyleValidator *styleValidator = [SPTPersistentCacheObjectDescriptionStyleValidator new]; 67| 1| 68| 1| NSString * const description = self.persistentCacheResponse.description; 69| 1| 70| 1| XCTAssertTrue([styleValidator isValidStyleDescription:description], @"The description string should follow our style."); 71| 1|} 72| | 73| |- (void)testDescriptionContainsClassName 74| 1|{ 75| 1| NSString * const description = self.persistentCacheResponse.description; 76| 1| 77| 1| const NSRange classNameRange = [description rangeOfString:@"SPTPersistentCacheResponse"]; 78| 1| XCTAssertNotEqual(classNameRange.location, NSNotFound, @"The class name should exist in the description"); 79| 1|} 80| | 81| |- (void)testDebugDescriptionContainsClassName 82| 1|{ 83| 1| NSString * const debugDescription = self.persistentCacheResponse.debugDescription; 84| 1| 85| 1| const NSRange classNameRange = [debugDescription rangeOfString:@"SPTPersistentCacheResponse"]; 86| 1| XCTAssertNotEqual(classNameRange.location, NSNotFound, @"The class name should exist in the debugDescription"); 87| 1|} 88| | 89| |- (void)testStringFromResponseCodeUniqueness 90| 1|{ 91| 1| SPTPersistentCacheResponseCode code = SPTPersistentCacheResponseCodeOperationSucceeded; 92| 1| 93| 1| NSArray *allResponses; 94| 1| 95| 1| switch (code) { // Ensure this method includes all states of enum. 96| 1| case SPTPersistentCacheResponseCodeOperationSucceeded: 97| 1| case SPTPersistentCacheResponseCodeNotFound: 98| 1| case SPTPersistentCacheResponseCodeOperationError: { 99| 1| allResponses = @[NSStringFromSPTPersistentCacheResponseCode(SPTPersistentCacheResponseCodeOperationSucceeded), 100| 1| NSStringFromSPTPersistentCacheResponseCode(SPTPersistentCacheResponseCodeNotFound), 101| 1| NSStringFromSPTPersistentCacheResponseCode(SPTPersistentCacheResponseCodeOperationError)]; 102| 1| } 103| 1| } 104| 1| 105| 1| NSSet *uniqueResponses = [NSSet setWithArray:allResponses]; 106| 1| 107| 1| XCTAssertEqual(allResponses.count, 108| 1| uniqueResponses.count, 109| 1| @"Each string for the response codes should be unique."); 110| 1| 111| 1|} 112| | 113| | 114| |@end /Users/runner/work/SPTPersistentCache/SPTPersistentCache/Tests/SPTPersistentCacheTests.m: 1| |/* 2| | Copyright (c) 2020 Spotify AB. 3| | 4| | Licensed to the Apache Software Foundation (ASF) under one 5| | or more contributor license agreements. See the NOTICE file 6| | distributed with this work for additional information 7| | regarding copyright ownership. The ASF licenses this file 8| | to you under the Apache License, Version 2.0 (the 9| | "License"); you may not use this file except in compliance 10| | with the License. You may obtain a copy of the License at 11| | 12| | http://www.apache.org/licenses/LICENSE-2.0 13| | 14| | Unless required by applicable law or agreed to in writing, 15| | software distributed under the License is distributed on an 16| | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17| | KIND, either express or implied. See the License for the 18| | specific language governing permissions and limitations 19| | under the License. 20| | */ 21| |#import 22| | 23| |#if TARGET_OS_IPHONE 24| |#import 25| 959|#define ImageClass UIImage 26| |#elif TARGET_OS_MAC 27| |#import 28| |#define ImageClass NSImage 29| |#endif 30| |#import 31| | 32| |#import 33| |#import 34| |#import 35| | 36| |#import "SPTPersistentCacheGarbageCollector.h" 37| |#import "SPTPersistentCache+Private.h" 38| |#import "SPTPersistentCacheFileManager.h" 39| |#import "NSFileManagerMock.h" 40| |#import "SPTPersistentCachePosixWrapperMock.h" 41| | 42| |#include 43| |#include 44| |#include 45| | 46| |static const char* kImages[] = { 47| | "b91998ae68b9639cee6243df0886d69bdeb75854", 48| | "c3b19963fc076930dd36ce3968757704bbc97357", 49| | "fc22d4f65c1ba875f6bb5ba7d35a7fd12851ed5c", 50| | "c5aec3eef2478bfe47aef16787a6b4df31eb45f2", 51| | "ee678d23b8dba2997c52741e88fa7a1fdeaf2863", 52| | "f7501f27f70162a9a7da196c5d2ece3151a2d80a", 53| | "e5a1921f8f75d42412e08aff4da33e1132f7ee8a", 54| | "e5b8abdc091921d49e86687e28b74abb3139df70", 55| | "ee6b44ab07fa3937a6d37f449355b64c09677295", 56| | "f50512901688b79a7852999d384d097a71fad788", 57| | "eee9747e967c4440eb90bb812d148aa3d0056700", 58| | "f1eeb834607dcc2b01909bd740d4356f2abb4cd1", //12 59| | "b02c1be08c00bac5f4f1a62c6f353a24487bb024", 60| | "b3e04bf446b486412a13659af71e3a333c6152f4", 61| | "b53aed36cdc67dd43b496db74843ac32fe1f64bb", 62| | "aad0e75ab0a6828d0a9b37a68198cc9d70d84850", 63| | "ab3d97d4d7b3df5417490aa726c5a49b9ee98038", //17 64| | NULL 65| |}; 66| | 67| |#pragma pack(1) 68| |typedef struct 69| |{ 70| | NSUInteger ttl; 71| | BOOL locked; 72| | BOOL last; 73| | int corruptReason; // -1 not currupted 74| |} StoreParamsType; 75| |#pragma pack() 76| | 77| |static const NSUInteger kTTL1 = 7200; 78| |static const NSUInteger kTTL2 = 604800; 79| |static const NSUInteger kTTL3 = 1495; 80| |static const NSUInteger kTTL4 = 86400; 81| |static const NSUInteger kCorruptedFileSize = 15; 82| |static const NSUInteger kTestEpochTime = 1488; 83| |static const NSTimeInterval kDefaultWaitTime = 6.0; //sec 84| | 85| |static const StoreParamsType kParams[] = { 86| | {0, YES, NO, -1}, 87| | {0, YES, NO, -1}, 88| | {0, NO, NO, -1}, 89| | {0, NO, NO, -1}, 90| | {kTTL1, YES, NO, -1}, 91| | {kTTL2, YES, NO, -1}, 92| | {kTTL3, NO, NO, -1}, 93| | {kTTL4, NO, NO, -1}, 94| | {0, NO, NO, -1}, 95| | {0, NO, NO, -1}, 96| | {0, NO, NO, -1}, 97| | {0, NO, NO, -1}, // 12 98| | {0, NO, NO, SPTPersistentCacheLoadingErrorMagicMismatch}, 99| | {0, NO, NO, SPTPersistentCacheLoadingErrorWrongHeaderSize}, 100| | {0, NO, NO, SPTPersistentCacheLoadingErrorWrongPayloadSize}, 101| | {0, NO, NO, SPTPersistentCacheLoadingErrorInvalidHeaderCRC}, 102| | {0, NO, NO, SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader}, // 17 103| | 104| | {kTTL4, NO, YES, -1} 105| |}; 106| | 107| |static NSUInteger params_GetFilesNumber(BOOL locked); 108| |static NSUInteger params_GetCorruptedFilesNumber(void); 109| |static NSUInteger params_GetDefaultExpireFilesNumber(void); 110| | 111| |static BOOL spt_test_ReadHeaderForFile(const char* path, BOOL validate, SPTPersistentCacheRecordHeader *header); 112| | 113| |typedef NSTimeInterval (^SPTPersistentCacheCurrentTimeSecCallback)(void); 114| | 115| |@interface SPTPersistentCacheForUnitTests : SPTPersistentCache 116| |@property (nonatomic, copy) SPTPersistentCacheCurrentTimeSecCallback timeIntervalCallback; 117| | 118| |@property (nonatomic, strong, readwrite) NSOperationQueue *test_workQueue; 119| |@property (nonatomic, strong, readwrite) NSFileManager *test_fileManager; 120| |@property (nonatomic, strong, readwrite) SPTPersistentCachePosixWrapper *test_posixWrapper; 121| |@property (nonatomic, copy, readwrite) SPTPersistentCacheDebugCallback test_debugOutput; 122| | 123| |@property (nonatomic, assign) BOOL test_didWork; 124| |@end 125| | 126| |@implementation SPTPersistentCacheForUnitTests 127| | 128| |- (NSOperationQueue *)workQueue 129| 1.15k|{ 130| 1.15k| return self.test_workQueue ?: super.workQueue; 131| 1.15k|} 132| | 133| |- (NSFileManager *)fileManager 134| 1.34k|{ 135| 1.34k| return self.test_fileManager ?: super.fileManager; 136| 1.34k|} 137| | 138| |- (SPTPersistentCachePosixWrapper *)posixWrapper 139| 608|{ 140| 608| return self.test_posixWrapper ?: super.posixWrapper; 141| 608|} 142| | 143| |- (SPTPersistentCacheDebugCallback)debugOutput 144| 296|{ 145| 296| return self.test_debugOutput ?: super.debugOutput; 146| 296|} 147| | 148| |- (NSTimeInterval)currentDateTimeInterval 149| 1.08k|{ 150| 1.08k| if (self.timeIntervalCallback) { 151| 1.08k| return self.timeIntervalCallback(); 152| 1.08k| } else { 153| 2| return [super currentDateTimeInterval]; 154| 2| } 155| 1.08k|} 156| | 157| |- (void)doWork:(void (^)(void))block priority:(NSOperationQueuePriority)priority qos:(NSQualityOfService)qos 158| 1.15k|{ 159| 1.15k| [super doWork:block priority:priority qos:qos]; 160| 1.15k| self.test_didWork = YES; 161| 1.15k|} 162| | 163| |@end 164| | 165| | 166| |@interface SPTPersistentCacheTests : XCTestCase 167| |@property (nonatomic, strong) SPTPersistentCacheForUnitTests *cache; 168| |@property (nonatomic, strong) NSMutableArray *imageNames; 169| |@property (nonatomic, strong) NSString *cachePath; 170| |@property (nonatomic, strong) NSBundle *thisBundle; 171| |@end 172| | 173| |/** DO NOT DELETE: 174| | Failed seeds: 1417002282, 1417004699, 1417005389, 1417006103 175| | Prune size failed: 1417004677, 1417004725, 1417003704 176| | */ 177| |@implementation SPTPersistentCacheTests 178| | 179| |/* 180| | 1. Write/Read test 181| | - Locked file 182| | - Unlocked file 183| | - Locked file with TTL 184| | - Unlocked file with TTL 185| | - Randomly generate data set 186| | - Use concurrent write 187| | */ 188| |- (void)setUp 189| 53|{ 190| 53| [super setUp]; 191| 53| 192| 53| time_t seed = time(NULL); 193| 53| NSLog(@"Seed:%ld", seed); 194| 53| srand((unsigned int)seed); 195| 53| 196| 53| // Form array of images for shuffling 197| 53| self.imageNames = [NSMutableArray array]; 198| 53| 199| 53| { 200| 53| int i = 0; 201| 954| while (kImages[i] != NULL) { 202| 901| NSString *image = @(kImages[i++]); 203| 901| [self.imageNames addObject:image]; 204| 901| } 205| 53| } 206| 53| 207| 53| @autoreleasepool { 208| 53| NSUInteger imageCount = self.imageNames.count; 209| 53| if (imageCount > 1) { 210| 901| for (NSUInteger oldIdx = 0; oldIdx < imageCount - 1; ++oldIdx) { 211| 848| NSUInteger remainingCount = imageCount - oldIdx; 212| 848| NSUInteger exchangeIdx = oldIdx + arc4random_uniform((u_int32_t)remainingCount); 213| 848| [self.imageNames exchangeObjectAtIndex:oldIdx withObjectAtIndex:exchangeIdx]; 214| 848| } 215| 53| } 216| 53| } 217| 53| 218| 53| self.cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent: 219| 53| [NSString stringWithFormat:@"pdc %@.tmp", [[NSProcessInfo processInfo] globallyUniqueString]]]; 220| 53| 221| 53| NSLog(@"%@", self.cachePath); 222| 53| 223| 910| self.cache = [self createCacheWithTimeCallback:^ NSTimeInterval(){ 224| 910| return kTestEpochTime; 225| 910| } expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 226| 53| SPTPersistentCacheFileManager *fileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:self.cache.options]; 227| 53| 228| 53| self.thisBundle = [NSBundle bundleForClass:[self class]]; 229| 53| 230| 53| const NSUInteger count = self.imageNames.count; 231| 53| 232| 954| for (NSUInteger i = 0; i < count; ++i) { 233| 901| XCTAssert(kParams[i].last != YES, @"Last param element reached"); 234| 901| NSString *fileName = [self.thisBundle pathForResource:self.imageNames[i] ofType:nil]; 235| 901| __weak XCTestExpectation *expectation = [self expectationWithDescription:fileName]; 236| 901| [self putFile:fileName inCache:self.cache withKey:self.imageNames[i] ttl:kParams[i].ttl locked:kParams[i].locked expectation:expectation]; 237| 901| NSData *data = [NSData dataWithContentsOfFile:fileName]; 238| 901| ImageClass *image = [[ImageClass alloc] initWithData:data]; 239| 901| XCTAssertNotNil(image, @"Image is invalid"); 240| 901| } 241| 53| 242| 53| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 243| 53| 244| 954| for (NSUInteger i = 0; !kParams[i].last; ++i) { 245| 901| if (kParams[i].corruptReason > -1) { 246| 265| NSString *filePath = [fileManager pathForKey:self.imageNames[i]]; 247| 265| [self corruptFile:filePath pdcError:kParams[i].corruptReason]; 248| 265| } 249| 901| } 250| 53|} 251| | 252| |- (void)tearDown 253| 53|{ 254| 53| [[NSFileManager defaultManager] removeItemAtPath:self.cachePath error:nil]; 255| 53| self.cache = nil; 256| 53| self.imageNames = nil; 257| 53| self.thisBundle = nil; 258| 53| 259| 53| [super tearDown]; 260| 53|} 261| | 262| |/* 263| | Use read 264| | Check data is ok. 265| | */ 266| |- (void)testCorrectWriteAndRead 267| 1|{ 268| 1| // No expiration 269| 21| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 270| 21| return [NSDate timeIntervalSinceReferenceDate]; 271| 21| } 272| 1| expirationTime:[NSDate timeIntervalSinceReferenceDate]]; 273| 1| SPTPersistentCacheFileManager *fileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:cache.options]; 274| 1| NSUInteger __block calls = 0; 275| 1| NSUInteger __block errorCalls = 0; 276| 1| 277| 1| const NSUInteger count = self.imageNames.count; 278| 1| 279| 18| for (NSUInteger i = 0; i < count; ++i) { 280| 17| 281| 17| NSString *cacheKey = self.imageNames[i]; 282| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 283| 17| 284| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *response) { 285| 17| 286| 17| calls += 1; 287| 17| 288| 17| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 289| 10| XCTAssertNotNil(response.record, @"Expected valid not nil record"); 290| 10| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)response.record.data]; 291| 10| XCTAssertNotNil(image, @"Expected valid not nil image"); 292| 10| XCTAssertNil(response.error, @"error is not expected to be here"); 293| 10| 294| 10| BOOL locked = response.record.refCount > 0; 295| 10| XCTAssertEqual(kParams[i].locked, locked, @"Same files must be locked"); 296| 10| XCTAssertEqual(kParams[i].ttl, response.record.ttl, @"Same files must have same TTL"); 297| 10| XCTAssertEqualObjects(self.imageNames[i], response.record.key, @"Same files must have same key"); 298| 10| } else if (response.result == SPTPersistentCacheResponseCodeNotFound) { 299| 2| XCTAssertNil(response.record, @"Expected valid nil record"); 300| 2| XCTAssertNil(response.error, @"error is not expected to be here"); 301| 2| 302| 5| } else if (response.result == SPTPersistentCacheResponseCodeOperationError) { 303| 5| XCTAssertNil(response.record, @"Expected valid nil record"); 304| 5| XCTAssertNotNil(response.error, @"Valid error is expected to be here"); 305| 5| errorCalls += 1; 306| 5| 307| 5| } else { 308| 0| XCTAssert(NO, @"Unexpected result code on LOAD"); 309| 0| } 310| 17| 311| 17| [exp fulfill]; 312| 17| } onQueue:dispatch_get_main_queue()]; 313| 17| 314| 17| XCTAssert(kParams[i].last != YES, @"Last param element reached"); 315| 17| } 316| 1| 317| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 318| 1| 319| 1| // Check that updat time was modified when access cache (both case ttl==0, ttl>0) 320| 18| for (NSUInteger i = 0; i < count; ++i) { 321| 17| NSString *path = [fileManager pathForKey:self.imageNames[i]]; 322| 17| [self checkUpdateTimeForFileAtPath:path validate:kParams[i].corruptReason == -1 referenceTimeCheck:^(uint64_t updateTime) { 323| 12| if (kParams[i].ttl > 0) { 324| 4| XCTAssertEqual(updateTime, kTestEpochTime, @"Time must not be altered for records with TTL > 0 on cache access"); 325| 8| } else { 326| 8| XCTAssertNotEqual(updateTime, kTestEpochTime, @"Time must be altered since cache was accessed"); 327| 8| } 328| 12| }]; 329| 17| } 330| 1| 331| 1| XCTAssertEqual(calls, self.imageNames.count, @"Number of checked files must match"); 332| 1| XCTAssertEqual(errorCalls, params_GetCorruptedFilesNumber(), @"Number of checked files must match"); 333| 1|} 334| | 335| |/** 336| | This test also checks Req.#1.1a of cache API 337| | WARNING: This test depend on hardcoded data 338| |- Do 1 339| |- find keys with same prefix 340| |- load and check 341| |*/ 342| |- (void)testLoadWithPrefixesSuccess 343| 1|{ 344| 1| // No expiration 345| 3| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 346| 3| return [NSDate timeIntervalSinceReferenceDate]; 347| 3| } expirationTime:[NSDate timeIntervalSinceReferenceDate]]; 348| 1| 349| 1| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"testLoadWithPrefixesSuccess"]; 350| 1| 351| 1| // Thas hardcode logic: 10th element should be safe to get 352| 1| const NSUInteger index = 10; 353| 1| NSString *prefix = self.imageNames[index]; 354| 1| prefix = [prefix substringToIndex:2]; 355| 1| NSString * __block key = nil; 356| 1| 357| 1| [cache loadDataForKeysWithPrefix:prefix chooseKeyCallback:^NSString *(NSArray *keys) { 358| 1| XCTAssert([keys count] >= 1, @"We expect at least 1 key here"); 359| 1| key = keys.firstObject; 360| 1| XCTAssertTrue([key hasPrefix:prefix]); 361| 1| return key; 362| 1| 363| 1| } withCallback:^(SPTPersistentCacheResponse *response) { 364| 1| 365| 1| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 366| 1| XCTAssertNotNil(response.record, @"Expected valid not nil record"); 367| 1| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)response.record.data]; 368| 1| XCTAssertNotNil(image, @"Expected valid not nil image"); 369| 1| XCTAssertNil(response.error, @"error is not expected to be here"); 370| 1| 371| 1| NSUInteger idx = [self.imageNames indexOfObject:key]; 372| 1| XCTAssertNotEqual(idx, NSNotFound); 373| 1| 374| 1| BOOL locked = response.record.refCount > 0; 375| 1| XCTAssertEqual(kParams[idx].locked, locked, @"Same files must be locked"); 376| 1| XCTAssertEqual(kParams[idx].ttl, response.record.ttl, @"Same files must have same TTL"); 377| 1| XCTAssertEqualObjects(key, response.record.key, @"Same files must have same key"); 378| 1| 379| 1| } else if (response.result == SPTPersistentCacheResponseCodeNotFound) { 380| 0| XCTAssert(NO, @"This shouldnt happen"); 381| 0| 382| 0| } else if (response.result == SPTPersistentCacheResponseCodeOperationError ){ 383| 0| XCTAssertNotNil(response.error, @"error is not expected to be here"); 384| 0| } 385| 1| 386| 1| [expectation fulfill]; 387| 1| } onQueue:dispatch_get_main_queue()]; 388| 1| 389| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 390| 1|} 391| | 392| |/** 393| | This test also checks Req.#1.1b of cache API 394| | */ 395| |- (void)testLoadWithPrefixesFail 396| 1|{ 397| 1| // No expiration 398| 2| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 399| 2| return [NSDate timeIntervalSinceReferenceDate]; 400| 2| } expirationTime:[NSDate timeIntervalSinceReferenceDate]]; 401| 1| 402| 1| __weak XCTestExpectation *expectation = [self expectationWithDescription:@"testLoadWithPrefixesFail"]; 403| 1| 404| 1| // Thas hardcode logic: 10th element should be safe to get 405| 1| const NSUInteger index = 9; 406| 1| NSString *prefix = self.imageNames[index]; 407| 1| prefix = [prefix substringToIndex:2]; 408| 1| NSString * __block key = nil; 409| 1| 410| 1| [cache loadDataForKeysWithPrefix:prefix chooseKeyCallback:^NSString *(NSArray *keys) { 411| 1| XCTAssert([keys count] >= 1, @"We expect at least 1 key here"); 412| 1| key = keys.firstObject; 413| 1| XCTAssertTrue([key hasPrefix:prefix]); 414| 1| 415| 1| // Refuse to open 416| 1| return nil; 417| 1| 418| 1| } withCallback:^(SPTPersistentCacheResponse *response) { 419| 1| 420| 1| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 421| 0| XCTAssert(NO, @"This shouldnt happen"); 422| 1| } else if (response.result == SPTPersistentCacheResponseCodeNotFound) { 423| 1| XCTAssertNil(response.record, @"Expected valid nil record"); 424| 1| XCTAssertNil(response.error, @"Valid nil error is expected to be here"); 425| 1| } else { 426| 0| XCTAssert(NO, @"This shouldnt happen"); 427| 0| } 428| 1| 429| 1| [expectation fulfill]; 430| 1| } onQueue:dispatch_get_main_queue()]; 431| 1| 432| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 433| 1|} 434| | 435| |/* 436| | - Do 1 437| | - Lock unlocked files 438| | - Unlock locked files 439| | - Concurrent read 440| | - Check data is ok. 441| | */ 442| |- (void)testLockUnlock 443| 1|{ 444| 30| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 445| 30| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec - 1; 446| 30| } 447| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 448| 1| 449| 1| SPTPersistentCacheFileManager *fileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:cache.options]; 450| 1| 451| 1| NSMutableArray *toLock = [NSMutableArray array]; 452| 1| NSMutableArray *toUnlock = [NSMutableArray array]; 453| 1| 454| 1| const NSUInteger count = self.imageNames.count; 455| 18| for (NSUInteger i = 0; i < count; ++i) { 456| 17| if (kParams[i].locked) { 457| 4| [toUnlock addObject:self.imageNames[i]]; 458| 13| } else { 459| 13| [toLock addObject:self.imageNames[i]]; 460| 13| } 461| 17| } 462| 1| 463| 1| // Wait untill all lock/unlock is done 464| 1| __weak XCTestExpectation *lockExp = [self expectationWithDescription:@"lock"]; 465| 1| NSInteger __block toLockCount = (NSInteger)[toLock count]; 466| 13| [cache lockDataForKeys:toLock callback:^(SPTPersistentCacheResponse *response) { 467| 13| if (--toLockCount == 0) { 468| 1| [lockExp fulfill]; 469| 1| } 470| 13| } onQueue:dispatch_get_main_queue()]; 471| 1| 472| 1| __weak XCTestExpectation *unlockExp = [self expectationWithDescription:@"unlock"]; 473| 1| NSInteger __block toUnlockCount = (NSInteger)[toUnlock count]; 474| 4| [cache unlockDataForKeys:toUnlock callback:^(SPTPersistentCacheResponse *response) { 475| 4| if (--toUnlockCount == 0){ 476| 1| [unlockExp fulfill]; 477| 1| } 478| 4| } onQueue:dispatch_get_main_queue()]; 479| 1| 480| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 481| 1| 482| 1| // Now check that updateTime is not altered by lock unlock calls for all not corrupted files 483| 18| for (NSUInteger i = 0; i < count; ++i) { 484| 17| NSString *path = [fileManager pathForKey:self.imageNames[i]]; 485| 17| [self checkUpdateTimeForFileAtPath:path validate:kParams[i].corruptReason == -1 referenceTimeCheck:^(uint64_t updateTime) { 486| 12| XCTAssertEqual(updateTime, kTestEpochTime, @"Time must match for initial value i.e. not altering"); 487| 12| }]; 488| 17| } 489| 1| 490| 1| NSUInteger __block calls = 0; 491| 1| NSUInteger __block errorCalls = 0; 492| 1| 493| 18| for (NSUInteger i = 0; i < count; ++i) { 494| 17| 495| 17| NSString *cacheKey = self.imageNames[i]; 496| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 497| 17| 498| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *response) { 499| 17| calls += 1; 500| 17| 501| 17| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 502| 12| XCTAssertNotNil(response.record, @"Expected valid not nil record"); 503| 12| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)response.record.data]; 504| 12| XCTAssertNotNil(image, @"Expected valid not nil image"); 505| 12| XCTAssertNil(response.error, @"error is not expected to be here"); 506| 12| 507| 12| BOOL locked = response.record.refCount > 0; 508| 12| XCTAssertNotEqual(kParams[i].locked, locked, @"Same files must be locked"); 509| 12| XCTAssertEqual(kParams[i].ttl, response.record.ttl, @"Same files must have same TTL"); 510| 12| XCTAssertEqualObjects(self.imageNames[i], response.record.key, @"Same files must have same key"); 511| 12| } else if (response.result == SPTPersistentCacheResponseCodeNotFound) { 512| 0| XCTAssertNil(response.record, @"Expected valid nil record"); 513| 0| XCTAssertNil(response.error, @"error is not expected to be here"); 514| 0| 515| 5| } else if (response.result == SPTPersistentCacheResponseCodeOperationError) { 516| 5| XCTAssertNil(response.record, @"Expected valid nil record"); 517| 5| XCTAssertNotNil(response.error, @"Valid error is expected to be here"); 518| 5| errorCalls += 1; 519| 5| 520| 5| } else { 521| 0| XCTAssert(NO, @"Unexpected result code on LOAD"); 522| 0| } 523| 17| 524| 17| [exp fulfill]; 525| 17| } onQueue:dispatch_get_main_queue()]; 526| 17| } 527| 1| 528| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 529| 1| 530| 1| XCTAssertEqual(calls, self.imageNames.count, @"Number of checked files must match"); 531| 1| XCTAssertEqual(errorCalls, params_GetCorruptedFilesNumber(), @"Number of checked files must match"); 532| 1|} 533| | 534| |/* 535| | 3. Remove test 536| | - Do 1 w/o * 537| | - Remove data set with keys 538| | - Check file system is empty 539| | */ 540| |- (void)testRemoveItems 541| 1|{ 542| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 543| 0| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec - 1; 544| 0| } 545| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 546| 1| __weak XCTestExpectation *removeExpectation = [self expectationWithDescription:@"remove"]; 547| 1| NSUInteger __block calls = 0; 548| 1| const NSUInteger imageCount = self.imageNames.count; 549| 1| 550| 1| NSMutableArray *expectations = [NSMutableArray arrayWithCapacity:imageCount]; 551| 18| for (NSUInteger i = 0; i < imageCount; ++i) { 552| 17| NSString *cacheKey = self.imageNames[i]; 553| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 554| 17| [expectations addObject:exp]; 555| 17| } 556| 1| 557| 1| [cache removeDataForKeys:self.imageNames callback:^(SPTPersistentCacheResponse * _Nonnull response) { 558| 1| [removeExpectation fulfill]; 559| 1| const NSUInteger count = self.imageNames.count; 560| 1| 561| 18| for (NSUInteger i = 0; i < count; ++i) { 562| 17| 563| 17| NSString *cacheKey = self.imageNames[i]; 564| 17| 565| 17| // This just give us guarantee that files should be deleted 566| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *loadResponse) { 567| 17| calls += 1; 568| 17| XCTAssertEqual(loadResponse.result, SPTPersistentCacheResponseCodeNotFound, @"We expect file wouldn't be found after removing"); 569| 17| XCTAssertNil(loadResponse.record, @"Expected valid nil record"); 570| 17| XCTAssertNil(loadResponse.error, @"error is not expected to be here"); 571| 17| [expectations[i] fulfill]; 572| 17| } onQueue:dispatch_get_main_queue()]; 573| 17| } 574| 1| } onQueue:dispatch_get_main_queue()]; 575| 1| 576| 1| 577| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 578| 1| 579| 1| XCTAssert(calls == self.imageNames.count, @"Number of checked files must match"); 580| 1| 581| 1| // Check file system, that there are no files left 582| 1| NSUInteger files = [self getFilesNumberAtPath:self.cachePath]; 583| 1| XCTAssertEqual(files, 0u, @"There shouldn't be files left"); 584| 1|} 585| | 586| |/* 587| | 4. Test prune 588| | - Do 1 w/o * 589| | - prune 590| | - test file system is clean 591| | */ 592| |- (void)testPureCache 593| 1|{ 594| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 595| 0| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec - 1; 596| 0| } 597| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 598| 1| const NSUInteger count = self.imageNames.count; 599| 1| 600| 1| NSMutableArray *expectations = [NSMutableArray arrayWithCapacity:count]; 601| 18| for (NSUInteger i = 0; i < count; ++i) { 602| 17| NSString *cacheKey = self.imageNames[i]; 603| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 604| 17| [expectations addObject:exp]; 605| 17| } 606| 1| NSUInteger __block calls = 0; 607| 1| [cache pruneWithCallback:^(SPTPersistentCacheResponse * _Nonnull response) { 608| 18| for (NSUInteger i = 0; i < count; ++i) { 609| 17| 610| 17| NSString *cacheKey = self.imageNames[i]; 611| 17| 612| 17| // This just give us guarantee that files should be deleted 613| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *loadResponse) { 614| 17| 615| 17| calls += 1; 616| 17| 617| 17| XCTAssertEqual(loadResponse.result, SPTPersistentCacheResponseCodeNotFound, @"We expect file wouldn't be found after removing"); 618| 17| XCTAssertNil(loadResponse.record, @"Expected valid nil record"); 619| 17| XCTAssertNil(loadResponse.error, @"error is not expected to be here"); 620| 17| 621| 17| [expectations[i] fulfill]; 622| 17| } onQueue:dispatch_get_main_queue()]; 623| 17| } 624| 1| } onQueue:dispatch_get_main_queue()]; 625| 1| 626| 1| 627| 1| 628| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 629| 1| 630| 1| XCTAssert(calls == self.imageNames.count, @"Number of checked files must match"); 631| 1| 632| 1| // Check file syste, that there are no files left 633| 1| NSUInteger files = [self getFilesNumberAtPath:self.cachePath]; 634| 1| XCTAssertEqual(files, 0u, @"There shouldn't be files left"); 635| 1|} 636| | 637| | 638| |/** 639| | 5. Test wipe locked 640| | - Do 1 w/o * 641| | - wipe locked 642| | - check no locked files on fs 643| | - check unlocked is remains untouched 644| | */ 645| |- (void)testWipeLocked 646| 1|{ 647| 1| // We consider that no expiration occure in this test 648| 1| // If pass nil in time callback then files with TTL would be expired 649| 15| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 650| 15| return kTestEpochTime; 651| 15| } 652| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 653| 1| const NSUInteger count = self.imageNames.count; 654| 1| NSMutableArray *expectations = [NSMutableArray arrayWithCapacity:count]; 655| 18| for (NSUInteger i = 0; i < count; ++i) { 656| 17| NSString *cacheKey = self.imageNames[i]; 657| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 658| 17| [expectations addObject:exp]; 659| 17| } 660| 1| NSUInteger __block calls = 0; 661| 1| NSUInteger __block notFoundCalls = 0; 662| 1| NSUInteger __block errorCalls = 0; 663| 1| 664| 1| BOOL __block locked = NO; 665| 1| const NSUInteger reallyLocked = params_GetFilesNumber(YES); 666| 1| 667| 1| [cache wipeLockedFilesWithCallback:^(SPTPersistentCacheResponse * _Nonnull response) { 668| 18| for (unsigned i = 0; i < count; ++i) { 669| 17| NSString *cacheKey = self.imageNames[i]; 670| 17| 671| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *loadResponse) { 672| 17| calls += 1; 673| 17| 674| 17| if (loadResponse.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 675| 8| XCTAssertNotNil(loadResponse.record, @"Expected valid not nil record"); 676| 8| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)loadResponse.record.data]; 677| 8| XCTAssertNotNil(image, @"Expected valid not nil image"); 678| 8| XCTAssertNil(loadResponse.error, @"error is not expected to be here"); 679| 8| 680| 8| locked = loadResponse.record.refCount > 0; 681| 8| XCTAssertEqual(kParams[i].locked, locked, @"Same files must be locked"); 682| 8| XCTAssertEqual(kParams[i].ttl, loadResponse.record.ttl, @"Same files must have same TTL"); 683| 8| XCTAssertEqualObjects(self.imageNames[i], loadResponse.record.key, @"Same files must have same key"); 684| 9| } else if (loadResponse.result == SPTPersistentCacheResponseCodeNotFound) { 685| 4| XCTAssertNil(loadResponse.record, @"Expected valid nil record"); 686| 4| XCTAssertNil(loadResponse.error, @"error is not expected to be here"); 687| 4| notFoundCalls += 1; 688| 5| } else if (loadResponse.result == SPTPersistentCacheResponseCodeOperationError) { 689| 5| XCTAssertNil(loadResponse.record, @"Expected valid nil record"); 690| 5| XCTAssertNotNil(loadResponse.error, @"Valid error is expected to be here"); 691| 5| errorCalls += 1; 692| 5| 693| 5| } else { 694| 0| XCTAssert(NO, @"Unexpected result code on LOAD"); 695| 0| } 696| 17| 697| 17| [expectations[i] fulfill]; 698| 17| } onQueue:dispatch_get_main_queue()]; 699| 17| } 700| 1| 701| 1| } onQueue:dispatch_get_main_queue()]; 702| 1| 703| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 704| 1| 705| 1| XCTAssert(calls == self.imageNames.count, @"Number of checked files must match"); 706| 1| XCTAssertEqual(notFoundCalls, reallyLocked, @"Number of really locked files files is not the same we deleted"); 707| 1| 708| 1| // Check file syste, that there are no files left 709| 1| NSUInteger files = [self getFilesNumberAtPath:self.cachePath]; 710| 1| XCTAssertEqual(files, self.imageNames.count-(NSUInteger)reallyLocked, @"There shouldn't be files left"); 711| 1| XCTAssertEqual(errorCalls, params_GetCorruptedFilesNumber(), @"Number of checked files must match"); 712| 1|} 713| | 714| |/* 715| | 6. Test wipe unlocked 716| | - Do 1 w/o * 717| | - wipe unlocked 718| | - check no unlocked files on fs 719| | - check locked is remains untouched 720| |*/ 721| |- (void)testWipeUnlocked 722| 1|{ 723| 6| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 724| 6| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec - 1; 725| 6| } 726| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 727| 1| 728| 1| const NSUInteger count = self.imageNames.count; 729| 1| NSMutableArray *expectations = [NSMutableArray arrayWithCapacity:count]; 730| 18| for (NSUInteger i = 0; i < count; ++i) { 731| 17| NSString *cacheKey = self.imageNames[i]; 732| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 733| 17| [expectations addObject:exp]; 734| 17| } 735| 1| 736| 1| NSUInteger __block calls = 0; 737| 1| NSUInteger __block notFoundCalls = 0; 738| 1| NSUInteger __block errorCalls = 0; 739| 1| BOOL __block unlocked = YES; 740| 1| // +1 stands for SPTPersistentCacheLoadingErrorWrongPayloadSize since technically it has corrent header. 741| 1| const NSUInteger reallyUnlocked = params_GetFilesNumber(NO) + 1; 742| 1| 743| 1| [cache wipeNonLockedFilesWithCallback:^(SPTPersistentCacheResponse * _Nonnull response) { 744| 18| for (unsigned i = 0; i < count; ++i) { 745| 17| 746| 17| NSString *cacheKey = self.imageNames[i]; 747| 17| 748| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *loadResponse) { 749| 17| calls += 1; 750| 17| 751| 17| if (loadResponse.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 752| 4| XCTAssertNotNil(loadResponse.record, @"Expected valid not nil record"); 753| 4| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)loadResponse.record.data]; 754| 4| XCTAssertNotNil(image, @"Expected valid not nil image"); 755| 4| XCTAssertNil(loadResponse.error, @"error is not expected to be here"); 756| 4| 757| 4| unlocked = loadResponse.record.refCount == 0; 758| 4| XCTAssertEqual(kParams[i].locked, !unlocked, @"Same files must be locked"); 759| 4| XCTAssertEqual(kParams[i].ttl, loadResponse.record.ttl, @"Same files must have same TTL"); 760| 4| XCTAssertEqualObjects(self.imageNames[i], loadResponse.record.key, @"Same files must have same key"); 761| 13| } else if (loadResponse.result == SPTPersistentCacheResponseCodeNotFound) { 762| 9| XCTAssertNil(loadResponse.record, @"Expected valid nil record"); 763| 9| XCTAssertNil(loadResponse.error, @"error is not expected to be here"); 764| 9| notFoundCalls += 1; 765| 9| 766| 9| } else if (loadResponse.result == SPTPersistentCacheResponseCodeOperationError) { 767| 4| XCTAssertNil(loadResponse.record, @"Expected valid nil record"); 768| 4| XCTAssertNotNil(loadResponse.error, @"Valid error is expected to be here"); 769| 4| 770| 4| errorCalls += 1; 771| 4| 772| 4| } else { 773| 0| XCTAssert(NO, @"Unexpected result code on LOAD"); 774| 0| } 775| 17| 776| 17| [expectations[i] fulfill]; 777| 17| } onQueue:dispatch_get_main_queue()]; 778| 17| } 779| 1| } onQueue:dispatch_get_main_queue()]; 780| 1| 781| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 782| 1| 783| 1| XCTAssert(calls == self.imageNames.count, @"Number of checked files must match"); 784| 1| XCTAssertEqual(notFoundCalls, reallyUnlocked, @"Number of really locked files files is not the same we deleted"); 785| 1| 786| 1| // Check file system, that there are no files left 787| 1| NSUInteger files = [self getFilesNumberAtPath:self.cachePath]; 788| 1| XCTAssertEqual(files, self.imageNames.count-(NSUInteger)reallyUnlocked, @"There shouldn't be files left"); 789| 1| XCTAssertEqual(errorCalls, params_GetCorruptedFilesNumber()-1, @"Number of checked files must match"); 790| 1|} 791| | 792| |/* 793| | 7. Test used size 794| | - Do 1 w/o * 795| | - test 796| | */ 797| |- (void)testUsedSize 798| 1|{ 799| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 800| 0| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec - 1; 801| 0| } 802| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 803| 1| 804| 1| NSUInteger expectedSize = [self calculateExpectedSize]; 805| 1| NSUInteger realUsedSize = [cache totalUsedSizeInBytes]; 806| 1| XCTAssertEqual(realUsedSize, expectedSize); 807| 1|} 808| | 809| |/* 810| | 8. Test locked size 811| | - Do 1 w/o * 812| | - test 813| | */ 814| |- (void)testLockedSize 815| 1|{ 816| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 817| 0| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec - 1; 818| 0| } 819| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 820| 1| 821| 1| NSUInteger expectedSize = 0; 822| 1| 823| 18| for (unsigned i = 0; !kParams[i].last; ++i) { 824| 17| 825| 17| if (kParams[i].locked) { 826| 4| 827| 4| NSString *fileName = [self.thisBundle pathForResource:self.imageNames[i] ofType:nil]; 828| 4| NSData *data = [NSData dataWithContentsOfFile:fileName]; 829| 4| XCTAssertNotNil(data, @"Data must be valid"); 830| 4| expectedSize += ([data length] + (NSUInteger)SPTPersistentCacheRecordHeaderSize); 831| 4| } 832| 17| } 833| 1| 834| 1| NSUInteger realUsedSize = [cache lockedItemsSizeInBytes]; 835| 1| XCTAssertEqual(realUsedSize, expectedSize); 836| 1|} 837| | 838| |- (void)testScheduleGarbageCollection 839| 1|{ 840| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 841| 0| // Exceed expiration interval by 1 sec 842| 0| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec + 1; 843| 0| } 844| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 845| 1| 846| 1| 847| 1| XCTAssertFalse(cache.garbageCollector.isGarbageCollectionScheduled); 848| 1| 849| 1| [cache scheduleGarbageCollector]; 850| 1| 851| 1| XCTAssertTrue(cache.garbageCollector.isGarbageCollectionScheduled); 852| 1|} 853| | 854| |- (void)testUnscheduleGarbageCollection 855| 1|{ 856| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 857| 0| // Exceed expiration interval by 1 sec 858| 0| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec + 1; 859| 0| } 860| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 861| 1| 862| 1| 863| 1| XCTAssertFalse(cache.garbageCollector.isGarbageCollectionScheduled); 864| 1| 865| 1| [cache scheduleGarbageCollector]; 866| 1| 867| 1| XCTAssertTrue(cache.garbageCollector.isGarbageCollectionScheduled); 868| 1| 869| 1| [cache unscheduleGarbageCollector]; 870| 1| 871| 1| XCTAssertFalse(cache.garbageCollector.isGarbageCollectionScheduled); 872| 1|} 873| | 874| |/** 875| | This test also checks Req.#1.2 of cache API. 876| | */ 877| |- (void)testExpirationWithDefaultTimeout 878| 1|{ 879| 15| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 880| 15| // Exceed expiration interval by 1 sec 881| 15| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec + 1; 882| 15| } 883| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 884| 1| 885| 1| const NSUInteger count = self.imageNames.count; 886| 1| NSUInteger __block calls = 0; 887| 1| NSUInteger __block notFoundCalls = 0; 888| 1| NSUInteger __block errorCalls = 0; 889| 1| NSUInteger __block successCalls = 0; 890| 1| BOOL __block unlocked = YES; 891| 1| 892| 18| for (unsigned i = 0; i < count; ++i) { 893| 17| NSString *cacheKey = self.imageNames[i]; 894| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 895| 17| 896| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *response) { 897| 17| calls += 1; 898| 17| 899| 17| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 900| 6| ++successCalls; 901| 6| XCTAssertNotNil(response.record, @"Expected valid not nil record"); 902| 6| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)response.record.data]; 903| 6| XCTAssertNotNil(image, @"Expected valid not nil image"); 904| 6| XCTAssertNil(response.error, @"error is not expected to be here"); 905| 6| 906| 6| unlocked = response.record.refCount == 0; 907| 6| XCTAssertEqual(kParams[i].locked, !unlocked, @"Same files must be locked"); 908| 6| XCTAssertEqual(kParams[i].ttl, response.record.ttl, @"Same files must have same TTL"); 909| 6| XCTAssertEqualObjects(self.imageNames[i], response.record.key, @"Same files must have same key"); 910| 11| } else if (response.result == SPTPersistentCacheResponseCodeNotFound) { 911| 7| XCTAssertNil(response.record, @"Expected valid nil record"); 912| 7| XCTAssertNil(response.error, @"error is not expected to be here"); 913| 7| notFoundCalls += 1; 914| 7| 915| 7| } else if (response.result == SPTPersistentCacheResponseCodeOperationError) { 916| 4| XCTAssertNil(response.record, @"Expected valid nil record"); 917| 4| XCTAssertNotNil(response.error, @"Valid error is expected to be here"); 918| 4| errorCalls += 1; 919| 4| 920| 4| } else { 921| 0| XCTAssert(NO, @"Unexpected result code on LOAD"); 922| 0| } 923| 17| 924| 17| [exp fulfill]; 925| 17| } onQueue:dispatch_get_main_queue()]; 926| 17| } 927| 1| 928| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 929| 1| 930| 1| const NSUInteger normalFilesCount = params_GetDefaultExpireFilesNumber(); 931| 1| const NSUInteger corrupted = params_GetCorruptedFilesNumber(); 932| 1| 933| 1| XCTAssert(calls == count, @"Number of checked files must match"); 934| 1| XCTAssertEqual(successCalls, count-normalFilesCount-corrupted, @"There should be exact number of locked files"); 935| 1| // -1 stands for payload error since technically header is correct and returned as Not found 936| 1| XCTAssertEqual(notFoundCalls-1, normalFilesCount, @"Number of not found files must match"); 937| 1| // -1 stands for payload error since technically header is correct 938| 1| XCTAssertEqual(errorCalls, corrupted-1, @"Number of not found files must match"); 939| 1|} 940| | 941| |- (void)testExpirationWithTTL 942| 1|{ 943| 15| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 944| 15| // Take largest TTL of non locked + 1 sec 945| 15| return kTestEpochTime + kTTL4 + 1; 946| 15| } 947| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 948| 1| 949| 1| const NSUInteger count = self.imageNames.count; 950| 1| NSUInteger __block calls = 0; 951| 1| NSUInteger __block notFoundCalls = 0; 952| 1| NSUInteger __block errorCalls = 0; 953| 1| NSUInteger __block successCalls = 0; 954| 1| BOOL __block unlocked = YES; 955| 1| 956| 18| for (unsigned i = 0; i < count; ++i) { 957| 17| NSString *cacheKey = self.imageNames[i]; 958| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 959| 17| 960| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *response) { 961| 17| calls += 1; 962| 17| 963| 17| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 964| 4| ++successCalls; 965| 4| XCTAssertNotNil(response.record, @"Expected valid not nil record"); 966| 4| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)response.record.data]; 967| 4| XCTAssertNotNil(image, @"Expected valid not nil image"); 968| 4| XCTAssertNil(response.error, @"error is not expected to be here"); 969| 4| 970| 4| unlocked = response.record.refCount == 0; 971| 4| XCTAssertEqual(kParams[i].locked, !unlocked, @"Same files must be locked"); 972| 4| XCTAssertEqual(kParams[i].ttl, response.record.ttl, @"Same files must have same TTL"); 973| 4| XCTAssertEqualObjects(self.imageNames[i], response.record.key, @"Same files must have same key"); 974| 13| } else if (response.result == SPTPersistentCacheResponseCodeNotFound) { 975| 9| XCTAssertNil(response.record, @"Expected valid nil record"); 976| 9| XCTAssertNil(response.error, @"error is not expected to be here"); 977| 9| notFoundCalls += 1; 978| 9| 979| 9| } else if (response.result == SPTPersistentCacheResponseCodeOperationError) { 980| 4| XCTAssertNil(response.record, @"Expected valid nil record"); 981| 4| XCTAssertNotNil(response.error, @"Valid error is expected to be here"); 982| 4| errorCalls += 1; 983| 4| 984| 4| } else { 985| 0| XCTAssert(NO, @"Unexpected result code on LOAD"); 986| 0| } 987| 17| 988| 17| [exp fulfill]; 989| 17| } onQueue:dispatch_get_main_queue()]; 990| 17| } 991| 1| 992| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 993| 1| 994| 1| const NSUInteger normalFilesCount = params_GetFilesNumber(NO); 995| 1| 996| 1| XCTAssert(calls == count, @"Number of checked files must match"); 997| 1| XCTAssertEqual(successCalls, params_GetFilesNumber(YES), @"There should be exact number of locked files"); 998| 1| // -1 stands for payload error since technically header is correct and returned as Not found 999| 1| XCTAssertEqual(notFoundCalls-1, normalFilesCount, @"Number of not found files must match"); 1000| 1| // -1 stands for payload error since technically header is correct 1001| 1| XCTAssertEqual(errorCalls, params_GetCorruptedFilesNumber()-1, @"Number of not found files must match"); 1002| 1|} 1003| | 1004| |/** 1005| | This test also checks Req.#1.2 for cache API 1006| | */ 1007| |- (void)testTouchOnlyRecordsWithDefaultExpiration 1008| 1|{ 1009| 43| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 1010| 43| return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec - 1; 1011| 43| } 1012| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 1013| 1| 1014| 1| SPTPersistentCacheFileManager *fileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:cache.options]; 1015| 1| 1016| 1| const NSUInteger count = self.imageNames.count; 1017| 1| 1018| 18| for (unsigned i = 0; i < count; ++i) { 1019| 17| NSString *cacheKey = self.imageNames[i]; 1020| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 1021| 17| 1022| 17| [cache touchDataForKey:cacheKey callback:^(SPTPersistentCacheResponse *response) { 1023| 17| [exp fulfill]; 1024| 17| } onQueue:dispatch_get_main_queue()]; 1025| 17| } 1026| 1| 1027| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1028| 1| 1029| 1| // Now check that updateTime is not altered for files with TTL 1030| 18| for (unsigned i = 0; i < count; ++i) { 1031| 17| NSString *path = [fileManager pathForKey:self.imageNames[i]]; 1032| 17| [self checkUpdateTimeForFileAtPath:path validate:kParams[i].corruptReason == -1 referenceTimeCheck:^(uint64_t updateTime) { 1033| 12| if (kParams[i].ttl == 0) { 1034| 8| XCTAssertNotEqual(updateTime, kTestEpochTime, @"Time must not match for initial value i.e. touched"); 1035| 8| } else { 1036| 4| XCTAssertEqual(updateTime, kTestEpochTime, @"Time must match for initial value i.e. not touched"); 1037| 4| } 1038| 12| }]; 1039| 17| } 1040| 1| 1041| 1| // Now do regular check of data integrity after touch 1042| 1| NSUInteger __block calls = 0; 1043| 1| NSUInteger __block notFoundCalls = 0; 1044| 1| NSUInteger __block errorCalls = 0; 1045| 1| NSUInteger __block successCalls = 0; 1046| 1| BOOL __block unlocked = YES; 1047| 1| 1048| 18| for (unsigned i = 0; i < count; ++i) { 1049| 17| NSString *cacheKey = self.imageNames[i]; 1050| 17| __weak XCTestExpectation *exp = [self expectationWithDescription:cacheKey]; 1051| 17| 1052| 17| [cache loadDataForKey:cacheKey withCallback:^(SPTPersistentCacheResponse *response) { 1053| 17| calls += 1; 1054| 17| 1055| 17| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 1056| 12| ++successCalls; 1057| 12| XCTAssertNotNil(response.record, @"Expected valid not nil record"); 1058| 12| ImageClass *image = [[ImageClass alloc] initWithData:(NSData *)response.record.data]; 1059| 12| XCTAssertNotNil(image, @"Expected valid not nil image"); 1060| 12| XCTAssertNil(response.error, @"error is not expected to be here"); 1061| 12| 1062| 12| unlocked = response.record.refCount == 0; 1063| 12| XCTAssertEqual(kParams[i].locked, !unlocked, @"Same files must be locked"); 1064| 12| XCTAssertEqual(kParams[i].ttl, response.record.ttl, @"Same files must have same TTL"); 1065| 12| XCTAssertEqualObjects(self.imageNames[i], response.record.key, @"Same files must have same key"); 1066| 12| } else if (response.result == SPTPersistentCacheResponseCodeNotFound) { 1067| 0| XCTAssertNil(response.record, @"Expected valid nil record"); 1068| 0| XCTAssertNil(response.error, @"error is not expected to be here"); 1069| 0| notFoundCalls += 1; 1070| 0| 1071| 5| } else if (response.result == SPTPersistentCacheResponseCodeOperationError) { 1072| 5| XCTAssertNil(response.record, @"Expected valid nil record"); 1073| 5| XCTAssertNotNil(response.error, @"Valid error is expected to be here"); 1074| 5| errorCalls += 1; 1075| 5| 1076| 5| } else { 1077| 0| XCTAssert(NO, @"Unexpected result code on LOAD"); 1078| 0| } 1079| 17| 1080| 17| [exp fulfill]; 1081| 17| } onQueue:dispatch_get_main_queue()]; 1082| 17| } 1083| 1| 1084| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1085| 1| 1086| 1| const NSUInteger corrupted = params_GetCorruptedFilesNumber(); 1087| 1| 1088| 1| XCTAssert(calls == count, @"Number of checked files must match"); 1089| 1| XCTAssertEqual(successCalls, count-corrupted, @"There should be exact number of locked files"); 1090| 1| XCTAssertEqual(notFoundCalls, (NSUInteger)0, @"Number of not found files must match"); 1091| 1| XCTAssertEqual(errorCalls, corrupted, @"Number of not found files must match"); 1092| 1|} 1093| | 1094| |// WARNING: This test is dependent on hardcoded data TTL4 1095| |- (void)testRegularGCWithTTL 1096| 1|{ 1097| 13| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{ 1098| 13| // Take largest TTL4 of non locked 1099| 13| return kTestEpochTime + kTTL4; 1100| 13| } 1101| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 1102| 1| 1103| 1| SPTPersistentCacheFileManager *fileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:cache.options]; 1104| 1| 1105| 1| const NSUInteger count = self.imageNames.count; 1106| 1| 1107| 1| [cache runRegularGC]; 1108| 1| 1109| 1| // After GC we have to have only locked files and corrupted 1110| 1| NSUInteger lockedCount = 0; 1111| 1| NSUInteger removedCount = 0; 1112| 1| 1113| 18| for (unsigned i = 0; i < count; ++i) { 1114| 17| NSString *path = [fileManager pathForKey:self.imageNames[i]]; 1115| 17| 1116| 17| SPTPersistentCacheRecordHeader header; 1117| 17| BOOL opened = spt_test_ReadHeaderForFile(path.UTF8String, YES, &header); 1118| 17| if (kParams[i].locked) { 1119| 4| ++lockedCount; 1120| 4| XCTAssertTrue(opened, @"Locked files expected to be at place"); 1121| 13| } else if (kParams[i].ttl == kTTL4) { 1122| 1| XCTAssertTrue(opened, @"TTL4 file expected to be at place"); 1123| 12| } else { 1124| 12| ++removedCount; 1125| 12| XCTAssertFalse(opened, @"Not locked files expected to removed thus unable to be opened"); 1126| 12| } 1127| 17| } 1128| 1| 1129| 1| XCTAssertEqual(lockedCount, params_GetFilesNumber(YES), @"Locked files count must match"); 1130| 1| // We add number of corrupted since we couldn't open them anyway 1131| 1| // -1 stands for wrong payload 1132| 1| XCTAssertEqual(removedCount, params_GetFilesNumber(NO)+params_GetCorruptedFilesNumber() -1, @"Removed files count must match"); 1133| 1|} 1134| | 1135| |- (void)testPruneWithSizeRestriction 1136| 1|{ 1137| 1| const NSUInteger count = self.imageNames.count; 1138| 1| 1139| 1| // Just dummy cache to get path to items 1140| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:nil expirationTime:0]; 1141| 1| 1142| 1| SPTPersistentCacheFileManager *fileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:cache.options]; 1143| 1| 1144| 1| // Alter update time for our data set so it monotonically increase from the past starting at index 0 to count-1 1145| 18| for (unsigned i = 0; i < count; ++i) { 1146| 17| NSString *path = [fileManager pathForKey:self.imageNames[i]]; 1147| 17| 1148| 17| struct timeval t[2]; 1149| 17| t[0].tv_sec = (__darwin_time_t)(kTestEpochTime - 5*(i+1)); 1150| 17| t[0].tv_usec = 0; 1151| 17| t[1] = t[0]; 1152| 17| int ret = utimes(path.UTF8String, t); 1153| 17| XCTAssertNotEqual(ret, -1, @"Failed to set file access time"); 1154| 17| } 1155| 1| 1156| 1| NSMutableArray *savedItems = [NSMutableArray array]; 1157| 1| 1158| 1| // Take 6 first elements which are least oldest 1159| 1| const NSUInteger dropCount = 6; 1160| 1| NSUInteger expectedSize = 0; 1161| 1| 1162| 7| for (unsigned i = 0; i < count && i < dropCount; ++i) { 1163| 6| NSUInteger size = [self dataSizeForItem:self.imageNames[i]]; 1164| 6| expectedSize -= (size + (NSUInteger)SPTPersistentCacheRecordHeaderSize); 1165| 6| [savedItems addObject:self.imageNames[i]]; 1166| 6| } 1167| 1| 1168| 1| SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new]; 1169| 1| options.cachePath = self.cachePath; 1170| 1| options.cacheIdentifier = @"test"; 1171| 1| options.sizeConstraintBytes = expectedSize; 1172| 22| options.debugOutput = ^(NSString *str) { 1173| 22| NSLog(@"%@", str); 1174| 22| }; 1175| 1| 1176| 1| cache = [[SPTPersistentCache alloc] initWithOptions:options]; 1177| 1| 1178| 1| [cache pruneBySize]; 1179| 1| 1180| 1| // Check that size reached its required level 1181| 1| NSUInteger realSize = [cache totalUsedSizeInBytes]; 1182| 1| XCTAssert(realSize <= expectedSize, @"real cache size has to be less or equal to what we expect"); 1183| 1| 1184| 1| // Check that files supposed to be deleted was actually removed 1185| 7| for (unsigned i = 0; i < savedItems.count; ++i) { 1186| 6| 1187| 6| // Skip not locked files since they could be deleted 1188| 6| NSUInteger idx = [self.imageNames indexOfObject:savedItems[i]]; 1189| 6| if (!kParams[idx].locked) { 1190| 2| continue; 1191| 2| } 1192| 4| 1193| 4| NSString *path = [fileManager pathForKey:savedItems[i]]; 1194| 4| SPTPersistentCacheRecordHeader header; 1195| 4| BOOL opened = spt_test_ReadHeaderForFile(path.UTF8String, YES, &header); 1196| 4| XCTAssertTrue(opened, @"Saved files expected to in place"); 1197| 4| } 1198| 1| 1199| 1| // Call once more to make sure nothing will be droped 1200| 1| [cache pruneBySize]; 1201| 1| 1202| 1| NSUInteger realSize2 = [cache totalUsedSizeInBytes]; 1203| 1| XCTAssertEqual(realSize, realSize2); 1204| 1|} 1205| | 1206| |/** 1207| | At least 2 serial stores with lock for same key doesn't increment refCount. 1208| | Detect change in TTL and in refCount when parameters changed. 1209| | This is for Req.#1.0 of cache API 1210| | */ 1211| |- (void)testSerialStoreWithLockDoesntIncrementRefCount 1212| 1|{ 1213| 1| const NSTimeInterval refTime = kTestEpochTime + 1.0; 1214| 3| SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval(){ return refTime; } 1215| 1| expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 1216| 1| 1217| 1| SPTPersistentCacheFileManager *fileManager = [[SPTPersistentCacheFileManager alloc] initWithOptions:cache.options]; 1218| 1| // Index of file to put. It should be file without any problems. 1219| 1| const NSUInteger putIndex = 2; 1220| 1| NSString *key = self.imageNames[putIndex]; 1221| 1| NSString *fileName = [self.thisBundle pathForResource:key ofType:nil]; 1222| 1| NSString *path = [fileManager pathForKey:key]; 1223| 1| 1224| 1| // Check that image is valid just in case 1225| 1| NSData *data = [NSData dataWithContentsOfFile:fileName]; 1226| 1| ImageClass *image = [[ImageClass alloc] initWithData:data]; 1227| 1| XCTAssertNotNil(image, @"Image is invalid"); 1228| 1| 1229| 1| // Put file for existing name and expect new ttl and lock status 1230| 1| __weak XCTestExpectation *exp1 = [self expectationWithDescription:@"exp1"]; 1231| 1| [self putFile:fileName inCache:cache withKey:key ttl:kTTL1 locked:YES expectation:exp1]; 1232| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1233| 1| 1234| 1| // Check data 1235| 1| SPTPersistentCacheRecordHeader header; 1236| 1| XCTAssertTrue(spt_test_ReadHeaderForFile(path.UTF8String, YES, &header), @"Expect valid record"); 1237| 1| XCTAssertEqual(header.ttl, kTTL1, @"TTL must match"); 1238| 1| XCTAssertEqual(header.refCount, 1u, @"refCount must match"); 1239| 1| 1240| 1| // Now same call with new ttl and same lock status. Expect no change in refCount according to API Req.#1.0 1241| 1| __weak XCTestExpectation *exp2 = [self expectationWithDescription:@"exp2"]; 1242| 1| [self putFile:fileName inCache:cache withKey:key ttl:kTTL2 locked:YES expectation:exp2]; 1243| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1244| 1| 1245| 1| // Check data 1246| 1| XCTAssertTrue(spt_test_ReadHeaderForFile(path.UTF8String, YES, &header), @"Expect valid record"); 1247| 1| XCTAssertEqual(header.ttl, kTTL2, @"TTL must match"); 1248| 1| XCTAssertEqual(header.refCount, 1u, @"refCount must match"); 1249| 1| 1250| 1| // Now same call with new ttl and new lock status. Expect no change in refCount according to API Req.#1.0 1251| 1| __weak XCTestExpectation *exp3 = [self expectationWithDescription:@"exp3"]; 1252| 1| [self putFile:fileName inCache:cache withKey:key ttl:kTTL1 locked:NO expectation:exp3]; 1253| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1254| 1| 1255| 1| // Check data 1256| 1| XCTAssertTrue(spt_test_ReadHeaderForFile(path.UTF8String, YES, &header), @"Expect valid record"); 1257| 1| XCTAssertEqual(header.ttl, kTTL1, @"TTL must match"); 1258| 1| XCTAssertEqual(header.refCount, 0u, @"refCount must match"); 1259| 1|} 1260| | 1261| |- (void)testInitNilWhenCannotCreateCacheDirectory 1262| 1|{ 1263| 1| SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new]; 1264| 1| options.cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"/com.spotify.temppersistent.image.cache"]; 1265| 1| options.cacheIdentifier = @"test"; 1266| 1| 1267| 1| Method originalMethod = class_getClassMethod(NSFileManager.class, @selector(defaultManager)); 1268| 1| IMP originalMethodImplementation = method_getImplementation(originalMethod); 1269| 2| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 1270| 2| return nil; 1271| 2| }); 1272| 1| method_setImplementation(originalMethod, fakeMethodImplementation); 1273| 1| SPTPersistentCache *cache = [[SPTPersistentCache alloc] initWithOptions:options]; 1274| 1| method_setImplementation(originalMethod, originalMethodImplementation); 1275| 1| 1276| 1| XCTAssertNil(cache, @"The cache should be nil if it could not create the directory"); 1277| 1|} 1278| | 1279| |- (void)testFailToLoadDataWhenCallbackAbsent 1280| 1|{ 1281| 1| BOOL result = [self.cache loadDataForKey:@"Thing" withCallback:nil onQueue:nil]; 1282| 1| XCTAssertFalse(result); 1283| 1|} 1284| | 1285| |- (void)testFailToLoadDataForKeysWithPrefixWhenCallbackAbsent 1286| 1|{ 1287| 1| BOOL result = [self.cache loadDataForKeysWithPrefix:@"T" chooseKeyCallback:nil withCallback:nil onQueue:nil]; 1288| 1| XCTAssertFalse(result); 1289| 1|} 1290| | 1291| |- (void)testFailToRetrieveDirectoryContents 1292| 1|{ 1293| 1| NSFileManagerMock *fileManager = [NSFileManagerMock new]; 1294| 1| fileManager.mock_contentsOfDirectoryAtPaths = @{}; 1295| 1| self.cache.test_fileManager = fileManager; 1296| 1| 1297| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1298| 1| [self.cache loadDataForKeysWithPrefix:@"T" 1299| 1| chooseKeyCallback:^ NSString *(NSArray *keys) { 1300| 0| return keys.firstObject; 1301| 0| } 1302| 1| withCallback:^(SPTPersistentCacheResponse *response) { 1303| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1304| 1| [expectation fulfill]; 1305| 1| } 1306| 1| onQueue:dispatch_get_main_queue()]; 1307| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1308| 1|} 1309| | 1310| |- (void)testNotFoundIfCacheDirectoryIsDeleted 1311| 1|{ 1312| 1| [[NSFileManager defaultManager] removeItemAtPath:self.cachePath error:nil]; 1313| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1314| 1| [self.cache loadDataForKeysWithPrefix:@"T" chooseKeyCallback:^ NSString *(NSArray *keys) { 1315| 0| return keys.firstObject; 1316| 1| } withCallback:^ (SPTPersistentCacheResponse *response) { 1317| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeNotFound); 1318| 1| [expectation fulfill]; 1319| 1| } onQueue:dispatch_get_main_queue()]; 1320| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1321| 1|} 1322| | 1323| |- (void)testNoValidKeys 1324| 1|{ 1325| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1326| 1| [self.cache loadDataForKeysWithPrefix:@"T" chooseKeyCallback:^ NSString *(NSArray *keys) { 1327| 0| return keys.firstObject; 1328| 1| } withCallback:^ (SPTPersistentCacheResponse *response) { 1329| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeNotFound); 1330| 1| [expectation fulfill]; 1331| 1| } onQueue:dispatch_get_main_queue()]; 1332| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1333| 1|} 1334| | 1335| |- (void)DISABLED_testTouchDataWithExpiredHeader 1336| 0|{ 1337| 0| for (NSUInteger i = 0; i < self.imageNames.count; ++i) { 1338| 0| if (kParams[i].ttl == 0) { 1339| 0| continue; 1340| 0| } 1341| 0| NSString *key = self.imageNames[i]; 1342| 0| [self.cache unlockDataForKeys:@[key] callback:nil onQueue:nil]; 1343| 0| self.cache.timeIntervalCallback = ^ { 1344| 0| return kTestEpochTime * 100.0; 1345| 0| }; 1346| 0| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1347| 0| [self.cache touchDataForKey:key callback:^(SPTPersistentCacheResponse *response) { 1348| 0| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeNotFound); 1349| 0| [expectation fulfill]; 1350| 0| } onQueue:dispatch_get_main_queue()]; 1351| 0| break; 1352| 0| } 1353| 0| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1354| 0|} 1355| | 1356| |- (void)testLockDataWithExpiredHeader 1357| 1|{ 1358| 5| for (NSUInteger i = 0; i < self.imageNames.count; ++i) { 1359| 5| if (kParams[i].ttl == 0) { 1360| 4| continue; 1361| 4| } 1362| 1| NSString *key = self.imageNames[i]; 1363| 1| [self.cache unlockDataForKeys:@[key] callback:nil onQueue:nil]; 1364| 1| self.cache.timeIntervalCallback = ^ { 1365| 1| return kTestEpochTime * 100.0; 1366| 1| }; 1367| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1368| 1| [self.cache lockDataForKeys:@[key] callback:^(SPTPersistentCacheResponse *response) { 1369| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeNotFound); 1370| 1| [expectation fulfill]; 1371| 1| } onQueue:dispatch_get_main_queue()]; 1372| 1| break; 1373| 1| } 1374| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1375| 1|} 1376| | 1377| |- (void)testUnlockDataMoreTimesThanLocked 1378| 1|{ 1379| 5| for (NSUInteger i = 0; i < self.imageNames.count; ++i) { 1380| 5| if (kParams[i].ttl == 0) { 1381| 4| continue; 1382| 4| } 1383| 1| NSString *key = self.imageNames[i]; 1384| 1| __block BOOL called = NO; 1385| 51| for (NSInteger repeat = 0; repeat < 50; ++repeat) { 1386| 50| [self.cache unlockDataForKeys:@[key] callback:^(SPTPersistentCacheResponse *response) { 1387| 50| called = YES; 1388| 50| } onQueue:dispatch_get_main_queue()]; 1389| 50| } 1390| 1| XCTAssertFalse(called); 1391| 1| break; 1392| 1| } 1393| 1|} 1394| | 1395| |- (void)testCurrentDataTimeInterval 1396| 1|{ 1397| 1| SPTPersistentCache *cache = [self createCacheWithTimeCallback:nil expirationTime:0]; 1398| 1| 1399| 1| NSTimeInterval firstTimeInterval = [cache currentDateTimeInterval]; 1400| 1| NSTimeInterval secondTimeInterval = [cache currentDateTimeInterval]; 1401| 1| 1402| 1| XCTAssertGreaterThan(secondTimeInterval, firstTimeInterval); 1403| 1|} 1404| | 1405| |- (void)testUnlockDataWithNoKeys 1406| 1|{ 1407| 1| BOOL result = [self.cache unlockDataForKeys:@[] callback:nil onQueue:nil]; 1408| 1| XCTAssertFalse(result); 1409| 1|} 1410| | 1411| |- (void)testLockedItemSizeInBytesWithInvalidDirectoryAttributes 1412| 1|{ 1413| 1| Method originalMethod = class_getInstanceMethod(NSURL.class, @selector(getResourceValue:forKey:error:)); 1414| 1| IMP originalMethodImplementation = method_getImplementation(originalMethod); 1415| 31| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 1416| 31| return nil; 1417| 31| }); 1418| 1| method_setImplementation(originalMethod, fakeMethodImplementation); 1419| 1| NSUInteger lockedItemsSizeInBytes = self.cache.lockedItemsSizeInBytes; 1420| 1| method_setImplementation(originalMethod, originalMethodImplementation); 1421| 1| XCTAssertEqual(lockedItemsSizeInBytes, 0u); 1422| 1|} 1423| | 1424| |- (void)testErrorWhenCannotReadFile 1425| 1|{ 1426| 1| NSString *key = self.imageNames.firstObject; 1427| 1| Method originalMethod = class_getClassMethod(NSMutableData.class, @selector(dataWithContentsOfFile:options:error:)); 1428| 1| IMP originalMethodImplementation = method_getImplementation(originalMethod); 1429| 1| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 1430| 1| return nil; 1431| 1| }); 1432| 1| method_setImplementation(originalMethod, fakeMethodImplementation); 1433| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1434| 1| [self.cache loadDataForKey:key withCallback:^(SPTPersistentCacheResponse *response) { 1435| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1436| 1| [expectation fulfill]; 1437| 1| } onQueue:dispatch_get_main_queue()]; 1438| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1439| 1| method_setImplementation(originalMethod, originalMethodImplementation); 1440| 1|} 1441| | 1442| |- (void)testLockDataWithNoKeys 1443| 1|{ 1444| 1| BOOL result = [self.cache lockDataForKeys:@[] callback:nil onQueue:nil]; 1445| 1| XCTAssertFalse(result); 1446| 1|} 1447| | 1448| |- (void)testWriteToHeaderFailed 1449| 1|{ 1450| 1| NSString *key = self.imageNames.firstObject; 1451| 1| Method originalMethod = class_getInstanceMethod(NSData.class, @selector(writeToFile:options:error:)); 1452| 1| IMP originalMethodImplementation = method_getImplementation(originalMethod); 1453| 1| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 1454| 1| return nil; 1455| 1| }); 1456| 1| method_setImplementation(originalMethod, fakeMethodImplementation); 1457| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1458| 1| [self.cache loadDataForKey:key withCallback:^(SPTPersistentCacheResponse *response) { 1459| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationSucceeded); 1460| 1| [expectation fulfill]; 1461| 1| } onQueue:dispatch_get_main_queue()]; 1462| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1463| 1| method_setImplementation(originalMethod, originalMethodImplementation); 1464| 1|} 1465| | 1466| |- (void)testWriteFailedOnStoreData 1467| 1|{ 1468| 1| Method originalMethod = class_getInstanceMethod(NSData.class, @selector(writeToFile:options:error:)); 1469| 1| IMP originalMethodImplementation = method_getImplementation(originalMethod); 1470| 1| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 1471| 1| return nil; 1472| 1| }); 1473| 1| method_setImplementation(originalMethod, fakeMethodImplementation); 1474| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1475| 1| NSData *tmpData = [@"TEST" dataUsingEncoding:NSUTF8StringEncoding]; 1476| 1| [self.cache storeData:tmpData forKey:@"TEST" locked:NO withCallback:^(SPTPersistentCacheResponse *response) { 1477| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1478| 1| [expectation fulfill]; 1479| 1| } onQueue:dispatch_get_main_queue()]; 1480| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1481| 1| method_setImplementation(originalMethod, originalMethodImplementation); 1482| 1|} 1483| | 1484| |- (void)testOpenFailure 1485| 1|{ 1486| 1| NSFileManagerMock *fileManagerMock = [NSFileManagerMock new]; 1487| 1| self.cache.test_fileManager = fileManagerMock; 1488| 1| __weak __typeof(fileManagerMock) weakFileManagerMock = fileManagerMock; 1489| 1| fileManagerMock.blockCalledOnFileExistsAtPath = ^ { 1490| 1| __strong __typeof(weakFileManagerMock) strongFileManagerMock = weakFileManagerMock; 1491| 1| [[NSFileManager defaultManager] removeItemAtPath:strongFileManagerMock.lastPathCalledOnExists error:nil]; 1492| 1| }; 1493| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1494| 1| [self.cache touchDataForKey:self.imageNames[0] callback:^(SPTPersistentCacheResponse *response) { 1495| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1496| 1| [expectation fulfill]; 1497| 1| } onQueue:dispatch_get_main_queue()]; 1498| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1499| 1|} 1500| | 1501| |- (void)testCloseFailure 1502| 1|{ 1503| 1| SPTPersistentCachePosixWrapperMock *posixWrapperMock = [SPTPersistentCachePosixWrapperMock new]; 1504| 1| self.cache.test_posixWrapper = posixWrapperMock; 1505| 1| posixWrapperMock.closeValue = -1; 1506| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1507| 1| [self.cache touchDataForKey:self.imageNames[0] callback:^(SPTPersistentCacheResponse *response) { 1508| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1509| 1| [expectation fulfill]; 1510| 1| } onQueue:dispatch_get_main_queue()]; 1511| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1512| 1|} 1513| | 1514| |- (void)testReadFailure 1515| 1|{ 1516| 1| SPTPersistentCachePosixWrapperMock *posixWrapperMock = [SPTPersistentCachePosixWrapperMock new]; 1517| 1| self.cache.test_posixWrapper = posixWrapperMock; 1518| 1| posixWrapperMock.readValue = -1; 1519| 1| posixWrapperMock.readOverridden = YES; 1520| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1521| 1| [self.cache touchDataForKey:self.imageNames[0] callback:^(SPTPersistentCacheResponse *response) { 1522| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1523| 1| [expectation fulfill]; 1524| 1| } onQueue:dispatch_get_main_queue()]; 1525| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1526| 1|} 1527| | 1528| |- (void)testlseekFailure 1529| 1|{ 1530| 2| self.cache.timeIntervalCallback = ^ { 1531| 2| return kTestEpochTime * 10.0; 1532| 2| }; 1533| 1| SPTPersistentCachePosixWrapperMock *posixWrapperMock = [SPTPersistentCachePosixWrapperMock new]; 1534| 1| self.cache.test_posixWrapper = posixWrapperMock; 1535| 1| posixWrapperMock.lseekValue = -1; 1536| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1537| 1| [self.cache touchDataForKey:self.imageNames[0] callback:^(SPTPersistentCacheResponse *response) { 1538| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1539| 1| [expectation fulfill]; 1540| 1| } onQueue:dispatch_get_main_queue()]; 1541| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1542| 1|} 1543| | 1544| |- (void)testWriteFailure 1545| 1|{ 1546| 2| self.cache.timeIntervalCallback = ^ { 1547| 2| return kTestEpochTime * 10.0; 1548| 2| }; 1549| 1| SPTPersistentCachePosixWrapperMock *posixWrapperMock = [SPTPersistentCachePosixWrapperMock new]; 1550| 1| self.cache.test_posixWrapper = posixWrapperMock; 1551| 1| posixWrapperMock.writeValue = 0; 1552| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1553| 1| [self.cache touchDataForKey:self.imageNames[0] callback:^(SPTPersistentCacheResponse *response) { 1554| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1555| 1| [expectation fulfill]; 1556| 1| } onQueue:dispatch_get_main_queue()]; 1557| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1558| 1|} 1559| | 1560| |- (void)testFsyncFailure 1561| 1|{ 1562| 2| self.cache.timeIntervalCallback = ^ { 1563| 2| return kTestEpochTime * 10.0; 1564| 2| }; 1565| 1| SPTPersistentCachePosixWrapperMock *posixWrapperMock = [SPTPersistentCachePosixWrapperMock new]; 1566| 1| self.cache.test_posixWrapper = posixWrapperMock; 1567| 1| posixWrapperMock.writeValue = (ssize_t)SPTPersistentCacheRecordHeaderSize; 1568| 1| posixWrapperMock.fsyncValue = -1; 1569| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1570| 1| [self.cache touchDataForKey:self.imageNames[0] callback:^(SPTPersistentCacheResponse *response) { 1571| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1572| 1| [expectation fulfill]; 1573| 1| } onQueue:dispatch_get_main_queue()]; 1574| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1575| 1|} 1576| | 1577| |- (void)testStoreLargeTTL 1578| 1|{ 1579| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1580| 1| NSString *key = @"TEST"; 1581| 1| NSData *testData = [@"TEST" dataUsingEncoding:NSUTF8StringEncoding]; 1582| 1| [self.cache storeData:testData 1583| 1| forKey:key 1584| 1| ttl:86400 * 31 * 2 * 2 1585| 1| locked:NO 1586| 1| withCallback:^(SPTPersistentCacheResponse *response) { 1587| 1| [expectation fulfill]; 1588| 1| } 1589| 1| onQueue:dispatch_get_main_queue()]; 1590| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1591| 1| 1592| 1| __weak XCTestExpectation * const debugExpectation = [self expectationWithDescription:@"debug expectation"]; 1593| 1| self.cache.test_debugOutput = ^(NSString *output) { 1594| 1| [debugExpectation fulfill]; 1595| 1| }; 1596| 1| [self.cache touchDataForKey:key callback:nil onQueue:nil]; 1597| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1598| 1| 1599| 1|} 1600| | 1601| |- (void)testWipeAllFiles 1602| 1|{ 1603| 1| __block BOOL called = NO; 1604| 18| self.cache.test_debugOutput = ^(NSString *output) { 1605| 18| called = YES; 1606| 18| }; 1607| 1| [self.cache collectGarbageForceExpire:YES forceLocked:YES]; 1608| 1| XCTAssertTrue(called); 1609| 1|} 1610| | 1611| |- (void)testURLAttributeFailure 1612| 1|{ 1613| 1| __block BOOL called = NO; 1614| 32| self.cache.test_debugOutput = ^(NSString *output) { 1615| 32| called = YES; 1616| 32| }; 1617| 1| Method originalMethod = class_getInstanceMethod(NSURL.class, @selector(getResourceValue:forKey:error:)); 1618| 1| IMP originalMethodImplementation = method_getImplementation(originalMethod); 1619| 31| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 1620| 31| return nil; 1621| 31| }); 1622| 1| method_setImplementation(originalMethod, fakeMethodImplementation); 1623| 1| [self.cache collectGarbageForceExpire:YES forceLocked:YES]; 1624| 1| method_setImplementation(originalMethod, originalMethodImplementation); 1625| 1| XCTAssertTrue(called); 1626| 1|} 1627| | 1628| |- (void)testPruneBySizeOnUnconstrainedCache 1629| 1|{ 1630| 1| BOOL result = [self.cache pruneBySize]; 1631| 1| XCTAssertFalse(result); 1632| 1|} 1633| | 1634| |- (void)testPruneBySizeRemoveFileFailure 1635| 1|{ 1636| 1| __block BOOL called = NO; 1637| 21| self.cache.test_debugOutput = ^(NSString *output) { 1638| 21| called = YES; 1639| 21| }; 1640| 1| //TODO not sure why self.cache.test_workQueue = dispatch_get_main_queue(); 1641| 1| NSData *data = [@"TEST" dataUsingEncoding:NSUTF8StringEncoding]; 1642| 1| [self.cache storeData:data 1643| 1| forKey:@"TEST" 1644| 1| locked:YES 1645| 1| withCallback:nil 1646| 1| onQueue:nil]; 1647| 1| self.cache.options.sizeConstraintBytes = 1; 1648| 1| NSFileManagerMock *fileManagerMock = [NSFileManagerMock new]; 1649| 1| fileManagerMock.disableRemoveFile = YES; 1650| 1| self.cache.test_fileManager = fileManagerMock; 1651| 1| [self.cache pruneBySize]; 1652| 1| XCTAssertTrue(called); 1653| 1|} 1654| | 1655| |- (void)testStatFailure 1656| 1|{ 1657| 1| //TODO not sure why self.cache.test_workQueue = dispatch_get_main_queue(); 1658| 1| NSData *data = [@"TEST" dataUsingEncoding:NSUTF8StringEncoding]; 1659| 1| [self.cache storeData:data 1660| 1| forKey:@"TEST" 1661| 1| locked:YES 1662| 1| withCallback:nil 1663| 1| onQueue:nil]; 1664| 1| self.cache.options.sizeConstraintBytes = 1; 1665| 1| SPTPersistentCachePosixWrapperMock *posixWrapperMock = [SPTPersistentCachePosixWrapperMock new]; 1666| 1| posixWrapperMock.statValue = -1; 1667| 1| self.cache.test_posixWrapper = posixWrapperMock; 1668| 1| __block BOOL called = NO; 1669| 21| self.cache.test_debugOutput = ^(NSString *output) { 1670| 21| called = YES; 1671| 21| }; 1672| 1| [self.cache pruneBySize]; 1673| 1| XCTAssertTrue(called); 1674| 1|} 1675| | 1676| |- (void)testResourceInfoURLFailure 1677| 1|{ 1678| 1| self.cache.options.sizeConstraintBytes = 1; 1679| 1| __block BOOL called = NO; 1680| 62| self.cache.test_debugOutput = ^(NSString *output) { 1681| 62| called = YES; 1682| 62| }; 1683| 1| Method originalMethod = class_getInstanceMethod(NSURL.class, @selector(getResourceValue:forKey:error:)); 1684| 1| IMP originalMethodImplementation = method_getImplementation(originalMethod); 1685| 62| IMP fakeMethodImplementation = imp_implementationWithBlock(^ { 1686| 62| return nil; 1687| 62| }); 1688| 1| method_setImplementation(originalMethod, fakeMethodImplementation); 1689| 1| [self.cache pruneBySize]; 1690| 1| method_setImplementation(originalMethod, originalMethodImplementation); 1691| 1| XCTAssertTrue(called); 1692| 1|} 1693| | 1694| |- (void)testStoreDataWithCallbackAndNoQueue 1695| 1|{ 1696| 1| BOOL result = [self.cache storeData:[NSData data] 1697| 1| forKey:@"TEST" 1698| 1| locked:NO 1699| 1| withCallback:^(SPTPersistentCacheResponse *response) {} 1700| 1| onQueue:nil]; 1701| 1| XCTAssertFalse(result); 1702| 1|} 1703| | 1704| |#pragma mark Test Dispatching Empty and Error Responses 1705| | 1706| |- (void)testDispatchEmptyResponseWithNilCallbackDoesNothing 1707| 1|{ 1708| 1| SPTPersistentCacheForUnitTests * const cache = [self createCacheWithTimeCallback:^ NSTimeInterval(){ 1709| 0| return kTestEpochTime; 1710| 0| } expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 1711| 1| 1712| 1| [cache dispatchEmptyResponseWithResult:SPTPersistentCacheResponseCodeNotFound 1713| 1| callback:nil 1714| 1| onQueue:nil]; 1715| 1| 1716| 1| XCTAssertFalse(cache.test_didWork); 1717| 1|} 1718| | 1719| |- (void)testDispatchEmptyResponse 1720| 1|{ 1721| 1| SPTPersistentCacheForUnitTests * const cache = [self createCacheWithTimeCallback:^ NSTimeInterval(){ 1722| 0| return kTestEpochTime; 1723| 0| } expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 1724| 1| 1725| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1726| 1| SPTPersistentCacheResponseCallback callback = ^(SPTPersistentCacheResponse *response){ 1727| 1| XCTAssertNotNil(response); 1728| 1| XCTAssertNil(response.error); 1729| 1| XCTAssertNil(response.record); 1730| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeNotFound); 1731| 1| 1732| 1| [expectation fulfill]; 1733| 1| }; 1734| 1| [cache dispatchEmptyResponseWithResult:SPTPersistentCacheResponseCodeNotFound 1735| 1| callback:callback 1736| 1| onQueue:dispatch_get_main_queue()]; 1737| 1| 1738| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1739| 1|} 1740| | 1741| |- (void)testDispatchErrorWithNilCallbackDoesNothing 1742| 1|{ 1743| 1| SPTPersistentCacheForUnitTests * const cache = [self createCacheWithTimeCallback:^ NSTimeInterval(){ 1744| 0| return kTestEpochTime; 1745| 0| } expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 1746| 1| 1747| 1| [cache dispatchError:[NSError errorWithDomain:SPTPersistentCacheErrorDomain code:0 userInfo:nil] 1748| 1| result:SPTPersistentCacheResponseCodeOperationError 1749| 1| callback:nil 1750| 1| onQueue:dispatch_get_main_queue()]; 1751| 1| 1752| 1| XCTAssertFalse(cache.test_didWork); 1753| 1|} 1754| | 1755| |- (void)testDispatchError 1756| 1|{ 1757| 1| SPTPersistentCacheForUnitTests * const cache = [self createCacheWithTimeCallback:^ NSTimeInterval(){ 1758| 0| return kTestEpochTime; 1759| 0| } expirationTime:SPTPersistentCacheDefaultExpirationTimeSec]; 1760| 1| 1761| 1| NSError * const error = [NSError errorWithDomain:SPTPersistentCacheErrorDomain 1762| 1| code:SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader 1763| 1| userInfo:nil]; 1764| 1| 1765| 1| __weak XCTestExpectation * const expectation = [self expectationWithDescription:@"callback expectation"]; 1766| 1| SPTPersistentCacheResponseCallback callback = ^(SPTPersistentCacheResponse *response){ 1767| 1| XCTAssertNotNil(response); 1768| 1| XCTAssertEqualObjects(response.error, error); 1769| 1| XCTAssertNil(response.record); 1770| 1| XCTAssertEqual(response.result, SPTPersistentCacheResponseCodeOperationError); 1771| 1| 1772| 1| [expectation fulfill]; 1773| 1| }; 1774| 1| 1775| 1| [cache dispatchError:error 1776| 1| result:SPTPersistentCacheResponseCodeOperationError 1777| 1| callback:callback 1778| 1| onQueue:dispatch_get_main_queue()]; 1779| 1| 1780| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1781| 1|} 1782| | 1783| |#pragma mark Test Dispatching Blocks 1784| | 1785| |- (void)testDispatchBlockSync 1786| 1|{ 1787| 1| XCTAssertTrue([NSThread isMainThread]); 1788| 1| 1789| 1| __block BOOL didExecuteBlock = NO; 1790| 1| dispatch_block_t block = ^{ 1791| 1| didExecuteBlock = YES; 1792| 1| XCTAssertTrue([NSThread isMainThread]); 1793| 1| }; 1794| 1| 1795| 1| SPTPersistentCacheSafeDispatch(nil, block); 1796| 1| 1797| 1| XCTAssertTrue(didExecuteBlock); 1798| 1|} 1799| | 1800| |- (void)testDispatchBlockAsync 1801| 1|{ 1802| 1| XCTAssertTrue([NSThread isMainThread]); 1803| 1| 1804| 1| dispatch_queue_t queue = dispatch_queue_create("com.spotify.persistentcache.proposed-queue", DISPATCH_QUEUE_SERIAL); 1805| 1| 1806| 1| static const char qKey; 1807| 1| int qValue = 123; 1808| 1| dispatch_queue_set_specific(queue, &qKey, &qValue, NULL); 1809| 1| 1810| 1| __weak XCTestExpectation * const expecation = [self expectationWithDescription:@"block was executed"]; 1811| 1| dispatch_block_t block = ^{ 1812| 1| int *ptr = dispatch_queue_get_specific(queue, &qKey); 1813| 1| XCTAssertTrue(ptr != NULL); 1814| 1| XCTAssertTrue(*ptr == 123); 1815| 1| [expecation fulfill]; 1816| 1| }; 1817| 1| 1818| 1| SPTPersistentCacheSafeDispatch(queue, block); 1819| 1| 1820| 1| [self waitForExpectationsWithTimeout:kDefaultWaitTime handler:nil]; 1821| 1|} 1822| | 1823| |#pragma mark - Internal methods 1824| | 1825| |- (void)putFile:(NSString *)file 1826| | inCache:(SPTPersistentCache *)cache 1827| | withKey:(NSString *)key 1828| | ttl:(NSUInteger)ttl 1829| | locked:(BOOL)locked 1830| | expectation:(__weak XCTestExpectation *)expectation 1831| 904|{ 1832| 904| NSData *data = [NSData dataWithContentsOfFile:file]; 1833| 904| XCTAssertNotNil(data, @"Unable to get data from file:%@", file); 1834| 904| XCTAssertNotNil(key, @"Key must be specified"); 1835| 904| SPTPersistentCacheResponseCallback callback = ^(SPTPersistentCacheResponse *response) { 1836| 904| if (response.result == SPTPersistentCacheResponseCodeOperationSucceeded) { 1837| 904| XCTAssertNil(response.record, @"record expected to be nil"); 1838| 904| XCTAssertNil(response.error, @"error xpected to be nil"); 1839| 904| } else if (response.result == SPTPersistentCacheResponseCodeOperationError) { 1840| 0| XCTAssertNil(response.record, @"record expected to be nil"); 1841| 0| XCTAssertNotNil(response.error, @"error must exist for when STORE failed"); 1842| 0| } else { 1843| 0| XCTAssert(NO, @"This is not expected result code for STORE operation"); 1844| 0| } 1845| 904| [expectation fulfill]; 1846| 904| }; 1847| 904| 1848| 904| if (ttl == 0) { 1849| 689| [cache storeData:data forKey:key locked:locked withCallback:callback onQueue:dispatch_get_main_queue()]; 1850| 689| } else { 1851| 215| [cache storeData:data forKey:key ttl:ttl locked:locked withCallback:callback onQueue:dispatch_get_main_queue()]; 1852| 215| } 1853| 904|} 1854| | 1855| |/* 1856| |SPTPersistentCacheLoadingErrorMagicMismatch, 1857| |SPTPersistentCacheLoadingErrorWrongHeaderSize, 1858| |SPTPersistentCacheLoadingErrorWrongPayloadSize, 1859| |SPTPersistentCacheLoadingErrorInvalidHeaderCRC, 1860| |SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader, 1861| |*/ 1862| | 1863| |- (void)corruptFile:(NSString *)filePath 1864| | pdcError:(int)pdcError 1865| 265|{ 1866| 265| int flags = O_RDWR; 1867| 265| if (pdcError == SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader) { 1868| 53| flags |= O_TRUNC; 1869| 53| } 1870| 265| 1871| 265| int fd = open([filePath UTF8String], flags); 1872| 265| if (fd == -1) { 1873| 0| XCTAssert(fd != -1, @"Could not open file while trying to simulate corruption"); 1874| 0| return; 1875| 0| } 1876| 265| 1877| 265| SPTPersistentCacheRecordHeader header; 1878| 265| memset(&header, 0, (size_t)SPTPersistentCacheRecordHeaderSize); 1879| 265| 1880| 265| if (pdcError != SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader) { 1881| 212| 1882| 212| ssize_t readSize = read(fd, &header, (size_t)SPTPersistentCacheRecordHeaderSize); 1883| 212| if (readSize != (ssize_t)SPTPersistentCacheRecordHeaderSize) { 1884| 0| XCTAssert(readSize == (ssize_t)SPTPersistentCacheRecordHeaderSize, @"Header not read"); 1885| 0| close(fd); 1886| 0| return; 1887| 0| } 1888| 265| } 1889| 265| 1890| 265| NSUInteger headerSize = (NSUInteger)SPTPersistentCacheRecordHeaderSize; 1891| 265| 1892| 265| switch (pdcError) { 1893| 53| case SPTPersistentCacheLoadingErrorMagicMismatch: { 1894| 53| header.magic = 0xFFFF5454; 1895| 53| break; 1896| 0| } 1897| 53| case SPTPersistentCacheLoadingErrorWrongHeaderSize: { 1898| 53| header.headerSize = (uint32_t)SPTPersistentCacheRecordHeaderSize + 1u + arc4random_uniform(106); 1899| 53| header.crc = SPTPersistentCacheCalculateHeaderCRC(&header); 1900| 53| break; 1901| 0| } 1902| 53| case SPTPersistentCacheLoadingErrorWrongPayloadSize: { 1903| 53| header.payloadSizeBytes += (1 + (arc4random_uniform((uint32_t)header.payloadSizeBytes) - (header.payloadSizeBytes-1)/2)); 1904| 53| header.crc = SPTPersistentCacheCalculateHeaderCRC(&header); 1905| 53| break; 1906| 0| } 1907| 53| case SPTPersistentCacheLoadingErrorInvalidHeaderCRC: { 1908| 53| header.crc = header.crc + 5; 1909| 53| break; 1910| 0| } 1911| 53| case SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader: { 1912| 53| headerSize = kCorruptedFileSize; 1913| 53| break; 1914| 0| } 1915| 0| default: 1916| 0| NSAssert(NO, @"Gotcha!"); 1917| 0| break; 1918| 265| } 1919| 265| 1920| 265| off_t ret = lseek(fd, SEEK_SET, 0); 1921| 265| XCTAssert(ret != -1); 1922| 265| 1923| 265| ssize_t written = write(fd, &header, headerSize); 1924| 265| XCTAssert(written == (ssize_t)headerSize, @"header was not written"); 1925| 265| fsync(fd); 1926| 265| close(fd); 1927| 265|} 1928| | 1929| |- (void)alterUpdateTime:(uint64_t)updateTime forFileAtPath:(NSString *)filePath 1930| 0|{ 1931| 0| int fd = open([filePath UTF8String], O_RDWR); 1932| 0| if (fd == -1) { 1933| 0| XCTAssert(fd != -1, @"Could open file for altering"); 1934| 0| return; 1935| 0| } 1936| 0| 1937| 0| SPTPersistentCacheRecordHeader header; 1938| 0| memset(&header, 0, (size_t)SPTPersistentCacheRecordHeaderSize); 1939| 0| 1940| 0| ssize_t readSize = read(fd, &header, (size_t)SPTPersistentCacheRecordHeaderSize); 1941| 0| if (readSize != (ssize_t)SPTPersistentCacheRecordHeaderSize) { 1942| 0| close(fd); 1943| 0| return; 1944| 0| } 1945| 0| 1946| 0| header.updateTimeSec = updateTime; 1947| 0| header.crc = SPTPersistentCacheCalculateHeaderCRC(&header); 1948| 0| 1949| 0| off_t ret = lseek(fd, SEEK_SET, 0); 1950| 0| XCTAssert(ret != -1); 1951| 0| 1952| 0| ssize_t written = write(fd, &header, (size_t)SPTPersistentCacheRecordHeaderSize); 1953| 0| XCTAssert(written == (ssize_t)SPTPersistentCacheRecordHeaderSize, @"header was not written"); 1954| 0| fsync(fd); 1955| 0| close(fd); 1956| 0|} 1957| | 1958| |- (SPTPersistentCacheForUnitTests *)createCacheWithTimeCallback:(SPTPersistentCacheCurrentTimeSecCallback)currentTime 1959| | expirationTime:(NSTimeInterval)expirationTimeSec 1960| 76|{ 1961| 76| SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new]; 1962| 76| options.cachePath = self.cachePath; 1963| 76| options.cacheIdentifier = @"Test"; 1964| 76| options.defaultExpirationPeriod = (NSUInteger)expirationTimeSec; 1965| 221| options.debugOutput = ^(NSString *message) { 1966| 221| NSLog(@"%@", message); 1967| 221| }; 1968| 76| 1969| 76| SPTPersistentCacheForUnitTests *cache = [[SPTPersistentCacheForUnitTests alloc] initWithOptions:options]; 1970| 76| cache.timeIntervalCallback = currentTime; 1971| 76| 1972| 76| return cache; 1973| 76|} 1974| | 1975| |- (NSUInteger)getFilesNumberAtPath:(NSString *)path 1976| 4|{ 1977| 4| NSUInteger count = 0; 1978| 4| NSURL *urlPath = [NSURL fileURLWithPath:path]; 1979| 4| NSDirectoryEnumerator *dirEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:urlPath 1980| 4| includingPropertiesForKeys:@[NSURLIsDirectoryKey] 1981| 4| options:NSDirectoryEnumerationSkipsHiddenFiles 1982| 4| errorHandler:nil]; 1983| 4| 1984| 4| // Enumerate the dirEnumerator results, each value is stored in allURLs 1985| 77| for (NSURL *theURL in dirEnumerator) { 1986| 77| 1987| 77| // Retrieve the file name. From cached during the enumeration. 1988| 77| NSNumber *isDirectory; 1989| 77| if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]) { 1990| 77| if ([isDirectory boolValue] == NO) { 1991| 21| ++count; 1992| 21| } 1993| 77| } 1994| 77| } 1995| 4| 1996| 4| return count; 1997| 4|} 1998| | 1999| |- (void)checkUpdateTimeForFileAtPath:(NSString *)path validate:(BOOL)validate referenceTimeCheck:(void(^)(uint64_t updateTime))timeCheck 2000| 51|{ 2001| 51| XCTAssertNotNil(path, @"Path is nil"); 2002| 51| SPTPersistentCacheRecordHeader header; 2003| 51| if (validate) { 2004| 36| XCTAssertTrue(spt_test_ReadHeaderForFile(path.UTF8String, validate, &header), @"Unable to read and validate header"); 2005| 36| timeCheck(header.updateTimeSec); 2006| 36| } 2007| 51|} 2008| | 2009| |- (NSUInteger)dataSizeForItem:(NSString *)item 2010| 22|{ 2011| 22| NSString *fileName = [self.thisBundle pathForResource:item ofType:nil]; 2012| 22| NSData *data = [NSData dataWithContentsOfFile:fileName]; 2013| 22| XCTAssertNotNil(data, @"Data must be valid"); 2014| 22| return [data length]; 2015| 22|} 2016| | 2017| |- (NSUInteger)calculateExpectedSize 2018| 1|{ 2019| 1| NSUInteger expectedSize = 0; 2020| 1| 2021| 18| for (NSUInteger i = 0; i < self.imageNames.count; ++i) { 2022| 17| if (kParams[i].corruptReason == SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader) { 2023| 1| expectedSize += kCorruptedFileSize; 2024| 16| } else { 2025| 16| expectedSize += ([self dataSizeForItem:self.imageNames[i]] + (NSUInteger)SPTPersistentCacheRecordHeaderSize); 2026| 16| } 2027| 17| } 2028| 1| 2029| 1| return expectedSize; 2030| 1|} 2031| | 2032| |@end 2033| | 2034| |static BOOL spt_test_ReadHeaderForFile(const char* path, BOOL validate, SPTPersistentCacheRecordHeader *header) 2035| 60|{ 2036| 60| int fd = open(path, O_RDONLY); 2037| 60| if (fd == -1) { 2038| 8| return NO; 2039| 8| } 2040| 52| 2041| 52| assert(header != NULL); 2042| 52| memset(header, 0, (size_t)SPTPersistentCacheRecordHeaderSize); 2043| 52| 2044| 52| ssize_t readSize = read(fd, header, (size_t)SPTPersistentCacheRecordHeaderSize); 2045| 52| close(fd); 2046| 52| 2047| 52| if (readSize != (ssize_t)SPTPersistentCacheRecordHeaderSize) { 2048| 1| return NO; 2049| 1| } 2050| 51| 2051| 51| if (validate && SPTPersistentCacheValidateHeader(header) != -1) { 2052| 3| return NO; 2053| 3| } 2054| 48| 2055| 48| uint32_t crc = SPTPersistentCacheCalculateHeaderCRC(header); 2056| 48| return crc == header->crc; 2057| 48|} 2058| | 2059| |static NSUInteger params_GetFilesNumber(BOOL locked) 2060| 6|{ 2061| 6| NSUInteger c = 0; 2062| 108| for (NSUInteger i = 0; kParams[i].last != YES; ++i) { 2063| 102| if (kParams[i].corruptReason == -1) { 2064| 72| c += (kParams[i].locked == locked) ? 1 : 0; 2065| 72| } 2066| 102| } 2067| 6| return c; 2068| 6|} 2069| | 2070| |static NSUInteger params_GetCorruptedFilesNumber(void) 2071| 8|{ 2072| 8| NSUInteger c = 0; 2073| 144| for (NSUInteger i = 0; kParams[i].last != YES; ++i) { 2074| 136| if (kParams[i].corruptReason != -1) { 2075| 40| c += 1; 2076| 40| } 2077| 136| } 2078| 8| return c; 2079| 8|} 2080| | 2081| |static NSUInteger params_GetDefaultExpireFilesNumber(void) 2082| 1|{ 2083| 1| NSUInteger c = 0; 2084| 18| for (NSUInteger i = 0; kParams[i].last != YES; ++i) { 2085| 17| if (kParams[i].ttl == 0 && 2086| 17| kParams[i].corruptReason == -1 && 2087| 17| kParams[i].locked == NO) { 2088| 6| c += 1; 2089| 6| } 2090| 17| } 2091| 1| return c; 2092| 1|} <<<<<< EOF # path=fixes ./include/SPTPersistentCache/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./include/SPTPersistentCache/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./include/SPTPersistentCache/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./include/SPTPersistentCache/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./include/SPTPersistentCache/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./include/SPTPersistentCache/SPTPersistentCache.h:3,11,13,21 ./Tests/SPTPersistentCachePosixWrapperMock.h:3,11,13,22,27,57 ./Tests/SPTPersistentCacheGarbageCollectorTests.m:3,11,13,21,25,30,31,32,40,42,44,47,48,50,54,55,57,64,66,68,70,72,74,77,81,82,84,86,90,91,93,95,98,101,107,108,110,116,117,119,124,125,127,130,133,135,136,138,142,143,145,155,156 ./Tests/NSFileManagerMock.h:3,11,13,22,27,40,43,46,49 ./Tests/SPTPersistentCacheObjectDescriptionStyleValidator.m:3,11,13,22,24,26,28,30,32,36,41,42,44,45,47,51,52,56,58,59 ./Tests/SPTPersistentCacheHeaderTests.m:3,11,13,22,26,27,29,31,33,35,38,40,42,43,45,48,50,52,53,55,61,62,64,66,68,69,71,73,75,76,78,83,84,89,102,103 ./Tests/SPTPersistentCacheTests.m:3,11,13,22,31,35,41,45,66,69,76,84,103,106,110,112,114,117,122,125,127,129,131,132,134,136,137,139,141,142,144,146,147,149,154,155,156,158,161,162,164,165,172,178,189,191,195,198,199,204,205,206,214,215,216,217,220,222,227,229,231,240,241,243,248,249,250,251,253,258,260,261,267,271,276,278,280,283,285,287,293,301,306,309,310,313,315,316,318,327,329,330,333,334,343,348,350,356,362,364,370,373,378,381,384,385,388,390,391,396,401,403,409,414,417,419,427,428,431,433,434,443,446,448,450,453,460,461,462,469,471,477,479,481,488,489,492,494,497,500,506,514,519,522,523,526,527,529,532,533,541,544,549,555,556,560,562,564,573,575,576,578,580,584,585,593,596,599,605,609,611,614,616,620,623,625,626,627,629,631,635,636,637,646,651,659,663,666,670,673,679,692,695,696,699,700,702,704,707,712,713,722,725,727,734,735,742,745,747,750,756,765,769,771,774,775,778,780,782,785,790,791,798,801,803,807,808,815,818,820,822,824,826,831,832,833,836,837,839,843,845,846,848,850,852,853,855,859,861,862,864,866,868,870,872,873,878,882,884,891,895,898,905,914,919,922,923,926,927,929,932,939,940,942,946,948,955,959,962,969,978,983,986,987,990,991,993,995,1002,1003,1008,1011,1013,1015,1017,1021,1025,1026,1028,1037,1039,1040,1047,1051,1054,1061,1070,1075,1078,1079,1082,1083,1085,1087,1092,1093,1096,1100,1102,1104,1106,1108,1112,1115,1126,1127,1128,1133,1134,1136,1138,1141,1143,1147,1154,1155,1157,1161,1166,1167,1175,1177,1179,1183,1186,1191,1192,1197,1198,1201,1204,1205,1212,1216,1223,1228,1233,1239,1244,1249,1254,1259,1260,1262,1266,1275,1277,1278,1280,1283,1284,1286,1289,1290,1292,1296,1301,1305,1308,1309,1311,1321,1322,1324,1333,1334,1336,1340,1352,1354,1355,1357,1361,1373,1375,1376,1378,1382,1389,1392,1393,1394,1396,1398,1401,1403,1404,1406,1409,1410,1412,1422,1423,1425,1440,1441,1443,1446,1447,1449,1464,1465,1467,1482,1483,1485,1499,1500,1502,1512,1513,1515,1526,1527,1529,1542,1543,1545,1558,1559,1561,1575,1576,1578,1588,1591,1598,1599,1600,1602,1609,1610,1612,1626,1627,1629,1632,1633,1635,1653,1654,1656,1674,1675,1677,1692,1693,1695,1702,1703,1705,1707,1711,1715,1717,1718,1720,1724,1731,1737,1739,1740,1742,1746,1751,1753,1754,1756,1760,1764,1771,1774,1779,1781,1782,1784,1786,1788,1794,1796,1798,1799,1801,1803,1805,1809,1817,1819,1821,1822,1824,1831,1844,1847,1852,1853,1854,1862,1865,1869,1870,1875,1876,1879,1881,1887,1888,1889,1891,1896,1901,1906,1910,1914,1918,1919,1922,1927,1928,1930,1935,1936,1939,1944,1945,1948,1951,1956,1957,1960,1968,1971,1973,1974,1976,1983,1986,1992,1993,1994,1995,1997,1998,2000,2006,2007,2008,2010,2015,2016,2018,2020,2026,2027,2028,2030,2031,2033,2035,2039,2040,2043,2046,2049,2050,2053,2054,2057,2058,2060,2065,2066,2068,2069,2071,2076,2077,2079,2080,2082,2089,2090,2092 ./Tests/SPTPersistentCachePerformanceTests.m:3,11,13,21,24,27,29,34,39,41,43,45,47,49,51,53,54,56,58,59,65,67,69,71,73,93,95,98,100,107,109,110,111,117,118,141,144,150,155,162,165,166,168,172,173,175,176,178,188,193,201,202,210,211,213,219,224,225,226,228,231,233,235,236 ./Tests/SPTPersistentCacheFileManagerTests.m:3,11,13,22,25,27,29,31,34,36,38,40,42,43,45,47,48,50,51,53,59,61,63,65,69,74,76,77,79,81,83,84,86,89,92,96,97,99,105,108,109,111,113,115,117,120,121,123,125,127,129,132,133,135,137,139,141,143,146,147,149,151,153,155,157,159,160,162,164,166,168,169,171,173,175,177,178,180,183,186,188,191,194,197,198,200,205,209,212,213,215,229,230,232,237,241,244,245,247,249,251,253,255,260,263,265,266,268,270,272,274,279,282,284,285 ./Tests/NSError+SPTPersistentCacheDomainErrorsTests.m:3,11,13,22,24,26,28,30,31,33,35,37,40,41,42 ./Tests/SPTPersistentCachePosixWrapperMock.m:3,11,13,22,24,26,28,29,31,34,36,37,39,41,42,44,46,47,49,51,52,54,56,57 ./Tests/SPTPersistentCacheObjectDescriptionStyleValidator.h:3,11,13,22,25,32,35,37,41 ./Tests/NSFileManagerMock.m:3,11,13,22,24,26,31,33,34,36,39,41,42,44,47,49,50,52,55,57,58,60,63,65,66 ./Tests/SPTPersistentCacheObjectDescriptionTests.m:3,11,13,22,25,27,29,31,33,35,37,40,41,43,45,47,48,50,53,55,57,58,60,62,64,66,70,73,74,76,78,80,82,86,89,92,94,95,97,99,102,104,108,111,112,114,116,119,121,125,128,129,131,133,135,137,141,144,147,148,150,152,154,156,160,163,166,167,169,171,173,175,179,182,185,186,188,190,192,194,198,201,204,207,208,210,212,215,217,218,220,222,224,227,230,231,233,235,237,240,242,243 ./Tests/SPTPersistentCacheRecordTests.m:3,11,13,21,26,27,32,36,38,40,42,44,49,50,52,57,58,60,62,64,66,68,69,71,73,76,77,79,81,83,85,86,88,90,93,94 ./Tests/SPTPersistentCacheOptionsTests.m:3,11,13,22,25,27,31,33,35,37,39,40,42,49,50,52,61,64,65,67,69,77,80,83,84,86,95,98,99,101,103,111,114,117,118,120,122,133,136,138,140,142,146,151,152,154,156,159,161,163,167,169,170,172,175,178,180,182,183,185,188,191,193,195,196,198,200,202,204,206,207,209,211,214,215,217,219,221,223,224,226,228,231,232 ./Tests/SPTPersistentCacheResponseTests.m:3,11,13,21,28,29,31,37,39,41,43,47,49,53,54,56,60,61,63,65,67,69,71,72,74,76,79,80,82,84,87,88,90,92,94,102,103,104,106,110,111,112,113 ./Tests/SPTPersistentCacheDebugUtilitiesTests.m:3,11,13,21,24,25,28,30,32,36,37,39,41,46,50,51 ./Viewer/MainWindowController.h:3,11,13,22,24 ./Viewer/AppDelegate.h:3,11,13,22,24,25,27 ./Viewer/MainWindowController.m:3,11,13,23,26,36,41,44,46,48,51,52,55,58,63,64,65,67,68,69,72,76,77,86,90,91,93,97,98,100,102,103,105,108,110,111,113,122,123,126,127,130,132,133,135,137,138,140,145,146,149,151,155,156,160,161,163,174,177,179,182,183 ./Viewer/main.m:3,11,13,22,25 ./Viewer/AppDelegate.m:3,11,13,23,25,29,31,37,38,40,42,43,46,47 ./Sources/SPTPersistentCacheGarbageCollector.h:3,11,13,22,25,27,32,37,42,45,53,56,62,65,70 ./Sources/SPTPersistentCacheTypeUtilities.h:3,11,13,21,23,26 ./Sources/SPTPersistentCacheHeader.m:3,11,13,22,24,26,29,34,36,40,41,46,47,51,59,61,62,64,67,68,70,71,73,76,77,81,82,86,87,92,93,97,98,100,101,103,107,108,110,111,113,116,117,119,120,121 ./Sources/SPTPersistentCacheResponse+Private.h:3,11,13,22,24,26,30 ./Sources/SPTPersistentCacheRecord+Private.h:3,11,13,22,24,29 ./Sources/SPTPersistentCacheFileManager+Private.h:3,11,13,23,25,28,32,34 ./Sources/SPTPersistentCache+Private.h:3,11,13,21,25,29,31,33,36,38,40,43,46,48,51,54,60,64,69,71,73,75 ./Sources/crc32iso3309.h:3,11,13,23,25,29,32,34,36 ./Sources/SPTPersistentCachePosixWrapper.h:3,11,13,22,24,29,67 ./Sources/SPTPersistentCacheOptions.m:3,11,13,24,26,30,33,34,36,38,39,41,43,45,47,49,54,67,68,70,71,73,75,81,82,84,86,89,94,95,97,100,105,106,108,110,112,116,120,123,133,135,136,138,140,142,143,145,154,155,157,158,160,166,168,172,175,177,178,180,181,183,185,186,188,190,191,193,195,196,198,200,201,203,204,206,209,210,213 ./Sources/SPTPersistentCacheResponse.m:3,11,13,25,27,31,33,35,39,45,47,48,50,52,57,58,59,61,63,64,66,71,72 ./Sources/NSError+SPTPersistentCacheDomainErrors.h:3,11,13,22,24,29,35 ./Sources/SPTPersistentCacheFileManager.m:3,11,13,24,26,28,30,32,34,40,42,43,45,47,49,56,57,66,68,69,70,72,73,78,81,85,86,88,89,91,93,95,96,98,100,105,114,117,118,119,120,121,123,125,127,130,131,132,134,139,141,142,144,151,155,161,164,167,168,169,171,172,174,176,178,185,190,192,195,196,198,199 ./Sources/SPTPersistentCache.m:3,11,13,22,27,36,39,41,44,47,51,53,55,61,62,63,65,67,69,71,73,74,76,83,92,93,96,97,99,100,104,107,108,117,118,123,126,132,136,149,151,152,157,159,161,166,172,174,175,182,183,185,192,193,197,199,200,206,207,209,210,217,218,221,222,231,232,233,238,241,242,247,249,256,260,261,264,270,271,276,279,280,282,285,286,287,291,295,304,307,308,309,313,316,329,332,340,345,346,347,351,352,356,359,371,372,379,380,384,385,387,389,390,392,394,395,398,410,413,414,417,429,432,433,434,437,449,452,453,455,457,458,460,467,471,477,488,489,492,493,494,496,497,499,501,502,509,511,530,539,540,543,552,553,555,566,567,575,576,580,581,586,595,604,605,606,611,612,613,614,615,625,627,630,633,635,640,643,645,651,656,660,661,662,664,665,675,679,681,685,697,698,700,712,713,715,716,717,725,727,740,741,744,748,749,756,757,759,761,764,770,771,784,799,812,813,814,815,816,821,822,827,832,835,836,838,839,844,846,847,849,851,852,854,856,862,866,871,895,900,901,904,905,906,907,911,914,915,922,923,928,931,932,939,940,942,944,950,952,953,954,956,959,960,963,968,969,971,976,984,985,987,989,990,992,994,1002,1005,1009,1013,1017,1020,1027,1030,1031,1034,1040,1041,1049,1052,1055,1056,1057,1064,1066,1068,1069,1071,1073,1074,1076,1081,1082,1084,1089,1090,1091 ./Sources/SPTPersistentCacheRecord.m:3,11,13,23,25,27,32,39,41,42,44,46,48,49,51,53,54 ./Sources/SPTPersistentCacheObjectDescription.h:3,11,13,22,25,29,32,35,39,43,46,49 ./Sources/SPTPersistentCacheDebugUtilities.m:3,11,13,21,23,26,29,30 ./Sources/SPTPersistentCacheTypeUtilities.m:3,11,13,21,23,24,26,28 ./Sources/SPTPersistentCacheGarbageCollector.m:3,11,13,24,26,28,33,34,36,38,42,48,50,51,53,56,60,61,63,65,73,75,82,83,85,90,92,93,96,99,100,106,108,110,111,113,118,120,121,124,126,128,129,131,133,135,137,141,142 ./Sources/SPTPersistentCachePosixWrapper.m:3,11,13,22,24,26,28,29,31,33,34,36,38,39,41,43,44,46,48,49,51,53,54 ./Sources/crc32iso3309.c:3,11,13,22,26,94,100,102,105,109,111,114,116,117,120,122 ./Sources/SPTPersistentCacheFileManager.h:3,11,13,22,24,26,28,30,35,38,41,44,48,54,57,61,64,68,73,76,80,83,87,90,94,96 ./Sources/NSError+SPTPersistentCacheDomainErrors.m:3,11,13,22,24,26,30,31 ./Sources/SPTPersistentCacheDebugUtilities.h:3,11,13,21,24,27 ./Sources/SPTPersistentCacheObjectDescription.m:3,11,13,22,24,26,28,29,31,34,37,41,42,46,47,49,50,51,52,54,57,58,61,66,69,70,75,77 ./build/DerivedData/common/Build/Intermediates.noindex/SPTPersistentCacheFramework.build/Release-watchos/SPTPersistentCache-Watch.build/DerivedSources/SPTPersistentCache_vers.c:3 ./build/DerivedData/common/Build/Intermediates.noindex/SPTPersistentCacheFramework.build/Release-appletvos/SPTPersistentCache-TV.build/DerivedSources/SPTPersistentCache_vers.c:3 ./build/DerivedData/common/Build/Intermediates.noindex/SPTPersistentCacheFramework.build/Release/SPTPersistentCache-OSX.build/DerivedSources/SPTPersistentCache_vers.c:3 ./build/DerivedData/common/Build/Intermediates.noindex/SPTPersistentCacheFramework.build/Release-iphonesimulator/SPTPersistentCache-iOS.build/DerivedSources/SPTPersistentCache_vers.c:3 ./build/DerivedData/common/Build/Intermediates.noindex/SPTPersistentCacheFramework.build/Release-watchsimulator/SPTPersistentCache-Watch.build/DerivedSources/SPTPersistentCache_vers.c:3 ./build/DerivedData/common/Build/Intermediates.noindex/SPTPersistentCacheFramework.build/Release-iphoneos/SPTPersistentCache-iOS.build/DerivedSources/SPTPersistentCache_vers.c:3 ./build/DerivedData/common/Build/Intermediates.noindex/SPTPersistentCacheFramework.build/Release-appletvsimulator/SPTPersistentCache-TV.build/DerivedSources/SPTPersistentCache_vers.c:3 ./build/DerivedData/common/Build/Products/Release-watchos/SPTPersistentCache.framework/Headers/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./build/DerivedData/common/Build/Products/Release-watchos/SPTPersistentCache.framework/Headers/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./build/DerivedData/common/Build/Products/Release-watchos/SPTPersistentCache.framework/Headers/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./build/DerivedData/common/Build/Products/Release-watchos/SPTPersistentCache.framework/Headers/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./build/DerivedData/common/Build/Products/Release-watchos/SPTPersistentCache.framework/Headers/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./build/DerivedData/common/Build/Products/Release-watchos/SPTPersistentCache.framework/Headers/SPTPersistentCache.h:3,11,13,21 ./build/DerivedData/common/Build/Products/Release-appletvos/SPTPersistentCache.framework/Headers/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./build/DerivedData/common/Build/Products/Release-appletvos/SPTPersistentCache.framework/Headers/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./build/DerivedData/common/Build/Products/Release-appletvos/SPTPersistentCache.framework/Headers/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./build/DerivedData/common/Build/Products/Release-appletvos/SPTPersistentCache.framework/Headers/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./build/DerivedData/common/Build/Products/Release-appletvos/SPTPersistentCache.framework/Headers/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./build/DerivedData/common/Build/Products/Release-appletvos/SPTPersistentCache.framework/Headers/SPTPersistentCache.h:3,11,13,21 ./build/DerivedData/common/Build/Products/Release/SPTPersistentCache.framework/Versions/A/Headers/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./build/DerivedData/common/Build/Products/Release/SPTPersistentCache.framework/Versions/A/Headers/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./build/DerivedData/common/Build/Products/Release/SPTPersistentCache.framework/Versions/A/Headers/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./build/DerivedData/common/Build/Products/Release/SPTPersistentCache.framework/Versions/A/Headers/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./build/DerivedData/common/Build/Products/Release/SPTPersistentCache.framework/Versions/A/Headers/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./build/DerivedData/common/Build/Products/Release/SPTPersistentCache.framework/Versions/A/Headers/SPTPersistentCache.h:3,11,13,21 ./build/DerivedData/common/Build/Products/Release-iphonesimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./build/DerivedData/common/Build/Products/Release-iphonesimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./build/DerivedData/common/Build/Products/Release-iphonesimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./build/DerivedData/common/Build/Products/Release-iphonesimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./build/DerivedData/common/Build/Products/Release-iphonesimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./build/DerivedData/common/Build/Products/Release-iphonesimulator/SPTPersistentCache.framework/Headers/SPTPersistentCache.h:3,11,13,21 ./build/DerivedData/common/Build/Products/Release-watchsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./build/DerivedData/common/Build/Products/Release-watchsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./build/DerivedData/common/Build/Products/Release-watchsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./build/DerivedData/common/Build/Products/Release-watchsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./build/DerivedData/common/Build/Products/Release-watchsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./build/DerivedData/common/Build/Products/Release-watchsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCache.h:3,11,13,21 ./build/DerivedData/common/Build/Products/Release-iphoneos/SPTPersistentCache.framework/Headers/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./build/DerivedData/common/Build/Products/Release-iphoneos/SPTPersistentCache.framework/Headers/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./build/DerivedData/common/Build/Products/Release-iphoneos/SPTPersistentCache.framework/Headers/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./build/DerivedData/common/Build/Products/Release-iphoneos/SPTPersistentCache.framework/Headers/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./build/DerivedData/common/Build/Products/Release-iphoneos/SPTPersistentCache.framework/Headers/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./build/DerivedData/common/Build/Products/Release-iphoneos/SPTPersistentCache.framework/Headers/SPTPersistentCache.h:3,11,13,21 ./build/DerivedData/common/Build/Products/Release-appletvsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheImplementation.h:3,11,13,22,25,27,29,68,73,74,76,85,86,88,103,109,250,252 ./build/DerivedData/common/Build/Products/Release-appletvsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheHeader.h:3,11,13,22,27,39,60,69,71 ./build/DerivedData/common/Build/Products/Release-appletvsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheOptions.h:3,11,13,22,24,26,32,41,52,61,62,64,73,76,85,86,88,93,95,101,103,116,124,126,143,156,158,188,190,197,204,206,207,209,214,230,248,250 ./build/DerivedData/common/Build/Products/Release-appletvsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheResponse.h:3,11,13,22,24,26,48,54,69,71 ./build/DerivedData/common/Build/Products/Release-appletvsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCacheRecord.h:3,11,13,22,24,33,51,53 ./build/DerivedData/common/Build/Products/Release-appletvsimulator/SPTPersistentCache.framework/Headers/SPTPersistentCache.h:3,11,13,21 ./SPTPersistentCacheDemo/SPTPersistentCacheDemo/AppDelegate.h:3,11,13,22,24,26,27,29 ./SPTPersistentCacheDemo/SPTPersistentCacheDemo/DetailViewController.m:3,11,13,22,26,28,30,32,34,36,39,42,43,44,46,54,57,59,60,61,63,65,69,70,72,75,76 ./SPTPersistentCacheDemo/SPTPersistentCacheDemo/MasterViewController.h:3,11,13,22,24,26,28,29,31 ./SPTPersistentCacheDemo/SPTPersistentCacheDemo/main.m:3,11,13,23,27,28 ./SPTPersistentCacheDemo/SPTPersistentCacheDemo/AppDelegate.m:3,11,13,23,25,27,29,30,38,39,43,44,48,49,52,53,56,57,60,61,63,70,71,72 ./SPTPersistentCacheDemo/SPTPersistentCacheDemo/MasterViewController.m:3,11,13,22,24,26,28,31,33,35,37,39,46,56,59,65,66,68,71,72,74,77,78,80,82,86,90,99,101,105,107,108,110,112,121,122,123,125,127,129,130,132,134,135,137,139,143,144,146,149,150,154,157,163,164,165,167,169,173,175,191,192 ./SPTPersistentCacheDemo/SPTPersistentCacheDemo/DetailViewController.h:3,11,13,22,24,26,30,32 <<<<<< EOF