TwitterImagePipelineTests/TIPImagePipelineTests.m (630 lines of code) (raw):

// // TIPImagePipelineTests.m // TwitterImagePipeline // // Created on 4/27/15. // Copyright (c) 2015 Twitter. All rights reserved. // #import "TIPGlobalConfiguration+Project.h" #import "TIPImageDiskCache.h" #import "TIPImageMemoryCache.h" #import "TIPImagePipeline+Project.h" #import "TIPImageRenderedCache.h" #import "TIPTests.h" #import "TIPTestsSharedUtils.h" @interface TestImageStoreRequest : NSObject <TIPImageStoreRequest> @property (nonatomic) NSURL *imageURL; @property (nonatomic, copy) NSString *imageFilePath; @end @implementation TestImageStoreRequest @end @interface TIPImagePipelineTests_Base : TIPImagePipelineBaseTests - (void)runFillingTheCaches:(TIPImagePipeline *)pipeline bps:(uint64_t)bps testCacheHits:(BOOL)testCacheHits; @end @interface TIPImagePipelineTests_One : TIPImagePipelineTests_Base @end @interface TIPImagePipelineTests_Two : TIPImagePipelineTests_Base @end @interface TIPImagePipelineTests_Three : TIPImagePipelineTests_Base @end @implementation TIPImagePipelineTests_Base - (void)runFillingTheCaches:(TIPImagePipeline *)pipeline bps:(uint64_t)bps testCacheHits:(BOOL)testCacheHits { id<TIPImageFetchDownloadProviderWithStubbingSupport> provider = (id<TIPImageFetchDownloadProviderWithStubbingSupport>)[TIPGlobalConfiguration sharedInstance].imageFetchDownloadProvider; NSMutableArray *URLs = [NSMutableArray array]; for (NSUInteger i = 0; i < 10; i++) { [URLs addObject:[TIPImagePipelineBaseTests dummyURLWithPath:[NSUUID UUID].UUIDString]]; } // First pass, load em up // Second pass (if testCacheHits), reload since older version will have been purged by full cache const NSUInteger numberOfRuns = (testCacheHits) ? 2 : 1; for (NSUInteger i = 0; i < numberOfRuns; i++) { for (NSURL *URL in URLs) { @autoreleasepool { TIPImagePipelineTestFetchRequest *request = [[TIPImagePipelineTestFetchRequest alloc] init]; request.imageType = TIPImageTypeJPEG; request.progressiveSource = YES; request.imageURL = URL; request.targetDimensions = kCarnivalImageDimensions; request.targetContentMode = UIViewContentModeScaleToFill; TIPImagePipelineTestContext *context = [[TIPImagePipelineTestContext alloc] init]; [TIPImagePipelineTestFetchRequest stubRequest:request bitrate:bps resumable:YES]; TIPImageFetchOperation *op = [pipeline undeprecatedFetchImageWithRequest:request context:context delegate:self]; [op waitUntilFinishedWithoutBlockingRunLoop]; [provider removeDownloadStubForRequestURL:request.imageURL]; XCTAssertEqual(op.state, TIPImageFetchOperationStateSucceeded); XCTAssertEqual(context.finalSource, TIPImageLoadSourceNetwork); } } } // visit in reverse order NSUInteger memMatches = 0; NSUInteger diskMatches = 0; for (NSURL *URL in URLs.reverseObjectEnumerator) { TIPImageLoadSource source = TIPImageLoadSourceUnknown; @autoreleasepool { TIPImagePipelineTestFetchRequest *request = [[TIPImagePipelineTestFetchRequest alloc] init]; request.imageType = TIPImageTypeJPEG; request.progressiveSource = YES; request.imageURL = URL; request.targetDimensions = kCarnivalImageDimensions; request.targetContentMode = UIViewContentModeScaleToFill; TIPImagePipelineTestContext *context = [[TIPImagePipelineTestContext alloc] init]; [TIPImagePipelineTestFetchRequest stubRequest:request bitrate:bps resumable:YES]; TIPImageFetchOperation *op = [pipeline undeprecatedFetchImageWithRequest:request context:context delegate:self]; [op waitUntilFinishedWithoutBlockingRunLoop]; [provider removeDownloadStubForRequestURL:request.imageURL]; XCTAssertEqual(op.state, TIPImageFetchOperationStateSucceeded); source = op.finalResult.imageSource; if (source == TIPImageLoadSourceMemoryCache) { memMatches++; } else if (source == TIPImageLoadSourceDiskCache) { diskMatches++; } else { break; } } } if (testCacheHits) { XCTAssertGreaterThan(memMatches, (NSUInteger)0); XCTAssertGreaterThan(diskMatches, (NSUInteger)0); } } - (void)checkFileAttributes:(TIPImagePipeline *)pipeline { // Check the file attributes XCTestExpectation *expectation = [self expectationWithDescription:@"wait for inspection to complete expectation"]; __block NSDictionary<NSString *, id> *attributes = nil; __block NSArray<NSString *> *attributeNames = nil; NSDictionary<NSString *, Class> *attributeKeyKindMap = @{ @"ANI" : [NSNumber class] /*BOOL*/, @"LAD" : [NSDate class], @"LMD" : [NSString class], @"TTL" : [NSNumber class], @"URL" : [NSURL class], @"clen" : [NSNumber class], @"dX" : [NSNumber class], @"dY" : [NSNumber class], @"uTTL" : [NSNumber class] /*BOOL*/, // @"pl" : [NSNumber class] /*BOOL*/, }; NSArray<NSString *> *expectedAttributeNames = [attributeKeyKindMap.allKeys sortedArrayUsingSelector:@selector(compare:)]; [pipeline inspect:^(TIPImagePipelineInspectionResult * _Nullable result) { if (!result.completeDiskEntries.count) { [expectation fulfill]; return; } [pipeline copyDiskCacheFileWithIdentifier:result.completeDiskEntries.firstObject.identifier completion:^(NSString * _Nullable temporaryFilePath, NSError * _Nullable error) { XCTAssertNil(error); XCTAssertNotNil(temporaryFilePath); attributeNames = [TIPListXAttributesForFile(temporaryFilePath) sortedArrayUsingSelector:@selector(compare:)]; attributes = TIPGetXAttributesForFile(temporaryFilePath, attributeKeyKindMap); // Fulfill async after 1 second. // If anything allocated in the attributes lookups deallocs we // want to fail rather than have a race condition that might succeed. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [expectation fulfill]; }); }]; }]; [self waitForExpectations:@[expectation] timeout:10]; XCTAssertNotNil(attributeNames); XCTAssertEqualObjects(attributeNames, expectedAttributeNames); NSArray<NSString *> *parsedAttributeNames = [attributes.allKeys sortedArrayUsingSelector:@selector(compare:)]; XCTAssertNotNil(parsedAttributeNames); XCTAssertEqualObjects(parsedAttributeNames, expectedAttributeNames); } @end @implementation TIPImagePipelineTests_One - (void)testImagePipelineConstruction { NSString *identifier = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz_0123456789-"; TIPImagePipeline *pipeline = nil; @autoreleasepool { pipeline = [[TIPImagePipeline alloc] initWithIdentifier:identifier]; XCTAssertNotNil(pipeline); pipeline = nil; } @autoreleasepool { pipeline = [[TIPImagePipeline alloc] initWithIdentifier:identifier]; XCTAssertNotNil(pipeline); } @autoreleasepool { TIPImagePipeline *pipeline2 = [[TIPImagePipeline alloc] initWithIdentifier:identifier]; XCTAssertNil(pipeline2); pipeline = nil; } @autoreleasepool { pipeline = [[TIPImagePipeline alloc] initWithIdentifier:[identifier stringByReplacingOccurrencesOfString:@"." withString:@" "]]; XCTAssertNil(pipeline); pipeline = nil; } @autoreleasepool { pipeline = [[TIPImagePipeline alloc] initWithIdentifier:[TIPImagePipelineBaseTests sharedPipeline].identifier]; XCTAssertNil(pipeline); pipeline = nil; } } - (void)testConcurrentManifestLoad { NSArray *(^buildComparablesFromPipeline)(TIPImagePipeline *) = ^(TIPImagePipeline *pipeline) { __block NSArray *comparables = nil; XCTestExpectation *builtComparables = [self expectationWithDescription:@"built comparables"]; [pipeline inspect:^(TIPImagePipelineInspectionResult * _Nullable result) { NSArray *entries = [result.completeDiskEntries arrayByAddingObjectsFromArray:result.partialDiskEntries]; NSMutableArray *comparablesMutable = [NSMutableArray arrayWithCapacity:entries.count]; for (id<TIPImagePipelineInspectionResultEntry> entry in entries) { [comparablesMutable addObject:@[[entry identifier], [entry URL], [NSValue valueWithCGSize:[entry dimensions]], @([entry bytesUsed]), @([entry progress])]]; }; comparables = comparablesMutable; [builtComparables fulfill]; }]; [self waitForExpectationsWithTimeout:20 handler:nil]; builtComparables = nil; return comparables; }; NSString *identifier = @"concurrentManifestLoadTest"; [self _safelyOpenPipelineWithIdentifier:identifier executingBlock:^(TIPImagePipeline *initialPipeline) { TIPImagePipelineTestFetchRequest *request = [[TIPImagePipelineTestFetchRequest alloc] init]; request.cannedImageFilePath = [TIPTestsResourceBundle() pathForResource:@"twitterfied" ofType:@"pjpg"]; id<TIPImageFetchDownloadProviderWithStubbingSupport> provider = (id<TIPImageFetchDownloadProviderWithStubbingSupport>)[TIPGlobalConfiguration sharedInstance].imageFetchDownloadProvider; NSMutableArray<NSURL *> *stubbedRequestURLs = [NSMutableArray array]; NSOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{}]; for (NSUInteger i = 0; i < 150; i++) { request.imageURL = [TIPImagePipelineBaseTests dummyURLWithPath:[NSUUID UUID].UUIDString]; [TIPImagePipelineTestFetchRequest stubRequest:request bitrate:UINT64_MAX resumable:YES]; [stubbedRequestURLs addObject:request.imageURL]; TIPImageFetchOperation *op = [initialPipeline undeprecatedFetchImageWithRequest:request context:nil delegate:nil]; [blockOp addDependency:op]; } NSOperationQueue *opQ = [[NSOperationQueue alloc] init]; opQ.maxConcurrentOperationCount = 1; [opQ addOperation:blockOp]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; while (!blockOp.isFinished) { [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.250]]; } for (NSURL *URL in stubbedRequestURLs) { [provider removeDownloadStubForRequestURL:URL]; } }]; __block NSArray *concurrentComparables1 = nil; [self _safelyOpenPipelineWithIdentifier:identifier executingBlock:^(TIPImagePipeline *concurrentlyLoadedPipeline) { concurrentComparables1 = buildComparablesFromPipeline(concurrentlyLoadedPipeline); }]; XCTAssertNotNil(concurrentComparables1); __block NSArray *concurrentComparables2 = nil; [self _safelyOpenPipelineWithIdentifier:identifier executingBlock:^(TIPImagePipeline *concurrentlyLoadedPipeline) { concurrentComparables2 = buildComparablesFromPipeline(concurrentlyLoadedPipeline); }]; XCTAssertNotNil(concurrentComparables2); XCTAssertEqualObjects([NSSet setWithArray:concurrentComparables1], [NSSet setWithArray:concurrentComparables2]); TIPImagePipeline *pipeline = [[TIPImagePipeline alloc] initWithIdentifier:identifier]; [pipeline clearDiskCache]; [pipeline inspect:^(TIPImagePipelineInspectionResult * _Nullable result) {}]; XCTestExpectation *expectation = [self expectationForNotification:TIPImagePipelineDidTearDownImagePipelineNotification object:nil handler:^BOOL(NSNotification *note) { return [identifier isEqualToString:note.userInfo[TIPImagePipelineImagePipelineIdentifierNotificationKey]]; }]; pipeline = nil; [self waitForExpectationsWithTimeout:20 handler:NULL]; expectation = nil; } - (void)_safelyOpenPipelineWithIdentifier:(NSString *)identifier executingBlock:(void (^)(TIPImagePipeline *pipeline))executingBlock { @autoreleasepool { TIPImagePipeline *pipeline = [[TIPImagePipeline alloc] initWithIdentifier:identifier]; XCTAssertNotNil(pipeline); executingBlock(pipeline); [self expectationForNotification:TIPImagePipelineDidTearDownImagePipelineNotification object:nil handler:^BOOL(NSNotification *note) { return [identifier isEqualToString:note.userInfo[TIPImagePipelineImagePipelineIdentifierNotificationKey]]; }]; [pipeline inspect:^(TIPImagePipelineInspectionResult * _Nullable result) {}]; pipeline = nil; } [self waitForExpectationsWithTimeout:20 handler:nil]; } - (void)testMergingFetches { TIPImagePipelineTestFetchRequest *request = [[TIPImagePipelineTestFetchRequest alloc] init]; request.imageType = TIPImageTypeJPEG; request.progressiveSource = YES; request.imageURL = [TIPImagePipelineBaseTests dummyURLWithPath:[NSUUID UUID].UUIDString]; request.targetDimensions = kCarnivalImageDimensions; request.targetContentMode = UIViewContentModeScaleAspectFit; [TIPImagePipelineTestFetchRequest stubRequest:request bitrate:2 * kMegaBits resumable:YES]; TIPImageFetchOperation *op1 = nil; TIPImageFetchOperation *op2 = nil; TIPImagePipelineTestContext *context1 = nil; TIPImagePipelineTestContext *context2 = nil; [[TIPImagePipelineBaseTests sharedPipeline] clearMemoryCaches]; [[TIPImagePipelineBaseTests sharedPipeline] clearDiskCache]; context1 = [[TIPImagePipelineTestContext alloc] init]; context2 = [[TIPImagePipelineTestContext alloc] init]; context1.otherContext = context2; op1 = [[TIPImagePipelineBaseTests sharedPipeline] undeprecatedFetchImageWithRequest:request context:context1 delegate:self]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; op2 = [[TIPImagePipelineBaseTests sharedPipeline] undeprecatedFetchImageWithRequest:request context:context2 delegate:self]; [op1 waitUntilFinishedWithoutBlockingRunLoop]; [op2 waitUntilFinishedWithoutBlockingRunLoop]; XCTAssertEqual(context1.didStart, YES); XCTAssertNotNil(context1.finalImageContainer); XCTAssertNil(context1.finalError); XCTAssertEqual(context1.finalSource, TIPImageLoadSourceNetwork); XCTAssertEqualObjects(context1.finalImageContainer, op1.finalResult.imageContainer); XCTAssertEqual(context1.finalSource, op1.finalResult.imageSource); XCTAssertEqual(op1.state, TIPImageFetchOperationStateSucceeded); XCTAssertNotNil(context1.associatedDownloadContext); XCTAssertEqual(context2.didStart, YES); XCTAssertNotNil(context2.finalImageContainer); XCTAssertNil(context2.finalError); XCTAssertEqual(context2.finalSource, TIPImageLoadSourceNetwork); XCTAssertEqualObjects(context2.finalImageContainer, op2.finalResult.imageContainer); XCTAssertEqual(context2.finalSource, op2.finalResult.imageSource); XCTAssertEqual(op2.state, TIPImageFetchOperationStateSucceeded); XCTAssertNotNil(context2.associatedDownloadContext); XCTAssertEqual((__bridge void *)context1.associatedDownloadContext, (__bridge void *)context2.associatedDownloadContext); // Cancel original [[TIPImagePipelineBaseTests sharedPipeline] clearMemoryCaches]; [[TIPImagePipelineBaseTests sharedPipeline] clearDiskCache]; context1 = [[TIPImagePipelineTestContext alloc] init]; context1.shouldCancelOnOtherContextFirstProgress = YES; context2 = [[TIPImagePipelineTestContext alloc] init]; context1.otherContext = context2; op1 = [[TIPImagePipelineBaseTests sharedPipeline] undeprecatedFetchImageWithRequest:request context:context1 delegate:self]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; op2 = [[TIPImagePipelineBaseTests sharedPipeline] undeprecatedFetchImageWithRequest:request context:context2 delegate:self]; [op1 waitUntilFinishedWithoutBlockingRunLoop]; [op2 waitUntilFinishedWithoutBlockingRunLoop]; XCTAssertEqual(context1.didStart, YES); XCTAssertNil(context1.finalImageContainer); XCTAssertNotNil(context1.finalError); XCTAssertEqual(op1.state, TIPImageFetchOperationStateCancelled); XCTAssertNotNil(context1.associatedDownloadContext); XCTAssertEqual(context2.didStart, YES); XCTAssertNotNil(context2.finalImageContainer); XCTAssertNil(context2.finalError); XCTAssertEqual(context2.finalSource, TIPImageLoadSourceNetwork); XCTAssertEqualObjects(context2.finalImageContainer, op2.finalResult.imageContainer); XCTAssertEqual(context2.finalSource, op2.finalResult.imageSource); XCTAssertEqual(op2.state, TIPImageFetchOperationStateSucceeded); XCTAssertNotNil(context2.associatedDownloadContext); XCTAssertEqual((__bridge void *)context1.associatedDownloadContext, (__bridge void *)context2.associatedDownloadContext); } - (void)testCopyingDiskEntry { [[TIPImagePipelineBaseTests sharedPipeline] clearDiskCache]; [[TIPImagePipelineBaseTests sharedPipeline] clearMemoryCaches]; NSString *copyFinishedNotificationName = @"copy_finished"; TIPImagePipelineTestFetchRequest *request = [[TIPImagePipelineTestFetchRequest alloc] init]; request.imageURL = [TIPImagePipelineBaseTests dummyURLWithPath:[NSUUID UUID].UUIDString]; request.imageType = TIPImageTypeJPEG; request.progressiveSource = NO; __block NSString *tempFile = nil; __block NSError *copyError = nil; XCTestExpectation *finisedCopyExpectation = nil; TIPImagePipelineCopyFileCompletionBlock completion = ^(NSString *temporaryFilePath, NSError *error) { tempFile = temporaryFilePath; copyError = error; NSTimeInterval delay = (tempFile != nil) ? 0.5 : 0.1; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:copyFinishedNotificationName object:request]; }); }; [TIPImagePipelineTestFetchRequest stubRequest:request bitrate:1024 * kMegaBits resumable:YES]; // Attempt with empty caches tempFile = nil; copyError = nil; finisedCopyExpectation = [self expectationForNotification:copyFinishedNotificationName object:nil handler:NULL]; [[TIPImagePipelineBaseTests sharedPipeline] copyDiskCacheFileWithIdentifier:request.imageURL.absoluteString completion:completion]; [self waitForExpectationsWithTimeout:5.0 handler:NULL]; XCTAssertNil(tempFile); XCTAssertNotNil(copyError); // Fill cache with item TIPImageFetchOperation *op = [[TIPImagePipelineBaseTests sharedPipeline] operationWithRequest:request context:nil completion:NULL]; [[TIPImagePipelineBaseTests sharedPipeline] fetchImageWithOperation:op]; [op waitUntilFinishedWithoutBlockingRunLoop]; XCTAssertNotNil(op.finalResult.imageContainer); // Attempt with cache entries tempFile = nil; copyError = nil; finisedCopyExpectation = [self expectationForNotification:copyFinishedNotificationName object:nil handler:NULL]; [[TIPImagePipelineBaseTests sharedPipeline] copyDiskCacheFileWithIdentifier:request.imageURL.absoluteString completion:completion]; [self waitForExpectationsWithTimeout:5.0 handler:NULL]; XCTAssertNotNil(tempFile); XCTAssertNil(copyError); XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:tempFile]); // Attempt with no disk cache entry tempFile = nil; copyError = nil; [[TIPImagePipelineBaseTests sharedPipeline] clearDiskCache]; finisedCopyExpectation = [self expectationForNotification:copyFinishedNotificationName object:nil handler:NULL]; [[TIPImagePipelineBaseTests sharedPipeline] copyDiskCacheFileWithIdentifier:request.imageURL.absoluteString completion:completion]; [self waitForExpectationsWithTimeout:5.0 handler:NULL]; XCTAssertNil(tempFile); XCTAssertNotNil(copyError); } - (void)testGettingKnownPipelines { TestImageStoreRequest *storeRequest = [[TestImageStoreRequest alloc] init]; storeRequest.imageFilePath = [[self class] pathForImageOfType:TIPImageTypeJPEG progressive:NO]; XCTAssertNotNil(storeRequest.imageFilePath); storeRequest.imageURL = [[self class] dummyURLWithPath:@"dummy.image.jpg"]; NSString *signalIdentifier = [NSString stringWithFormat:@"%@", @(time(NULL))]; __block XCTestExpectation *expectation = nil; __block NSSet *knownIds = nil; __block BOOL didStore = NO; void (^getKnownImagePiplineIdentifiers)(void) = ^ { expectation = [self expectationWithDescription:@"Waiting for known image pipeline identifiers"]; [TIPImagePipeline getKnownImagePipelineIdentifiers:^(NSSet *identifiers) { knownIds = [identifiers copy]; /*NSLog(@"Known Image Pipeline Identifiers: %@", knownIds.allObjects);*/ [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:20.0 handler:NULL]; }; // 1) Assert the pipeline we are looking for doesn't exist getKnownImagePiplineIdentifiers(); XCTAssertFalse([knownIds containsObject:signalIdentifier]); // 2) Create a pipeline and store an image, assert it does now exist expectation = [self expectationWithDescription:@"Storing Image"]; TIPImagePipeline *pipeline = [[TIPImagePipeline alloc] initWithIdentifier:signalIdentifier]; [pipeline storeImageWithRequest:storeRequest completion:^(NSObject<TIPDependencyOperation> *storeOp, BOOL succeeded, NSError *error) { didStore = succeeded; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:20.0 handler:NULL]; getKnownImagePiplineIdentifiers(); XCTAssertTrue([knownIds containsObject:signalIdentifier]); // 3) Clear the pipeline and dealloc, assert it no longer exists [pipeline clearDiskCache]; pipeline = nil; getKnownImagePiplineIdentifiers(); XCTAssertFalse([knownIds containsObject:signalIdentifier]); } - (void)testCrossPipelineLoad { NSString *pipelineIdentifier1 = @"cross.pipeline.1"; NSString *pipelineIdentifier2 = @"cross.pipeline.2"; TIPImagePipeline *pipeline1 = [[TIPImagePipeline alloc] initWithIdentifier:pipelineIdentifier1]; TIPImagePipeline *pipeline2 = [[TIPImagePipeline alloc] initWithIdentifier:pipelineIdentifier2]; [pipeline1 clearDiskCache]; [pipeline2 clearDiskCache]; __block TIPImageLoadSource loadSource; XCTestExpectation *expectation; TIPImageFetchOperation *op; NSURL *URL = [NSURL URLWithString:@"http://cross.pipeline.com/image.jpg"]; NSString *imagePath = [[self class] pathForImageOfType:TIPImageTypeJPEG progressive:NO]; XCTAssertNotNil(imagePath); TestImageStoreRequest *storeRequest = [[TestImageStoreRequest alloc] init]; storeRequest.imageURL = URL; storeRequest.imageFilePath = imagePath; TIPImagePipelineTestFetchRequest *fetchRequest = [[TIPImagePipelineTestFetchRequest alloc] init]; fetchRequest.imageURL = URL; fetchRequest.imageType = TIPImageTypeJPEG; fetchRequest.progressiveSource = NO; [TIPImagePipelineTestFetchRequest stubRequest:fetchRequest bitrate:0 resumable:YES]; expectation = [self expectationWithDescription:@"Cross Pipeline Fetch Image 1"]; op = [pipeline2 operationWithRequest:fetchRequest context:nil completion:^(id<TIPImageFetchResult> result, NSError *error) { loadSource = result.imageSource; [expectation fulfill]; }]; [pipeline2 fetchImageWithOperation:op]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; XCTAssertEqual(TIPImageLoadSourceNetwork, loadSource); [pipeline2 clearDiskCache]; [pipeline2 clearMemoryCaches]; expectation = [self expectationWithDescription:@"Clear Caches"]; [pipeline2 inspect:^(TIPImagePipelineInspectionResult *result) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; expectation = [self expectationWithDescription:@"Cross Pipeline Store Image"]; [pipeline1 storeImageWithRequest:storeRequest completion:^(NSObject<TIPDependencyOperation> *storeOp, BOOL succeeded, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; expectation = [self expectationWithDescription:@"Cross Pipeline Fetch Image 2"]; op = [pipeline2 operationWithRequest:fetchRequest context:nil completion:^(id<TIPImageFetchResult> result, NSError *error) { loadSource = result.imageSource; [expectation fulfill]; }]; [pipeline2 fetchImageWithOperation:op]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; XCTAssertEqual(TIPImageLoadSourceDiskCache, loadSource); [pipeline1 clearDiskCache]; [pipeline2 clearDiskCache]; pipeline1 = nil; pipeline2 = nil; } - (void)testRenamedEntry { NSString *pipelineIdentifier = @"dummy.pipeline"; TIPImagePipeline *pipeline = [[TIPImagePipeline alloc] initWithIdentifier:pipelineIdentifier]; [pipeline clearDiskCache]; __block TIPImageLoadSource loadSource; __block NSError *loadError; XCTestExpectation *expectation; TIPImageFetchOperation *op; NSURL *URL1 = [NSURL URLWithString:@"http://dummy.pipeline.com/image.jpg"]; NSURL *URL2 = [NSURL URLWithString:@"fake://fake.pipeline.com/fake.jpg"]; TIPImagePipelineTestFetchRequest *fetchRequest1 = [[TIPImagePipelineTestFetchRequest alloc] init]; fetchRequest1.imageURL = URL1; fetchRequest1.imageType = TIPImageTypeJPEG; fetchRequest1.progressiveSource = NO; TIPImagePipelineTestFetchRequest *fetchRequest2 = [[TIPImagePipelineTestFetchRequest alloc] init]; fetchRequest2.imageURL = URL1; fetchRequest2.imageIdentifier = [URL2 absoluteString]; fetchRequest2.imageType = TIPImageTypeJPEG; fetchRequest2.progressiveSource = NO; fetchRequest2.loadingSources = TIPImageFetchLoadingSourcesAll & ~(TIPImageFetchLoadingSourceNetwork | TIPImageFetchLoadingSourceNetworkResumed); // no network! [TIPImagePipelineTestFetchRequest stubRequest:fetchRequest1 bitrate:0 resumable:YES]; [TIPImagePipelineTestFetchRequest stubRequest:fetchRequest2 bitrate:0 resumable:NO]; // just to ensure we don't hit the network expectation = [self expectationWithDescription:@"Pipeline Fetch Image 1"]; op = [pipeline operationWithRequest:fetchRequest1 context:nil completion:^(id<TIPImageFetchResult> result, NSError *error) { loadSource = result.imageSource; loadError = error; [expectation fulfill]; }]; [pipeline fetchImageWithOperation:op]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; XCTAssertEqual(TIPImageLoadSourceNetwork, loadSource); XCTAssertNil(loadError); expectation = [self expectationWithDescription:@"Pipeline Fetch Image 2"]; op = [pipeline operationWithRequest:fetchRequest2 context:nil completion:^(id<TIPImageFetchResult> result, NSError *error) { loadSource = result.imageSource; loadError = error; [expectation fulfill]; }]; [pipeline fetchImageWithOperation:op]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; XCTAssertEqual(TIPImageLoadSourceUnknown, loadSource); XCTAssertNotNil(loadError); expectation = [self expectationWithDescription:@"Move Image"]; [pipeline changeIdentifierForImageWithIdentifier:[URL1 absoluteString] toIdentifier:[URL2 absoluteString] completion:^(NSObject<TIPDependencyOperation> *moveOp, BOOL succeeded, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; expectation = [self expectationWithDescription:@"Pipeline Fetch Image 2"]; op = [pipeline operationWithRequest:fetchRequest2 context:nil completion:^(id<TIPImageFetchResult> result, NSError *error) { loadSource = result.imageSource; loadError = error; [expectation fulfill]; }]; [pipeline fetchImageWithOperation:op]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; XCTAssertEqual(TIPImageLoadSourceDiskCache, loadSource); XCTAssertNil(loadError); expectation = [self expectationWithDescription:@"Pipeline Fetch Image 1"]; op = [pipeline operationWithRequest:fetchRequest1 context:nil completion:^(id<TIPImageFetchResult> result, NSError *error) { loadSource = result.imageSource; loadError = error; [expectation fulfill]; }]; [pipeline fetchImageWithOperation:op]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; XCTAssertEqual(TIPImageLoadSourceNetwork, loadSource); // Not cache! XCTAssertNil(loadError); [pipeline clearDiskCache]; [pipeline clearMemoryCaches]; expectation = [self expectationWithDescription:@"Clear Caches"]; [pipeline inspect:^(TIPImagePipelineInspectionResult *result) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:10.0 handler:NULL]; pipeline = nil; } - (void)testInvalidPseudoFilePathFetch { TIPImagePipelineTestFetchRequest *request = [[TIPImagePipelineTestFetchRequest alloc] init]; request.imageType = TIPImageTypeJPEG; request.progressiveSource = YES; request.imageURL = [TIPImagePipelineBaseTests dummyURLWithPath:[NSUUID UUID].UUIDString]; request.targetDimensions = kCarnivalImageDimensions; request.targetContentMode = UIViewContentModeScaleAspectFit; request.cannedImageFilePath = [request.cannedImageFilePath stringByAppendingPathExtension:@"dne"]; [TIPImagePipelineTestFetchRequest stubRequest:request bitrate:1 * kMegaBits resumable:YES]; TIPImageFetchOperation *op = nil; TIPImagePipelineTestContext *context = nil; [[TIPImagePipelineBaseTests sharedPipeline] clearMemoryCaches]; [[TIPImagePipelineBaseTests sharedPipeline] clearDiskCache]; context = [[TIPImagePipelineTestContext alloc] init]; op = [[TIPImagePipelineBaseTests sharedPipeline] undeprecatedFetchImageWithRequest:request context:context delegate:self]; [op waitUntilFinishedWithoutBlockingRunLoop]; XCTAssertNil(op.finalResult.imageContainer); XCTAssertNotNil(op.error); TIPImageFetchMetricInfo *metricInfo = [op.metrics metricInfoForSource:TIPImageLoadSourceNetwork]; (void)metricInfo; } @end @implementation TIPImagePipelineTests_Two - (void)testFillingMultipleCaches { TIPGlobalConfiguration *config = [TIPGlobalConfiguration sharedInstance]; __block SInt64 preDeallocDiskSize; __block SInt64 preDeallocMemSize; __block SInt64 preDeallocRendSize; __block SInt64 preDeallocPipelineDiskSize; __block SInt64 preDeallocPipelineMemSize; __block SInt64 preDeallocPipelineRendSize; NSString *tmpPipelineIdentifier = @"temp.pipeline.identifier"; XCTestExpectation *expectation = [self expectationForNotification:TIPImagePipelineDidTearDownImagePipelineNotification object:nil handler:^BOOL(NSNotification *note) { return [tmpPipelineIdentifier isEqualToString:note.userInfo[TIPImagePipelineImagePipelineIdentifierNotificationKey]]; }]; @autoreleasepool { [[TIPImagePipelineBaseTests sharedPipeline] clearMemoryCaches]; [[TIPImagePipelineBaseTests sharedPipeline] clearDiskCache]; TIPImagePipeline *temporaryPipeline = [[TIPImagePipeline alloc] initWithIdentifier:tmpPipelineIdentifier]; [self runFillingTheCaches:[TIPImagePipelineBaseTests sharedPipeline] bps:1024 * kMegaBits testCacheHits:NO]; TIPGlobalConfiguration *globalConfig = [TIPGlobalConfiguration sharedInstance]; XCTAssertGreaterThan([[TIPImagePipelineBaseTests sharedPipeline] cacheOfType:TIPImageCacheTypeRendered].manifest.numberOfEntries, (NSUInteger)0); dispatch_sync(globalConfig.queueForMemoryCaches, ^{ XCTAssertGreaterThan([[TIPImagePipelineBaseTests sharedPipeline] cacheOfType:TIPImageCacheTypeMemory].manifest.numberOfEntries, (NSUInteger)0); }); dispatch_sync(globalConfig.queueForDiskCaches, ^{ XCTAssertGreaterThan([[TIPImagePipelineBaseTests sharedPipeline] cacheOfType:TIPImageCacheTypeDisk].manifest.numberOfEntries, (NSUInteger)0); }); [self runFillingTheCaches:temporaryPipeline bps:1024 * kMegaBits testCacheHits:NO]; XCTAssertGreaterThan([temporaryPipeline cacheOfType:TIPImageCacheTypeRendered].manifest.numberOfEntries, (NSUInteger)0); XCTAssertEqual([[TIPImagePipelineBaseTests sharedPipeline] cacheOfType:TIPImageCacheTypeRendered].manifest.numberOfEntries, (NSUInteger)0); dispatch_sync(globalConfig.queueForMemoryCaches, ^{ XCTAssertGreaterThan([temporaryPipeline cacheOfType:TIPImageCacheTypeMemory].manifest.numberOfEntries, (NSUInteger)0); XCTAssertEqual([[TIPImagePipelineBaseTests sharedPipeline] cacheOfType:TIPImageCacheTypeMemory].manifest.numberOfEntries, (NSUInteger)0); }); dispatch_sync(globalConfig.queueForMemoryCaches, ^{ XCTAssertGreaterThan([temporaryPipeline cacheOfType:TIPImageCacheTypeDisk].manifest.numberOfEntries, (NSUInteger)0); XCTAssertEqual([[TIPImagePipelineBaseTests sharedPipeline] cacheOfType:TIPImageCacheTypeDisk].manifest.numberOfEntries, (NSUInteger)0); }); dispatch_sync(globalConfig.queueForDiskCaches, ^{ preDeallocDiskSize = [config internalTotalBytesForAllCachesOfType:TIPImageCacheTypeDisk]; }); dispatch_sync(globalConfig.queueForMemoryCaches, ^{ preDeallocMemSize = [config internalTotalBytesForAllCachesOfType:TIPImageCacheTypeMemory]; }); preDeallocRendSize = [config internalTotalBytesForAllCachesOfType:TIPImageCacheTypeRendered]; preDeallocPipelineDiskSize = (SInt64)[temporaryPipeline cacheOfType:TIPImageCacheTypeDisk].totalCost; preDeallocPipelineMemSize = (SInt64)[temporaryPipeline cacheOfType:TIPImageCacheTypeMemory].totalCost; preDeallocPipelineRendSize = (SInt64)[temporaryPipeline cacheOfType:TIPImageCacheTypeRendered].totalCost; temporaryPipeline = nil; } NSLog(@"Waiting for %@", TIPImagePipelineDidTearDownImagePipelineNotification); // Wait for the pipeline to release [self waitForExpectationsWithTimeout:120.0 handler:^(NSError *error) { if (error) { NSLog(@"%@", error); } else { NSLog(@"Received %@", TIPImagePipelineDidTearDownImagePipelineNotification); } }]; expectation = nil; __block SInt64 postDeallocDiskSize; __block SInt64 postDeallocMemSize; __block SInt64 postDeallocRendSize; const NSUInteger cacheSizeCheckMax = 30; NSUInteger cacheSizeCheck; for (cacheSizeCheck = 1; cacheSizeCheck <= cacheSizeCheckMax; cacheSizeCheck++) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.3]]; dispatch_sync([TIPGlobalConfiguration sharedInstance].queueForDiskCaches, ^{ postDeallocDiskSize = [config internalTotalBytesForAllCachesOfType:TIPImageCacheTypeDisk]; }); dispatch_sync([TIPGlobalConfiguration sharedInstance].queueForMemoryCaches, ^{ postDeallocMemSize = [config internalTotalBytesForAllCachesOfType:TIPImageCacheTypeMemory]; }); postDeallocRendSize = [config internalTotalBytesForAllCachesOfType:TIPImageCacheTypeRendered]; if (postDeallocDiskSize == 0 && postDeallocMemSize == 0 && postDeallocRendSize == 0) { break; } } if (cacheSizeCheck <= cacheSizeCheckMax) { NSLog(@"Caches were relieved after %tu seconds", cacheSizeCheck); } else { NSLog(@"ERR: Caches were not relieved after %tu seconds", cacheSizeCheck - 1); } XCTAssertEqual(postDeallocDiskSize, preDeallocDiskSize - preDeallocPipelineDiskSize); XCTAssertEqual(postDeallocMemSize, preDeallocMemSize - preDeallocPipelineMemSize); XCTAssertEqual(postDeallocRendSize, preDeallocRendSize - preDeallocPipelineRendSize); } @end @implementation TIPImagePipelineTests_Three - (void)testFillingTheCaches { TIPImagePipeline *pipeline = [TIPImagePipelineBaseTests sharedPipeline]; [self runFillingTheCaches:pipeline bps:1024 * kMegaBits testCacheHits:YES]; [self checkFileAttributes:pipeline]; } @end