// 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 "ios/chrome/browser/sessions/model/session_internal_util.h"
#import "base/files/file_enumerator.h"
#import "base/files/scoped_temp_dir.h"
#import "base/time/time.h"
#import "ios/chrome/browser/sessions/model/proto/storage.pb.h"
#import "ios/chrome/browser/sessions/model/proto_util.h"
#import "ios/chrome/browser/sessions/model/session_ios.h"
#import "ios/chrome/browser/sessions/model/session_window_ios.h"
#import "ios/web/public/session/crw_session_storage.h"
#import "ios/web/public/session/crw_session_user_data.h"
#import "ios/web/public/web_state_id.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
using SessionInternalUtilTest = PlatformTest;
namespace {
// Constants used to construct filenames.
const base::FilePath::CharType kFilename[] = FILE_PATH_LITERAL("file");
const base::FilePath::CharType kDirname1[] = FILE_PATH_LITERAL("dir1");
const base::FilePath::CharType kDirname2[] = FILE_PATH_LITERAL("dir2");
const base::FilePath::CharType kDirname3[] = FILE_PATH_LITERAL("dir3");
const base::FilePath::CharType kFromName[] = FILE_PATH_LITERAL("from");
const base::FilePath::CharType kDestName[] = FILE_PATH_LITERAL("dest");
// A sub-class of google::protobuf::MessageLite that cannot be serialized.
//
// Note: sub-classing google::protobuf::MessageLite is not supported, so
// this may break at any point. If this break, we may consider removing
// this class and the test using it (the class exists to get as much
// coverage as possible for `WriteProto` function).
//
// The implementation is broken, and the only goal is to have the call to
// `SerializeToArray()` in `WriteProto` to fail. This is achieved by using
// a mutable state that allow returning a size of serialized data that is
// increasing each time `ByteSizeLong()` is called (thus resulting in an
// allocation that is considered too small by `SerializeToArray()`).
//
// All the other methods are overridden to be no-op.
class UnserializableMessage : public google::protobuf::MessageLite {
public:
// google::protobuf::MessageLite
std::string GetTypeName() const override { return "UnserializableMessage"; }
MessageLite* New(google::protobuf::Arena* arena) const override {
return nullptr;
}
void Clear() override {}
bool IsInitialized() const override { return true; }
void CheckTypeAndMergeFrom(const MessageLite& other) override {}
size_t ByteSizeLong() const override {
return ++call_count_ * sizeof(double);
}
int GetCachedSize() const override {
return static_cast<int>(ByteSizeLong());
}
uint8_t* _InternalSerialize(
uint8_t* ptr,
google::protobuf::io::EpsCopyOutputStream* stream) const override {
return ptr;
}
private:
// Record how many time `ByteSizeLong()` is called, allowing to return a
// different size for the serialized data each time it is called, which
// eventually leads to a failure of `SerializeToArray()`.
mutable size_t call_count_ = 1;
};
// Creates a SessionWindowIOS* with fake data.
SessionWindowIOS* CreateSessionWindowIOS() {
CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
session_storage.stableIdentifier = [[NSUUID UUID] UUIDString];
session_storage.uniqueIdentifier = web::WebStateID::NewUnique();
session_storage.creationTime = base::Time::Now();
session_storage.lastActiveTime = session_storage.creationTime;
session_storage.lastCommittedItemIndex = -1;
session_storage.itemStorages = @[];
return [[SessionWindowIOS alloc] initWithSessions:@[ session_storage ]
tabGroups:@[]
selectedIndex:0];
}
// Returns the list of item at `path`.
std::set<base::FilePath> DirectoryContent(const base::FilePath& path) {
using FileEnumerator = base::FileEnumerator;
constexpr int all_items =
FileEnumerator::FileType::FILES | FileEnumerator::FileType::DIRECTORIES;
std::set<base::FilePath> result;
FileEnumerator e(path, false, all_items);
for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
result.insert(e.GetInfo().GetName());
}
return result;
}
// Compares the content of two path and check they are identical (recursively).
bool PathAreIdentical(const base::FilePath& lhs, const base::FilePath& rhs) {
// If both path are file, check whether they have the same content.
if (ios::sessions::FileExists(lhs) && ios::sessions::FileExists(rhs)) {
NSData* lhs_data = ios::sessions::ReadFile(lhs);
NSData* rhs_data = ios::sessions::ReadFile(rhs);
return [lhs_data isEqualToData:rhs_data];
}
// If either path is not a directory, then they content is not identical.
if (!ios::sessions::DirectoryExists(lhs) ||
!ios::sessions::DirectoryExists(rhs)) {
return false;
}
// Both paths are directory, check the content recursively.
const std::set<base::FilePath> lhs_names = DirectoryContent(lhs);
const std::set<base::FilePath> rhs_names = DirectoryContent(rhs);
if (lhs_names != rhs_names) {
return false;
}
// If the list of items are identical, compare them recursively.
for (const base::FilePath& name : lhs_names) {
if (!PathAreIdentical(lhs.Append(name), rhs.Append(name))) {
return false;
}
}
return true;
}
} // namespace
// Fake object that cannot be serialized. Used to test `ArchiveRootObject`
// failure code paths.
@interface UnserializableObject : NSObject <NSCoding>
@end
@implementation UnserializableObject
- (void)encodeWithCoder:(NSCoder*)coder {
// Use a decoding method during encoding to cause the encoding to fail.
[coder decodeObjectForKey:@"error"];
}
- (instancetype)initWithCoder:(NSCoder*)coder {
return nil;
}
@end
// Fake object that cannot be decoded. Used to test `DecodeRootObject`
// failure code paths.
@interface UndecodableObject : NSObject <NSCoding>
@end
@implementation UndecodableObject
- (void)encodeWithCoder:(NSCoder*)coder {
}
- (instancetype)initWithCoder:(NSCoder*)coder {
// Use an encoding method during decoding to cause the decoding to fail.
if ((self = [super init])) {
[coder encodeObject:@{} forKey:@"error"];
}
return self;
}
@end
// Tests that `FileExists` return true if the path corresponds to an existing
// file or false otherwise (e.g. corresponds to a directory, or does not exist).
TEST_F(SessionInternalUtilTest, FileExists) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Check that FileExists() returns false if the file does not exist.
EXPECT_FALSE(ios::sessions::FileExists(root.Append(kFilename)));
// Create a file and check that FileExists() returns true.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(root.Append(kFilename), data));
EXPECT_TRUE(ios::sessions::FileExists(root.Append(kFilename)));
// Create a directory and check that FileExists() returns false.
EXPECT_TRUE(ios::sessions::CreateDirectory(root.Append(kDirname1)));
EXPECT_FALSE(ios::sessions::FileExists(root.Append(kDirname1)));
}
// Tests that `DirectoryExists` return true if the path corresponds to an
// existing directory or false otherwise (e.g. corresponds to a file, or does
// not exist).
TEST_F(SessionInternalUtilTest, DirectoryExists) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Check that DirectoryExists() returns false if the file does not exist.
EXPECT_FALSE(ios::sessions::DirectoryExists(root.Append(kDirname1)));
// Create a directory and check that DirectoryExists() returns true.
EXPECT_TRUE(ios::sessions::CreateDirectory(root.Append(kDirname1)));
EXPECT_TRUE(ios::sessions::DirectoryExists(root.Append(kDirname1)));
// Create a file and check that DirectoryExists() returns true.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(root.Append(kFilename), data));
EXPECT_FALSE(ios::sessions::DirectoryExists(root.Append(kFilename)));
}
// Tests that `RenameFile` correctly move a file.
TEST_F(SessionInternalUtilTest, RenameFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFilename);
const base::FilePath dest = root.Append(kDirname2).Append(kFilename);
// Create a file in a sub-directory.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(from, data));
// Check that moving the file is a success, and that the file content
// is the expected one.
EXPECT_TRUE(ios::sessions::RenameFile(from, dest));
NSData* read = ios::sessions::ReadFile(dest);
EXPECT_NSEQ(read, data);
}
// Tests that `RenameFile` fails if it cannot create destination directory.
TEST_F(SessionInternalUtilTest, RenameFile_FailureCreatingDirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFilename);
const base::FilePath dest = root.Append(kDirname2).Append(kFilename);
// Create a file in a sub-directory.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(from, data));
// Create a file at the path `dest.DirName()` which will cause the
// call to `CreateDirectory` to fail.
EXPECT_TRUE(ios::sessions::WriteFile(dest.DirName(), data));
// Check that trying to move fail while trying to create the directory.
EXPECT_FALSE(ios::sessions::RenameFile(from, dest));
}
// Tests that `RenameFile` fails if it cannot write the destination file.
TEST_F(SessionInternalUtilTest, RenameFile_FailureRenamingFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFilename);
const base::FilePath dest = root.Append(kDirname2).Append(kFilename);
// Check that trying to move a non-existent file fails.
EXPECT_FALSE(ios::sessions::RenameFile(from, dest));
}
// Tests that `CreateDirectory` correctly create the directory, or return
// a success if the destination exists and is a directory.
TEST_F(SessionInternalUtilTest, CreateDirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1).Append(kDirname2);
EXPECT_FALSE(ios::sessions::DirectoryExists(dir));
EXPECT_FALSE(ios::sessions::DirectoryExists(dir.DirName()));
// Check that creating the directory succeed and that the directory
// exists after the successful creation. If should have created the
// parent directory too.
EXPECT_TRUE(ios::sessions::CreateDirectory(dir));
EXPECT_TRUE(ios::sessions::DirectoryExists(dir));
EXPECT_TRUE(ios::sessions::DirectoryExists(dir.DirName()));
// Check that trying to create an existing directory result in a success.
EXPECT_TRUE(ios::sessions::CreateDirectory(dir));
}
// Tests that `CreateDirectory` returns false in case of failure.
TEST_F(SessionInternalUtilTest, CreateDirectory_Failure) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath file = root.Append(kFilename);
// Create a file at the path where we will try to create the directory.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
EXPECT_TRUE(ios::sessions::FileExists(file));
// Check that trying to create a directory at a path where a file exists
// results in a failure to create the directory.
const base::FilePath dir = file.Append(kDirname2);
EXPECT_FALSE(ios::sessions::CreateDirectory(dir));
EXPECT_FALSE(ios::sessions::DirectoryExists(dir));
}
// Tests that `DirectoryEmpty` returns true if the directory exists and is
// empty, false otherwise (not a directory or a directory that is not empty).
TEST_F(SessionInternalUtilTest, DirectoryEmpty) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Check that `DirectoryEmpty` returns false if the path is not a directory
// (e.g. the path does not exists).
EXPECT_FALSE(ios::sessions::DirectoryEmpty(dir));
// Check that `DirectoryEmpty` returns true if the path is a directory and
// the directory is empty (i.e. just created with no file inside).
EXPECT_TRUE(ios::sessions::CreateDirectory(dir));
EXPECT_TRUE(ios::sessions::DirectoryEmpty(dir));
// Create a file inside `dir` and check that `DirectoryEmpty` now returns
// false.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
EXPECT_TRUE(ios::sessions::FileExists(file));
EXPECT_FALSE(ios::sessions::DirectoryEmpty(dir));
}
// Tests that `DeleteRecursively` correctly remove a file/directory and in
// the case of a directory, all its content recursively.
TEST_F(SessionInternalUtilTest, DeleteRecursively) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kDirname2).Append(kFilename);
// Create a file deeply nested.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
EXPECT_TRUE(ios::sessions::FileExists(file));
// Check that deleting `dir` delete everything below.
EXPECT_TRUE(ios::sessions::DeleteRecursively(dir));
EXPECT_FALSE(ios::sessions::DirectoryExists(dir));
EXPECT_FALSE(ios::sessions::FileExists(file));
// Create a file deeply nested.
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
EXPECT_TRUE(ios::sessions::FileExists(file));
// Check that deleting `file` only delete the file and nothing else.
EXPECT_TRUE(ios::sessions::DeleteRecursively(file));
EXPECT_TRUE(ios::sessions::DirectoryExists(dir.DirName()));
EXPECT_TRUE(ios::sessions::DirectoryExists(dir));
EXPECT_FALSE(ios::sessions::FileExists(file));
}
// Tests that `DeleteRecursively` returns false in case of failure.
TEST_F(SessionInternalUtilTest, DeleteRecursively_Failure) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kDirname2).Append(kFilename);
// Check that trying to delete a non-existent file fails.
EXPECT_FALSE(ios::sessions::FileExists(file));
EXPECT_FALSE(ios::sessions::DeleteRecursively(file));
}
// Tests that `CopyDirectory` correctly copy the directory structure
// recursively.
TEST_F(SessionInternalUtilTest, CopyDirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Create the source directory with some sub-directories and files.
const base::FilePath from = root.Append(kFromName);
const base::FilePath from_dir1 = from.Append(kDirname1);
const base::FilePath from_dir2 = from.Append(kDirname2);
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data));
ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data));
// Check that copying recursively to inexistent destination works.
const base::FilePath dest = root.Append(kDestName);
EXPECT_TRUE(ios::sessions::CopyDirectory(from, dest));
EXPECT_TRUE(PathAreIdentical(from, dest));
}
// Tests that `CopyDirectory` correctly replaces the content of the target
// directory if it exists.
TEST_F(SessionInternalUtilTest, CopyDirectory_OverExistingDirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Create the source directory with some sub-directories and files.
const base::FilePath from = root.Append(kFromName);
const base::FilePath from_dir1 = from.Append(kDirname1);
const base::FilePath from_dir2 = from.Append(kDirname2);
NSData* data0 = [@"data0" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data0));
ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data0));
// Create the target directory with a different content.
const base::FilePath dest = root.Append(kDestName);
const base::FilePath dest_dir3 = from.Append(kDirname3);
NSData* data1 = [@"data1" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(dest_dir3.Append(kFilename), data1));
ASSERT_TRUE(ios::sessions::WriteFile(dest.Append(kFilename), data1));
// Check that both directories have distinct content.
ASSERT_FALSE(PathAreIdentical(from, dest));
// Check that copying recursively to existing directory works and erase
// all content in des.
EXPECT_TRUE(ios::sessions::CopyDirectory(from, dest));
EXPECT_TRUE(PathAreIdentical(from, dest));
}
// Tests that `CopyDirectory` succeeds even if the destination requires
// creating the parent directories.
TEST_F(SessionInternalUtilTest, CopyDirectory_TargetNestedInNonExistentDir) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Create the source directory with some sub-directories and files.
const base::FilePath from = root.Append(kFromName);
const base::FilePath from_dir1 = from.Append(kDirname1);
const base::FilePath from_dir2 = from.Append(kDirname2);
NSData* data0 = [@"data0" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data0));
ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data0));
// Use a destination directory that is deeply nested and change that the
// copy succeed (and has the same content as the source).
const base::FilePath deep = root.Append(kDirname1).Append(kDirname2);
ASSERT_FALSE(ios::sessions::DirectoryExists(deep));
ASSERT_FALSE(ios::sessions::DirectoryExists(deep.DirName()));
const base::FilePath dest = deep.Append(kDestName);
EXPECT_TRUE(ios::sessions::CopyDirectory(from, dest));
EXPECT_TRUE(PathAreIdentical(from, dest));
}
// Tests that `CopyDirectory` fails if target is a file.
TEST_F(SessionInternalUtilTest, CopyDirectory_FailureDestinationIsAFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Create the source directory with some sub-directories and files.
const base::FilePath from = root.Append(kFromName);
const base::FilePath from_dir1 = from.Append(kDirname1);
const base::FilePath from_dir2 = from.Append(kDirname2);
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data));
ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data));
// Create a file with the same name as target directory.
const base::FilePath dest = root.Append(kDestName);
ASSERT_TRUE(ios::sessions::WriteFile(dest, data));
// Check that trying to copy source over a file fails.
EXPECT_FALSE(ios::sessions::CopyDirectory(from, dest));
}
// Tests that `CopyDirectory` fails if source is a file.
TEST_F(SessionInternalUtilTest, CopyDirectory_FailureSourceNotADirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Create a file named like source.
const base::FilePath from = root.Append(kFromName);
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from, data));
// Check that CopyDirectory fails when the source is a file.
const base::FilePath dest = root.Append(kDestName);
EXPECT_FALSE(ios::sessions::CopyDirectory(from, dest));
}
// Tests that `CopyDirectory` fails if it cannot create the parent of the
// target directory.
TEST_F(SessionInternalUtilTest, CopyDirectory_FailureCannotCreateTargetParent) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Create the source directory with some sub-directories and files.
const base::FilePath from = root.Append(kFromName);
const base::FilePath from_dir1 = from.Append(kDirname1);
const base::FilePath from_dir2 = from.Append(kDirname2);
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data));
ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data));
// Use a destination directory that is deeply nested.
const base::FilePath deep = root.Append(kDirname1).Append(kDirname2);
const base::FilePath dest = deep.Append(kDestName);
ASSERT_FALSE(ios::sessions::DirectoryExists(deep));
ASSERT_FALSE(ios::sessions::DirectoryExists(deep.DirName()));
// Create a file in the location of the target parent directory. This
// should cause the creation of the parent directory to fail and thus
// the failure of the copy.
ASSERT_TRUE(ios::sessions::WriteFile(deep, data));
// Check that the copy failed and that the file that was in the way
// has not been modified.
EXPECT_FALSE(ios::sessions::CopyDirectory(from, dest));
EXPECT_NSEQ(ios::sessions::ReadFile(deep), data);
}
// Tests that `CopyFile` returns success if the file can be copied.
TEST_F(SessionInternalUtilTest, CopyFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFromName);
const base::FilePath dest = root.Append(kDirname2).Append(kDestName);
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from, data));
// Check that copying the file leave the source file intact, creates
// the directory structure for destination file, and both files have
// the same content.
EXPECT_TRUE(ios::sessions::CopyFile(from, dest));
EXPECT_TRUE(ios::sessions::FileExists(from));
EXPECT_NSEQ(ios::sessions::ReadFile(from), data);
EXPECT_TRUE(ios::sessions::FileExists(dest));
EXPECT_NSEQ(ios::sessions::ReadFile(dest), data);
}
// Tests that `CopyFile` returns success and overwritten destination if
// it exists and is a file.
TEST_F(SessionInternalUtilTest, CopyFile_OverwriteDestination) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFromName);
const base::FilePath dest = root.Append(kDirname2).Append(kDestName);
NSData* data1 = [@"data1" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from, data1));
NSData* data2 = [@"data2" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(dest, data2));
ASSERT_NSEQ(ios::sessions::ReadFile(dest), data2);
// Check that copying the file leave the source file intact, overwrite the
// destination file, and both files have the same content.
EXPECT_TRUE(ios::sessions::CopyFile(from, dest));
EXPECT_TRUE(ios::sessions::FileExists(from));
EXPECT_NSEQ(ios::sessions::ReadFile(from), data1);
EXPECT_TRUE(ios::sessions::FileExists(dest));
EXPECT_NSEQ(ios::sessions::ReadFile(dest), data1);
}
// Tests that `CopyFile` fails if the source is not a file.
TEST_F(SessionInternalUtilTest, CopyFile_FailureSourceIsADirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFromName);
const base::FilePath dest = root.Append(kDirname2).Append(kDestName);
ASSERT_TRUE(ios::sessions::CreateDirectory(from));
// Check that trying to copy `from` which is a directory using CopyFile()`
// fails with an error.
EXPECT_FALSE(ios::sessions::CopyFile(from, dest));
}
// Tests that `CopyFile` fails if the source does not exist.
TEST_F(SessionInternalUtilTest, CopyFile_FailureSourceMissing) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFromName);
const base::FilePath dest = root.Append(kDirname2).Append(kDestName);
// Check that trying to copy `from` which is a directory using CopyFile()`
// fails with an error.
EXPECT_FALSE(ios::sessions::CopyFile(from, dest));
}
// Tests that `CopyFile` fails if the destination path is a directory.
TEST_F(SessionInternalUtilTest, CopyFile_FailureDestinationIsADirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath from = root.Append(kDirname1).Append(kFromName);
const base::FilePath dest = root.Append(kDirname2).Append(kDestName);
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
ASSERT_TRUE(ios::sessions::WriteFile(from, data));
ASSERT_TRUE(ios::sessions::CreateDirectory(dest));
// Check that trying to copy a file to a path that is a directory fails.
EXPECT_FALSE(ios::sessions::CopyFile(from, dest));
}
// Tests that `WriteFile` returns success when the file is created and the
// data written to disk.
TEST_F(SessionInternalUtilTest, WriteFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
EXPECT_FALSE(ios::sessions::FileExists(file));
// Check that creating a file creates its parent directory (recursively)
// and correctly write the data to the disk.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
EXPECT_TRUE(ios::sessions::DirectoryExists(dir));
EXPECT_TRUE(ios::sessions::FileExists(file));
NSData* read = ios::sessions::ReadFile(file);
EXPECT_NSEQ(read, data);
}
// Tests that `WriteFile` fails if it cannot create the parent directory.
TEST_F(SessionInternalUtilTest, WriteFile_FailureCreateDirectory) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a file named `dir` which should prevent creating a directory
// with the same path in the next call to `WriteFile`.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(dir, data));
// Check that creating a file named `file` will fail because the parent
// directory cannot be created.
EXPECT_FALSE(ios::sessions::WriteFile(file, data));
}
// Tests that `WriteFile` fails if it cannot write the data.
TEST_F(SessionInternalUtilTest, WriteFile_FailureWritingFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a directory named `file` which should prevent creating a file
// with the same path in the next call to `WriteFile`.
EXPECT_TRUE(ios::sessions::CreateDirectory(file));
// Check that creating a file named `file` will fail because the parent
// directory cannot be created.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_FALSE(ios::sessions::WriteFile(file, data));
}
// Tests that `ReadFile` read the data from disk or return nil on failure.
TEST_F(SessionInternalUtilTest, ReadFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Check that reading from an inexistent file fails and return nil.
EXPECT_FALSE(ios::sessions::FileExists(file));
EXPECT_NSEQ(nil, ios::sessions::ReadFile(file));
// Create a file with some data.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
// Check that reading the file return the written data.
NSData* read = ios::sessions::ReadFile(file);
EXPECT_NSEQ(read, data);
}
// Tests that `WriteProto` correctly write serialized protobuf message to disk.
TEST_F(SessionInternalUtilTest, WriteProto) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
// Create a protobuf message that is not empty.
ios::proto::WebStateListStorage proto;
proto.set_active_index(-1);
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Check that writing the protobuf message succeed and that some data
// is written to disk.
EXPECT_FALSE(ios::sessions::FileExists(file));
EXPECT_TRUE(ios::sessions::WriteProto(file, proto));
EXPECT_NSNE(nil, ios::sessions::ReadFile(file));
}
// Tests that `WriteProto` fails if it cannot serialize the protobuf message.
TEST_F(SessionInternalUtilTest, WriteProto_FailureSerializeMessage) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Check that writing the protobuf message succeed and that some data
// is written to disk.
EXPECT_FALSE(ios::sessions::FileExists(file));
EXPECT_FALSE(ios::sessions::WriteProto(file, UnserializableMessage{}));
}
// Tests that `ParseProto` succeed when reading a protobuf message written
// using `WriteProto`.
TEST_F(SessionInternalUtilTest, ParseProto) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Serialize a non-empty protobuf to `file`.
ios::proto::WebStateListStorage proto;
proto.add_items()->set_identifier(10);
proto.set_active_index(-1);
EXPECT_TRUE(ios::sessions::WriteProto(file, proto));
// Check that reading the protobuf message succeed and that the content
// is identical.
ios::proto::WebStateListStorage parsed;
EXPECT_TRUE(ios::sessions::ParseProto(file, parsed));
EXPECT_EQ(parsed, proto);
}
// Tests that `ParseProto` fails if it cannot read the file.
TEST_F(SessionInternalUtilTest, ParseProto_FailureReadFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Check that reading the protobuf message fails if the file does not exist.
ios::proto::WebStateListStorage parsed;
EXPECT_FALSE(ios::sessions::ParseProto(file, parsed));
}
// Tests that `ParseProto` fails if it cannot parse the file as a valid
// protobuf message.
TEST_F(SessionInternalUtilTest, ParseProto_FailureParseMessage) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Write unstructured data to the file, that is not a valid serialized
// protobuf message.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
// Check that reading the protobuf message fails if the data cannot be
// parsed as a valid protobuf message.
ios::proto::WebStateListStorage parsed;
EXPECT_FALSE(ios::sessions::ParseProto(file, parsed));
}
// Tests that `ArchiveRootObject` returns a non-null data when serialization
// of the object is a success.
TEST_F(SessionInternalUtilTest, ArchiveRootObject) {
NSObject<NSCoding>* root = @"data";
NSData* data = ios::sessions::ArchiveRootObject(root);
EXPECT_NSNE(data, nil);
}
// Tests that `ArchiveRootObject` returns nil when serialization fails.
TEST_F(SessionInternalUtilTest, ArchiveRootObject_FailureUnserializable) {
NSObject<NSCoding>* root = [[UnserializableObject alloc] init];
NSData* data = ios::sessions::ArchiveRootObject(root);
EXPECT_NSEQ(data, nil);
}
// Tests that `DecodeRootObject` returns an object that is equal to the
// serialized one when invoked with the output of `ArchiveRootObject`.
TEST_F(SessionInternalUtilTest, DecodeRootObject) {
NSObject<NSCoding>* root = @"data";
NSData* data = ios::sessions::ArchiveRootObject(root);
EXPECT_NSNE(data, nil);
NSObject<NSCoding>* decoded = ios::sessions::DecodeRootObject(data);
EXPECT_NSEQ(decoded, root);
}
// Tests that `DecodeRootObject` returns nil if the data cannot be
// parsed as a valid encoding.
TEST_F(SessionInternalUtilTest, DecodeRootObject_FailureInvalidData) {
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
NSObject<NSCoding>* decoded = ios::sessions::DecodeRootObject(data);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `DecodeRootObject` returns nil if the data is a valid
// encoding, but cannot be decoded.
TEST_F(SessionInternalUtilTest, DecodeRootObject_FailureDecodeObject) {
UndecodableObject* root = [[UndecodableObject alloc] init];
NSData* data = ios::sessions::ArchiveRootObject(root);
NSObject<NSCoding>* decoded = ios::sessions::DecodeRootObject(data);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `DecodeRootObject` returns nil if the data is nil.
TEST_F(SessionInternalUtilTest, DecodeRootObject_FailureNil) {
NSObject<NSCoding>* decoded = ios::sessions::DecodeRootObject(nil);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `ReadSessionsWindowFromPath` succeed if a file containing a
// valid `CRWSessionIOS*` encoding is written at the path.
TEST_F(SessionInternalUtilTest, ReadSessionWindow) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a fake session and write it to disk.
SessionWindowIOS* session = CreateSessionWindowIOS();
EXPECT_TRUE(ios::sessions::WriteSessionWindow(file, session));
// Check that reading the file succeed and return an object that is equal.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, session);
}
// Tests that `ReadSessionsWindowFromPath` succeed if a file containing a
// valid `SessionIOS*` encoding with a single window is written at the path.
TEST_F(SessionInternalUtilTest, ReadSessionWindow_SessionIOS) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a fake session and write it to disk.
SessionWindowIOS* session = CreateSessionWindowIOS();
SessionIOS* session_ios = [[SessionIOS alloc] initWithWindows:@[ session ]];
NSData* data = ios::sessions::ArchiveRootObject(session_ios);
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
// Check that reading the file succeed and return an object that is equal.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, session);
}
// Tests that `ReadSessionsWindowFromPath` fails if the session file does
// not exists.
TEST_F(SessionInternalUtilTest, ReadSessionWindow_FailureNoSession) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Check that reading the file fails if it does not exist.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `ReadSessionsWindowFromPath` fails if the session file cannot
// be decoded.
TEST_F(SessionInternalUtilTest, ReadSessionWindow_FailureInvalidData) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a file containing garbage.
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
// Check that reading the file fails if it does not exist.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `ReadSessionsWindowFromPath` fails if the session file contains
// a valid `SessionsIOS*` encoding with no window.
TEST_F(SessionInternalUtilTest, ReadSessionWindow_FailureNoWindows) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a session file containing a SessionIOS object without any window.
SessionIOS* session_ios = [[SessionIOS alloc] initWithWindows:@[]];
NSData* data = ios::sessions::ArchiveRootObject(session_ios);
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
// Check that reading the file succeed and return an object that is equal.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `ReadSessionsWindowFromPath` fails if the session file contains
// a valid `SessionsIOS*` encoding with too many windows.
TEST_F(SessionInternalUtilTest, ReadSessionWindow_FailureExtraWindows) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a session file containing a SessionIOS object with two windows.
SessionIOS* session_ios = [[SessionIOS alloc]
initWithWindows:@[ CreateSessionWindowIOS(), CreateSessionWindowIOS() ]];
NSData* data = ios::sessions::ArchiveRootObject(session_ios);
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
// Check that reading the file succeed and return an object that is equal.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `ReadSessionsWindowFromPath` fails if the session file contains
// a valid encoding of an unexpected type.
TEST_F(SessionInternalUtilTest, ReadSessionWindow_UnexpectedObject) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a file containing an unexpected object.
NSData* data = ios::sessions::ArchiveRootObject(@"data");
EXPECT_TRUE(ios::sessions::WriteFile(file, data));
// Check that reading the file succeed and return an object that is equal.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, nil);
}
// Tests that `WriteSessionWindow` succeed writing the session to file.
TEST_F(SessionInternalUtilTest, WriteSessionWindow) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a fake session and serialize it to disk.
SessionWindowIOS* session = CreateSessionWindowIOS();
EXPECT_TRUE(ios::sessions::WriteSessionWindow(file, session));
EXPECT_TRUE(ios::sessions::FileExists(file));
// Check that reading the file succeed and return an object that is equal.
SessionWindowIOS* decoded = ios::sessions::ReadSessionWindow(file);
EXPECT_NSEQ(decoded, session);
}
// Tests that `WriteSessionWindow` fails if it cannot serialize the session.
TEST_F(SessionInternalUtilTest, WriteSessionWindow_FailureSerialization) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a CRWSessionUserData user data containing an unserializable object.
CRWSessionUserData* user_data = [[CRWSessionUserData alloc] init];
[user_data setObject:[[UnserializableObject alloc] init] forKey:@"error"];
// Create a session containing an unserializable object.
SessionWindowIOS* session = CreateSessionWindowIOS();
session.sessions[0].userData = user_data;
// Check that writing the session fails as the session cannot be
// serialized and that the file is not created.
EXPECT_FALSE(ios::sessions::WriteSessionWindow(file, session));
EXPECT_FALSE(ios::sessions::FileExists(file));
}
// Tests that `WriteSessionWindow` fails if it cannot create the session file.
TEST_F(SessionInternalUtilTest, WriteSessionWindow_FailureWriteFile) {
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath root = scoped_temp_dir.GetPath();
const base::FilePath dir = root.Append(kDirname1);
const base::FilePath file = dir.Append(kFilename);
// Create a directory at the same location as the session file.
EXPECT_TRUE(ios::sessions::CreateDirectory(file));
// Check that writing the session fails as the session cannot be
// serialized and that the file is not created.
SessionWindowIOS* session = CreateSessionWindowIOS();
EXPECT_FALSE(ios::sessions::WriteSessionWindow(file, session));
}