Files
PINRemoteImage/Example/PINRemoteImage Tests/PINRemoteImage_Tests.m
2016-03-06 11:34:52 -08:00

788 lines
33 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// PINRemoteImage_Tests.m
// PINRemoteImage Tests
//
// Created by Garrett Moon on 11/6/14.
// Copyright (c) 2014 Garrett Moon. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <PINRemoteImage/PINRemoteImage.h>
#import <PINRemoteImage/PINURLSessionManager.h>
#import <PINRemoteImage/PINImageView+PINRemoteImage.h>
#if USE_FLANIMATED_IMAGE
#import <FLAnimatedImage/FLAnimatedImage.h>
#endif
#import <PINCache/PINCache.h>
#if DEBUG
@interface PINRemoteImageManager ()
@property (nonatomic, strong) PINURLSessionManager *sessionManager;
@property (nonatomic, readonly) NSUInteger totalDownloads;
- (float)currentBytesPerSecond;
- (void)addTaskBPS:(float)bytesPerSecond endDate:(NSDate *)endDate;
- (void)setCurrentBytesPerSecond:(float)currentBPS;
@end
@interface PINURLSessionManager ()
@property (nonatomic, strong) NSURLSession *session;
@end
#endif
@interface PINRemoteImage_Tests : XCTestCase <PINURLSessionManagerDelegate>
@property (nonatomic, strong) PINRemoteImageManager *imageManager;
@property (nonatomic, strong) NSData *data;
@property (nonatomic, strong) NSURLSessionTask *task;
@property (nonatomic, strong) NSError *error;
@end
@implementation PINRemoteImage_Tests
- (NSTimeInterval)timeoutTimeInterval {
return 10.0;
}
- (dispatch_time_t)timeoutWithInterval:(NSTimeInterval)interval {
return dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC));
}
- (dispatch_time_t)timeout {
return [self timeoutWithInterval:[self timeoutTimeInterval]];
}
- (NSURL *)GIFURL
{
return [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/originals/90/f5/77/90f577fc6abcd24f9a5f9f55b2d7482b.jpg"];
}
- (NSURL *)emptyURL
{
return [NSURL URLWithString:@""];
}
- (NSURL *)fourZeroFourURL
{
return [NSURL URLWithString:@"https://www.pinterest.com/404/"];
}
- (NSURL *)headersURL
{
return [NSURL URLWithString:@"https://httpbin.org/headers"];
}
- (NSURL *)JPEGURL_Small
{
return [NSURL URLWithString:@"https://media-cache-ec0.pinimg.com/345x/1b/bc/c2/1bbcc264683171eb3815292d2f546e92.jpg"];
}
- (NSURL *)JPEGURL_Medium
{
return [NSURL URLWithString:@"https://media-cache-ec0.pinimg.com/600x/1b/bc/c2/1bbcc264683171eb3815292d2f546e92.jpg"];
}
- (NSURL *)JPEGURL_Large
{
return [NSURL URLWithString:@"https://media-cache-ec0.pinimg.com/750x/1b/bc/c2/1bbcc264683171eb3815292d2f546e92.jpg"];
}
- (NSURL *)JPEGURL
{
return [self JPEGURL_Medium];
}
- (NSURL *)nonTransparentWebPURL
{
return [NSURL URLWithString:@"https://www.gstatic.com/webp/gallery/5.webp"];
}
- (NSURL *)transparentWebPURL
{
return [NSURL URLWithString:@"https://www.gstatic.com/webp/gallery3/4_webp_ll.webp"];
}
#pragma mark - <PINURLSessionManagerDelegate>
- (void)didReceiveData:(NSData *)data forTask:(NSURLSessionTask *)task
{
self.data = data;
self.task = task;
}
- (void)didCompleteTask:(NSURLSessionTask *)task withError:(NSError *)error
{
self.task = task;
self.error = error;
}
- (void)setUp
{
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
self.imageManager = [[PINRemoteImageManager alloc] init];
}
- (void)tearDown
{
// Put teardown code here. This method is called after the invocation of each test method in the class.
//clear disk cache
[self.imageManager.cache.diskCache removeAllObjects];
self.imageManager = nil;
[super tearDown];
}
#if USE_FLANIMATED_IMAGE
- (void)testGIFDownload
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Download animatedImage"];
[self.imageManager downloadImageWithURL:[self GIFURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
UIImage *outImage = result.image;
FLAnimatedImage *outAnimatedImage = result.animatedImage;
XCTAssert(outAnimatedImage && [outAnimatedImage isKindOfClass:[FLAnimatedImage class]], @"Failed downloading animatedImage or animatedImage is not an FLAnimatedImage.");
XCTAssert(outImage == nil, @"Image is not nil.");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
#endif
- (void)testInitWithNilConfiguration
{
self.imageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:nil];
XCTAssertNotNil(self.imageManager.sessionManager.session.configuration);
}
- (void)testInitWithConfiguration
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.HTTPAdditionalHeaders = @{ @"Authorization" : @"Pinterest 123456" };
self.imageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:configuration];
XCTAssert([self.imageManager.sessionManager.session.configuration.HTTPAdditionalHeaders isEqualToDictionary:@{ @"Authorization" : @"Pinterest 123456" }]);
}
- (void)testCustomHeaderIsAddedToImageRequests
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom header was added to image request"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.HTTPAdditionalHeaders = @{ @"X-Custom-Header" : @"Custom Header Value" };
self.imageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:configuration];
self.imageManager.sessionManager.delegate = self;
[self.imageManager downloadImageWithURL:[self headersURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
NSDictionary *headers = [[NSJSONSerialization JSONObjectWithData:self.data options:NSJSONReadingMutableContainers error:nil] valueForKey:@"headers"];
XCTAssert([headers[@"X-Custom-Header"] isEqualToString:@"Custom Header Value"]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testSkipFLAnimatedImageDownload
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Download animated image"];
[self.imageManager downloadImageWithURL:[self GIFURL]
options:PINRemoteImageManagerDownloadOptionsIgnoreGIFs
completion:^(PINRemoteImageManagerResult *result)
{
UIImage *outImage = result.image;
FLAnimatedImage *outAnimatedImage = result.animatedImage;
XCTAssert(outImage && [outImage isKindOfClass:[UIImage class]], @"Failed downloading image or image is not a UIImage.");
XCTAssert(outAnimatedImage == nil, @"Animated image is not nil.");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testJPEGDownload
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Downloading JPEG image"];
[self.imageManager downloadImageWithURL:[self JPEGURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
UIImage *outImage = result.image;
FLAnimatedImage *outAnimatedImage = result.animatedImage;
XCTAssert(outImage && [outImage isKindOfClass:[UIImage class]], @"Failed downloading image or image is not a UIImage.");
XCTAssert(outAnimatedImage == nil, @"Animated image is not nil.");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testErrorOnNilURLDownload
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
XCTestExpectation *expectation = [self expectationWithDescription:@"Error on nil image url download"];
[self.imageManager downloadImageWithURL:nil
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
#pragma clang diagnostic pop
{
NSError *outError = result.error;
XCTAssert([outError.domain isEqualToString:NSURLErrorDomain]);
XCTAssert(outError.code == NSURLErrorUnsupportedURL);
XCTAssert([outError.localizedDescription isEqualToString:@"unsupported URL"]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testErrorOnEmptyURLDownload
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Error on empty image url download"];
[self.imageManager downloadImageWithURL:[self emptyURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
NSError *outError = result.error;
XCTAssert([outError.domain isEqualToString:NSURLErrorDomain]);
XCTAssert(outError.code == NSURLErrorUnsupportedURL);
// iOS8 (and presumably 10.10) returns NSURLErrorUnsupportedURL which means the HTTP NSURLProtocol does not accept it
NSArray *validErrorMessages = @[ @"unsupported URL", @"The operation couldnt be completed. (NSURLErrorDomain error -1002.)"];
XCTAssert([validErrorMessages containsObject:outError.localizedDescription], @"%@", outError.localizedDescription);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testErrorOn404Response
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Error on 404 response"];
[self.imageManager downloadImageWithURL:[self fourZeroFourURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
NSError *outError = result.error;
XCTAssert([outError.domain isEqualToString:NSURLErrorDomain]);
XCTAssert(outError.code == NSURLErrorRedirectToNonExistentLocation);
XCTAssert([outError.localizedDescription isEqualToString:@"The requested URL was not found on this server."]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testDecoding
{
dispatch_group_t group = dispatch_group_create();
__block UIImage *outImageDecoded = nil;
__block UIImage *outImageEncoded = nil;
PINRemoteImageManager *encodedManager = [[PINRemoteImageManager alloc] init];
dispatch_group_enter(group);
[self.imageManager downloadImageWithURL:[self JPEGURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
outImageDecoded = result.image;
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_enter(group);
[encodedManager downloadImageWithURL:[self JPEGURL]
options:PINRemoteImageManagerDownloadOptionsSkipDecode
completion:^(PINRemoteImageManagerResult *result)
{
outImageEncoded = result.image;
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
CFTimeInterval before = CACurrentMediaTime();
[self drawImage:outImageEncoded];
CFTimeInterval after = CACurrentMediaTime();
CFTimeInterval encodedDrawTime = after - before;
before = CACurrentMediaTime();
[self drawImage:outImageDecoded];
after = CACurrentMediaTime();
CFTimeInterval decodedDrawTime = after - before;
XCTAssert(outImageEncoded && [outImageEncoded isKindOfClass:[UIImage class]], @"Failed downloading image or image is not a UIImage.");
XCTAssert(outImageDecoded && [outImageDecoded isKindOfClass:[UIImage class]], @"Failed downloading image or image is not a UIImage.");
XCTAssert(encodedDrawTime / decodedDrawTime > 2, @"Drawing decoded image should be much faster");
}
- (void)drawImage:(UIImage *)image
{
UIGraphicsBeginImageContext(image.size);
[image drawAtPoint:CGPointZero];
UIGraphicsEndImageContext();
}
- (void)waitForImageWithURLToBeCached:(NSURL *)URL
{
NSString *key = [self.imageManager cacheKeyForURL:URL processorKey:nil];
for (NSUInteger idx = 0; idx < 100; idx++) {
if ([[self.imageManager cache] objectForKey:key] != nil) {
break;
}
if (idx == 99) {
XCTAssert(NO, @"image never set to cache.");
}
usleep(50000);
}
}
- (void)testTransparentWebPDownload
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Download transparent WebP image"];
[self.imageManager downloadImageWithURL:[self transparentWebPURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
XCTAssert(result.error == nil, @"error is non-nil: %@", result.error);
UIImage *outImage = result.image;
FLAnimatedImage *outAnimatedImage = result.animatedImage;
XCTAssert(outImage && [outImage isKindOfClass:[UIImage class]], @"Failed downloading image or image is not a UIImage.");
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(outImage.CGImage);
BOOL opaque = alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast;
XCTAssert(opaque == NO, @"transparent WebP image is opaque.");
XCTAssert(outAnimatedImage == nil, @"Animated image is not nil.");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testNonTransparentWebPDownload
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Download non transparent WebP image"];
[self.imageManager downloadImageWithURL:[self nonTransparentWebPURL]
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
UIImage *outImage = result.image;
FLAnimatedImage *outAnimatedImage = result.animatedImage;
XCTAssert(outImage && [outImage isKindOfClass:[UIImage class]], @"Failed downloading image or image is not a UIImage.");
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(outImage.CGImage);
BOOL opaque = alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast;
XCTAssert(opaque == YES, @"non transparent WebP image is not opaque.");
XCTAssert(outAnimatedImage == nil, @"Animated image is not nil.");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testPrefetchImage
{
id object = [[self.imageManager cache] objectForKey:[self.imageManager cacheKeyForURL:[self JPEGURL] processorKey:nil]];
XCTAssert(object == nil, @"image should not be in cache");
[self.imageManager prefetchImageWithURL:[self JPEGURL]];
sleep([self timeoutTimeInterval]);
object = [[self.imageManager cache] objectForKey:[self.imageManager cacheKeyForURL:[self JPEGURL] processorKey:nil]];
XCTAssert(object, @"image was not prefetched or was not stored in cache");
}
- (void)testUIImageView
{
XCTestExpectation *imageSetExpectation = [self expectationWithDescription:@"imageView did not have image set"];
UIImageView *imageView = [[UIImageView alloc] init];
__weak UIImageView *weakImageView = imageView;
[imageView pin_setImageFromURL:[self JPEGURL]
completion:^(PINRemoteImageManagerResult *result)
{
if (weakImageView.image)
[imageSetExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
#if USE_FLANIMATED_IMAGE
- (void)testFLAnimatedImageView
{
XCTestExpectation *imageSetExpectation = [self expectationWithDescription:@"animatedImageView did not have animated image set"];
FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init];
__weak FLAnimatedImageView *weakImageView = imageView;
[imageView pin_setImageFromURL:[self GIFURL]
completion:^(PINRemoteImageManagerResult *result)
{
if (weakImageView.animatedImage)
[imageSetExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
#endif
- (void)testEarlyReturn
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Download JPEG image"];
[self.imageManager downloadImageWithURL:[self JPEGURL] completion:^(PINRemoteImageManagerResult *result) {
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
// callback can occur *before* image is stored in cache this is an optimization to avoid waiting on the cache to write.
// So, wait until it's actually in the cache.
[self waitForImageWithURLToBeCached:[self JPEGURL]];
__block UIImage *image = nil;
[self.imageManager downloadImageWithURL:[self JPEGURL] completion:^(PINRemoteImageManagerResult *result) {
image = result.image;
}];
XCTAssert(image != nil, @"image callback did not occur synchronously.");
}
#if USE_FLANIMATED_IMAGE
- (void)testload
{
srand([[NSDate date] timeIntervalSince1970]);
dispatch_group_t group = dispatch_group_create();
__block NSInteger count = 0;
const NSInteger numIntervals = 10000;
NSLock *countLock = [[NSLock alloc] init];
for (NSUInteger idx = 0; idx < numIntervals; idx++) {
dispatch_group_enter(group);
NSURL *url = nil;
if (rand() % 2 == 0) {
url = [self JPEGURL];
} else {
url = [self GIFURL];
}
[self.imageManager downloadImageWithURL:url
options:PINRemoteImageManagerDownloadOptionsNone
completion:^(PINRemoteImageManagerResult *result)
{
[countLock lock];
count++;
XCTAssert(count <= numIntervals, @"callback called too many times");
[countLock unlock];
XCTAssert((result.image && !result.animatedImage) || (result.animatedImage && !result.image), @"image or animatedImage not downloaded");
if (rand() % 2) {
[[self.imageManager cache] removeObjectForKey:[self.imageManager cacheKeyForURL:url processorKey:nil]];
}
dispatch_group_leave(group);
}];
}
XCTAssert(dispatch_group_wait(group, [self timeoutWithInterval:100]) == 0, @"Group timed out.");
}
#endif
- (void)testInvalidObject
{
[self.imageManager.cache setObject:@"invalid" forKey:[self.imageManager cacheKeyForURL:[self JPEGURL] processorKey:nil]];
XCTestExpectation *expectation = [self expectationWithDescription:@"Download JPEG image"];
[self.imageManager downloadImageWithURL:[self JPEGURL] completion:^(PINRemoteImageManagerResult *result) {
UIImage *image = result.image;
XCTAssert([image isKindOfClass:[UIImage class]], @"image should be UIImage");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testProcessingLoad
{
dispatch_group_t group = dispatch_group_create();
__block UIImage *image = nil;
const NSUInteger numIntervals = 1000;
__block NSInteger processCount = 0;
__block UIImage *processedImage = nil;
NSLock *processCountLock = [[NSLock alloc] init];
for (NSUInteger idx = 0; idx < numIntervals; idx++) {
dispatch_group_enter(group);
[self.imageManager downloadImageWithURL:[self JPEGURL] options:PINRemoteImageManagerDownloadOptionsNone
processorKey:@"process"
processor:^UIImage *(PINRemoteImageManagerResult *result, NSUInteger *cost)
{
[processCountLock lock];
processCount++;
[processCountLock unlock];
UIImage *inputImage = result.image;
XCTAssert(inputImage, @"no input image");
UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect destRect = CGRectMake(0, 0, inputImage.size.width, inputImage.size.height);
[[UIColor clearColor] set];
CGContextFillRect(context, destRect);
CGRect pathRect = CGRectMake(0, 0, inputImage.size.width, inputImage.size.height);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:pathRect
cornerRadius:MIN(inputImage.size.width, inputImage.size.height) / 2.0];
CGContextAddPath(context, path.CGPath);
CGContextClosePath(context);
CGContextClip(context);
[inputImage drawInRect:CGRectMake(0, 0, inputImage.size.width, inputImage.size.height)];
UIImage *roundedImage = nil;
roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
processedImage = roundedImage;
return roundedImage;
}
completion:^(PINRemoteImageManagerResult *result)
{
image = result.image;
dispatch_group_leave(group);
XCTAssert([image isKindOfClass:[UIImage class]] && image == processedImage, @"result image is not a UIImage");
}];
}
XCTAssert(dispatch_group_wait(group, [self timeout]) == 0, @"Group timed out.");
XCTAssert(processCount <= 1, @"image processed too many times");
XCTAssert([image isKindOfClass:[UIImage class]], @"result image is not a UIImage");
}
- (void)testProcessingCancel
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSUUID *processUUID = [self.imageManager downloadImageWithURL:[self JPEGURL] options:PINRemoteImageManagerDownloadOptionsNone
processorKey:@"process"
processor:^UIImage *(PINRemoteImageManagerResult *result, NSUInteger *cost)
{
XCTAssert(NO, @"Process should have been canceled and callback should not have been called.");
return nil;
}
completion:^(PINRemoteImageManagerResult *result)
{
XCTAssert(NO, @"Process should have been canceled and callback should not have been called.");
dispatch_semaphore_signal(semaphore);
}];
[self.imageManager cancelTaskWithUUID:processUUID];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) != 0, @"Semaphore should time out.");
XCTAssert(self.imageManager.totalDownloads == 0, @"image should not have been downloaded either.");
}
- (void)testNumberDownloads
{
dispatch_group_t group = dispatch_group_create();
__block UIImage *image = nil;
const NSUInteger numIntervals = 1000;
for (NSUInteger idx = 0; idx < numIntervals; idx++) {
dispatch_group_enter(group);
[self.imageManager downloadImageWithURL:[self JPEGURL] completion:^(PINRemoteImageManagerResult *result) {
dispatch_group_leave(group);
XCTAssert([result.image isKindOfClass:[UIImage class]], @"result image is not a UIImage");
image = result.image;
}];
}
XCTAssert(dispatch_group_wait(group, [self timeout]) == 0, @"Group timed out.");
XCTAssert(self.imageManager.totalDownloads <= 1, @"image downloaded too many times");
XCTAssert([image isKindOfClass:[UIImage class]], @"result image is not a UIImage");
}
- (BOOL)isFloat:(float)one equalToFloat:(float)two
{
if (fabsf(one - two) < FLT_EPSILON) {
return YES;
}
return NO;
}
- (void)testBytesPerSecond
{
XCTestExpectation *finishExpectation = [self expectationWithDescription:@"Finished testing off the main thread."];
//currentBytesPerSecond is not public, should not be called on the main queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
XCTAssert([self.imageManager currentBytesPerSecond] == -1, @"Without any tasks added, should be -1");
[self.imageManager addTaskBPS:100 endDate:[NSDate dateWithTimeIntervalSinceNow:-61]];
XCTAssert([self.imageManager currentBytesPerSecond] == -1, @"With only old task, should be -1");
[self.imageManager addTaskBPS:100 endDate:[NSDate date]];
XCTAssert([self isFloat:[self.imageManager currentBytesPerSecond] equalToFloat:100.0f], @"One task should be same as added task");
[self.imageManager addTaskBPS:50 endDate:[NSDate dateWithTimeIntervalSinceNow:-30]];
XCTAssert([self isFloat:[self.imageManager currentBytesPerSecond] equalToFloat:75.0f], @"Two tasks should be average of both tasks");
[self.imageManager addTaskBPS:100 endDate:[NSDate dateWithTimeIntervalSinceNow:-61]];
XCTAssert([self isFloat:[self.imageManager currentBytesPerSecond] equalToFloat:75.0f], @"Old task shouldn't be counted");
[self.imageManager addTaskBPS:50 endDate:[NSDate date]];
[self.imageManager addTaskBPS:50 endDate:[NSDate date]];
[self.imageManager addTaskBPS:50 endDate:[NSDate date]];
[self.imageManager addTaskBPS:50 endDate:[NSDate date]];
[self.imageManager addTaskBPS:50 endDate:[NSDate date]];
XCTAssert([self isFloat:[self.imageManager currentBytesPerSecond] equalToFloat:50.0f], @"Only last 5 tasks should be used");
[finishExpectation fulfill];
});
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
- (void)testQOS
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.imageManager setHighQualityBPSThreshold:10 completion:^{
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
[self.imageManager setLowQualityBPSThreshold:5 completion:^{
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
[self.imageManager setShouldUpgradeLowQualityImages:NO completion:^{
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
__block UIImage *image;
[self.imageManager downloadImageWithURLs:@[[self JPEGURL_Small], [self JPEGURL_Medium], [self JPEGURL_Large]]
options:PINRemoteImageManagerDownloadOptionsNone
progressImage:nil
completion:^(PINRemoteImageManagerResult *result)
{
image = result.image;
XCTAssert(image.size.width == 750, @"Large image should be downloaded. result.image: %@, result.error: %@", result.image, result.error);
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
// callback can occur *before* image is stored in cache this is an optimization to avoid waiting on the cache to write.
// So, wait until it's actually in the cache.
[self waitForImageWithURLToBeCached:[self JPEGURL_Large]];
[self.imageManager setCurrentBytesPerSecond:5];
[self.imageManager downloadImageWithURLs:@[[self JPEGURL_Small], [self JPEGURL_Medium], [self JPEGURL_Large]]
options:PINRemoteImageManagerDownloadOptionsNone
progressImage:nil
completion:^(PINRemoteImageManagerResult *result)
{
image = result.image;
XCTAssert(image.size.width == 750, @"Large image should be found in cache");
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
[self.imageManager.cache removeAllObjects];
[self.imageManager downloadImageWithURLs:@[[self JPEGURL_Small], [self JPEGURL_Medium], [self JPEGURL_Large]]
options:PINRemoteImageManagerDownloadOptionsNone
progressImage:nil
completion:^(PINRemoteImageManagerResult *result)
{
image = result.image;
XCTAssert(image.size.width == 345, @"Small image should be downloaded at low bps");
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
[self waitForImageWithURLToBeCached:[self JPEGURL_Small]];
[self.imageManager setCurrentBytesPerSecond:100];
[self.imageManager downloadImageWithURLs:@[[self JPEGURL_Small], [self JPEGURL_Medium], [self JPEGURL_Large]]
options:PINRemoteImageManagerDownloadOptionsNone
progressImage:nil
completion:^(PINRemoteImageManagerResult *result)
{
image = result.image;
XCTAssert(image.size.width == 345, @"Small image should be found in cache");
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
[self.imageManager setShouldUpgradeLowQualityImages:YES completion:^{
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
[self.imageManager setCurrentBytesPerSecond:7];
[self.imageManager downloadImageWithURLs:@[[self JPEGURL_Small], [self JPEGURL_Medium], [self JPEGURL_Large]]
options:PINRemoteImageManagerDownloadOptionsNone
progressImage:nil
completion:^(PINRemoteImageManagerResult *result)
{
image = result.image;
XCTAssert(image.size.width == 600, @"Medium image should be now downloaded");
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
//small image should have been removed from cache
NSString *key = [self.imageManager cacheKeyForURL:[self JPEGURL_Small] processorKey:nil];
for (NSUInteger idx = 0; idx < 100; idx++) {
if ([[self.imageManager cache] objectForKey:key] == nil) {
break;
}
sleep(50);
}
XCTAssert([[self.imageManager cache] objectForKey:[self.imageManager cacheKeyForURL:[self JPEGURL_Small] processorKey:nil]] == nil, @"Small image should have been removed from cache");
[self.imageManager.cache removeAllObjects];
[self.imageManager setShouldUpgradeLowQualityImages:NO completion:^{
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
[self.imageManager setCurrentBytesPerSecond:7];
[self.imageManager downloadImageWithURLs:@[[self JPEGURL_Small], [self JPEGURL_Large]]
options:PINRemoteImageManagerDownloadOptionsNone
progressImage:nil
completion:^(PINRemoteImageManagerResult *result)
{
image = result.image;
XCTAssert(image.size.width == 345, @"Small image should be now downloaded");
dispatch_semaphore_signal(semaphore);
}];
XCTAssert(dispatch_semaphore_wait(semaphore, [self timeout]) == 0, @"Semaphore timed out.");
}
- (void)testAuthentication
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Authentification challenge was called"];
[self.imageManager setAuthenticationChallenge:^(NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, PINRemoteImageManagerAuthenticationChallengeCompletionHandler aHandler) {
aHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
[expectation fulfill];
}];
[self.imageManager downloadImageWithURL: [NSURL URLWithString:@"https://media-cache-ec0.pinimg.com/600x/1b/bc/c2/1bbcc264683171eb3815292d2f546e92.jpg"]
options:PINRemoteImageManagerDownloadOptionsNone
completion:nil];
[self waitForExpectationsWithTimeout:[self timeoutTimeInterval] handler:nil];
}
@end