root/ppapi/tests/test_file_ref.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ReportMismatch
  2. Init
  3. MakeExternalFileRef
  4. DeleteDirectoryRecursively
  5. RunTests
  6. TestCreate
  7. TestGetFileSystemType
  8. TestGetName
  9. TestGetPath
  10. TestGetParent
  11. TestMakeDirectory
  12. TestQueryAndTouchFile
  13. TestDeleteFileAndDirectory
  14. TestRenameFileAndDirectory
  15. TestQuery
  16. TestFileNameEscaping
  17. TestReadDirectoryEntries

// Copyright (c) 2011 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 "ppapi/tests/test_file_ref.h"

#include <stdio.h>

#include <sstream>
#include <vector>

#include "ppapi/c/pp_errors.h"
#include "ppapi/c/ppb_file_io.h"
#include "ppapi/c/private/ppb_testing_private.h"
#include "ppapi/cpp/directory_entry.h"
#include "ppapi/cpp/file_io.h"
#include "ppapi/cpp/file_ref.h"
#include "ppapi/cpp/file_system.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/url_loader.h"
#include "ppapi/cpp/url_request_info.h"
#include "ppapi/cpp/url_response_info.h"
#include "ppapi/tests/test_utils.h"
#include "ppapi/tests/testing_instance.h"

REGISTER_TEST_CASE(FileRef);

namespace {

const char* kPersFileName = "persistent";
const char* kTempFileName = "temporary";
const char* kParentPath = "/foo/bar";
const char* kPersFilePath = "/foo/bar/persistent";
const char* kTempFilePath = "/foo/bar/temporary";
const char* kTerribleName = "!@#$%^&*()-_=+{}[] ;:'\"|`~\t\n\r\b?";

typedef std::vector<pp::DirectoryEntry> DirEntries;

std::string ReportMismatch(const std::string& method_name,
                           const std::string& returned_result,
                           const std::string& expected_result) {
  return method_name + " returned '" + returned_result + "'; '" +
      expected_result + "' expected.";
}

}  // namespace

bool TestFileRef::Init() {
  return CheckTestingInterface() && EnsureRunningOverHTTP();
}

