|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/files/file_proxy.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/files/file.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/files/scoped_temp_dir.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/threading/thread.h" | 
|  | #include "base/threading/thread_restrictions.h" | 
|  | #include "build/build_config.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | class FileProxyTest : public testing::Test { | 
|  | public: | 
|  | FileProxyTest() | 
|  | : file_thread_("FileProxyTestFileThread"), | 
|  | error_(File::FILE_OK), | 
|  | bytes_written_(-1), | 
|  | weak_factory_(this) {} | 
|  |  | 
|  | void SetUp() override { | 
|  | ASSERT_TRUE(dir_.CreateUniqueTempDir()); | 
|  | ASSERT_TRUE(file_thread_.Start()); | 
|  | } | 
|  |  | 
|  | void DidFinish(File::Error error) { | 
|  | error_ = error; | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | } | 
|  |  | 
|  | void DidCreateOrOpen(File::Error error) { | 
|  | error_ = error; | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | } | 
|  |  | 
|  | void DidCreateTemporary(File::Error error, | 
|  | const FilePath& path) { | 
|  | error_ = error; | 
|  | path_ = path; | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | } | 
|  |  | 
|  | void DidGetFileInfo(File::Error error, | 
|  | const File::Info& file_info) { | 
|  | error_ = error; | 
|  | file_info_ = file_info; | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | } | 
|  |  | 
|  | void DidRead(File::Error error, | 
|  | const char* data, | 
|  | int bytes_read) { | 
|  | error_ = error; | 
|  | buffer_.resize(bytes_read); | 
|  | memcpy(&buffer_[0], data, bytes_read); | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | } | 
|  |  | 
|  | void DidWrite(File::Error error, | 
|  | int bytes_written) { | 
|  | error_ = error; | 
|  | bytes_written_ = bytes_written; | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void CreateProxy(uint32_t flags, FileProxy* proxy) { | 
|  | proxy->CreateOrOpen( | 
|  | TestPath(), flags, | 
|  | BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  | EXPECT_TRUE(proxy->IsValid()); | 
|  | } | 
|  |  | 
|  | TaskRunner* file_task_runner() const { | 
|  | return file_thread_.task_runner().get(); | 
|  | } | 
|  | const FilePath& TestDirPath() const { return dir_.GetPath(); } | 
|  | const FilePath TestPath() const { return dir_.GetPath().AppendASCII("test"); } | 
|  |  | 
|  | ScopedTempDir dir_; | 
|  | MessageLoopForIO message_loop_; | 
|  | Thread file_thread_; | 
|  |  | 
|  | File::Error error_; | 
|  | FilePath path_; | 
|  | File::Info file_info_; | 
|  | std::vector<char> buffer_; | 
|  | int bytes_written_; | 
|  | WeakPtrFactory<FileProxyTest> weak_factory_; | 
|  | }; | 
|  |  | 
|  | TEST_F(FileProxyTest, CreateOrOpen_Create) { | 
|  | FileProxy proxy(file_task_runner()); | 
|  | proxy.CreateOrOpen( | 
|  | TestPath(), File::FLAG_CREATE | File::FLAG_READ, | 
|  | BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_TRUE(proxy.IsValid()); | 
|  | EXPECT_TRUE(proxy.created()); | 
|  | EXPECT_TRUE(PathExists(TestPath())); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, CreateOrOpen_Open) { | 
|  | // Creates a file. | 
|  | base::WriteFile(TestPath(), nullptr, 0); | 
|  | ASSERT_TRUE(PathExists(TestPath())); | 
|  |  | 
|  | // Opens the created file. | 
|  | FileProxy proxy(file_task_runner()); | 
|  | proxy.CreateOrOpen( | 
|  | TestPath(), File::FLAG_OPEN | File::FLAG_READ, | 
|  | BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_TRUE(proxy.IsValid()); | 
|  | EXPECT_FALSE(proxy.created()); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, CreateOrOpen_OpenNonExistent) { | 
|  | FileProxy proxy(file_task_runner()); | 
|  | proxy.CreateOrOpen( | 
|  | TestPath(), File::FLAG_OPEN | File::FLAG_READ, | 
|  | BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  | EXPECT_EQ(File::FILE_ERROR_NOT_FOUND, error_); | 
|  | EXPECT_FALSE(proxy.IsValid()); | 
|  | EXPECT_FALSE(proxy.created()); | 
|  | EXPECT_FALSE(PathExists(TestPath())); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, CreateOrOpen_AbandonedCreate) { | 
|  | bool prev = ThreadRestrictions::SetIOAllowed(false); | 
|  | { | 
|  | FileProxy proxy(file_task_runner()); | 
|  | proxy.CreateOrOpen( | 
|  | TestPath(), File::FLAG_CREATE | File::FLAG_READ, | 
|  | BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr())); | 
|  | } | 
|  | RunLoop().Run(); | 
|  | ThreadRestrictions::SetIOAllowed(prev); | 
|  |  | 
|  | EXPECT_TRUE(PathExists(TestPath())); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, Close) { | 
|  | // Creates a file. | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy(File::FLAG_CREATE | File::FLAG_WRITE, &proxy); | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | // This fails on Windows if the file is not closed. | 
|  | EXPECT_FALSE(base::Move(TestPath(), TestDirPath().AppendASCII("new"))); | 
|  | #endif | 
|  |  | 
|  | proxy.Close(BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_FALSE(proxy.IsValid()); | 
|  |  | 
|  | // Now it should pass on all platforms. | 
|  | EXPECT_TRUE(base::Move(TestPath(), TestDirPath().AppendASCII("new"))); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, CreateTemporary) { | 
|  | { | 
|  | FileProxy proxy(file_task_runner()); | 
|  | proxy.CreateTemporary(0 /* additional_file_flags */, | 
|  | BindOnce(&FileProxyTest::DidCreateTemporary, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | EXPECT_TRUE(proxy.IsValid()); | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_TRUE(PathExists(path_)); | 
|  |  | 
|  | // The file should be writable. | 
|  | proxy.Write(0, "test", 4, | 
|  | BindOnce(&FileProxyTest::DidWrite, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_EQ(4, bytes_written_); | 
|  | } | 
|  |  | 
|  | // Make sure the written data can be read from the returned path. | 
|  | std::string data; | 
|  | EXPECT_TRUE(ReadFileToString(path_, &data)); | 
|  | EXPECT_EQ("test", data); | 
|  |  | 
|  | // Make sure we can & do delete the created file to prevent leaks on the bots. | 
|  | EXPECT_TRUE(base::DeleteFile(path_, false)); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, SetAndTake) { | 
|  | File file(TestPath(), File::FLAG_CREATE | File::FLAG_READ); | 
|  | ASSERT_TRUE(file.IsValid()); | 
|  | FileProxy proxy(file_task_runner()); | 
|  | EXPECT_FALSE(proxy.IsValid()); | 
|  | proxy.SetFile(std::move(file)); | 
|  | EXPECT_TRUE(proxy.IsValid()); | 
|  | EXPECT_FALSE(file.IsValid()); | 
|  |  | 
|  | file = proxy.TakeFile(); | 
|  | EXPECT_FALSE(proxy.IsValid()); | 
|  | EXPECT_TRUE(file.IsValid()); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, DuplicateFile) { | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy(File::FLAG_CREATE | File::FLAG_WRITE, &proxy); | 
|  | ASSERT_TRUE(proxy.IsValid()); | 
|  |  | 
|  | base::File duplicate = proxy.DuplicateFile(); | 
|  | EXPECT_TRUE(proxy.IsValid()); | 
|  | EXPECT_TRUE(duplicate.IsValid()); | 
|  |  | 
|  | FileProxy invalid_proxy(file_task_runner()); | 
|  | ASSERT_FALSE(invalid_proxy.IsValid()); | 
|  |  | 
|  | base::File invalid_duplicate = invalid_proxy.DuplicateFile(); | 
|  | EXPECT_FALSE(invalid_proxy.IsValid()); | 
|  | EXPECT_FALSE(invalid_duplicate.IsValid()); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, GetInfo) { | 
|  | // Setup. | 
|  | ASSERT_EQ(4, base::WriteFile(TestPath(), "test", 4)); | 
|  | File::Info expected_info; | 
|  | GetFileInfo(TestPath(), &expected_info); | 
|  |  | 
|  | // Run. | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy(File::FLAG_OPEN | File::FLAG_READ, &proxy); | 
|  | proxy.GetInfo( | 
|  | BindOnce(&FileProxyTest::DidGetFileInfo, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | // Verify. | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_EQ(expected_info.size, file_info_.size); | 
|  | EXPECT_EQ(expected_info.is_directory, file_info_.is_directory); | 
|  | EXPECT_EQ(expected_info.is_symbolic_link, file_info_.is_symbolic_link); | 
|  | EXPECT_EQ(expected_info.last_modified, file_info_.last_modified); | 
|  | EXPECT_EQ(expected_info.creation_time, file_info_.creation_time); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, Read) { | 
|  | // Setup. | 
|  | const char expected_data[] = "bleh"; | 
|  | int expected_bytes = arraysize(expected_data); | 
|  | ASSERT_EQ(expected_bytes, | 
|  | base::WriteFile(TestPath(), expected_data, expected_bytes)); | 
|  |  | 
|  | // Run. | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy(File::FLAG_OPEN | File::FLAG_READ, &proxy); | 
|  |  | 
|  | proxy.Read(0, 128, | 
|  | BindOnce(&FileProxyTest::DidRead, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | // Verify. | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_EQ(expected_bytes, static_cast<int>(buffer_.size())); | 
|  | for (size_t i = 0; i < buffer_.size(); ++i) { | 
|  | EXPECT_EQ(expected_data[i], buffer_[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, WriteAndFlush) { | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy(File::FLAG_CREATE | File::FLAG_WRITE, &proxy); | 
|  |  | 
|  | const char data[] = "foo!"; | 
|  | int data_bytes = arraysize(data); | 
|  | proxy.Write(0, data, data_bytes, | 
|  | BindOnce(&FileProxyTest::DidWrite, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  | EXPECT_EQ(data_bytes, bytes_written_); | 
|  |  | 
|  | // Flush the written data.  (So that the following read should always | 
|  | // succeed.  On some platforms it may work with or without this flush.) | 
|  | proxy.Flush(BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  |  | 
|  | // Verify the written data. | 
|  | char buffer[10]; | 
|  | EXPECT_EQ(data_bytes, base::ReadFile(TestPath(), buffer, data_bytes)); | 
|  | for (int i = 0; i < data_bytes; ++i) { | 
|  | EXPECT_EQ(data[i], buffer[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(OS_ANDROID) | 
|  | // Flaky on Android, see http://crbug.com/489602 | 
|  | #define MAYBE_SetTimes DISABLED_SetTimes | 
|  | #else | 
|  | #define MAYBE_SetTimes SetTimes | 
|  | #endif | 
|  | TEST_F(FileProxyTest, MAYBE_SetTimes) { | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy( | 
|  | File::FLAG_CREATE | File::FLAG_WRITE | File::FLAG_WRITE_ATTRIBUTES, | 
|  | &proxy); | 
|  |  | 
|  | Time last_accessed_time = Time::Now() - TimeDelta::FromDays(12345); | 
|  | Time last_modified_time = Time::Now() - TimeDelta::FromHours(98765); | 
|  |  | 
|  | proxy.SetTimes( | 
|  | last_accessed_time, last_modified_time, | 
|  | BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  | EXPECT_EQ(File::FILE_OK, error_); | 
|  |  | 
|  | File::Info info; | 
|  | GetFileInfo(TestPath(), &info); | 
|  |  | 
|  | // The returned values may only have the seconds precision, so we cast | 
|  | // the double values to int here. | 
|  | EXPECT_EQ(static_cast<int>(last_modified_time.ToDoubleT()), | 
|  | static_cast<int>(info.last_modified.ToDoubleT())); | 
|  | EXPECT_EQ(static_cast<int>(last_accessed_time.ToDoubleT()), | 
|  | static_cast<int>(info.last_accessed.ToDoubleT())); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, SetLength_Shrink) { | 
|  | // Setup. | 
|  | const char kTestData[] = "0123456789"; | 
|  | ASSERT_EQ(10, base::WriteFile(TestPath(), kTestData, 10)); | 
|  | File::Info info; | 
|  | GetFileInfo(TestPath(), &info); | 
|  | ASSERT_EQ(10, info.size); | 
|  |  | 
|  | // Run. | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy(File::FLAG_OPEN | File::FLAG_WRITE, &proxy); | 
|  | proxy.SetLength( | 
|  | 7, BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | // Verify. | 
|  | GetFileInfo(TestPath(), &info); | 
|  | ASSERT_EQ(7, info.size); | 
|  |  | 
|  | char buffer[7]; | 
|  | EXPECT_EQ(7, base::ReadFile(TestPath(), buffer, 7)); | 
|  | int i = 0; | 
|  | for (; i < 7; ++i) | 
|  | EXPECT_EQ(kTestData[i], buffer[i]); | 
|  | } | 
|  |  | 
|  | TEST_F(FileProxyTest, SetLength_Expand) { | 
|  | // Setup. | 
|  | const char kTestData[] = "9876543210"; | 
|  | ASSERT_EQ(10, base::WriteFile(TestPath(), kTestData, 10)); | 
|  | File::Info info; | 
|  | GetFileInfo(TestPath(), &info); | 
|  | ASSERT_EQ(10, info.size); | 
|  |  | 
|  | // Run. | 
|  | FileProxy proxy(file_task_runner()); | 
|  | CreateProxy(File::FLAG_OPEN | File::FLAG_WRITE, &proxy); | 
|  | proxy.SetLength( | 
|  | 53, BindOnce(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr())); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | // Verify. | 
|  | GetFileInfo(TestPath(), &info); | 
|  | ASSERT_EQ(53, info.size); | 
|  |  | 
|  | char buffer[53]; | 
|  | EXPECT_EQ(53, base::ReadFile(TestPath(), buffer, 53)); | 
|  | int i = 0; | 
|  | for (; i < 10; ++i) | 
|  | EXPECT_EQ(kTestData[i], buffer[i]); | 
|  | for (; i < 53; ++i) | 
|  | EXPECT_EQ(0, buffer[i]); | 
|  | } | 
|  |  | 
|  | }  // namespace base |