// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
#import "base/apple/foundation_util.h"
#import "base/files/file_path.h"
#import "base/files/file_util.h"
#import "base/files/scoped_temp_dir.h"
#import "base/functional/bind.h"
#import "base/functional/callback_forward.h"
#import "base/run_loop.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "components/sessions/core/session_id.h"
#import "ios/chrome/browser/snapshots/model/features.h"
#import "ios/chrome/browser/snapshots/model/legacy_image_file_manager.h"
#import "ios/chrome/browser/snapshots/model/model_swift.h"
#import "ios/chrome/browser/snapshots/model/snapshot_id.h"
#import "ios/chrome/browser/snapshots/model/snapshot_id_wrapper.h"
#import "ios/chrome/browser/snapshots/model/snapshot_scale.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
namespace {
const NSUInteger kSnapshotCount = 10;
const NSUInteger kSnapshotPixelSize = 8;
class LegacyImageFileManagerTest : public PlatformTest {
protected:
void SetUp() override {
PlatformTest::SetUp();
ASSERT_TRUE(CreateImageFileManager());
}
void TearDown() override {
ClearAllImages();
[image_file_manager_ shutdown];
image_file_manager_ = nil;
PlatformTest::TearDown();
}
// Build an array of snapshot IDs and an array of UIImages filled with
// random colors.
[[nodiscard]] bool CreateImageFileManager() {
DCHECK(!image_file_manager_);
if (!scoped_temp_directory_.CreateUniqueTempDir()) {
return false;
}
image_file_manager_ = [[LegacyImageFileManager alloc]
initWithStoragePath:scoped_temp_directory_.GetPath()
legacyPath:base::FilePath()];
CGFloat scale = [SnapshotImageScale floatImageScaleForDevice];
srand(1);
for (NSUInteger i = 0; i < kSnapshotCount; ++i) {
test_images_.insert(std::make_pair(
SnapshotID(SessionID::NewUnique().id()), GenerateRandomImage(scale)));
}
return true;
}
LegacyImageFileManager* GetImageFileManager() {
DCHECK(image_file_manager_);
return image_file_manager_;
}
// Generates an image of `scale`, filled with a random color.
UIImage* GenerateRandomImage(CGFloat scale) {
CGSize size = CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize);
UIGraphicsImageRendererFormat* format =
[UIGraphicsImageRendererFormat preferredFormat];
format.scale = scale;
format.opaque = NO;
UIGraphicsImageRenderer* renderer =
[[UIGraphicsImageRenderer alloc] initWithSize:size format:format];
return [renderer
imageWithActions:^(UIGraphicsImageRendererContext* UIContext) {
CGContextRef context = UIContext.CGContext;
CGFloat r = rand() / CGFloat(RAND_MAX);
CGFloat g = rand() / CGFloat(RAND_MAX);
CGFloat b = rand() / CGFloat(RAND_MAX);
CGContextSetRGBStrokeColor(context, r, g, b, 1.0);
CGContextSetRGBFillColor(context, r, g, b, 1.0);
CGContextFillRect(context, CGRectMake(0.0, 0.0, kSnapshotPixelSize,
kSnapshotPixelSize));
}];
}
// Flushes all the runloops internally used by the snapshot storage. This is
// done by asking to retrieve a non-existent image from disk and blocking
// until the callback is invoked.
void FlushRunLoops() {
if (!image_file_manager_) {
return;
}
base::RunLoop run_loop;
[image_file_manager_
readImageWithSnapshotID:SnapshotID(SessionID::NewUnique().id())
completion:base::BindOnce(base::IgnoreArgs<UIImage*>(
run_loop.QuitClosure()))];
run_loop.Run();
}
// This function removes all snapshots stored in disk.
void ClearAllImages() {
if (!image_file_manager_) {
return;
}
[image_file_manager_ removeAllImages];
FlushRunLoops();
__block BOOL foundImage = NO;
__block NSUInteger numCallbacks = 0;
for (auto [snapshot_id, _] : test_images_) {
const base::FilePath path =
[image_file_manager_ imagePathForSnapshotID:snapshot_id];
// Checks that the snapshot is not on disk.
EXPECT_FALSE(base::PathExists(path));
// Check that the snapshot is not in the dictionary.
[image_file_manager_
readImageWithSnapshotID:snapshot_id
completion:base::BindOnce(^(UIImage* image) {
++numCallbacks;
if (image) {
foundImage = YES;
}
})];
}
// Expect that all the callbacks ran and that none retrieved an image.
FlushRunLoops();
EXPECT_EQ(test_images_.size(), numCallbacks);
EXPECT_FALSE(foundImage);
}
// Guesses the order of the color channels in the image.
// Supports RGB, BGR, RGBA, BGRA, ARGB, ABGR.
// Returns the position of each channel between 0 and 3.
void ComputeColorComponents(CGImageRef cgImage,
int* red,
int* green,
int* blue) {
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
int byteOrder = bitmapInfo & kCGBitmapByteOrderMask;
*red = 0;
*green = 1;
*blue = 2;
if (alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaNoneSkipLast) {
*red = 1;
*green = 2;
*blue = 3;
}
if (byteOrder != kCGBitmapByteOrder32Host) {
int lastChannel = (CGImageGetBitsPerPixel(cgImage) == 24) ? 2 : 3;
*red = lastChannel - *red;
*green = lastChannel - *green;
*blue = lastChannel - *blue;
}
}
web::WebTaskEnvironment task_environment_;
base::ScopedTempDir scoped_temp_directory_;
LegacyImageFileManager* image_file_manager_;
std::map<SnapshotID, UIImage*> test_images_;
};
// Tests that the color of all snapshots in the storage reloaded from disk.
TEST_F(LegacyImageFileManagerTest, CheckImageColors) {
LegacyImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// Put all images to disk.
for (auto [snapshot_id, image] : test_images_) {
[file_manager writeImage:image withSnapshotID:snapshot_id];
}
FlushRunLoops();
for (auto [snapshot_id, reference_image] : test_images_) {
// Check that images are on the disk.
const base::FilePath path =
[file_manager imagePathForSnapshotID:snapshot_id];
EXPECT_TRUE(base::PathExists(path));
// Check image colors by comparing the first pixel against the reference
// image.
UIImage* image =
[UIImage imageWithContentsOfFile:base::SysUTF8ToNSString(path.value())];
CGImageRef cgImage = [image CGImage];
ASSERT_TRUE(cgImage != nullptr);
base::apple::ScopedCFTypeRef<CFDataRef> pixelData(
CGDataProviderCopyData(CGImageGetDataProvider(cgImage)));
const char* pixels =
reinterpret_cast<const char*>(CFDataGetBytePtr(pixelData.get()));
EXPECT_TRUE(pixels);
CGImageRef referenceCgImage = [reference_image CGImage];
base::apple::ScopedCFTypeRef<CFDataRef> referenceData(
CGDataProviderCopyData(CGImageGetDataProvider(referenceCgImage)));
const char* referencePixels =
reinterpret_cast<const char*>(CFDataGetBytePtr(referenceData.get()));
EXPECT_TRUE(referencePixels);
if (pixels != nil && referencePixels != nil) {
// Color components may not be in the same order,
// because of writing to disk and reloading.
int red, green, blue;
ComputeColorComponents(cgImage, &red, &green, &blue);
int referenceRed, referenceGreen, referenceBlue;
ComputeColorComponents(referenceCgImage, &referenceRed, &referenceGreen,
&referenceBlue);
// Colors may not be exactly the same (compression or rounding errors)
// thus a small difference is allowed.
EXPECT_NEAR(referencePixels[referenceRed], pixels[red], 1);
EXPECT_NEAR(referencePixels[referenceGreen], pixels[green], 1);
EXPECT_NEAR(referencePixels[referenceBlue], pixels[blue], 1);
}
}
}
// Tests that old images are deleted.
TEST_F(LegacyImageFileManagerTest, PurgeImagesOlderThan) {
LegacyImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// Put all images in the storage.
for (auto [snapshot_id, image] : test_images_) {
[file_manager writeImage:image withSnapshotID:snapshot_id];
}
ASSERT_FALSE(test_images_.empty());
std::vector<SnapshotID> liveSnapshotIDs = {test_images_.begin()->first};
// Purge the storage.
[file_manager purgeImagesOlderThan:(base::Time::Now() - base::Hours(1))
keeping:liveSnapshotIDs];
FlushRunLoops();
// Check that nothing has been deleted.
for (auto [snapshot_id, _] : test_images_) {
// Check that images are on the disk.
const base::FilePath path =
[file_manager imagePathForSnapshotID:snapshot_id];
EXPECT_TRUE(base::PathExists(path));
}
// Purge the storage.
[file_manager purgeImagesOlderThan:base::Time::Now() keeping:liveSnapshotIDs];
FlushRunLoops();
// Check that the file have been deleted.
for (auto [snapshot_id, _] : test_images_) {
// Check that images are on the disk.
const base::FilePath path =
[file_manager imagePathForSnapshotID:snapshot_id];
if (snapshot_id == *liveSnapshotIDs.begin()) {
EXPECT_TRUE(base::PathExists(path));
} else {
EXPECT_FALSE(base::PathExists(path));
}
}
}
// Tests that migration code correctly rename the specified files and leave
// the other files untouched.
TEST_F(LegacyImageFileManagerTest, RenameSnapshots) {
LegacyImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// This snapshot will be renamed.
NSString* image1_id = [[NSUUID UUID] UUIDString];
base::FilePath image1_path =
[file_manager legacyImagePathForSnapshotID:image1_id];
ASSERT_TRUE(base::WriteFile(image1_path, "image1"));
// This snapshot will not be renamed.
NSString* image2_id = [[NSUUID UUID] UUIDString];
base::FilePath image2_path =
[file_manager legacyImagePathForSnapshotID:image2_id];
ASSERT_TRUE(base::WriteFile(image2_path, "image2"));
SnapshotID new_id = SnapshotID(SessionID::NewUnique().id());
[file_manager renameSnapshotsWithIDs:@[ image1_id ] toIDs:{new_id}];
FlushRunLoops();
// image1 should have been moved.
EXPECT_FALSE(base::PathExists(image1_path));
EXPECT_TRUE(base::PathExists([file_manager imagePathForSnapshotID:new_id]));
// image2 should not have moved.
EXPECT_TRUE(base::PathExists(image2_path));
}
// Tests that image size and scale are preserved when writing and reading
// from disk.
TEST_F(LegacyImageFileManagerTest, SizeAndScalePreservation) {
LegacyImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// Create an image with the expected snapshot scale.
CGFloat scale = [SnapshotImageScale floatImageScaleForDevice];
UIImage* image = GenerateRandomImage(scale);
// Add the image to the storage and ensure the file is written to disk.
const SnapshotID kSnapshotID(SessionID::NewUnique().id());
[file_manager writeImage:image withSnapshotID:kSnapshotID];
FlushRunLoops();
// Retrive the image and have the callback verify the size and scale.
__block BOOL callbackComplete = NO;
[file_manager
readImageWithSnapshotID:kSnapshotID
completion:base::BindOnce(^(UIImage* imageFromDisk) {
EXPECT_EQ(image.size.width, imageFromDisk.size.width);
EXPECT_EQ(image.size.height, imageFromDisk.size.height);
EXPECT_EQ(image.scale, imageFromDisk.scale);
callbackComplete = YES;
})];
FlushRunLoops();
EXPECT_TRUE(callbackComplete);
}
// Tests that retina-scale images are deleted properly.
TEST_F(LegacyImageFileManagerTest, DeleteRetinaImages) {
LegacyImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
if ([SnapshotImageScale floatImageScaleForDevice] != 2.0) {
return;
}
// Create an image with retina scale.
UIImage* image = GenerateRandomImage(2.0);
// Add the image and ensure the file is written to disk.
const SnapshotID kSnapshotID(SessionID::NewUnique().id());
[file_manager writeImage:image withSnapshotID:kSnapshotID];
FlushRunLoops();
// Verify the file was written with @2x in the file name.
base::FilePath retinaFile = [file_manager imagePathForSnapshotID:kSnapshotID];
EXPECT_TRUE(base::PathExists(retinaFile));
// Delete the image and ensure the file is removed.
[file_manager removeImageWithSnapshotID:kSnapshotID];
FlushRunLoops();
EXPECT_FALSE(base::PathExists(retinaFile));
}
// Tests that an image is immediately deleted when calling
// `-removeImageWithSnapshotID:`.
TEST_F(LegacyImageFileManagerTest, ImageDeleted) {
LegacyImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
UIImage* image = GenerateRandomImage(0);
const SnapshotID kSnapshotID(SessionID::NewUnique().id());
[file_manager writeImage:image withSnapshotID:kSnapshotID];
base::FilePath image_path = [file_manager imagePathForSnapshotID:kSnapshotID];
// Remove the image and ensure the file is removed.
[file_manager removeImageWithSnapshotID:kSnapshotID];
FlushRunLoops();
EXPECT_FALSE(base::PathExists(image_path));
}
// Tests that all images are deleted when calling `-removeAllImages`.
TEST_F(LegacyImageFileManagerTest, AllImagesDeleted) {
LegacyImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
UIImage* image = GenerateRandomImage(0);
const SnapshotID kSnapshotID1(SessionID::NewUnique().id());
const SnapshotID kSnapshotID2(SessionID::NewUnique().id());
[file_manager writeImage:image withSnapshotID:kSnapshotID1];
[file_manager writeImage:image withSnapshotID:kSnapshotID2];
base::FilePath image_1_path =
[file_manager imagePathForSnapshotID:kSnapshotID1];
base::FilePath image_2_path =
[file_manager imagePathForSnapshotID:kSnapshotID2];
// Remove all images and ensure the files are removed.
[file_manager removeAllImages];
FlushRunLoops();
EXPECT_FALSE(base::PathExists(image_1_path));
EXPECT_FALSE(base::PathExists(image_2_path));
}
// This is a duplicated test class of LegacyImageFileManagerTest to test
// ImageFileManager. We can't use value-parameterized tests because some
// public APIs are different from LegacyImageFileManager.
class ImageFileManagerTest : public PlatformTest {
protected:
void SetUp() override {
PlatformTest::SetUp();
ASSERT_TRUE(CreateImageFileManager());
}
void TearDown() override {
ClearAllImages();
image_file_manager_ = nil;
PlatformTest::TearDown();
}
// Build an array of snapshot IDs and an array of UIImages filled with
// random colors.
[[nodiscard]] bool CreateImageFileManager() {
DCHECK(!image_file_manager_);
if (!scoped_temp_directory_.CreateUniqueTempDir()) {
return false;
}
if (!scoped_temp_directory_for_legacy_path_.CreateUniqueTempDir()) {
return false;
}
NSURL* storage_url =
base::apple::FilePathToNSURL(scoped_temp_directory_.GetPath());
NSURL* legacy_url = base::apple::FilePathToNSURL(
scoped_temp_directory_for_legacy_path_.GetPath());
image_file_manager_ =
[[ImageFileManager alloc] initWithStorageDirectoryUrl:storage_url
legacyDirectoryUrl:legacy_url];
// Make sure that the storage directory is ready.
FlushRunLoops();
CGFloat scale = [SnapshotImageScale floatImageScaleForDevice];
srand(1);
for (NSUInteger i = 0; i < kSnapshotCount; ++i) {
test_images_.insert(std::make_pair(
[[SnapshotIDWrapper alloc]
initWithSnapshotID:SnapshotID(SessionID::NewUnique().id())],
GenerateRandomImage(scale)));
}
return true;
}
ImageFileManager* GetImageFileManager() {
DCHECK(image_file_manager_);
return image_file_manager_;
}
// Generates an image of `scale`, filled with a random color.
UIImage* GenerateRandomImage(CGFloat scale) {
CGSize size = CGSizeMake(kSnapshotPixelSize, kSnapshotPixelSize);
UIGraphicsImageRendererFormat* format =
[UIGraphicsImageRendererFormat preferredFormat];
format.scale = scale;
format.opaque = NO;
UIGraphicsImageRenderer* renderer =
[[UIGraphicsImageRenderer alloc] initWithSize:size format:format];
return [renderer
imageWithActions:^(UIGraphicsImageRendererContext* UIContext) {
CGContextRef context = UIContext.CGContext;
CGFloat r = rand() / CGFloat(RAND_MAX);
CGFloat g = rand() / CGFloat(RAND_MAX);
CGFloat b = rand() / CGFloat(RAND_MAX);
CGContextSetRGBStrokeColor(context, r, g, b, 1.0);
CGContextSetRGBFillColor(context, r, g, b, 1.0);
CGContextFillRect(context, CGRectMake(0.0, 0.0, kSnapshotPixelSize,
kSnapshotPixelSize));
}];
}
void FlushRunLoops() {
if (!image_file_manager_) {
return;
}
base::RunLoop run_loop;
base::RunLoop* run_loop_ptr = &run_loop;
[image_file_manager_ waitForAllTasksForTestingWithCallback:(^() {
run_loop_ptr->QuitClosure().Run();
})];
run_loop.Run();
}
// This function removes all snapshots stored in disk.
void ClearAllImages() {
if (!image_file_manager_) {
return;
}
[image_file_manager_ removeAllImages];
FlushRunLoops();
__block BOOL foundImage = NO;
__block NSUInteger numCallbacks = 0;
for (auto it = test_images_.begin(); it != test_images_.end(); ++it) {
__weak SnapshotIDWrapper* snapshot_id = it->first;
NSURL* image_url =
[image_file_manager_ imagePathWithSnapshotID:snapshot_id];
// Checks that the snapshot is not on disk.
EXPECT_FALSE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url path]]);
// Check that the snapshot is not in the dictionary.
[image_file_manager_ readImageWithSnapshotID:snapshot_id
completion:^(UIImage* image) {
++numCallbacks;
if (image) {
foundImage = YES;
}
}];
}
// Expect that all the callbacks ran and that none retrieved an image.
FlushRunLoops();
EXPECT_EQ(test_images_.size(), numCallbacks);
EXPECT_FALSE(foundImage);
}
// Guesses the order of the color channels in the image.
// Supports RGB, BGR, RGBA, BGRA, ARGB, ABGR.
// Returns the position of each channel between 0 and 3.
void ComputeColorComponents(CGImageRef cgImage,
int* red,
int* green,
int* blue) {
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
int byteOrder = bitmapInfo & kCGBitmapByteOrderMask;
*red = 0;
*green = 1;
*blue = 2;
if (alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaNoneSkipLast) {
*red = 1;
*green = 2;
*blue = 3;
}
if (byteOrder != kCGBitmapByteOrder32Host) {
int lastChannel = (CGImageGetBitsPerPixel(cgImage) == 24) ? 2 : 3;
*red = lastChannel - *red;
*green = lastChannel - *green;
*blue = lastChannel - *blue;
}
}
web::WebTaskEnvironment task_environment_;
base::ScopedTempDir scoped_temp_directory_;
base::ScopedTempDir scoped_temp_directory_for_legacy_path_;
ImageFileManager* image_file_manager_;
std::map<SnapshotIDWrapper*, UIImage*> test_images_;
};
// Tests that the color of all snapshots in the storage reloaded from disk.
TEST_F(ImageFileManagerTest, CheckImageColors) {
ImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// Put all images to disk.
for (auto it = test_images_.begin(); it != test_images_.end(); ++it) {
__weak SnapshotIDWrapper* snapshot_id = it->first;
__weak UIImage* image = it->second;
[file_manager writeWithImage:image snapshotID:snapshot_id];
}
FlushRunLoops();
for (auto it = test_images_.begin(); it != test_images_.end(); ++it) {
__weak SnapshotIDWrapper* snapshot_id = it->first;
__weak UIImage* reference_image = it->second;
// Check that images are on the disk.
NSURL* image_url = [file_manager imagePathWithSnapshotID:snapshot_id];
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url path]]);
// Check image colors by comparing the first pixel against the reference
// image.
UIImage* image = [UIImage imageWithContentsOfFile:[image_url path]];
CGImageRef cgImage = [image CGImage];
ASSERT_TRUE(cgImage != nullptr);
base::apple::ScopedCFTypeRef<CFDataRef> pixelData(
CGDataProviderCopyData(CGImageGetDataProvider(cgImage)));
const char* pixels =
reinterpret_cast<const char*>(CFDataGetBytePtr(pixelData.get()));
EXPECT_TRUE(pixels);
CGImageRef referenceCgImage = [reference_image CGImage];
base::apple::ScopedCFTypeRef<CFDataRef> referenceData(
CGDataProviderCopyData(CGImageGetDataProvider(referenceCgImage)));
const char* referencePixels =
reinterpret_cast<const char*>(CFDataGetBytePtr(referenceData.get()));
EXPECT_TRUE(referencePixels);
if (pixels != nil && referencePixels != nil) {
// Color components may not be in the same order,
// because of writing to disk and reloading.
int red, green, blue;
ComputeColorComponents(cgImage, &red, &green, &blue);
int referenceRed, referenceGreen, referenceBlue;
ComputeColorComponents(referenceCgImage, &referenceRed, &referenceGreen,
&referenceBlue);
// Colors may not be exactly the same (compression or rounding errors)
// thus a small difference is allowed.
EXPECT_NEAR(referencePixels[referenceRed], pixels[red], 1);
EXPECT_NEAR(referencePixels[referenceGreen], pixels[green], 1);
EXPECT_NEAR(referencePixels[referenceBlue], pixels[blue], 1);
}
}
}
// Tests that old images are deleted.
TEST_F(ImageFileManagerTest, PurgeImagesOlderThan) {
ImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// Put all images in the storage.
for (auto it = test_images_.begin(); it != test_images_.end(); ++it) {
__weak SnapshotIDWrapper* snapshot_id = it->first;
__weak UIImage* image = it->second;
[file_manager writeWithImage:image snapshotID:snapshot_id];
}
FlushRunLoops();
NSArray* live_snapshot_ids =
[[NSArray alloc] initWithObjects:test_images_.begin()->first, nil];
ASSERT_FALSE(test_images_.empty());
// Purge the storage.
[file_manager purgeImagesOlderThanWithThresholdDate:
[NSDate dateWithTimeIntervalSinceNow:-3600]
liveSnapshotIDs:live_snapshot_ids];
FlushRunLoops();
// Check that nothing has been deleted.
for (auto it = test_images_.begin(); it != test_images_.end(); ++it) {
__weak SnapshotIDWrapper* snapshot_id = it->first;
// Check that images are on the disk.
NSURL* image_url = [file_manager imagePathWithSnapshotID:snapshot_id];
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url path]]);
}
// Purge the storage.
[file_manager purgeImagesOlderThanWithThresholdDate:[NSDate now]
liveSnapshotIDs:live_snapshot_ids];
FlushRunLoops();
// Check that the file have been deleted.
for (auto it = test_images_.begin(); it != test_images_.end(); ++it) {
__weak SnapshotIDWrapper* snapshot_id = it->first;
// Check that images are on the disk.
NSURL* image_url = [file_manager imagePathWithSnapshotID:snapshot_id];
if (snapshot_id == live_snapshot_ids[0]) {
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url path]]);
} else {
EXPECT_FALSE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url path]]);
}
}
}
// Tests that migration code correctly rename the specified files and leave
// the other files untouched.
TEST_F(ImageFileManagerTest, RenameSnapshots) {
ImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// This snapshot will be renamed.
NSString* image1_id = [[NSUUID UUID] UUIDString];
NSURL* image1_url = [file_manager legacyImagePathWithSnapshotID:image1_id];
ASSERT_TRUE(base::WriteFile(
base::FilePath(base::SysNSStringToUTF8([image1_url path])), "image1"));
// This snapshot will not be renamed.
NSString* image2_id = [[NSUUID UUID] UUIDString];
NSURL* image2_url = [file_manager legacyImagePathWithSnapshotID:image2_id];
ASSERT_TRUE(base::WriteFile(
base::FilePath(base::SysNSStringToUTF8([image2_url path])), "image2"));
SnapshotIDWrapper* new_id = [[SnapshotIDWrapper alloc]
initWithSnapshotID:SnapshotID(SessionID::NewUnique().id())];
[file_manager renameSnapshotsWithOldIDs:@[ image1_id ] newIDs:@[ new_id ]];
FlushRunLoops();
// image1 should have been moved.
EXPECT_FALSE(
[[NSFileManager defaultManager] fileExistsAtPath:[image1_url path]]);
EXPECT_TRUE([[NSFileManager defaultManager]
fileExistsAtPath:[[file_manager imagePathWithSnapshotID:new_id] path]]);
// image2 should not have moved.
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[image2_url path]]);
}
// Tests that image size and scale are preserved when writing and reading
// from disk.
TEST_F(ImageFileManagerTest, SizeAndScalePreservation) {
ImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
// Create an image with the expected snapshot scale.
CGFloat scale = [SnapshotImageScale floatImageScaleForDevice];
UIImage* image = GenerateRandomImage(scale);
// Add the image to the storage and ensure the file is written to disk.
SnapshotIDWrapper* snapshot_id = [[SnapshotIDWrapper alloc]
initWithSnapshotID:SnapshotID(SessionID::NewUnique().id())];
[file_manager writeWithImage:image snapshotID:snapshot_id];
FlushRunLoops();
// Retrive the image and have the callback verify the size and scale.
__block BOOL callbackComplete = NO;
[file_manager
readImageWithSnapshotID:snapshot_id
completion:^(UIImage* imageFromDisk) {
EXPECT_EQ(image.size.width, imageFromDisk.size.width);
EXPECT_EQ(image.size.height, imageFromDisk.size.height);
EXPECT_EQ(image.scale, imageFromDisk.scale);
callbackComplete = YES;
}];
FlushRunLoops();
EXPECT_TRUE(callbackComplete);
}
// Tests that retina-scale images are deleted properly.
TEST_F(ImageFileManagerTest, DeleteRetinaImages) {
ImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
if ([SnapshotImageScale floatImageScaleForDevice] != 2.0) {
return;
}
// Create an image with retina scale.
UIImage* image = GenerateRandomImage(2.0);
// Add the image and ensure the file is written to disk.
SnapshotIDWrapper* snapshot_id = [[SnapshotIDWrapper alloc]
initWithSnapshotID:SnapshotID(SessionID::NewUnique().id())];
[file_manager writeWithImage:image snapshotID:snapshot_id];
FlushRunLoops();
// Verify the file was written with @2x in the file name.
NSURL* retinaFile = [file_manager imagePathWithSnapshotID:snapshot_id];
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[retinaFile path]]);
// Delete the image and ensure the file is removed.
[file_manager removeImageWithSnapshotID:snapshot_id];
FlushRunLoops();
EXPECT_FALSE(
[[NSFileManager defaultManager] fileExistsAtPath:[retinaFile path]]);
}
// Tests that an image is immediately deleted when calling
// `-removeImageWithSnapshotID:`.
TEST_F(ImageFileManagerTest, ImageDeleted) {
ImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
UIImage* image = GenerateRandomImage(0);
SnapshotIDWrapper* snapshot_id = [[SnapshotIDWrapper alloc]
initWithSnapshotID:SnapshotID(SessionID::NewUnique().id())];
[file_manager writeWithImage:image snapshotID:snapshot_id];
FlushRunLoops();
NSURL* image_url = [file_manager imagePathWithSnapshotID:snapshot_id];
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url path]]);
// Remove the image and ensure the file is removed.
[file_manager removeImageWithSnapshotID:snapshot_id];
FlushRunLoops();
EXPECT_FALSE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url path]]);
}
// Tests that all images are deleted when calling `-removeAllImages`.
TEST_F(ImageFileManagerTest, AllImagesDeleted) {
ImageFileManager* file_manager = GetImageFileManager();
ASSERT_TRUE(file_manager);
UIImage* image = GenerateRandomImage(0);
SnapshotIDWrapper* snapshot_id1 = [[SnapshotIDWrapper alloc]
initWithSnapshotID:SnapshotID(SessionID::NewUnique().id())];
SnapshotIDWrapper* snapshot_id2 = [[SnapshotIDWrapper alloc]
initWithSnapshotID:SnapshotID(SessionID::NewUnique().id())];
[file_manager writeWithImage:image snapshotID:snapshot_id1];
[file_manager writeWithImage:image snapshotID:snapshot_id2];
FlushRunLoops();
NSURL* image_url1 = [file_manager imagePathWithSnapshotID:snapshot_id1];
NSURL* image_url2 = [file_manager imagePathWithSnapshotID:snapshot_id2];
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url1 path]]);
EXPECT_TRUE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url2 path]]);
// Remove all images and ensure the files are removed.
[file_manager removeAllImages];
FlushRunLoops();
EXPECT_FALSE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url1 path]]);
EXPECT_FALSE(
[[NSFileManager defaultManager] fileExistsAtPath:[image_url2 path]]);
}
} // namespace