std::string TestFileRef::MakeExternalFileRef(pp::FileRef* file_ref_ext) {
  pp::URLRequestInfo request(instance_);
  request.SetURL("test_url_loader_data/hello.txt");
  request.SetStreamToFile(true);

  TestCompletionCallback callback(instance_->pp_instance(), callback_type());

  pp::URLLoader loader(instance_);
  callback.WaitForResult(loader.Open(request, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::URLResponseInfo response_info(loader.GetResponseInfo());
  ASSERT_FALSE(response_info.is_null());
  ASSERT_EQ(200, response_info.GetStatusCode());

  *file_ref_ext = pp::FileRef(response_info.GetBodyAsFileRef());
  ASSERT_EQ(PP_FILESYSTEMTYPE_EXTERNAL, file_ref_ext->GetFileSystemType());
  PASS();
}

int32_t TestFileRef::DeleteDirectoryRecursively(pp::FileRef* dir) {
  if (!dir)
    return PP_ERROR_BADARGUMENT;

  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
  TestCompletionCallbackWithOutput<DirEntries> output_callback(
      instance_->pp_instance(), callback_type());

  output_callback.WaitForResult(
      dir->ReadDirectoryEntries(output_callback.GetCallback()));
  int32_t rv = output_callback.result();
  if (rv != PP_OK && rv != PP_ERROR_FILENOTFOUND)
    return rv;

  DirEntries entries = output_callback.output();
  for (DirEntries::const_iterator it = entries.begin();
       it != entries.end();
       ++it) {
    pp::FileRef file_ref = it->file_ref();
    if (it->file_type() == PP_FILETYPE_DIRECTORY) {
      rv = DeleteDirectoryRecursively(&file_ref);
      if (rv != PP_OK && rv != PP_ERROR_FILENOTFOUND)
        return rv;
    } else {
      callback.WaitForResult(file_ref.Delete(callback.GetCallback()));
      rv = callback.result();
      if (rv != PP_OK && rv != PP_ERROR_FILENOTFOUND)
        return rv;
    }
  }
  callback.WaitForResult(dir->Delete(callback.GetCallback()));
  return callback.result();
}

void TestFileRef::RunTests(const std::string& filter) {
  RUN_CALLBACK_TEST(TestFileRef, Create, filter);
  RUN_CALLBACK_TEST(TestFileRef, GetFileSystemType, filter);
  RUN_CALLBACK_TEST(TestFileRef, GetName, filter);
  RUN_CALLBACK_TEST(TestFileRef, GetPath, filter);
  RUN_CALLBACK_TEST(TestFileRef, GetParent, filter);
  RUN_CALLBACK_TEST(TestFileRef, MakeDirectory, filter);
  RUN_CALLBACK_TEST(TestFileRef, QueryAndTouchFile, filter);
  RUN_CALLBACK_TEST(TestFileRef, DeleteFileAndDirectory, filter);
  RUN_CALLBACK_TEST(TestFileRef, RenameFileAndDirectory, filter);
  RUN_CALLBACK_TEST(TestFileRef, Query, filter);
  RUN_CALLBACK_TEST(TestFileRef, FileNameEscaping, filter);
  RUN_CALLBACK_TEST(TestFileRef, ReadDirectoryEntries, filter);
}

std::string TestFileRef::TestCreate() {
  std::vector<std::string> invalid_paths;
  invalid_paths.push_back("invalid_path");  // no '/' at the first character
  invalid_paths.push_back(std::string());   // empty path
  // The following are directory traversal checks
  invalid_paths.push_back("..");
  invalid_paths.push_back("/../invalid_path");
  invalid_paths.push_back("/../../invalid_path");
  invalid_paths.push_back("/invalid/../../path");
  const size_t num_invalid_paths = invalid_paths.size();

  pp::FileSystem file_system_pers(
      instance_, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
  pp::FileSystem file_system_temp(
      instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  for (size_t j = 0; j < num_invalid_paths; ++j) {
    pp::FileRef file_ref_pers(file_system_pers, invalid_paths[j].c_str());
    if (file_ref_pers.pp_resource() != 0) {
      return "file_ref_pers expected to be invalid for path: " +
          invalid_paths[j];
    }
    pp::FileRef file_ref_temp(file_system_temp, invalid_paths[j].c_str());
    if (file_ref_temp.pp_resource() != 0) {
      return "file_ref_temp expected to be invalid for path: " +
          invalid_paths[j];
    }
  }
  PASS();
}

std::string TestFileRef::TestGetFileSystemType() {
  pp::FileSystem file_system_pers(
      instance_, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
  pp::FileSystem file_system_temp(
      instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);

  pp::FileRef file_ref_pers(file_system_pers, kPersFilePath);
  if (file_ref_pers.GetFileSystemType() != PP_FILESYSTEMTYPE_LOCALPERSISTENT)
    return "file_ref_pers expected to be persistent.";

  pp::FileRef file_ref_temp(file_system_temp, kTempFilePath);
  if (file_ref_temp.GetFileSystemType() != PP_FILESYSTEMTYPE_LOCALTEMPORARY)
    return "file_ref_temp expected to be temporary.";

  pp::FileRef file_ref_ext;
  std::string result = MakeExternalFileRef(&file_ref_ext);
  if (!result.empty())
    return result;
  PASS();
}

std::string TestFileRef::TestGetName() {
  pp::FileSystem file_system_pers(
      instance_, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
  pp::FileSystem file_system_temp(
      instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);

  pp::FileRef file_ref_pers(file_system_pers, kPersFilePath);
  std::string name = file_ref_pers.GetName().AsString();
  if (name != kPersFileName)
    return ReportMismatch("FileRef::GetName", name, kPersFileName);

  pp::FileRef file_ref_temp(file_system_temp, kTempFilePath);
  name = file_ref_temp.GetName().AsString();
  if (name != kTempFileName)
    return ReportMismatch("FileRef::GetName", name, kTempFileName);

  // Test the "/" case.
  pp::FileRef file_ref_slash(file_system_temp, "/");
  name = file_ref_slash.GetName().AsString();
  if (name != "/")
    return ReportMismatch("FileRef::GetName", name, "/");

  pp::URLRequestInfo request(instance_);
  request.SetURL("test_url_loader_data/hello.txt");
  request.SetStreamToFile(true);

  TestCompletionCallback callback(instance_->pp_instance(), callback_type());

  pp::URLLoader loader(instance_);
  callback.WaitForResult(loader.Open(request, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::URLResponseInfo response_info(loader.GetResponseInfo());
  ASSERT_FALSE(response_info.is_null());
  ASSERT_EQ(200, response_info.GetStatusCode());

  pp::FileRef file_ref_ext(response_info.GetBodyAsFileRef());
  name = file_ref_ext.GetName().AsString();
  ASSERT_FALSE(name.empty());

  PASS();
}

std::string TestFileRef::TestGetPath() {
  pp::FileSystem file_system_pers(
      instance_, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
  pp::FileSystem file_system_temp(
      instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);

  pp::FileRef file_ref_pers(file_system_pers, kPersFilePath);
  ASSERT_EQ(kPersFilePath, file_ref_pers.GetPath().AsString());

  pp::FileRef file_ref_temp(file_system_temp, kTempFilePath);
  ASSERT_EQ(kTempFilePath, file_ref_temp.GetPath().AsString());

  pp::URLRequestInfo request(instance_);
  request.SetURL("test_url_loader_data/hello.txt");
  request.SetStreamToFile(true);

  TestCompletionCallback callback(instance_->pp_instance(), callback_type());

  pp::URLLoader loader(instance_);
  callback.WaitForResult(loader.Open(request, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::URLResponseInfo response_info(loader.GetResponseInfo());
  ASSERT_FALSE(response_info.is_null());
  ASSERT_EQ(200, response_info.GetStatusCode());

  pp::FileRef file_ref_ext(response_info.GetBodyAsFileRef());
  ASSERT_TRUE(file_ref_ext.GetPath().is_undefined());

  PASS();
}

std::string TestFileRef::TestGetParent() {
  pp::FileSystem file_system_pers(
      instance_, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
  pp::FileSystem file_system_temp(
      instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);

  pp::FileRef file_ref_pers(file_system_pers, kPersFilePath);
  ASSERT_EQ(kParentPath, file_ref_pers.GetParent().GetPath().AsString());

  pp::FileRef file_ref_temp(file_system_temp, kTempFilePath);
  ASSERT_EQ(kParentPath, file_ref_temp.GetParent().GetPath().AsString());

  // Test the "/" case.
  pp::FileRef file_ref_slash(file_system_temp, "/");
  ASSERT_EQ("/", file_ref_slash.GetParent().GetPath().AsString());

  // Test the "/foo" case (the parent is "/").
  pp::FileRef file_ref_with_root_parent(file_system_temp, "/foo");
  ASSERT_EQ("/", file_ref_with_root_parent.GetParent().GetPath().AsString());

  pp::URLRequestInfo request(instance_);
  request.SetURL("test_url_loader_data/hello.txt");
  request.SetStreamToFile(true);

  TestCompletionCallback callback(instance_->pp_instance(), callback_type());

  pp::URLLoader loader(instance_);
  callback.WaitForResult(loader.Open(request, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::URLResponseInfo response_info(loader.GetResponseInfo());
  ASSERT_FALSE(response_info.is_null());
  ASSERT_EQ(200, response_info.GetStatusCode());

  pp::FileRef file_ref_ext(response_info.GetBodyAsFileRef());
  ASSERT_TRUE(file_ref_ext.GetParent().is_null());

  PASS();
}

std::string TestFileRef::TestMakeDirectory() {
  TestCompletionCallback callback(instance_->pp_instance(), callback_type());

  // Open.
  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Make a directory.
  pp::FileRef dir_ref(file_system, "/dir_make_dir");
  callback.WaitForResult(
      dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Make a directory on the existing path without exclusive flag.
  callback.WaitForResult(
      dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Making a directory should be aborted.
  int32_t rv = PP_ERROR_FAILED;
  {
    rv = pp::FileRef(file_system, "/dir_make_dir_abort")
        .MakeDirectory(PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback());
  }
  callback.WaitForAbortResult(rv);
  CHECK_CALLBACK_BEHAVIOR(callback);

  // Make nested directories.
  dir_ref = pp::FileRef(file_system, "/dir_make_nested_dir_1/dir");
  callback.WaitForResult(
      dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_WITH_ANCESTORS,
                            callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  dir_ref = pp::FileRef(file_system, "/dir_make_nested_dir_2/dir");
  callback.WaitForResult(
      dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_ERROR_FILENOTFOUND, callback.result());

  // Ensure there is no directory on the path to test exclusive cases.
  dir_ref = pp::FileRef(file_system, "/dir_make_dir_exclusive");
  rv = DeleteDirectoryRecursively(&dir_ref);
  ASSERT_TRUE(rv == PP_OK || rv == PP_ERROR_FILENOTFOUND);

  // Make a directory exclusively.
  callback.WaitForResult(
      dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_EXCLUSIVE,
                            callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  callback.WaitForResult(
      dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_EXCLUSIVE,
                            callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_ERROR_FILEEXISTS, callback.result());

  PASS();
}

std::string TestFileRef::TestQueryAndTouchFile() {
  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef file_ref(file_system, "/file_touch");
  pp::FileIO file_io(instance_);
  callback.WaitForResult(
      file_io.Open(file_ref,
                   PP_FILEOPENFLAG_CREATE |
                   PP_FILEOPENFLAG_TRUNCATE |
                   PP_FILEOPENFLAG_WRITE,
                   callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Write some data to have a non-zero file size.
  callback.WaitForResult(file_io.Write(0, "test", 4, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(4, callback.result());

  // Touch.
  const PP_Time last_access_time = 123 * 24 * 3600.0;
  // last_modified_time's granularity is 2 seconds
  // See note in test_file_io.cc for why we use this time.
  const PP_Time last_modified_time = 100 * 24 * 3600.0;
  callback.WaitForResult(file_ref.Touch(last_access_time, last_modified_time,
                                        callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Touch aborted.
  int32_t rv = PP_ERROR_FAILED;
  {
    rv = pp::FileRef(file_system, "/file_touch_abort")
        .Touch(last_access_time, last_modified_time, callback.GetCallback());
  }
  callback.WaitForResult(rv);
  CHECK_CALLBACK_BEHAVIOR(callback);
  if (rv == PP_OK_COMPLETIONPENDING) {
    // Touch tried to run asynchronously and should have been aborted.
    ASSERT_EQ(PP_ERROR_ABORTED, callback.result());
  } else {
    // Touch ran synchronously and should have failed because the file does not
    // exist.
    ASSERT_EQ(PP_ERROR_FILENOTFOUND, callback.result());
  }

  // Query.
  PP_FileInfo info;
  callback.WaitForResult(file_io.Query(&info, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());
  ASSERT_EQ(4, info.size);
  ASSERT_EQ(PP_FILETYPE_REGULAR, info.type);
  ASSERT_EQ(PP_FILESYSTEMTYPE_LOCALTEMPORARY, info.system_type);

  // Disabled due to DST-related failure: crbug.com/314579
  // ASSERT_EQ(last_access_time, info.last_access_time);
  // ASSERT_EQ(last_modified_time, info.last_modified_time);

  // Cancellation test.
  // TODO(viettrungluu): this test causes a bunch of LOG(WARNING)s; investigate.
  // TODO(viettrungluu): check |info| for late writes.
  {
    rv = pp::FileRef(file_system, "/file_touch").Touch(
        last_access_time, last_modified_time, callback.GetCallback());
  }
  callback.WaitForAbortResult(rv);
  CHECK_CALLBACK_BEHAVIOR(callback);

  PASS();
}

std::string TestFileRef::TestDeleteFileAndDirectory() {
  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef file_ref(file_system, "/file_delete");
  pp::FileIO file_io(instance_);
  callback.WaitForResult(
      file_io.Open(file_ref, PP_FILEOPENFLAG_CREATE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  callback.WaitForResult(file_ref.Delete(callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef dir_ref(file_system, "/dir_delete");
  callback.WaitForResult(dir_ref.MakeDirectory(
      PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  callback.WaitForResult(dir_ref.Delete(callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef nested_dir_ref(file_system, "/dir_delete_1/dir_delete_2");
  callback.WaitForResult(
      nested_dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_WITH_ANCESTORS,
                                   callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Attempt to delete the parent directory (should fail; it's non-empty).
  pp::FileRef parent_dir_ref = nested_dir_ref.GetParent();
  callback.WaitForResult(parent_dir_ref.Delete(callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_ERROR_FAILED, callback.result());

  pp::FileRef nonexistent_file_ref(file_system, "/nonexistent_file_delete");
  callback.WaitForResult(nonexistent_file_ref.Delete(callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_ERROR_FILENOTFOUND, callback.result());

  // Delete aborted.
  int32_t rv = PP_ERROR_FAILED;
  {
    pp::FileRef file_ref_abort(file_system, "/file_delete_abort");
    pp::FileIO file_io_abort(instance_);
    callback.WaitForResult(
        file_io_abort.Open(file_ref_abort, PP_FILEOPENFLAG_CREATE,
                           callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(callback);
    ASSERT_EQ(PP_OK, callback.result());
    rv = file_ref_abort.Delete(callback.GetCallback());
  }
  callback.WaitForAbortResult(rv);
  CHECK_CALLBACK_BEHAVIOR(callback);

  PASS();
}

std::string TestFileRef::TestRenameFileAndDirectory() {
  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef file_ref(file_system, "/file_rename");
  pp::FileIO file_io(instance_);
  callback.WaitForResult(
      file_io.Open(file_ref, PP_FILEOPENFLAG_CREATE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef target_file_ref(file_system, "/target_file_rename");
  callback.WaitForResult(
      file_ref.Rename(target_file_ref, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef dir_ref(file_system, "/dir_rename");
  callback.WaitForResult(dir_ref.MakeDirectory(
      PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef target_dir_ref(file_system, "/target_dir_rename");
  callback.WaitForResult(
      dir_ref.Rename(target_dir_ref, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef nested_dir_ref(file_system, "/dir_rename_1/dir_rename_2");
  callback.WaitForResult(
      nested_dir_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_WITH_ANCESTORS,
                                   callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Try to rename nested directory to the parent name. Should fail.
  pp::FileRef target_nested_dir_ref(file_system, "/dir_rename_1");
  callback.WaitForResult(
      nested_dir_ref.Rename(target_nested_dir_ref, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_ERROR_FAILED, callback.result());

  // Rename aborted.
  // TODO(viettrungluu): Figure out what we want to do if the target file
  // resource is destroyed before completion.
  int32_t rv = PP_ERROR_FAILED;
  pp::FileRef target_file_ref_abort(file_system,
                                    "/target_file_rename_abort");
  {
    pp::FileRef file_ref_abort(file_system, "/file_rename_abort");
    pp::FileIO file_io_abort(instance_);
    callback.WaitForResult(
        file_io_abort.Open(file_ref_abort, PP_FILEOPENFLAG_CREATE,
                           callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(callback);
    ASSERT_EQ(PP_OK, callback.result());

    rv = file_ref_abort.Rename(target_file_ref_abort, callback.GetCallback());
  }
  callback.WaitForAbortResult(rv);
  CHECK_CALLBACK_BEHAVIOR(callback);

  PASS();
}

std::string TestFileRef::TestQuery() {
  TestCompletionCallback callback(instance_->pp_instance(), callback_type());

  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  pp::FileRef file_ref(file_system, "/file");
  pp::FileIO file_io(instance_);
  callback.WaitForResult(file_io.Open(file_ref, PP_FILEOPENFLAG_CREATE,
                                      callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // We touch the file so we can easily check access and modified time.
  callback.WaitForResult(file_io.Touch(0, 0, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  TestCompletionCallbackWithOutput<PP_FileInfo> out_callback(
      instance_->pp_instance(), callback_type());
  out_callback.WaitForResult(file_ref.Query(out_callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(out_callback);
  ASSERT_EQ(PP_OK, out_callback.result());

  PP_FileInfo info = out_callback.output();
  ASSERT_EQ(0, info.size);
  ASSERT_EQ(PP_FILETYPE_REGULAR, info.type);
  ASSERT_EQ(PP_FILESYSTEMTYPE_LOCALTEMPORARY, info.system_type);
  ASSERT_DOUBLE_EQ(0.0, info.last_access_time);
  ASSERT_DOUBLE_EQ(0.0, info.last_modified_time);

  // Query a file ref on an external filesystem.
  pp::FileRef file_ref_ext;
  std::string result = MakeExternalFileRef(&file_ref_ext);
  if (!result.empty())
    return result;
  out_callback.WaitForResult(file_ref_ext.Query(out_callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(out_callback);
  if (out_callback.result() != PP_OK)
    return ReportError("Query() result", out_callback.result());
  ASSERT_EQ(PP_OK, out_callback.result());

  info = out_callback.output();
  ASSERT_EQ(PP_FILETYPE_REGULAR, info.type);
  ASSERT_EQ(PP_FILESYSTEMTYPE_EXTERNAL, info.system_type);

  // We can't touch the file, so just sanity check the times.
  ASSERT_TRUE(info.creation_time >= 0.0);
  ASSERT_TRUE(info.last_modified_time >= 0.0);
  ASSERT_TRUE(info.last_access_time >= 0.0);

  // Query a file ref for a file that doesn't exist.
  pp::FileRef missing_file_ref(file_system, "/missing_file");
  out_callback.WaitForResult(missing_file_ref.Query(
      out_callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(out_callback);
  ASSERT_EQ(PP_ERROR_FILENOTFOUND, out_callback.result());

  PASS();
}

std::string TestFileRef::TestFileNameEscaping() {
  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  std::string test_dir_path = "/dir_for_escaping_test";
  // Create a directory in which to test.
  pp::FileRef test_dir_ref(file_system, test_dir_path.c_str());
  callback.WaitForResult(test_dir_ref.MakeDirectory(
      PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Create the file with the terrible name.
  std::string full_file_path = test_dir_path + "/" + kTerribleName;
  pp::FileRef file_ref(file_system, full_file_path.c_str());
  pp::FileIO file_io(instance_);
  callback.WaitForResult(
      file_io.Open(file_ref, PP_FILEOPENFLAG_CREATE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // FileRef::ReadDirectoryEntries only works out-of-process.
  if (testing_interface_->IsOutOfProcess()) {
    TestCompletionCallbackWithOutput<DirEntries>
        output_callback(instance_->pp_instance(), callback_type());

    output_callback.WaitForResult(
        test_dir_ref.ReadDirectoryEntries(output_callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(output_callback);
    ASSERT_EQ(PP_OK, output_callback.result());

    DirEntries entries = output_callback.output();
    ASSERT_EQ(1, entries.size());
    ASSERT_EQ(kTerribleName, entries.front().file_ref().GetName().AsString());
  }

  PASS();
}

std::string TestFileRef::TestReadDirectoryEntries() {
  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
  pp::FileSystem file_system(
      instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  // Setup testing directories and files.
  const char* test_dir_name = "/test_get_next_file";
  const char* file_prefix = "file_";
  const char* dir_prefix = "dir_";

  pp::FileRef test_dir(file_system, test_dir_name);
  int32_t rv = DeleteDirectoryRecursively(&test_dir);
  ASSERT_TRUE(rv == PP_OK || rv == PP_ERROR_FILENOTFOUND);

  callback.WaitForResult(test_dir.MakeDirectory(
      PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
  CHECK_CALLBACK_BEHAVIOR(callback);
  ASSERT_EQ(PP_OK, callback.result());

  static const int kNumFiles = 3;
  std::set<std::string> expected_file_names;
  for (int i = 1; i <= kNumFiles; ++i) {
    std::ostringstream buffer;
    buffer << test_dir_name << '/' << file_prefix << i;
    pp::FileRef file_ref(file_system, buffer.str().c_str());

    pp::FileIO file_io(instance_);
    callback.WaitForResult(
        file_io.Open(file_ref, PP_FILEOPENFLAG_CREATE, callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(callback);
    ASSERT_EQ(PP_OK, callback.result());

    expected_file_names.insert(buffer.str());
  }

  static const int kNumDirectories = 3;
  std::set<std::string> expected_dir_names;
  for (int i = 1; i <= kNumDirectories; ++i) {
    std::ostringstream buffer;
    buffer << test_dir_name << '/' << dir_prefix << i;
    pp::FileRef file_ref(file_system, buffer.str().c_str());

    callback.WaitForResult(file_ref.MakeDirectory(
        PP_MAKEDIRECTORYFLAG_NONE, callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(callback);
    ASSERT_EQ(PP_OK, callback.result());

    expected_dir_names.insert(buffer.str());
  }

  // Test that |ReadDirectoryEntries()| is able to fetch all
  // directories and files that we created.
  {
    TestCompletionCallbackWithOutput<DirEntries> output_callback(
        instance_->pp_instance(), callback_type());

    output_callback.WaitForResult(
        test_dir.ReadDirectoryEntries(output_callback.GetCallback()));
    CHECK_CALLBACK_BEHAVIOR(output_callback);
    ASSERT_EQ(PP_OK, output_callback.result());

    DirEntries entries = output_callback.output();
    size_t sum = expected_file_names.size() + expected_dir_names.size();
    ASSERT_EQ(sum, entries.size());

    for (DirEntries::const_iterator it = entries.begin();
         it != entries.end(); ++it) {
      pp::FileRef file_ref = it->file_ref();
      std::string file_path = file_ref.GetPath().AsString();
      std::set<std::string>::iterator found =
          expected_file_names.find(file_path);
      if (found != expected_file_names.end()) {
        if (it->file_type() != PP_FILETYPE_REGULAR)
          return file_path + " should have been a regular file.";
        expected_file_names.erase(found);
      } else {
        found = expected_dir_names.find(file_path);
        if (found == expected_dir_names.end())
          return "Unexpected file path: " + file_path;
        if (it->file_type() != PP_FILETYPE_DIRECTORY)
          return file_path + " should have been a directory.";
        expected_dir_names.erase(found);
      }
    }
    ASSERT_TRUE(expected_file_names.empty());
    ASSERT_TRUE(expected_dir_names.empty());
  }

  // Test cancellation of asynchronous |ReadDirectoryEntries()|.
  TestCompletionCallbackWithOutput<DirEntries> output_callback(
      instance_->pp_instance(), callback_type());
  {
    rv = pp::FileRef(file_system, test_dir_name)
        .ReadDirectoryEntries(output_callback.GetCallback());
  }
  output_callback.WaitForAbortResult(rv);
  CHECK_CALLBACK_BEHAVIOR(output_callback);


  PASS();
}

/* [<][>][^][v][top][bottom][index][help] */