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