Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | |
|
Paweł Hajdan Jr.
2015/01/30 12:24:29
nit: 2015
| |
| 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 <Foundation/Foundation.h> | |
| 8 | |
| 9 #include "base/files/file_path.h" | |
| 10 #include "base/files/file_util.h" | |
| 11 #include "base/format_macros.h" | |
| 12 #include "base/location.h" | |
| 13 #include "base/mac/bind_objc_block.h" | |
| 14 #include "base/mac/scoped_nsautorelease_pool.h" | |
| 15 #include "base/strings/sys_string_conversions.h" | |
| 16 #import "base/test/ios/wait_util.h" | |
| 17 #include "base/time/time.h" | |
| 18 #include "ios/chrome/browser/ui/ui_util.h" | |
| 19 #include "ios/web/public/test/test_web_thread.h" | |
| 20 #include "ios/web/public/web_thread.h" | |
| 21 #include "testing/gtest/include/gtest/gtest.h" | |
| 22 #include "testing/gtest_mac.h" | |
| 23 #include "testing/platform_test.h" | |
| 24 | |
| 25 static const NSUInteger kSessionCount = 10; | |
| 26 static const NSUInteger kSnapshotPixelSize = 8; | |
| 27 | |
| 28 // Promote some implementation methods to public. | |
| 29 @interface SnapshotCache (Testing) | |
| 30 - (base::FilePath)imagePathForSessionID:(NSString*)sessionID; | |
| 31 - (base::FilePath)greyImagePathForSessionID:(NSString*)sessionID; | |
| 32 - (void)handleLowMemory; | |
| 33 @end | |
| 34 | |
| 35 @interface SnapshotCache (TestingAdditions) | |
| 36 - (void)flushOperationQueue; | |
| 37 - (BOOL)hasImageInMemory:(NSString*)sessionID; | |
| 38 - (BOOL)hasGreyImageInMemory:(NSString*)sessionID; | |
| 39 @end | |
| 40 | |
| 41 @implementation SnapshotCache (TestingAdditions) | |
| 42 - (void)flushOperationQueue { | |
| 43 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | |
| 44 web::WebThread::PostBlockingPoolSequencedTask( | |
| 45 "SnapshotCacheSequenceToken", FROM_HERE, | |
| 46 base::BindBlock(^{ | |
| 47 dispatch_semaphore_signal(semaphore); | |
| 48 })); | |
| 49 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | |
| 50 dispatch_release(semaphore); | |
| 51 } | |
| 52 - (BOOL)hasImageInMemory:(NSString*)sessionID { | |
| 53 return [imageDictionary_ objectForKey:sessionID] != nil; | |
| 54 } | |
| 55 - (BOOL)hasGreyImageInMemory:(NSString*)sessionID { | |
| 56 return [greyImageDictionary_ objectForKey:sessionID] != nil; | |
| 57 } | |
| 58 @end | |
| 59 | |
| 60 namespace { | |
| 61 | |
| 62 class SnapshotCacheTest : public PlatformTest { | |
| 63 protected: | |
| 64 // Build an array of session names and an array of UIImages filled with | |
| 65 // random colors. | |
| 66 void SetUp() override { | |
| 67 file_thread_.reset(new web::TestWebThread(web::WebThread::FILE)); | |
| 68 file_thread_->Start(); | |
| 69 testImages_.reset([[NSMutableArray alloc] initWithCapacity:kSessionCount]); | |
| 70 testSessions_.reset( | |
| 71 [[NSMutableArray alloc] initWithCapacity:kSessionCount]); | |
| 72 | |
| 73 CGFloat scale = [SnapshotCache snapshotScaleForDevice]; | |
| 74 UIGraphicsBeginImageContextWithOptions( | |
| 75 CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize), NO, scale); | |
| 76 CGContextRef context = UIGraphicsGetCurrentContext(); | |
| 77 srand(1); | |
| 78 | |
| 79 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 80 UIImage* image = GenerateRandomImage(context); | |
| 81 [testImages_ addObject:image]; | |
| 82 [testSessions_ | |
| 83 addObject:[NSString stringWithFormat:@"SessionId-%" PRIuNS, i]]; | |
| 84 } | |
| 85 | |
| 86 UIGraphicsEndImageContext(); | |
| 87 } | |
| 88 | |
| 89 // Generates an image filled with a random color. | |
| 90 UIImage* GenerateRandomImage(CGContextRef context) { | |
| 91 CGFloat r = rand() / CGFloat(RAND_MAX); | |
| 92 CGFloat g = rand() / CGFloat(RAND_MAX); | |
| 93 CGFloat b = rand() / CGFloat(RAND_MAX); | |
| 94 CGContextSetRGBStrokeColor(context, r, g, b, 1.0); | |
| 95 CGContextSetRGBFillColor(context, r, g, b, 1.0); | |
| 96 CGContextFillRect( | |
| 97 context, CGRectMake(0.0, 0.0, kSnapshotPixelSize, kSnapshotPixelSize)); | |
| 98 return UIGraphicsGetImageFromCurrentImageContext(); | |
| 99 } | |
| 100 | |
| 101 // This function removes the snapshots both from dictionary and from disk. | |
| 102 void ClearDumpedImages() { | |
| 103 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 104 | |
| 105 NSString* sessionID; | |
| 106 for (sessionID in testSessions_.get()) | |
| 107 [cache removeImageWithSessionID:sessionID]; | |
| 108 | |
| 109 [cache flushOperationQueue]; | |
| 110 // The above calls to -removeImageWithSessionID remove both the color | |
| 111 // and grey snapshots for each sessionID, if they are on disk. However, | |
| 112 // ensure we also get rid of the grey snapshots in memory. | |
| 113 [cache removeGreyCache]; | |
| 114 | |
| 115 __block BOOL foundImage = NO; | |
| 116 __block NSUInteger numCallbacks = 0; | |
| 117 for (sessionID in testSessions_.get()) { | |
| 118 base::FilePath path([cache imagePathForSessionID:sessionID]); | |
| 119 | |
| 120 // Checks that the snapshot is not on disk. | |
| 121 EXPECT_FALSE(base::PathExists(path)); | |
| 122 | |
| 123 // Check that the snapshot is not in the dictionary. | |
| 124 [cache retrieveImageForSessionID:sessionID | |
| 125 callback:^(UIImage* image) { | |
| 126 numCallbacks++; | |
| 127 if (image) | |
| 128 foundImage = YES; | |
| 129 }]; | |
| 130 } | |
| 131 | |
| 132 // Spin the main message loop until all of the callbacks run. Time out | |
| 133 // after a while to avoid waiting forever. | |
| 134 NSUInteger expectedNumCallbacks = [testSessions_ count]; | |
| 135 NSTimeInterval kTimeoutSec = 3; | |
|
Paweł Hajdan Jr.
2015/01/30 12:24:29
I strongly second Nico's comment about using base/
| |
| 136 base::WaitUntilCondition(^bool() { | |
| 137 return numCallbacks >= expectedNumCallbacks; | |
| 138 }, nullptr, base::TimeDelta::FromSecondsD(kTimeoutSec)); | |
| 139 | |
| 140 // Expect that all the callbacks ran and that none retrieved an image. | |
| 141 EXPECT_EQ(expectedNumCallbacks, numCallbacks); | |
| 142 EXPECT_FALSE(foundImage); | |
| 143 } | |
| 144 | |
| 145 // Loads kSessionCount color images into the cache. If |waitForFilesOnDisk| | |
| 146 // is YES, will not return until the images have been written to disk. | |
| 147 void LoadAllColorImagesIntoCache(bool waitForFilesOnDisk) { | |
| 148 LoadColorImagesIntoCache(kSessionCount, waitForFilesOnDisk); | |
| 149 } | |
| 150 | |
| 151 // Loads |count| color images into the cache. If |waitForFilesOnDisk| | |
| 152 // is YES, will not return until the images have been written to disk. | |
| 153 void LoadColorImagesIntoCache(NSUInteger count, bool waitForFilesOnDisk) { | |
| 154 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 155 // Put color images in the cache. | |
| 156 for (NSUInteger i = 0; i < count; ++i) { | |
| 157 base::mac::ScopedNSAutoreleasePool pool; | |
| 158 UIImage* image = [testImages_ objectAtIndex:i]; | |
| 159 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 160 [cache setImage:image withSessionID:sessionID]; | |
| 161 } | |
| 162 if (waitForFilesOnDisk) { | |
| 163 [cache flushOperationQueue]; | |
| 164 for (NSUInteger i = 0; i < count; ++i) { | |
| 165 // Check that images are on the disk. | |
| 166 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 167 base::FilePath path([cache imagePathForSessionID:sessionID]); | |
| 168 EXPECT_TRUE(base::PathExists(path)); | |
| 169 } | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 // Waits for the grey images for the sessions in |testSessions_| to be written | |
| 174 // to disk, which happens in a background thread. | |
| 175 void WaitForAllGreyImagesOnDisk() { | |
| 176 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 177 NSString* sessionID; | |
| 178 NSTimeInterval kTimeoutSec = 3; | |
| 179 for (sessionID in testSessions_.get()) { | |
| 180 base::FilePath path([cache greyImagePathForSessionID:sessionID]); | |
| 181 base::WaitUntilCondition(^bool() { | |
| 182 return base::PathExists(path); | |
| 183 }, nullptr, base::TimeDelta::FromSecondsD(kTimeoutSec)); | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 // Waits for the first |count| grey images for sessions in |testSessions_| | |
| 188 // to be placed in the cache. | |
| 189 void WaitForGreyImagesInCache(NSUInteger count) { | |
| 190 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 191 NSTimeInterval kTimeoutSec = 3; | |
| 192 for (NSUInteger i = 0; i < count; i++) { | |
| 193 base::WaitUntilCondition(^bool() { | |
| 194 return [cache hasGreyImageInMemory:testSessions_[i]]; | |
| 195 }, nullptr, base::TimeDelta::FromSecondsD(kTimeoutSec)); | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 // Guesses the order of the color channels in the image. | |
| 200 // Supports RGB, BGR, RGBA, BGRA, ARGB, ABGR. | |
| 201 // Returns the position of each channel between 0 and 3. | |
| 202 void ComputeColorComponents(CGImageRef cgImage, | |
| 203 int* red, | |
| 204 int* green, | |
| 205 int* blue) { | |
| 206 CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage); | |
| 207 CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage); | |
| 208 int byteOrder = bitmapInfo & kCGBitmapByteOrderMask; | |
| 209 | |
| 210 *red = 0; | |
| 211 *green = 1; | |
| 212 *blue = 2; | |
| 213 | |
| 214 if (alphaInfo == kCGImageAlphaLast || | |
| 215 alphaInfo == kCGImageAlphaPremultipliedLast || | |
| 216 alphaInfo == kCGImageAlphaNoneSkipLast) { | |
| 217 *red = 1; | |
| 218 *green = 2; | |
| 219 *blue = 3; | |
| 220 } | |
| 221 | |
| 222 if (byteOrder != kCGBitmapByteOrder32Host) { | |
| 223 int lastChannel = (CGImageGetBitsPerPixel(cgImage) == 24) ? 2 : 3; | |
| 224 *red = lastChannel - *red; | |
| 225 *green = lastChannel - *green; | |
| 226 *blue = lastChannel - *blue; | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 const char* GetPixelData(CGImageRef cgImage) { | |
| 231 CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(cgImage)); | |
| 232 return reinterpret_cast<const char*>(CFDataGetBytePtr(data)); | |
| 233 } | |
| 234 | |
| 235 base::scoped_nsobject<NSMutableArray> testSessions_; | |
| 236 base::scoped_nsobject<NSMutableArray> testImages_; | |
| 237 scoped_ptr<web::TestWebThread> file_thread_; | |
| 238 }; | |
| 239 | |
| 240 // This test simply put all the snapshots in the cache and then gets them back | |
| 241 // As the snapshots are kept in memory, the same pointer can be retrieved. | |
| 242 // This test also checks that images are correctly removed from the disk. | |
| 243 TEST_F(SnapshotCacheTest, Cache) { | |
| 244 // Don't run on tablets because color snapshots are not cached so this test | |
| 245 // can't compare the UIImage pointers directly. | |
| 246 if (IsIPadIdiom()) { | |
| 247 return; | |
| 248 } | |
| 249 | |
| 250 ClearDumpedImages(); | |
| 251 | |
| 252 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 253 | |
| 254 // Put all images in the cache. | |
| 255 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 256 UIImage* image = [testImages_ objectAtIndex:i]; | |
| 257 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 258 [cache setImage:image withSessionID:sessionID]; | |
| 259 } | |
| 260 | |
| 261 // Get images back. | |
| 262 __block NSUInteger numberOfCallbacks = 0; | |
| 263 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 264 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 265 UIImage* expectedImage = [testImages_ objectAtIndex:i]; | |
| 266 EXPECT_TRUE(expectedImage != nil); | |
| 267 [cache retrieveImageForSessionID:sessionID | |
| 268 callback:^(UIImage* image) { | |
| 269 // Images have not been removed from the | |
| 270 // dictionnary. We expect the same pointer. | |
| 271 EXPECT_EQ(expectedImage, image); | |
| 272 ++numberOfCallbacks; | |
| 273 }]; | |
| 274 } | |
| 275 EXPECT_EQ(kSessionCount, numberOfCallbacks); | |
| 276 | |
| 277 ClearDumpedImages(); | |
| 278 } | |
| 279 | |
| 280 // This test puts all the snapshots in the cache and flushes them to disk. | |
| 281 // The snapshots are then reloaded from the disk, and the colors are compared. | |
| 282 TEST_F(SnapshotCacheTest, SaveToDisk) { | |
| 283 ClearDumpedImages(); | |
| 284 | |
| 285 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 286 | |
| 287 // Put all images in the cache. | |
| 288 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 289 UIImage* image = [testImages_ objectAtIndex:i]; | |
| 290 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 291 [cache setImage:image withSessionID:sessionID]; | |
| 292 } | |
| 293 [cache flushOperationQueue]; | |
| 294 | |
| 295 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 296 // Check that images are on the disk. | |
| 297 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 298 | |
| 299 base::FilePath path([cache imagePathForSessionID:sessionID]); | |
| 300 EXPECT_TRUE(base::PathExists(path)); | |
| 301 | |
| 302 // Check image colors by comparing the first pixel against the reference | |
| 303 // image. | |
| 304 UIImage* image = | |
| 305 [UIImage imageWithContentsOfFile:base::SysUTF8ToNSString(path.value())]; | |
| 306 CGImageRef cgImage = [image CGImage]; | |
| 307 const char* pixels = GetPixelData(cgImage); | |
| 308 EXPECT_TRUE(pixels); | |
| 309 | |
| 310 UIImage* referenceImage = [testImages_ objectAtIndex:i]; | |
| 311 CGImageRef referenceCgImage = [referenceImage CGImage]; | |
| 312 const char* referencePixels = GetPixelData(referenceCgImage); | |
| 313 EXPECT_TRUE(referencePixels); | |
| 314 | |
| 315 if (pixels != nil && referencePixels != nil) { | |
| 316 // Color components may not be in the same order, | |
| 317 // because of writing to disk and reloading. | |
| 318 int red, green, blue; | |
| 319 ComputeColorComponents(cgImage, &red, &green, &blue); | |
| 320 | |
| 321 int referenceRed, referenceGreen, referenceBlue; | |
| 322 ComputeColorComponents(referenceCgImage, &referenceRed, &referenceGreen, | |
| 323 &referenceBlue); | |
| 324 | |
| 325 // Colors may not be exactly the same (compression or rounding errors) | |
| 326 // thus a small difference is allowed. | |
| 327 EXPECT_NEAR(referencePixels[referenceRed], pixels[red], 1); | |
| 328 EXPECT_NEAR(referencePixels[referenceGreen], pixels[green], 1); | |
| 329 EXPECT_NEAR(referencePixels[referenceBlue], pixels[blue], 1); | |
| 330 } | |
| 331 } | |
| 332 | |
| 333 ClearDumpedImages(); | |
| 334 } | |
| 335 | |
| 336 TEST_F(SnapshotCacheTest, PurgeTest) { | |
| 337 ClearDumpedImages(); | |
| 338 | |
| 339 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 340 | |
| 341 // Put all images in the cache. | |
| 342 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 343 UIImage* image = [testImages_ objectAtIndex:i]; | |
| 344 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 345 [cache setImage:image withSessionID:sessionID]; | |
| 346 } | |
| 347 | |
| 348 NSMutableSet* liveSessions = [NSMutableSet setWithCapacity:1]; | |
| 349 [liveSessions addObject:[testSessions_ objectAtIndex:0]]; | |
| 350 | |
| 351 // Purge the cache. | |
| 352 [cache purgeCacheOlderThan:(base::Time::Now() - base::TimeDelta::FromHours(1)) | |
| 353 keeping:liveSessions]; | |
| 354 [cache flushOperationQueue]; | |
| 355 | |
| 356 // Check that nothing has been deleted. | |
| 357 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 358 // Check that images are on the disk. | |
| 359 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 360 | |
| 361 base::FilePath path([cache imagePathForSessionID:sessionID]); | |
| 362 EXPECT_TRUE(base::PathExists(path)); | |
| 363 } | |
| 364 | |
| 365 // Purge the cache. | |
| 366 [cache purgeCacheOlderThan:base::Time::Now() keeping:liveSessions]; | |
| 367 [cache flushOperationQueue]; | |
| 368 | |
| 369 // Check that the file have been deleted. | |
| 370 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 371 // Check that images are on the disk. | |
| 372 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 373 | |
| 374 base::FilePath path([cache imagePathForSessionID:sessionID]); | |
| 375 if (i == 0) | |
| 376 EXPECT_TRUE(base::PathExists(path)); | |
| 377 else | |
| 378 EXPECT_FALSE(base::PathExists(path)); | |
| 379 } | |
| 380 ClearDumpedImages(); | |
| 381 } | |
| 382 | |
| 383 // Loads the color images into the cache, and pins two of them. Ensures that | |
| 384 // only the two pinned IDs remain in memory after a call to -handleLowMemory. | |
| 385 TEST_F(SnapshotCacheTest, testHandleLowMemory) { | |
| 386 ClearDumpedImages(); | |
| 387 LoadAllColorImagesIntoCache(true); | |
| 388 | |
| 389 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 390 | |
| 391 NSString* firstPinnedID = [testSessions_ objectAtIndex:4]; | |
| 392 NSString* secondPinnedID = [testSessions_ objectAtIndex:6]; | |
| 393 NSMutableSet* set = [NSMutableSet set]; | |
| 394 [set addObject:firstPinnedID]; | |
| 395 [set addObject:secondPinnedID]; | |
| 396 cache.pinnedIDs = set; | |
| 397 | |
| 398 [cache handleLowMemory]; | |
| 399 | |
| 400 BOOL expectedValue = YES; | |
| 401 if (IsIPadIdiom()) { | |
| 402 expectedValue = NO; | |
| 403 } | |
| 404 EXPECT_EQ(expectedValue, [cache hasImageInMemory:firstPinnedID]); | |
| 405 EXPECT_EQ(expectedValue, [cache hasImageInMemory:secondPinnedID]); | |
| 406 | |
| 407 NSString* notPinnedID = [testSessions_ objectAtIndex:2]; | |
| 408 EXPECT_FALSE([cache hasImageInMemory:notPinnedID]); | |
| 409 | |
| 410 // Wait for the final image to be pulled off disk. | |
| 411 [cache flushOperationQueue]; | |
| 412 ClearDumpedImages(); | |
| 413 } | |
| 414 | |
| 415 // Tests that createGreyCache creates the grey snapshots in the background, | |
| 416 // from color images in the in-memory cache. When the grey images are all | |
| 417 // loaded into memory, tests that the request to retrieve the grey snapshot | |
| 418 // calls the callback immediately. | |
| 419 // Disabled on simulators because it sometimes crashes. crbug/421425 | |
| 420 #if !TARGET_IPHONE_SIMULATOR | |
| 421 TEST_F(SnapshotCacheTest, testCreateGreyCache) { | |
| 422 ClearDumpedImages(); | |
| 423 LoadAllColorImagesIntoCache(true); | |
| 424 | |
| 425 // Request the creation of a grey image cache for all images. | |
| 426 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 427 [cache createGreyCache:testSessions_]; | |
| 428 | |
| 429 // Wait for them to be put into the grey image cache. | |
| 430 WaitForGreyImagesInCache(kSessionCount); | |
| 431 | |
| 432 __block NSUInteger numberOfCallbacks = 0; | |
| 433 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 434 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 435 [cache retrieveGreyImageForSessionID:sessionID | |
| 436 callback:^(UIImage* image) { | |
| 437 EXPECT_TRUE(image); | |
| 438 ++numberOfCallbacks; | |
| 439 }]; | |
| 440 } | |
| 441 | |
| 442 EXPECT_EQ(numberOfCallbacks, kSessionCount); | |
| 443 ClearDumpedImages(); | |
| 444 } | |
| 445 | |
| 446 // Same as previous test, except that all the color images are on disk, | |
| 447 // rather than in memory. | |
| 448 // Disabled due to the greyImage crash. b/8048597 | |
| 449 TEST_F(SnapshotCacheTest, testCreateGreyCacheFromDisk) { | |
| 450 ClearDumpedImages(); | |
| 451 LoadAllColorImagesIntoCache(true); | |
| 452 | |
| 453 // Remove color images from in-memory cache. | |
| 454 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 455 [cache handleLowMemory]; | |
| 456 | |
| 457 // Request the creation of a grey image cache for all images. | |
| 458 [cache createGreyCache:testSessions_]; | |
| 459 | |
| 460 // Wait for them to be put into the grey image cache. | |
| 461 WaitForGreyImagesInCache(kSessionCount); | |
| 462 | |
| 463 __block NSUInteger numberOfCallbacks = 0; | |
| 464 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 465 NSString* sessionID = [testSessions_ objectAtIndex:i]; | |
| 466 [cache retrieveGreyImageForSessionID:sessionID | |
| 467 callback:^(UIImage* image) { | |
| 468 EXPECT_TRUE(image); | |
| 469 ++numberOfCallbacks; | |
| 470 }]; | |
| 471 } | |
| 472 | |
| 473 EXPECT_EQ(numberOfCallbacks, kSessionCount); | |
| 474 ClearDumpedImages(); | |
| 475 } | |
| 476 #endif // !TARGET_IPHONE_SIMULATOR | |
| 477 | |
| 478 // Tests mostRecentGreyBlock, which is a block to be called when the most | |
| 479 // recently requested grey image is finally loaded. | |
| 480 // The test requests three images be cached as grey images. Only the final | |
| 481 // callback of the three requests should be called. | |
| 482 // Disabled due to the greyImage crash. b/8048597 | |
| 483 TEST_F(SnapshotCacheTest, testMostRecentGreyBlock) { | |
| 484 ClearDumpedImages(); | |
| 485 | |
| 486 const NSUInteger kNumImages = 3; | |
| 487 base::scoped_nsobject<NSMutableArray> sessionIDs( | |
| 488 [[NSMutableArray alloc] initWithCapacity:kNumImages]); | |
| 489 [sessionIDs addObject:[testSessions_ objectAtIndex:0]]; | |
| 490 [sessionIDs addObject:[testSessions_ objectAtIndex:1]]; | |
| 491 [sessionIDs addObject:[testSessions_ objectAtIndex:2]]; | |
| 492 | |
| 493 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 494 | |
| 495 // Put 3 images in the cache. | |
| 496 LoadColorImagesIntoCache(kNumImages, true); | |
| 497 // Make sure the color images are only on disk, to ensure the background | |
| 498 // thread is slow enough to queue up the requests. | |
| 499 [cache handleLowMemory]; | |
| 500 | |
| 501 // Enable the grey image cache. | |
| 502 [cache createGreyCache:sessionIDs]; | |
| 503 | |
| 504 // Request the grey versions | |
| 505 __block BOOL firstCallbackCalled = NO; | |
| 506 __block BOOL secondCallbackCalled = NO; | |
| 507 __block BOOL thirdCallbackCalled = NO; | |
| 508 [cache greyImageForSessionID:[testSessions_ objectAtIndex:0] | |
| 509 callback:^(UIImage*) { | |
| 510 firstCallbackCalled = YES; | |
| 511 }]; | |
| 512 [cache greyImageForSessionID:[testSessions_ objectAtIndex:1] | |
| 513 callback:^(UIImage*) { | |
| 514 secondCallbackCalled = YES; | |
| 515 }]; | |
| 516 [cache greyImageForSessionID:[testSessions_ objectAtIndex:2] | |
| 517 callback:^(UIImage*) { | |
| 518 thirdCallbackCalled = YES; | |
| 519 }]; | |
| 520 | |
| 521 // Wait for them to be loaded. | |
| 522 WaitForGreyImagesInCache(kNumImages); | |
| 523 | |
| 524 EXPECT_FALSE(firstCallbackCalled); | |
| 525 EXPECT_FALSE(secondCallbackCalled); | |
| 526 EXPECT_TRUE(thirdCallbackCalled); | |
| 527 | |
| 528 ClearDumpedImages(); | |
| 529 } | |
| 530 | |
| 531 // Test the function used to save a grey copy of a color snapshot fully on a | |
| 532 // background thread when the application is backgrounded. | |
| 533 // Disabled due to the greyImage crash. b/8048597 | |
| 534 TEST_F(SnapshotCacheTest, testGreyImageAllInBackground) { | |
| 535 ClearDumpedImages(); | |
| 536 LoadAllColorImagesIntoCache(true); | |
| 537 | |
| 538 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 539 | |
| 540 // Now convert every image into a grey image, on disk, in the background. | |
| 541 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 542 [cache saveGreyInBackgroundForSessionID:[testSessions_ objectAtIndex:i]]; | |
| 543 } | |
| 544 | |
| 545 WaitForAllGreyImagesOnDisk(); | |
| 546 | |
| 547 for (NSUInteger i = 0; i < kSessionCount; ++i) { | |
| 548 base::FilePath greyImagePath( | |
| 549 [cache greyImagePathForSessionID:[testSessions_ objectAtIndex:i]]); | |
| 550 EXPECT_TRUE(base::PathExists(greyImagePath)); | |
| 551 base::DeleteFile(greyImagePath, false); | |
| 552 } | |
| 553 | |
| 554 ClearDumpedImages(); | |
| 555 } | |
| 556 | |
| 557 // Verifies that image size and scale are preserved when writing and reading | |
| 558 // from disk. | |
| 559 TEST_F(SnapshotCacheTest, TestSizeAndScalePreservation) { | |
| 560 // Create an image with the expected snapshot scale. | |
| 561 CGFloat scale = [SnapshotCache snapshotScaleForDevice]; | |
| 562 UIGraphicsBeginImageContextWithOptions( | |
| 563 CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize), NO, scale); | |
| 564 CGContextRef context = UIGraphicsGetCurrentContext(); | |
| 565 UIImage* image = GenerateRandomImage(context); | |
| 566 UIGraphicsEndImageContext(); | |
| 567 | |
| 568 // Add the image to the cache then call handle low memory to ensure the image | |
| 569 // is read from disk instead of the in-memory cache. | |
| 570 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 571 NSString* const kSession = @"foo"; | |
| 572 [cache setImage:image withSessionID:kSession]; | |
| 573 [cache flushOperationQueue]; // ensure the file is written to disk. | |
| 574 [cache handleLowMemory]; | |
| 575 | |
| 576 // Retrive the image and have the callback verify the size and scale. | |
| 577 __block BOOL callbackComplete = NO; | |
| 578 [cache retrieveImageForSessionID:kSession | |
| 579 callback:^(UIImage* imageFromDisk) { | |
| 580 EXPECT_EQ(image.size.width, | |
| 581 imageFromDisk.size.width); | |
| 582 EXPECT_EQ(image.size.height, | |
| 583 imageFromDisk.size.height); | |
| 584 EXPECT_EQ(image.scale, imageFromDisk.scale); | |
| 585 callbackComplete = YES; | |
| 586 }]; | |
| 587 | |
| 588 // Spin the main message loop until the callback is run. Time out after a | |
| 589 // a while to avoid waiting forever. | |
| 590 const NSTimeInterval kTimeoutSec = 3; | |
| 591 base::WaitUntilCondition(^bool() { | |
| 592 return callbackComplete; | |
| 593 }, nullptr, base::TimeDelta::FromSecondsD(kTimeoutSec)); | |
| 594 EXPECT_TRUE(callbackComplete); | |
| 595 } | |
| 596 | |
| 597 // Verifies that retina-scale images are deleted properly. | |
| 598 TEST_F(SnapshotCacheTest, TestDeleteRetinaImages) { | |
| 599 if ([SnapshotCache snapshotScaleForDevice] != 2.0) { | |
| 600 return; | |
| 601 } | |
| 602 | |
| 603 // Create an image with retina scale. | |
| 604 UIGraphicsBeginImageContextWithOptions( | |
| 605 CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize), NO, 2.0); | |
| 606 CGContextRef context = UIGraphicsGetCurrentContext(); | |
| 607 UIImage* image = GenerateRandomImage(context); | |
| 608 UIGraphicsEndImageContext(); | |
| 609 | |
| 610 // Add the image to the cache then call handle low memory to ensure the image | |
| 611 // is read from disk instead of the in-memory cache. | |
| 612 SnapshotCache* cache = [SnapshotCache sharedInstance]; | |
| 613 NSString* const kSession = @"foo"; | |
| 614 [cache setImage:image withSessionID:kSession]; | |
| 615 [cache flushOperationQueue]; // ensure the file is written to disk. | |
| 616 [cache handleLowMemory]; | |
| 617 | |
| 618 // Verify the file was writted with @2x in the file name. | |
| 619 base::FilePath retinaFile = [cache imagePathForSessionID:kSession]; | |
| 620 EXPECT_TRUE(base::PathExists(retinaFile)); | |
| 621 | |
| 622 // Delete the image. | |
| 623 [cache removeImageWithSessionID:kSession]; | |
| 624 [cache flushOperationQueue]; // ensure the file is removed. | |
| 625 | |
| 626 EXPECT_FALSE(base::PathExists(retinaFile)); | |
| 627 } | |
| 628 | |
| 629 } // namespace | |
| OLD | NEW |