1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ios/chrome/browser/snapshots/snapshot_cache.h" 6 7#import <UIKit/UIKit.h> 8 9#include "base/files/file_path.h" 10#include "base/files/file_util.h" 11#include "base/files/scoped_temp_dir.h" 12#include "base/format_macros.h" 13#include "base/location.h" 14#include "base/mac/scoped_cftyperef.h" 15#include "base/run_loop.h" 16#include "base/strings/sys_string_conversions.h" 17#include "base/task/thread_pool/thread_pool_instance.h" 18#include "base/time/time.h" 19#import "ios/chrome/browser/snapshots/snapshot_cache_internal.h" 20#import "ios/chrome/browser/snapshots/snapshot_cache_observer.h" 21#include "ios/web/public/test/web_task_environment.h" 22#include "ios/web/public/thread/web_thread.h" 23#include "testing/gtest/include/gtest/gtest.h" 24#include "testing/gtest_mac.h" 25#include "testing/platform_test.h" 26 27#if !defined(__has_feature) || !__has_feature(objc_arc) 28#error "This file requires ARC support." 29#endif 30 31static const NSUInteger kSnapshotCount = 10; 32static const NSUInteger kSnapshotPixelSize = 8; 33 34@interface FakeSnapshotCacheObserver : NSObject<SnapshotCacheObserver> 35@property(nonatomic, copy) NSString* lastUpdatedIdentifier; 36@end 37 38@implementation FakeSnapshotCacheObserver 39@synthesize lastUpdatedIdentifier = _lastUpdatedIdentifier; 40- (void)snapshotCache:(SnapshotCache*)snapshotCache 41 didUpdateSnapshotForIdentifier:(NSString*)identifier { 42 self.lastUpdatedIdentifier = identifier; 43} 44@end 45 46namespace { 47 48class SnapshotCacheTest : public PlatformTest { 49 protected: 50 // Build an array of snapshot IDs and an array of UIImages filled with 51 // random colors. 52 void SetUp() override { 53 PlatformTest::SetUp(); 54 ASSERT_TRUE(scoped_temp_directory_.CreateUniqueTempDir()); 55 snapshotCache_ = [[SnapshotCache alloc] 56 initWithStoragePath:scoped_temp_directory_.GetPath()]; 57 testImages_ = [[NSMutableArray alloc] initWithCapacity:kSnapshotCount]; 58 snapshotIDs_ = [[NSMutableArray alloc] initWithCapacity:kSnapshotCount]; 59 60 CGFloat scale = [snapshotCache_ snapshotScaleForDevice]; 61 UIGraphicsBeginImageContextWithOptions( 62 CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize), NO, scale); 63 CGContextRef context = UIGraphicsGetCurrentContext(); 64 srand(1); 65 66 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 67 UIImage* image = GenerateRandomImage(context); 68 [testImages_ addObject:image]; 69 [snapshotIDs_ 70 addObject:[NSString stringWithFormat:@"SnapshotID-%" PRIuNS, i]]; 71 } 72 73 UIGraphicsEndImageContext(); 74 75 ClearDumpedImages(); 76 } 77 78 void TearDown() override { 79 ClearDumpedImages(); 80 [snapshotCache_ shutdown]; 81 snapshotCache_ = nil; 82 PlatformTest::TearDown(); 83 } 84 85 SnapshotCache* GetSnapshotCache() { return snapshotCache_; } 86 87 // Adds a fake snapshot file into |directory| using |snapshot_id| in the 88 // filename. 89 base::FilePath AddSnapshotFileToDirectory(const base::FilePath directory, 90 NSString* snapshot_id) { 91 // Use the same filename as designated by SnapshotCache. 92 base::FilePath cache_image_path = 93 [GetSnapshotCache() imagePathForSnapshotID:snapshot_id]; 94 base::FilePath image_filename = cache_image_path.BaseName(); 95 base::FilePath image_path = directory.Append(image_filename); 96 97 EXPECT_TRUE(WriteFile(image_path, "")); 98 EXPECT_TRUE(base::PathExists(image_path)); 99 EXPECT_FALSE(base::PathExists(cache_image_path)); 100 return image_path; 101 } 102 103 // Generates an image filled with a random color. 104 UIImage* GenerateRandomImage(CGContextRef context) { 105 CGFloat r = rand() / CGFloat(RAND_MAX); 106 CGFloat g = rand() / CGFloat(RAND_MAX); 107 CGFloat b = rand() / CGFloat(RAND_MAX); 108 CGContextSetRGBStrokeColor(context, r, g, b, 1.0); 109 CGContextSetRGBFillColor(context, r, g, b, 1.0); 110 CGContextFillRect( 111 context, CGRectMake(0.0, 0.0, kSnapshotPixelSize, kSnapshotPixelSize)); 112 return UIGraphicsGetImageFromCurrentImageContext(); 113 } 114 115 // Generates an image of |size|, filled with a random color. 116 UIImage* GenerateRandomImage(CGSize size) { 117 UIGraphicsBeginImageContextWithOptions(size, /*opaque=*/NO, 118 UIScreen.mainScreen.scale); 119 CGContextRef context = UIGraphicsGetCurrentContext(); 120 UIImage* image = GenerateRandomImage(context); 121 UIGraphicsEndImageContext(); 122 return image; 123 } 124 125 // Flushes all the runloops internally used by the snapshot cache. 126 void FlushRunLoops() { 127 base::ThreadPoolInstance::Get()->FlushForTesting(); 128 base::RunLoop().RunUntilIdle(); 129 } 130 131 // This function removes the snapshots both from dictionary and from disk. 132 void ClearDumpedImages() { 133 SnapshotCache* cache = GetSnapshotCache(); 134 135 NSString* snapshotID; 136 for (snapshotID in snapshotIDs_) 137 [cache removeImageWithSnapshotID:snapshotID]; 138 139 FlushRunLoops(); 140 // The above calls to -removeImageWithSnapshotID remove both the color 141 // and grey snapshots for each snapshotID, if they are on disk. However, 142 // ensure we also get rid of the grey snapshots in memory. 143 [cache removeGreyCache]; 144 145 __block BOOL foundImage = NO; 146 __block NSUInteger numCallbacks = 0; 147 for (snapshotID in snapshotIDs_) { 148 base::FilePath path([cache imagePathForSnapshotID:snapshotID]); 149 150 // Checks that the snapshot is not on disk. 151 EXPECT_FALSE(base::PathExists(path)); 152 153 // Check that the snapshot is not in the dictionary. 154 [cache retrieveImageForSnapshotID:snapshotID 155 callback:^(UIImage* image) { 156 ++numCallbacks; 157 if (image) 158 foundImage = YES; 159 }]; 160 } 161 162 // Expect that all the callbacks ran and that none retrieved an image. 163 FlushRunLoops(); 164 EXPECT_EQ([snapshotIDs_ count], numCallbacks); 165 EXPECT_FALSE(foundImage); 166 } 167 168 // Loads kSnapshotCount color images into the cache. If |waitForFilesOnDisk| 169 // is YES, will not return until the images have been written to disk. 170 void LoadAllColorImagesIntoCache(bool waitForFilesOnDisk) { 171 LoadColorImagesIntoCache(kSnapshotCount, waitForFilesOnDisk); 172 } 173 174 // Loads |count| color images into the cache. If |waitForFilesOnDisk| 175 // is YES, will not return until the images have been written to disk. 176 void LoadColorImagesIntoCache(NSUInteger count, bool waitForFilesOnDisk) { 177 SnapshotCache* cache = GetSnapshotCache(); 178 // Put color images in the cache. 179 for (NSUInteger i = 0; i < count; ++i) { 180 @autoreleasepool { 181 UIImage* image = [testImages_ objectAtIndex:i]; 182 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 183 [cache setImage:image withSnapshotID:snapshotID]; 184 } 185 } 186 if (waitForFilesOnDisk) { 187 FlushRunLoops(); 188 for (NSUInteger i = 0; i < count; ++i) { 189 // Check that images are on the disk. 190 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 191 base::FilePath path([cache imagePathForSnapshotID:snapshotID]); 192 EXPECT_TRUE(base::PathExists(path)); 193 } 194 } 195 } 196 197 // Waits for the first |count| grey images for |snapshotIDs_| to be placed in 198 // the cache. 199 void WaitForGreyImagesInCache(NSUInteger count) { 200 SnapshotCache* cache = GetSnapshotCache(); 201 FlushRunLoops(); 202 for (NSUInteger i = 0; i < count; i++) 203 EXPECT_TRUE([cache hasGreyImageInMemory:snapshotIDs_[i]]); 204 } 205 206 // Guesses the order of the color channels in the image. 207 // Supports RGB, BGR, RGBA, BGRA, ARGB, ABGR. 208 // Returns the position of each channel between 0 and 3. 209 void ComputeColorComponents(CGImageRef cgImage, 210 int* red, 211 int* green, 212 int* blue) { 213 CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage); 214 CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage); 215 int byteOrder = bitmapInfo & kCGBitmapByteOrderMask; 216 217 *red = 0; 218 *green = 1; 219 *blue = 2; 220 221 if (alphaInfo == kCGImageAlphaLast || 222 alphaInfo == kCGImageAlphaPremultipliedLast || 223 alphaInfo == kCGImageAlphaNoneSkipLast) { 224 *red = 1; 225 *green = 2; 226 *blue = 3; 227 } 228 229 if (byteOrder != kCGBitmapByteOrder32Host) { 230 int lastChannel = (CGImageGetBitsPerPixel(cgImage) == 24) ? 2 : 3; 231 *red = lastChannel - *red; 232 *green = lastChannel - *green; 233 *blue = lastChannel - *blue; 234 } 235 } 236 237 void TriggerMemoryWarning() { 238 // _performMemoryWarning is a private API and must not be compiled into 239 // official builds. 240#pragma clang diagnostic push 241#pragma clang diagnostic ignored "-Wundeclared-selector" 242 [[UIApplication sharedApplication] 243 performSelector:@selector(_performMemoryWarning)]; 244#pragma clang diagnostic pop 245 } 246 247 web::WebTaskEnvironment task_environment_; 248 base::ScopedTempDir scoped_temp_directory_; 249 SnapshotCache* snapshotCache_; 250 NSMutableArray<NSString*>* snapshotIDs_; 251 NSMutableArray<UIImage*>* testImages_; 252}; 253 254// This test simply put all the snapshots in the cache and then gets them back 255// As the snapshots are kept in memory, the same pointer can be retrieved. 256// This test also checks that images are correctly removed from the disk. 257TEST_F(SnapshotCacheTest, Cache) { 258 SnapshotCache* cache = GetSnapshotCache(); 259 260 NSUInteger expectedCacheSize = MIN(kSnapshotCount, [cache lruCacheMaxSize]); 261 262 // Put all images in the cache. 263 for (NSUInteger i = 0; i < expectedCacheSize; ++i) { 264 UIImage* image = [testImages_ objectAtIndex:i]; 265 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 266 [cache setImage:image withSnapshotID:snapshotID]; 267 } 268 269 // Get images back. 270 __block NSUInteger numberOfCallbacks = 0; 271 for (NSUInteger i = 0; i < expectedCacheSize; ++i) { 272 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 273 UIImage* expectedImage = [testImages_ objectAtIndex:i]; 274 EXPECT_TRUE(expectedImage != nil); 275 [cache retrieveImageForSnapshotID:snapshotID 276 callback:^(UIImage* image) { 277 // Images have not been removed from the 278 // dictionnary. We expect the same pointer. 279 EXPECT_EQ(expectedImage, image); 280 ++numberOfCallbacks; 281 }]; 282 } 283 EXPECT_EQ(expectedCacheSize, numberOfCallbacks); 284} 285 286// This test puts all the snapshots in the cache and flushes them to disk. 287// The snapshots are then reloaded from the disk, and the colors are compared. 288TEST_F(SnapshotCacheTest, SaveToDisk) { 289 SnapshotCache* cache = GetSnapshotCache(); 290 291 // Put all images in the cache. 292 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 293 UIImage* image = [testImages_ objectAtIndex:i]; 294 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 295 [cache setImage:image withSnapshotID:snapshotID]; 296 } 297 FlushRunLoops(); 298 299 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 300 // Check that images are on the disk. 301 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 302 303 base::FilePath path([cache imagePathForSnapshotID:snapshotID]); 304 EXPECT_TRUE(base::PathExists(path)); 305 306 // Check image colors by comparing the first pixel against the reference 307 // image. 308 UIImage* image = 309 [UIImage imageWithContentsOfFile:base::SysUTF8ToNSString(path.value())]; 310 CGImageRef cgImage = [image CGImage]; 311 ASSERT_TRUE(cgImage != nullptr); 312 313 base::ScopedCFTypeRef<CFDataRef> pixelData( 314 CGDataProviderCopyData(CGImageGetDataProvider(cgImage))); 315 const char* pixels = 316 reinterpret_cast<const char*>(CFDataGetBytePtr(pixelData)); 317 EXPECT_TRUE(pixels); 318 319 UIImage* referenceImage = [testImages_ objectAtIndex:i]; 320 CGImageRef referenceCgImage = [referenceImage CGImage]; 321 base::ScopedCFTypeRef<CFDataRef> referenceData( 322 CGDataProviderCopyData(CGImageGetDataProvider(referenceCgImage))); 323 const char* referencePixels = 324 reinterpret_cast<const char*>(CFDataGetBytePtr(referenceData)); 325 EXPECT_TRUE(referencePixels); 326 327 if (pixels != nil && referencePixels != nil) { 328 // Color components may not be in the same order, 329 // because of writing to disk and reloading. 330 int red, green, blue; 331 ComputeColorComponents(cgImage, &red, &green, &blue); 332 333 int referenceRed, referenceGreen, referenceBlue; 334 ComputeColorComponents(referenceCgImage, &referenceRed, &referenceGreen, 335 &referenceBlue); 336 337 // Colors may not be exactly the same (compression or rounding errors) 338 // thus a small difference is allowed. 339 EXPECT_NEAR(referencePixels[referenceRed], pixels[red], 1); 340 EXPECT_NEAR(referencePixels[referenceGreen], pixels[green], 1); 341 EXPECT_NEAR(referencePixels[referenceBlue], pixels[blue], 1); 342 } 343 } 344} 345 346// Tests that migration moves only specified files to the current SnapshotCache 347// folder. Tests that the legacy folder and any remaining contents are deleted. 348TEST_F(SnapshotCacheTest, MigrationMovesFileAndDeletesSource) { 349 base::ScopedTempDir scoped_source_directory; 350 ASSERT_TRUE(scoped_source_directory.CreateUniqueTempDir()); 351 352 SnapshotCache* cache = GetSnapshotCache(); 353 base::FilePath source_folder = scoped_source_directory.GetPath(); 354 355 // This snapshot will be included in migration. 356 NSString* image1_id = [[NSUUID UUID] UUIDString]; 357 base::FilePath source_image1_path = 358 AddSnapshotFileToDirectory(source_folder, image1_id); 359 base::FilePath destination_image1_path = 360 [cache imagePathForSnapshotID:image1_id]; 361 362 // This snapshot will be excluded from migration. 363 NSString* image2_id = [[NSUUID UUID] UUIDString]; 364 base::FilePath source_image2_path = 365 AddSnapshotFileToDirectory(source_folder, image2_id); 366 base::FilePath destination_image2_path = 367 [cache imagePathForSnapshotID:image2_id]; 368 369 NSSet<NSString*>* snapshot_ids = [[NSSet alloc] initWithArray:@[ image1_id ]]; 370 [cache migrateSnapshotsWithIDs:snapshot_ids fromSourcePath:source_folder]; 371 FlushRunLoops(); 372 373 // image1 should have been moved to the destination path. 374 EXPECT_TRUE(base::PathExists(destination_image1_path)); 375 376 // image2 should not have been moved. 377 EXPECT_FALSE(base::PathExists(destination_image2_path)); 378 379 // The legacy folder should have been deleted. 380 EXPECT_FALSE(base::PathExists(source_image1_path)); 381 EXPECT_FALSE(base::PathExists(source_image1_path)); 382 EXPECT_FALSE(base::PathExists(source_folder)); 383} 384 385TEST_F(SnapshotCacheTest, Purge) { 386 SnapshotCache* cache = GetSnapshotCache(); 387 388 // Put all images in the cache. 389 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 390 UIImage* image = [testImages_ objectAtIndex:i]; 391 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 392 [cache setImage:image withSnapshotID:snapshotID]; 393 } 394 395 NSMutableSet* liveSnapshotIDs = [NSMutableSet setWithCapacity:1]; 396 [liveSnapshotIDs addObject:[snapshotIDs_ objectAtIndex:0]]; 397 398 // Purge the cache. 399 [cache purgeCacheOlderThan:(base::Time::Now() - base::TimeDelta::FromHours(1)) 400 keeping:liveSnapshotIDs]; 401 FlushRunLoops(); 402 403 // Check that nothing has been deleted. 404 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 405 // Check that images are on the disk. 406 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 407 408 base::FilePath path([cache imagePathForSnapshotID:snapshotID]); 409 EXPECT_TRUE(base::PathExists(path)); 410 } 411 412 // Purge the cache. 413 [cache purgeCacheOlderThan:base::Time::Now() keeping:liveSnapshotIDs]; 414 FlushRunLoops(); 415 416 // Check that the file have been deleted. 417 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 418 // Check that images are on the disk. 419 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 420 421 base::FilePath path([cache imagePathForSnapshotID:snapshotID]); 422 if (i == 0) 423 EXPECT_TRUE(base::PathExists(path)); 424 else 425 EXPECT_FALSE(base::PathExists(path)); 426 } 427} 428 429// Loads the color images into the cache, and pins two of them. Ensures that 430// only the two pinned IDs remain in memory after a memory warning. 431TEST_F(SnapshotCacheTest, HandleMemoryWarning) { 432 LoadAllColorImagesIntoCache(true); 433 434 SnapshotCache* cache = GetSnapshotCache(); 435 436 NSString* firstPinnedID = [snapshotIDs_ objectAtIndex:4]; 437 NSString* secondPinnedID = [snapshotIDs_ objectAtIndex:6]; 438 NSMutableSet* set = [NSMutableSet set]; 439 [set addObject:firstPinnedID]; 440 [set addObject:secondPinnedID]; 441 cache.pinnedIDs = set; 442 443 TriggerMemoryWarning(); 444 445 EXPECT_EQ(YES, [cache hasImageInMemory:firstPinnedID]); 446 EXPECT_EQ(YES, [cache hasImageInMemory:secondPinnedID]); 447 448 NSString* notPinnedID = [snapshotIDs_ objectAtIndex:2]; 449 EXPECT_FALSE([cache hasImageInMemory:notPinnedID]); 450 451 // Wait for the final image to be pulled off disk. 452 FlushRunLoops(); 453} 454 455// Tests that createGreyCache creates the grey snapshots in the background, 456// from color images in the in-memory cache. When the grey images are all 457// loaded into memory, tests that the request to retrieve the grey snapshot 458// calls the callback immediately. 459// Disabled on simulators because it sometimes crashes. crbug/421425 460#if !TARGET_IPHONE_SIMULATOR 461TEST_F(SnapshotCacheTest, CreateGreyCache) { 462 LoadAllColorImagesIntoCache(true); 463 464 // Request the creation of a grey image cache for all images. 465 SnapshotCache* cache = GetSnapshotCache(); 466 [cache createGreyCache:snapshotIDs_]; 467 468 // Wait for them to be put into the grey image cache. 469 WaitForGreyImagesInCache(kSnapshotCount); 470 471 __block NSUInteger numberOfCallbacks = 0; 472 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 473 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 474 [cache retrieveGreyImageForSnapshotID:snapshotID 475 callback:^(UIImage* image) { 476 EXPECT_TRUE(image); 477 ++numberOfCallbacks; 478 }]; 479 } 480 481 EXPECT_EQ(numberOfCallbacks, kSnapshotCount); 482} 483 484// Same as previous test, except that all the color images are on disk, 485// rather than in memory. 486// Disabled due to the greyImage crash. b/8048597 487TEST_F(SnapshotCacheTest, CreateGreyCacheFromDisk) { 488 LoadAllColorImagesIntoCache(true); 489 490 // Remove color images from in-memory cache. 491 SnapshotCache* cache = GetSnapshotCache(); 492 493 TriggerMemoryWarning(); 494 495 // Request the creation of a grey image cache for all images. 496 [cache createGreyCache:snapshotIDs_]; 497 498 // Wait for them to be put into the grey image cache. 499 WaitForGreyImagesInCache(kSnapshotCount); 500 501 __block NSUInteger numberOfCallbacks = 0; 502 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 503 NSString* snapshotID = [snapshotIDs_ objectAtIndex:i]; 504 [cache retrieveGreyImageForSnapshotID:snapshotID 505 callback:^(UIImage* image) { 506 EXPECT_TRUE(image); 507 ++numberOfCallbacks; 508 }]; 509 } 510 511 EXPECT_EQ(numberOfCallbacks, kSnapshotCount); 512} 513#endif // !TARGET_IPHONE_SIMULATOR 514 515// Tests mostRecentGreyBlock, which is a block to be called when the most 516// recently requested grey image is finally loaded. 517// The test requests three images be cached as grey images. Only the final 518// callback of the three requests should be called. 519// Disabled due to the greyImage crash. b/8048597 520TEST_F(SnapshotCacheTest, MostRecentGreyBlock) { 521 const NSUInteger kNumImages = 3; 522 NSMutableArray* snapshotIDs = 523 [[NSMutableArray alloc] initWithCapacity:kNumImages]; 524 [snapshotIDs addObject:[snapshotIDs_ objectAtIndex:0]]; 525 [snapshotIDs addObject:[snapshotIDs_ objectAtIndex:1]]; 526 [snapshotIDs addObject:[snapshotIDs_ objectAtIndex:2]]; 527 528 SnapshotCache* cache = GetSnapshotCache(); 529 530 // Put 3 images in the cache. 531 LoadColorImagesIntoCache(kNumImages, true); 532 // Make sure the color images are only on disk, to ensure the background 533 // thread is slow enough to queue up the requests. 534 TriggerMemoryWarning(); 535 536 // Enable the grey image cache. 537 [cache createGreyCache:snapshotIDs]; 538 539 // Request the grey versions 540 __block BOOL firstCallbackCalled = NO; 541 __block BOOL secondCallbackCalled = NO; 542 __block BOOL thirdCallbackCalled = NO; 543 [cache greyImageForSnapshotID:[snapshotIDs_ objectAtIndex:0] 544 callback:^(UIImage*) { 545 firstCallbackCalled = YES; 546 }]; 547 [cache greyImageForSnapshotID:[snapshotIDs_ objectAtIndex:1] 548 callback:^(UIImage*) { 549 secondCallbackCalled = YES; 550 }]; 551 [cache greyImageForSnapshotID:[snapshotIDs_ objectAtIndex:2] 552 callback:^(UIImage*) { 553 thirdCallbackCalled = YES; 554 }]; 555 556 // Wait for them to be loaded. 557 WaitForGreyImagesInCache(kNumImages); 558 559 EXPECT_FALSE(firstCallbackCalled); 560 EXPECT_FALSE(secondCallbackCalled); 561 EXPECT_TRUE(thirdCallbackCalled); 562} 563 564// Test the function used to save a grey copy of a color snapshot fully on a 565// background thread when the application is backgrounded. 566TEST_F(SnapshotCacheTest, GreyImageAllInBackground) { 567 LoadAllColorImagesIntoCache(true); 568 569 SnapshotCache* cache = GetSnapshotCache(); 570 571 // Now convert every image into a grey image, on disk, in the background. 572 for (NSUInteger i = 0; i < kSnapshotCount; ++i) { 573 [cache saveGreyInBackgroundForSnapshotID:[snapshotIDs_ objectAtIndex:i]]; 574 } 575 576 // Waits for the grey images for |snapshotIDs_| to be written to disk, which 577 // happens in a background thread. 578 FlushRunLoops(); 579 580 for (NSString* snapshotID in snapshotIDs_) { 581 base::FilePath path([cache greyImagePathForSnapshotID:snapshotID]); 582 EXPECT_TRUE(base::PathExists(path)); 583 base::DeleteFile(path); 584 } 585} 586 587// Verifies that image size and scale are preserved when writing and reading 588// from disk. 589TEST_F(SnapshotCacheTest, SizeAndScalePreservation) { 590 SnapshotCache* cache = GetSnapshotCache(); 591 592 // Create an image with the expected snapshot scale. 593 CGFloat scale = [cache snapshotScaleForDevice]; 594 UIGraphicsBeginImageContextWithOptions( 595 CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize), NO, scale); 596 CGContextRef context = UIGraphicsGetCurrentContext(); 597 UIImage* image = GenerateRandomImage(context); 598 UIGraphicsEndImageContext(); 599 600 // Add the image to the cache then call handle low memory to ensure the image 601 // is read from disk instead of the in-memory cache. 602 NSString* const kSnapshotID = @"foo"; 603 [cache setImage:image withSnapshotID:kSnapshotID]; 604 FlushRunLoops(); // ensure the file is written to disk. 605 TriggerMemoryWarning(); 606 607 // Retrive the image and have the callback verify the size and scale. 608 __block BOOL callbackComplete = NO; 609 [cache 610 retrieveImageForSnapshotID:kSnapshotID 611 callback:^(UIImage* imageFromDisk) { 612 EXPECT_EQ(image.size.width, imageFromDisk.size.width); 613 EXPECT_EQ(image.size.height, 614 imageFromDisk.size.height); 615 EXPECT_EQ(image.scale, imageFromDisk.scale); 616 callbackComplete = YES; 617 }]; 618 FlushRunLoops(); 619 EXPECT_TRUE(callbackComplete); 620} 621 622// Verifies that retina-scale images are deleted properly. 623TEST_F(SnapshotCacheTest, DeleteRetinaImages) { 624 SnapshotCache* cache = GetSnapshotCache(); 625 if ([cache snapshotScaleForDevice] != 2.0) { 626 return; 627 } 628 629 // Create an image with retina scale. 630 UIGraphicsBeginImageContextWithOptions( 631 CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize), NO, 2.0); 632 CGContextRef context = UIGraphicsGetCurrentContext(); 633 UIImage* image = GenerateRandomImage(context); 634 UIGraphicsEndImageContext(); 635 636 // Add the image to the cache then call handle low memory to ensure the image 637 // is read from disk instead of the in-memory cache. 638 NSString* const kSnapshotID = @"foo"; 639 [cache setImage:image withSnapshotID:kSnapshotID]; 640 FlushRunLoops(); // ensure the file is written to disk. 641 TriggerMemoryWarning(); 642 643 // Verify the file was writted with @2x in the file name. 644 base::FilePath retinaFile = [cache imagePathForSnapshotID:kSnapshotID]; 645 EXPECT_TRUE(base::PathExists(retinaFile)); 646 647 // Delete the image. 648 [cache removeImageWithSnapshotID:kSnapshotID]; 649 FlushRunLoops(); // ensure the file is removed. 650 651 EXPECT_FALSE(base::PathExists(retinaFile)); 652} 653 654// Tests that image immediately deletes when calling 655// |-removeImageWithSnapshotID:|. 656TEST_F(SnapshotCacheTest, ImageDeleted) { 657 SnapshotCache* cache = GetSnapshotCache(); 658 UIImage* image = 659 GenerateRandomImage(CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize)); 660 [cache setImage:image withSnapshotID:@"snapshotID"]; 661 base::FilePath image_path = [cache imagePathForSnapshotID:@"snapshotID"]; 662 [cache removeImageWithSnapshotID:@"snapshotID"]; 663 // Give enough time for deletion. 664 FlushRunLoops(); 665 EXPECT_FALSE(base::PathExists(image_path)); 666} 667 668// Tests that all images are deleted when calling |-removeAllImages|. 669TEST_F(SnapshotCacheTest, AllImagesDeleted) { 670 SnapshotCache* cache = GetSnapshotCache(); 671 UIImage* image = 672 GenerateRandomImage(CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize)); 673 [cache setImage:image withSnapshotID:@"snapshotID-1"]; 674 [cache setImage:image withSnapshotID:@"snapshotID-2"]; 675 base::FilePath image_1_path = [cache imagePathForSnapshotID:@"snapshotID-1"]; 676 base::FilePath image_2_path = [cache imagePathForSnapshotID:@"snapshotID-2"]; 677 [cache removeAllImages]; 678 // Give enough time for deletion. 679 FlushRunLoops(); 680 EXPECT_FALSE(base::PathExists(image_1_path)); 681 EXPECT_FALSE(base::PathExists(image_2_path)); 682} 683 684// Tests that observers are notified when a snapshot is cached and removed. 685TEST_F(SnapshotCacheTest, ObserversNotifiedOnSetAndRemoveImage) { 686 SnapshotCache* cache = GetSnapshotCache(); 687 FakeSnapshotCacheObserver* observer = 688 [[FakeSnapshotCacheObserver alloc] init]; 689 [cache addObserver:observer]; 690 EXPECT_NSEQ(nil, observer.lastUpdatedIdentifier); 691 UIImage* image = [testImages_ objectAtIndex:0]; 692 NSString* snapshotID = [snapshotIDs_ objectAtIndex:0]; 693 [cache setImage:image withSnapshotID:snapshotID]; 694 EXPECT_NSEQ(snapshotID, observer.lastUpdatedIdentifier); 695 observer.lastUpdatedIdentifier = nil; 696 [cache removeImageWithSnapshotID:snapshotID]; 697 EXPECT_NSEQ(snapshotID, observer.lastUpdatedIdentifier); 698 [cache removeObserver:observer]; 699} 700} // namespace 701