Base: Introduce a new version of FileUtilProxy that works with File.
BUG=322664
TEST=net_unittests
Review URL: https://codereview.chromium.org/180243015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@257290 0039d316-1c4b-4281-b951-d872f2087c98
CrOS-Libchrome-Original-Commit: cf88528eca7885ef1639f523fe4f70ad0e9cf7fe
diff --git a/base/files/file_proxy.cc b/base/files/file_proxy.cc
new file mode 100644
index 0000000..b517761
--- /dev/null
+++ b/base/files/file_proxy.cc
@@ -0,0 +1,345 @@
+// 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 "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/task_runner.h"
+#include "base/task_runner_util.h"
+
+namespace base {
+
+class FileHelper {
+ public:
+ FileHelper(FileProxy* proxy, File file)
+ : file_(file.Pass()),
+ proxy_(AsWeakPtr(proxy)),
+ error_(File::FILE_ERROR_FAILED) {
+ }
+
+ void PassFile() {
+ if (proxy_)
+ proxy_->SetFile(file_.Pass());
+ }
+
+ protected:
+ File file_;
+ WeakPtr<FileProxy> proxy_;
+ File::Error error_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileHelper);
+};
+
+namespace {
+
+class GenericFileHelper : public FileHelper {
+ public:
+ GenericFileHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, file.Pass()) {
+ }
+
+ void Close() {
+ file_.Close();
+ error_ = File::FILE_OK;
+ }
+
+ void SetTimes(Time last_access_time, Time last_modified_time) {
+ bool rv = file_.SetTimes(last_access_time, last_modified_time);
+ error_ = rv ? File::FILE_OK : File::FILE_ERROR_FAILED;
+ }
+
+ void SetLength(int64 length) {
+ if (file_.SetLength(length))
+ error_ = File::FILE_OK;
+ }
+
+ void Flush() {
+ if (file_.Flush())
+ error_ = File::FILE_OK;
+ }
+
+ void Reply(const FileProxy::StatusCallback& callback) {
+ PassFile();
+ if (!callback.is_null())
+ callback.Run(error_);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GenericFileHelper);
+};
+
+class CreateOrOpenHelper : public FileHelper {
+ public:
+ CreateOrOpenHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, file.Pass()) {
+ }
+
+ void RunWork(const FilePath& file_path, int file_flags) {
+ file_.Initialize(file_path, file_flags);
+ error_ = file_.IsValid() ? File::FILE_OK : file_.error_details();
+ }
+
+ void Reply(const FileProxy::StatusCallback& callback) {
+ DCHECK(!callback.is_null());
+ PassFile();
+ callback.Run(error_);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CreateOrOpenHelper);
+};
+
+class CreateTemporaryHelper : public FileHelper {
+ public:
+ CreateTemporaryHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, file.Pass()) {
+ }
+
+ void RunWork(uint32 additional_file_flags) {
+ // TODO(darin): file_util should have a variant of CreateTemporaryFile
+ // that returns a FilePath and a File.
+ if (!CreateTemporaryFile(&file_path_)) {
+ // TODO(davidben): base::CreateTemporaryFile should preserve the error
+ // code.
+ error_ = File::FILE_ERROR_FAILED;
+ return;
+ }
+
+ uint32 file_flags = File::FLAG_WRITE |
+ File::FLAG_TEMPORARY |
+ File::FLAG_CREATE_ALWAYS |
+ additional_file_flags;
+
+ file_.Initialize(file_path_, file_flags);
+ if (file_.IsValid()) {
+ error_ = File::FILE_OK;
+ } else {
+ error_ = file_.error_details();
+ DeleteFile(file_path_, false);
+ file_path_.clear();
+ }
+ }
+
+ void Reply(const FileProxy::CreateTemporaryCallback& callback) {
+ DCHECK(!callback.is_null());
+ PassFile();
+ callback.Run(error_, file_path_);
+ }
+
+ private:
+ FilePath file_path_;
+ DISALLOW_COPY_AND_ASSIGN(CreateTemporaryHelper);
+};
+
+class GetInfoHelper : public FileHelper {
+ public:
+ GetInfoHelper(FileProxy* proxy, File file)
+ : FileHelper(proxy, file.Pass()) {
+ }
+
+ void RunWork() {
+ if (file_.GetInfo(&file_info_))
+ error_ = File::FILE_OK;
+ }
+
+ void Reply(const FileProxy::GetFileInfoCallback& callback) {
+ PassFile();
+ DCHECK(!callback.is_null());
+ callback.Run(error_, file_info_);
+ }
+
+ private:
+ File::Info file_info_;
+ DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
+};
+
+class ReadHelper : public FileHelper {
+ public:
+ ReadHelper(FileProxy* proxy, File file, int bytes_to_read)
+ : FileHelper(proxy, file.Pass()),
+ buffer_(new char[bytes_to_read]),
+ bytes_to_read_(bytes_to_read),
+ bytes_read_(0) {
+ }
+
+ void RunWork(int64 offset) {
+ bytes_read_ = file_.Read(offset, buffer_.get(), bytes_to_read_);
+ error_ = (bytes_read_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
+ }
+
+ void Reply(const FileProxy::ReadCallback& callback) {
+ PassFile();
+ DCHECK(!callback.is_null());
+ callback.Run(error_, buffer_.get(), bytes_read_);
+ }
+
+ private:
+ scoped_ptr<char[]> buffer_;
+ int bytes_to_read_;
+ int bytes_read_;
+ DISALLOW_COPY_AND_ASSIGN(ReadHelper);
+};
+
+class WriteHelper : public FileHelper {
+ public:
+ WriteHelper(FileProxy* proxy,
+ File file,
+ const char* buffer, int bytes_to_write)
+ : FileHelper(proxy, file.Pass()),
+ buffer_(new char[bytes_to_write]),
+ bytes_to_write_(bytes_to_write),
+ bytes_written_(0) {
+ memcpy(buffer_.get(), buffer, bytes_to_write);
+ }
+
+ void RunWork(int64 offset) {
+ bytes_written_ = file_.Write(offset, buffer_.get(), bytes_to_write_);
+ error_ = (bytes_written_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
+ }
+
+ void Reply(const FileProxy::WriteCallback& callback) {
+ PassFile();
+ if (!callback.is_null())
+ callback.Run(error_, bytes_written_);
+ }
+
+ private:
+ scoped_ptr<char[]> buffer_;
+ int bytes_to_write_;
+ int bytes_written_;
+ DISALLOW_COPY_AND_ASSIGN(WriteHelper);
+};
+
+} // namespace
+
+FileProxy::FileProxy() : task_runner_(NULL) {
+}
+
+FileProxy::FileProxy(TaskRunner* task_runner) : task_runner_(task_runner) {
+}
+
+FileProxy::~FileProxy() {
+}
+
+bool FileProxy::CreateOrOpen(const FilePath& file_path,
+ uint32 file_flags,
+ const StatusCallback& callback) {
+ DCHECK(!file_.IsValid());
+ CreateOrOpenHelper* helper = new CreateOrOpenHelper(this, File());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&CreateOrOpenHelper::RunWork, Unretained(helper), file_path,
+ file_flags),
+ Bind(&CreateOrOpenHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::CreateTemporary(uint32 additional_file_flags,
+ const CreateTemporaryCallback& callback) {
+ DCHECK(!file_.IsValid());
+ CreateTemporaryHelper* helper = new CreateTemporaryHelper(this, File());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&CreateTemporaryHelper::RunWork, Unretained(helper),
+ additional_file_flags),
+ Bind(&CreateTemporaryHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::IsValid() const {
+ return file_.IsValid();
+}
+
+File FileProxy::TakeFile() {
+ return file_.Pass();
+}
+
+bool FileProxy::Close(const StatusCallback& callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, file_.Pass());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&GenericFileHelper::Close, Unretained(helper)),
+ Bind(&GenericFileHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::GetInfo(const GetFileInfoCallback& callback) {
+ DCHECK(file_.IsValid());
+ GetInfoHelper* helper = new GetInfoHelper(this, file_.Pass());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&GetInfoHelper::RunWork, Unretained(helper)),
+ Bind(&GetInfoHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::Read(int64 offset,
+ int bytes_to_read,
+ const ReadCallback& callback) {
+ DCHECK(file_.IsValid());
+ if (bytes_to_read < 0)
+ return false;
+
+ ReadHelper* helper = new ReadHelper(this, file_.Pass(), bytes_to_read);
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&ReadHelper::RunWork, Unretained(helper), offset),
+ Bind(&ReadHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::Write(int64 offset,
+ const char* buffer,
+ int bytes_to_write,
+ const WriteCallback& callback) {
+ DCHECK(file_.IsValid());
+ if (bytes_to_write <= 0 || buffer == NULL)
+ return false;
+
+ WriteHelper* helper =
+ new WriteHelper(this, file_.Pass(), buffer, bytes_to_write);
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&WriteHelper::RunWork, Unretained(helper), offset),
+ Bind(&WriteHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::SetTimes(Time last_access_time,
+ Time last_modified_time,
+ const StatusCallback& callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, file_.Pass());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&GenericFileHelper::SetTimes, Unretained(helper), last_access_time,
+ last_modified_time),
+ Bind(&GenericFileHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::SetLength(int64 length, const StatusCallback& callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, file_.Pass());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&GenericFileHelper::SetLength, Unretained(helper), length),
+ Bind(&GenericFileHelper::Reply, Owned(helper), callback));
+}
+
+bool FileProxy::Flush(const StatusCallback& callback) {
+ DCHECK(file_.IsValid());
+ GenericFileHelper* helper = new GenericFileHelper(this, file_.Pass());
+ return task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&GenericFileHelper::Flush, Unretained(helper)),
+ Bind(&GenericFileHelper::Reply, Owned(helper), callback));
+}
+
+void FileProxy::SetFile(File file) {
+ DCHECK(!file_.IsValid());
+ file_ = file.Pass();
+}
+
+} // namespace base
diff --git a/base/files/file_proxy.h b/base/files/file_proxy.h
new file mode 100644
index 0000000..f02960b
--- /dev/null
+++ b/base/files/file_proxy.h
@@ -0,0 +1,142 @@
+// 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.
+
+#ifndef BASE_FILES_FILE_PROXY_H_
+#define BASE_FILES_FILE_PROXY_H_
+
+#include "base/base_export.h"
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+
+namespace tracked_objects {
+class Location;
+};
+
+namespace base {
+
+class TaskRunner;
+class Time;
+
+// This class provides asynchronous access to a File. All methods follow the
+// same rules of the equivalent File method, as they are implemented by bouncing
+// the operation to File using a TaskRunner.
+//
+// This class does NOT perform automatic proxying to close the underlying file
+// at destruction, which means that it may potentially close the file in the
+// wrong thread (the current thread). If that is not appropriate, the caller
+// must ensure that Close() is called, so that the operation happens on the
+// desired thread.
+//
+// The TaskRunner is in charge of any sequencing of the operations, but a single
+// operation can be proxied at a time, regardless of the use of a callback.
+// In other words, having a sequence like
+//
+// proxy.Write(...);
+// delete proxy;
+//
+// will keep the file valid during the Write operation but will cause the file
+// to be closed in the current thread, when the operation finishes. If Close is
+// called right away after Write, the second call will fail because there is an
+// operation in progress.
+class BASE_EXPORT FileProxy : public SupportsWeakPtr<FileProxy> {
+ public:
+ // This callback is used by methods that report only an error code. It is
+ // valid to pass a null callback to some functions that takes a
+ // StatusCallback, in which case the operation will complete silently.
+ typedef Callback<void(File::Error)> StatusCallback;
+
+ typedef Callback<void(File::Error,
+ const FilePath&)> CreateTemporaryCallback;
+ typedef Callback<void(File::Error,
+ const File::Info&)> GetFileInfoCallback;
+ typedef Callback<void(File::Error,
+ const char* data,
+ int bytes_read)> ReadCallback;
+ typedef Callback<void(File::Error,
+ int bytes_written)> WriteCallback;
+
+ FileProxy();
+ explicit FileProxy(TaskRunner* task_runner);
+ ~FileProxy();
+
+ // Creates or opens a file with the given flags. It is invalid to pass a null
+ // callback. If File::FLAG_CREATE is set in |file_flags| it always tries to
+ // create a new file at the given |file_path| and fails if the file already
+ // exists.
+ //
+ // This returns false if task posting to |task_runner| has failed.
+ bool CreateOrOpen(const FilePath& file_path,
+ uint32 file_flags,
+ const StatusCallback& callback);
+
+ // Creates a temporary file for writing. The path and an open file are
+ // returned. It is invalid to pass a null callback. The additional file flags
+ // will be added on top of the default file flags which are:
+ // File::FLAG_CREATE_ALWAYS
+ // File::FLAG_WRITE
+ // File::FLAG_TEMPORARY.
+ //
+ // This returns false if task posting to |task_runner| has failed.
+ bool CreateTemporary(uint32 additional_file_flags,
+ const CreateTemporaryCallback& callback);
+
+ // Returns true if the underlying |file_| is valid.
+ bool IsValid() const;
+
+ // Returns true if a new file was created (or an old one truncated to zero
+ // length to simulate a new file), and false otherwise.
+ bool created() const { return file_.created(); }
+
+ File TakeFile();
+
+ // Proxies File::Close. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool Close(const StatusCallback& callback);
+
+ // Proxies File::GetInfo. The callback can't be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool GetInfo(const GetFileInfoCallback& callback);
+
+ // Proxies File::Read. The callback can't be null.
+ // This returns false if |bytes_to_read| is less than zero, or
+ // if task posting to |task_runner| has failed.
+ bool Read(int64 offset, int bytes_to_read, const ReadCallback& callback);
+
+ // Proxies File::Write. The callback can be null.
+ // This returns false if |bytes_to_write| is less than or equal to zero,
+ // if |buffer| is NULL, or if task posting to |task_runner| has failed.
+ bool Write(int64 offset,
+ const char* buffer,
+ int bytes_to_write,
+ const WriteCallback& callback);
+
+ // Proxies File::SetTimes. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool SetTimes(Time last_access_time,
+ Time last_modified_time,
+ const StatusCallback& callback);
+
+ // Proxies File::SetLength. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool SetLength(int64 length, const StatusCallback& callback);
+
+ // Proxies File::Flush. The callback can be null.
+ // This returns false if task posting to |task_runner| has failed.
+ bool Flush(const StatusCallback& callback);
+
+ private:
+ friend class FileHelper;
+ void SetFile(File file);
+
+ scoped_refptr<TaskRunner> task_runner_;
+ File file_;
+ DISALLOW_COPY_AND_ASSIGN(FileProxy);
+};
+
+} // namespace base
+
+#endif // BASE_FILES_FILE_PROXY_H_
diff --git a/base/files/file_proxy_unittest.cc b/base/files/file_proxy_unittest.cc
new file mode 100644
index 0000000..b5e1002
--- /dev/null
+++ b/base/files/file_proxy_unittest.cc
@@ -0,0 +1,346 @@
+// 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 <map>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.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) {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(file_thread_.Start());
+ }
+
+ void DidFinish(File::Error error) {
+ error_ = error;
+ MessageLoop::current()->QuitWhenIdle();
+ }
+
+ void DidCreateOrOpen(File::Error error) {
+ error_ = error;
+ MessageLoop::current()->QuitWhenIdle();
+ }
+
+ void DidCreateTemporary(File::Error error,
+ const FilePath& path) {
+ error_ = error;
+ path_ = path;
+ MessageLoop::current()->QuitWhenIdle();
+ }
+
+ void DidGetFileInfo(File::Error error,
+ const File::Info& file_info) {
+ error_ = error;
+ file_info_ = file_info;
+ MessageLoop::current()->QuitWhenIdle();
+ }
+
+ void DidRead(File::Error error,
+ const char* data,
+ int bytes_read) {
+ error_ = error;
+ buffer_.resize(bytes_read);
+ memcpy(&buffer_[0], data, bytes_read);
+ MessageLoop::current()->QuitWhenIdle();
+ }
+
+ void DidWrite(File::Error error,
+ int bytes_written) {
+ error_ = error;
+ bytes_written_ = bytes_written;
+ MessageLoop::current()->QuitWhenIdle();
+ }
+
+ protected:
+ void CreateProxy(uint32 flags, FileProxy* proxy) {
+ proxy->CreateOrOpen(
+ test_path(), flags,
+ Bind(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(proxy->IsValid());
+ }
+
+ TaskRunner* file_task_runner() const {
+ return file_thread_.message_loop_proxy().get();
+ }
+ const FilePath& test_dir_path() const { return dir_.path(); }
+ const FilePath test_path() const { return dir_.path().AppendASCII("test"); }
+
+ MessageLoopForIO message_loop_;
+ Thread file_thread_;
+
+ ScopedTempDir dir_;
+ 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(
+ test_path(),
+ File::FLAG_CREATE | File::FLAG_READ,
+ Bind(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_TRUE(proxy.IsValid());
+ EXPECT_TRUE(proxy.created());
+ EXPECT_TRUE(PathExists(test_path()));
+}
+
+TEST_F(FileProxyTest, CreateOrOpen_Open) {
+ // Creates a file.
+ base::WriteFile(test_path(), NULL, 0);
+ ASSERT_TRUE(PathExists(test_path()));
+
+ // Opens the created file.
+ FileProxy proxy(file_task_runner());
+ proxy.CreateOrOpen(
+ test_path(),
+ File::FLAG_OPEN | File::FLAG_READ,
+ Bind(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->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(
+ test_path(),
+ File::FLAG_OPEN | File::FLAG_READ,
+ Bind(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+ EXPECT_EQ(File::FILE_ERROR_NOT_FOUND, error_);
+ EXPECT_FALSE(proxy.IsValid());
+ EXPECT_FALSE(proxy.created());
+ EXPECT_FALSE(PathExists(test_path()));
+}
+
+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(test_path(), test_dir_path().AppendASCII("new")));
+#endif
+
+ proxy.Close(Bind(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+ EXPECT_FALSE(proxy.IsValid());
+
+ // Now it should pass on all platforms.
+ EXPECT_TRUE(base::Move(test_path(), test_dir_path().AppendASCII("new")));
+}
+
+TEST_F(FileProxyTest, CreateTemporary) {
+ {
+ FileProxy proxy(file_task_runner());
+ proxy.CreateTemporary(
+ 0 /* additional_file_flags */,
+ Bind(&FileProxyTest::DidCreateTemporary, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->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,
+ Bind(&FileProxyTest::DidWrite, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->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, GetInfo) {
+ // Setup.
+ ASSERT_EQ(4, base::WriteFile(test_path(), "test", 4));
+ File::Info expected_info;
+ GetFileInfo(test_path(), &expected_info);
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_READ, &proxy);
+ proxy.GetInfo(
+ Bind(&FileProxyTest::DidGetFileInfo, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->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);
+
+ File file = proxy.TakeFile();
+ EXPECT_TRUE(file.GetInfo(&expected_info));
+ 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(test_path(), expected_data, expected_bytes));
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_READ, &proxy);
+
+ proxy.Read(0, 128, Bind(&FileProxyTest::DidRead, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->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_UNSAFE(data);
+ proxy.Write(0, data, data_bytes,
+ Bind(&FileProxyTest::DidWrite, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->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(Bind(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+
+ // Verify the written data.
+ char buffer[10];
+ EXPECT_EQ(data_bytes, base::ReadFile(test_path(), buffer, data_bytes));
+ for (int i = 0; i < data_bytes; ++i) {
+ EXPECT_EQ(data[i], buffer[i]);
+ }
+}
+
+TEST_F(FileProxyTest, 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,
+ Bind(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+ EXPECT_EQ(File::FILE_OK, error_);
+
+ File::Info info;
+ GetFileInfo(test_path(), &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(test_path(), kTestData, 10));
+ File::Info info;
+ GetFileInfo(test_path(), &info);
+ ASSERT_EQ(10, info.size);
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_WRITE, &proxy);
+ proxy.SetLength(7,
+ Bind(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+
+ // Verify.
+ GetFileInfo(test_path(), &info);
+ ASSERT_EQ(7, info.size);
+
+ char buffer[7];
+ EXPECT_EQ(7, base::ReadFile(test_path(), 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(test_path(), kTestData, 10));
+ File::Info info;
+ GetFileInfo(test_path(), &info);
+ ASSERT_EQ(10, info.size);
+
+ // Run.
+ FileProxy proxy(file_task_runner());
+ CreateProxy(File::FLAG_OPEN | File::FLAG_WRITE, &proxy);
+ proxy.SetLength(53,
+ Bind(&FileProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+ MessageLoop::current()->Run();
+
+ // Verify.
+ GetFileInfo(test_path(), &info);
+ ASSERT_EQ(53, info.size);
+
+ char buffer[53];
+ EXPECT_EQ(53, base::ReadFile(test_path(), 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